本文详细介绍了形式验证的原理、工具及其在智能合约中的应用,强调了符号执行和形式验证提供的深入分析能力。文中列出了多种形式验证工具的特点及示例,包括Halmos、Ityfuzz和Kontrol等,并提供了一些代码实现示例,展示了如何利用这些工具确保智能合约的安全性和正确性。
正式验证(Formal Verification,FV)使用数学建模和逻辑分析来确保程序的安全性和正确性。FV的一个关键方面是符号执行,它为输入分配符号值,从而在一次分析中探索各种程序路径。
该技术有助于验证程序是否可以违反某些条件或到达危险状态,使用如SMT求解器等工具进行可行性评估。因此,符号执行增强了测试,提供了更全面的方式来确立程序的安全性和正确性。
与传统执行方法相比,符号执行使用输入的符号值,从而能够在一次分析中检查所有可能的执行路径。
这种方法旨在概括测试,常用于验证任何程序执行是否可以违反特定属性。
符号执行可以证明某些状态的不可达性,从而以更全面的方式确认程序的安全性或正确性。
状态空间的全面分析: 正式验证涉及计算和分析合约在执行期间可能存在的所有状态。这种细致的检查有助于识别传统测试方法中可能不明显的潜在缺陷或漏洞。
总之,正式验证适合于确保在更概念化和绝对的层面上的正确性和安全性,而模糊测试则更适合于发现智能合约中的特定错误和测试不变性。
function check_Invariant_Backdoor(bytes4 selector,address other, address caller) public {
// 执行任意交易
vm.assume(other != caller);
uint256 oldBalanceOther = IERCOLAS(token).balanceOf(other);
uint256 oldAllowance = IERCOLAS(token).allowance(other, caller);
vm.prank(caller);
(bool success,) = address(token).call(gen_calldata(selector));
vm.assume(success);
uint256 newBalanceOther = IERCOLAS(token).balanceOf(other);
if (newBalanceOther < oldBalanceOther) {
assert(oldAllowance >= oldBalanceOther - newBalanceOther);
}
}
/////////////////////////////////////////////////////////////////////////
// 处理程序
/////////////////////////////////////////////////////////////////////////
function gen_calldata(bytes4 selector) internal returns (bytes memory) {
// 创建要包含在调用数据中的符号值
address guy = svm.createAddress("guy");
address src = svm.createAddress("src");
address dst = svm.createAddress("dst");
uint256 wad = svm.createUint256("wad");
uint256 val = svm.createUint256("val");
uint256 pay = svm.createUint256("pay");
// 基于函数选择器生成调用数据
bytes memory args;
if (selector == IERCOLAS(token).changeOwner.selector) {
args = abi.encode(guy);
} else if (selector == IERCOLAS(token).changeMinter.selector) {
args = abi.encode(guy);
} else if (selector == IERCOLAS(token).mint.selector) {
args = abi.encode(guy, wad);
} else if (selector == IERCOLAS(token).inflationControl.selector) {
args = abi.encode(wad);
} else if (selector == IERCOLAS(token).inflationRemainder.selector) {
args = abi.encode();
} else if (selector == IERCOLAS(token).burn.selector) {
args = abi.encode(val);
} else if (selector == IERCOLAS(token).decreaseAllowance.selector) {
args = abi.encode(src, wad);
} else if (selector == IERCOLAS(token).increaseAllowance.selector) {
args = abi.encode(src, wad);
} else {
args = svm.createBytes(1024, "data");
}
return abi.encodePacked(selector, args);
}
function checkNoBackdoor(bytes4 selector, address caller, address other) public virtual {
// address caller = svm.createAddress("caller");
// address other = svm.createAddress("other");
bytes memory args = svm.createBytes(1024, 'data');
vm.assume(other != caller);
uint256 oldBalanceOther = (token).balanceOf(other);
uint256 oldAllowance = (token).allowance(other, caller);
vm.prank(caller);
(bool success,) = address(token).call(abi.encodePacked(selector, args));
vm.assume(success);
uint256 newBalanceOther = (token).balanceOf(other);
if (newBalanceOther < oldBalanceOther) {
assert(oldAllowance >= oldBalanceOther - newBalanceOther);
}
}
function _check_transferFrom(address caller, address from, address to, address other, uint256 amount) public virtual {
require(other != from);
require(other != to);
uint256 oldBalanceFrom = IERCOLAS(token).balanceOf(from);
uint256 oldBalanceTo = IERCOLAS(token).balanceOf(to);
uint256 oldBalanceOther = IERCOLAS(token).balanceOf(other);
uint256 oldAllowance = IERCOLAS(token).allowance(from, caller);
vm.prank(caller);
IERCOLAS(token).transferFrom(from, to, amount);
if (from != to) {
assert(IERCOLAS(token).balanceOf(from) <= oldBalanceFrom);
assert(IERCOLAS(token).balanceOf(from) == oldBalanceFrom - amount);
assert(IERCOLAS(token).balanceOf(to) >= oldBalanceTo);
assert(IERCOLAS(token).balanceOf(to) == oldBalanceTo + amount);
assert(oldAllowance >= amount); // 确保允许足够
assert(oldAllowance == type(uint256).max || IERCOLAS(token).allowance(from, caller) == oldAllowance - amount); // 如果不为最大值,允许减少
} else {
assert(IERCOLAS(token).balanceOf(from) == oldBalanceFrom);
assert(IERCOLAS(token).balanceOf(to) == oldBalanceTo);
}
assert(IERCOLAS(token).balanceOf(other) == oldBalanceOther);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "lib/solidity_utils/lib.sol";
contract Gate {
bool public locked = true;
uint256 public timestamp = block.timestamp;
uint8 private number1 = 10;
uint16 private number2 = 255;
bytes32[3] private data;
constructor(bytes32 _data1, bytes32 _data2, bytes32 _data3) {
data[0] = _data1;
data[1] = _data2;
data[2] = _data3;
}
modifier onlyThis() {
require(msg.sender == address(this), "只有合约可以调用此");
_;
}
function resolve(bytes memory _data) public {
require(msg.sender == tx.origin);
(bool success, ) = address(this).call(_data);
require(success, "调用失败");
}
function unlock(bytes memory _data) public onlyThis {
require(bytes16(_data) == bytes16(data[2]));
// locked = false;
bug();
}
// function isSolved() public view returns (bool) {
// return !locked;
// }
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.11;
import "lib/solidity_utils/lib.sol";
contract BytecodeVault {
// address public owner;
// constructor() public payable {
// owner = msg.sender;
// }
modifier onlyBytecode() {
require(msg.sender != tx.origin, "不允许使用高级合约!");
_;
}
function withdraw(bytes memory senderCode) external onlyBytecode {
uint256 sequence = 0xdeadbeef;
// bytes memory senderCode;
// address bytecaller = msg.sender;
// assembly {
// let size := extcodesize(bytecaller)
// senderCode := mload(0x40)
// mstore(0x40, add(senderCode, and(add(size, 0x20), 0x1f), not(0x1f)))
// mstore(senderCode, size)
// extcodecopy(bytecaller, add(senderCode, 0x20), 0, size)
// }
require(senderCode.length % 2 == 1, "字节码长度必须是奇数!");
for (uint256 i = 0; i < senderCode.length - 3; i++) {
if (senderCode[i] == bytes1(uint8(sequence >> 24))) {
if (senderCode[i + 1] == bytes1(uint8((sequence >> 16) & 0xFF))) {
if (senderCode[i + 2] == bytes1(uint8((sequence >> 8) & 0xFF))) {
if (senderCode[i + 3] == bytes1(uint8(sequence & 0xFF))) {
bug();
}
}
}
}
}
// revert("未找到序列!");
}
function isSolved() public view returns (bool) {
return address(this).balance == 0;
}
}
Kontrol是一个强大的工具,它基于KEVM构建,并具有非常实用的功能,比如:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "../src/token.sol";
contract TokenTest is Test {
Token token;
address Alice;
address Bob;
address Eve;
function setUp() public {
token = new Token();
Alice = makeAddr("Alice");
Bob = makeAddr("Bob");
Eve = makeAddr("Eve");
token.mint(Alice, 10 ether);
token.mint(Bob, 20 ether);
token.mint(Eve, 30 ether);
}
function testTransfer(address from, address to, uint256 amount) public {
// 这个证明在使用kontrol执行时有很多分支,
// 因为`from`和 `to`参数可以是以下每个地址中的一个
// (Alice, Bob, Eve, address(this), address(vm), address(token))。
// 前四个`vm.assume`调用允许符号参数`from`和`to`接受
// 在token存储中初始化并具有有效余额的值。
// 这是一个玩具示例,展示了分支和约束如何工作。
vm.assume(from == Alice || from == Bob || from == Eve);
vm.assume(to == Alice || to == Bob || to == Eve);
vm.assume(from != address(this) && from != address(vm) && from != address(token));
vm.assume(to != address(this) && to != address(vm) && to != address(token));
vm.assume(to != from);
vm.assume(token.balanceOf(from) >= amount);
uint256 preBalanceFrom = token.balanceOf(from);
uint256 preBalanceTo = token.balanceOf(to);
vm.prank(from);
token.transfer(to, amount);
if(from == to) {
assertEq(token.balanceOf(from), preBalanceFrom);
assertEq(token.balanceOf(to), preBalanceTo);
} else {
assertEq(token.balanceOf(from), preBalanceFrom - amount);
assertEq(token.balanceOf(to), preBalanceTo + amount);
}
}
}
这些是我们在其他项目中使用这些工具的示例,曾使用Halmos、Foundry、Echidna、Medusa、Ityfuzz进行分析......
在这里我们详细展示了我们为执行测试所做的工作:
为了进行有效的验证,我们需要:
我们提供灵活的定价结构,具体取决于项目的复杂性和规模。费用预计根据:
如需与我们联系,你可以给我们写信:
- 原文链接: github.com/ZealynxSecuri...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!