diff --git a/src/abstracts/contract-verifier.huff b/src/abstracts/contract-verifier.huff deleted file mode 100644 index 799b1e9..0000000 --- a/src/abstracts/contract-verifier.huff +++ /dev/null @@ -1,269 +0,0 @@ -// SPDX-License-Identifier: Unlicence - -/// @title Contract Verifier -/// @notice Abstract contract for the HyVM with call/store verifications - -/* -------------------------------------------------------------------------- */ -/* CONSTRUCTOR */ -/* -------------------------------------------------------------------------- */ - -#define macro CONSTRUCTOR() = takes(0) returns (0) { - // Set msg.sender as the owner - caller [OWNER_SSLOT] sstore -} - -/* -------------------------------------------------------------------------- */ -/* CONSTANTS */ -/* -------------------------------------------------------------------------- */ - -/// @dev Call verifier storage slot -#define constant CALL_VERIFIER_SSLOT = 0x58a8a9223af434080477cd2d1a2667aba3ce9f1301007611a4d07a0af708e65a - -/// @dev Reentrant storage slot -#define constant REENTRANT_SSLOT = 0xda8b995e2670b58d57272b2d0b50f8c7de2a7e927b4b9b06444cd9e378d215fb - -/// @dev Owner address storage slot. -/// This will ensure that when called with a regular call (not a delegatecall), -/// nobody can take the ownership here. But people will be able to take ownership -/// on their own instance because ONLY_OWNER() macro is OK when there is no owner set. -#define constant OWNER_SSLOT = 0x240bb37aa812ad108aae08a4b01536038aac1692fa2f4eabbc4a8a540d93e0be - -/// @dev Memory slot to store the result of call checking (1 word) -#define constant CALLRESULT_MSLOT = 0x220 - -/* -------------------------------------------------------------------------- */ -/* CALL ARGS */ -/* -------------------------------------------------------------------------- */ - -// Used to store call checking arguments. -// sum = 4 + 0x20 x 6 + 0x140 = 0x204 length , starting at 0x220 -#define constant CALLARGS_MSLOT = 0x240 // CALLRESULT_MSLOT + 0x20 (result size) -#define constant CALLARGS_MAX_DATALEN = 0x140 -#define constant CALLARGS_TOTALLEN = 0x204 -#define constant CALLARGS_MSLOT_selector = 0x240 // same as callargs_mslot -#define constant CALLARGS_MSLOT_opcode = 0x244 // selector + 0x4 (selector size) -#define constant CALLARGS_MSLOT_contract = 0x264 // +0x20 -#define constant CALLARGS_MSLOT_value = 0x284 // +0x20 -#define constant CALLARGS_MSLOT_calldata = 0x2A4 // +0x20 -#define constant CALLARGS_MSLOT_isTruncated = 0x2C4 // +0x20 -#define constant CALLARGS_MSLOT_calldata_len = 0x2E4 // +0x20 -#define constant CALLARGS_MSLOT_calldata_raw = 0x304 // +0x20 - -/// @dev This marks the end of memory space used by the contract verifier. -// +0x40, rounded to upper 20 (= CALLARGS_MSLOT + CALLARGS_TOTALLEN = CALLARGS_MSLOT_calldata_raw + CALLARGS_MAX_DATALEN) -#define constant HOST_MEMORY_START = 0x460 - -/* -------------------------------------------------------------------------- */ -/* MACROS */ -/* -------------------------------------------------------------------------- */ - -/// @dev Check that the pending SSTORE operation is OK -#define macro CHECK_SSTORE() = takes(0) returns (0) { - // The first value on stack is the key to store - dup1 [REENTRANT_SSLOT] sload eq - dup1 [CALL_VERIFIER_SSLOT] sload eq - or - dup1 [OWNER_SSLOT] sload eq - or - - // Jump to success if those slots are not protected - iszero checksstore_success jumpi - - // Check that we're owner (if we are, then we can proceed) - ONLY_OWNER() - - checksstore_success: -} - -/// @dev Check that the pending DELEGATECALL operation is OK -#define macro CHECK_DELEGATECALL() = takes(0) returns (0) { - // - check can call - dup4 // push argSize => [argSize, gas address argsOffset argsSize retOffset retSize] - dup4 // push argOffset => [argOffset, argSize, gas address argsOffset argsSize retOffset retSize] - 0x0 // push value => [value, argSize, gas address argsOffset argsSize retOffset retSize] - dup5 // push contract address => [contact, argSize, gas address argsOffset argsSize retOffset retSize] - 0xFA // push opcode => [opcode, argSize, gas address argsOffset argsSize retOffset retSize] - CHECK_GENERIC_CALL() -} - -/// @dev Check that the pending STATICCALL operation is OK -#define macro CHECK_STATICCALL() = takes(0) returns (0) { - // - check can call - dup4 // push argSize => [argSize, gas address argsOffset argsSize retOffset retSize] - dup4 // push argOffset => [argOffset, argSize, gas address argsOffset argsSize retOffset retSize] - 0x0 // push value => [value, argSize, gas address argsOffset argsSize retOffset retSize] - dup5 // push contract address => [contact, argSize, gas address argsOffset argsSize retOffset retSize] - 0xFA // push opcode => [opcode, argSize, gas address argsOffset argsSize retOffset retSize] - CHECK_GENERIC_CALL() // => [gas address argsOffset argsSize retOffset retSize] -} - -/// @dev Check that the pending CALLCODE operation is OK -#define macro CHECK_CALLCODE() = takes(0) returns (0) { - // - check can call - dup5 // push argSize - dup5 // push argOffset - dup5 // push value - dup5 // push contract address - 0xF4 // push opcode - CHECK_GENERIC_CALL() -} - -/// @dev Check that the pending CALL operation is OK -#define macro CHECK_CALL() = takes(0) returns (0) { - // - check can call - dup5 // push argSize - dup5 // push argOffset - dup5 // push value - dup5 // push contract address - 0xF1 // push opcode - CHECK_GENERIC_CALL() -} - -/// @dev Checks that a call can be performed -/// -> taking stack [callOpcode, callContract, sentValue, argOffset, argSize] -/// -/// Arguments : -/// - function selector (0x04 length... signature of "verifyCall(uint1,address,uint256,bytes,bool)") -/// - opcode (0x20 length packed) -/// - contract address (0x20 length packed) -/// - value sent (0x20 length packed) -/// - copy of the first 10 words of calldata: -/// * pointer to data (len 0x20) -/// * array len (len 0x20) -/// * actual data (len 0x140) -/// - isTruncated (0x20 length packed) -/// -/// Note : Do not use CONSOLE_LOG() in this macro because it writes to -/// CALLARGS_MSLOT (avoids allocating debug memory). -#define macro CHECK_GENERIC_CALL() = takes(4) returns (0) { - // copy opcode to args - [CALLARGS_MSLOT_opcode] mstore // => [callContract, sentValue, argOffset, argSize] - - // copy contract to args - [CALLARGS_MSLOT_contract] mstore // => [sentValue, argOffset, argSize] - - // copy sent value to args - [CALLARGS_MSLOT_value] mstore // => [argOffset, argSize] - - // store "isTrucated" as false - 0x0 [CALLARGS_MSLOT_isTruncated] mstore - - // stack => [argOffset, argSize] - swap1 // => [argSize, argOffset] - - // Check that is not greater than max, else push max - - dup1 [CALLARGS_MAX_DATALEN] lt // [calldata.length > CALLARGS_MAX_DATALEN, argSize, argOffset] - iszero - callargs_not_maxed jumpi - - // store "isTrucated" as true - 0x1 [CALLARGS_MSLOT_isTruncated] mstore - - // pop length, and push max args instead - pop - [CALLARGS_MAX_DATALEN] - - callargs_not_maxed: - // stack => [argSize, argOffset] - - // write signature of verifyCall(uint1,address,uint256,bytes,bool) to selector - 0x1c8eb044 0xE0 shl [CALLARGS_MSLOT_selector] mstore - - // write a data pointer to args data - 0xa0 [CALLARGS_MSLOT_calldata] mstore - - // write data length - dup1 [CALLARGS_MSLOT_calldata_len] mstore - - // copy calldata - [CALLARGS_MSLOT_calldata_raw] // => [target, argSize, argOffset] - swap2 // => [argOffset, argSize, target] - swap1 // => [argSize, argOffset, target] - - MEM_COPY() // => [] - - // call verifier smartcontract - 0x20 // retSize - [CALLRESULT_MSLOT] // retOffset - [CALLARGS_TOTALLEN] // argSize (we're giving it all, since we trust this contract... save a bit of code) - [CALLARGS_MSLOT] // argOffset - [CALL_VERIFIER_SSLOT] sload // contract to call - CHECK_VERIFIER() - gas - staticcall // => [success] - - // load result - [CALLRESULT_MSLOT] mload // => [ok, success] - - // check that the call succeeded - and checkcall_success jumpi // => [] - - // verification failed - 0x00 0x00 revert - - checkcall_success: -} - - // no reentrency - #define macro NON_REENTRANT() = takes (0) returns (0) { - [REENTRANT_SSLOT] // load execution - sload // [lock] - iszero // [is_unlocked] - unlocked // [unlocked_jumpdest] - jumpi // [] - 0x00 // [size] - 0x00 // [offset, size] - revert // [] - unlocked: // [] - 0x01 // [lock_value] - [REENTRANT_SSLOT] // [reentrant_sslot, lock_value] - sstore // [] - } - -/// @dev Revert if the sender is not the owner AND the owner is setted. -#define macro ONLY_OWNER() = takes(0) returns(0) { - [OWNER_SSLOT] sload - dup1 caller eq is_owner jumpi - 0x0 eq is_owner jumpi // allow when there is no owner - 0x00 0x00 revert - is_owner: -} - -#define macro CHECK_VERIFIER() = takes(1) returns (1) { - dup1 verifier_ok jumpi - 0x00 0x00 revert - verifier_ok: -} - -/** -copy memory -takes: size source target -*/ -#define macro MEM_COPY() = takes(3) returns (0) { - memcpy_one: - // -> [size source target] - // if nothing to copy, then return - dup1 iszero memcpy_end jumpi - - // copy one word (might embed a bit of extra memory if size is not multiple of 0x20, but its ok) - dup2 mload // => [data size source target] - dup4 mstore // => [size source target] - - // decrement size by 0x20, but maxed to size (size might not be a multiple of 0x20) - 0x20 dup2 lt // is size lower than 0x20 ? => [isSmall, size, source, target] - - memcpy_end jumpi // jump to end if smaller => [size, source, target] - - // decrement count - 0x20 swap1 sub // => [new size, source, target] - // increment source - swap1 0x20 add swap1 // => [new size, new source, target] - // increment target - swap2 0x20 add swap2 // => [new size, new source, new target] - - // restart - memcpy_one jump - memcpy_end: - pop pop pop -} \ No newline at end of file diff --git a/test/verifiers/CallVerifiers.sol b/test/verifiers/CallVerifiers.sol deleted file mode 100644 index 705dc07..0000000 --- a/test/verifiers/CallVerifiers.sol +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.16; - -import "./IHyVMCallVerifier.sol"; -import {Utils} from "../utils/Utils.sol"; -import "forge-std/console.sol"; - -contract VerifyAllCalls is IHyVMCallVerifier { - function verifyCall( - uint8 opcode, - address callContract, - uint256 valueSent, - bytes memory callDataSlice, - bool truncated - ) public view returns (bool) { - console.log("Will let this call pass:"); - console.log(opcode); - console.log(callContract); - console.log(valueSent); - console.log(Utils.iToHex(callDataSlice)); - console.log(truncated); - return true; - } -} - -contract VerifyOnlyCallsTo is IHyVMCallVerifier { - address _onlyContract; - - constructor(address onlyContract) { - _onlyContract = onlyContract; - } - - function verifyCall( - uint8 opcode, - address callContract, - uint256 valueSent, - bytes memory callDataSlice, - bool truncated - ) public view returns (bool) { - console.log("Verifying call..."); - console.log(opcode); - console.log(callContract); - console.log(valueSent); - console.log(Utils.iToHex(callDataSlice)); - console.log(truncated); - return _onlyContract == callContract; - } -} - -contract OnlyAllowExchengesWith is IHyVMCallVerifier { - address _user; - - constructor(address user) { - _user = user; - } - - function verifyCall( - uint8 opcode, - address callContract, - uint256 valueSent, - bytes memory callDataSlice, - bool truncated - ) public view returns (bool) { - bytes memory balanceOfSig = hex"70a08231"; - - // check that this call starts with the balanceOf() or transfer() selector - (bool success, uint256 index) = Utils.indexOf(callDataSlice, balanceOfSig, 0); - if (!success || index != 0) { - bytes memory transferSig = hex"a9059cbb"; - (success, index) = Utils.indexOf(callDataSlice, transferSig, 0); - if (!success || index != 0) { - console.log("Neither balanceOf() nor transfer() selectors found in calldata: "); - console.log(Utils.iToHex(callDataSlice)); - return false; - } - } - - // then, check that it has the right length (4 bytes for the selector + one address) - if (callDataSlice.length != (4 + 0x20)) { - return false; - } - - // find index of the given address - (success, index) = Utils.indexOf(callDataSlice, abi.encodePacked(_user), 4); - if (!success || index != 4) { - console.log("Given address not found in calldata: "); - console.log(Utils.iToHex(callDataSlice)); - return false; - } - return true; - } -} diff --git a/test/verifiers/Calls.t.sol b/test/verifiers/Calls.t.sol deleted file mode 100644 index 05faba6..0000000 --- a/test/verifiers/Calls.t.sol +++ /dev/null @@ -1,118 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.16; - -import "foundry-huff/HuffDeployer.sol"; -import "forge-std/Test.sol"; -import "forge-std/console.sol"; -import "./MyToken.sol"; -import {Utils} from "../utils/Utils.sol"; -import "./CallVerifiers.sol"; - -contract MyTokenTest is Test { - MyToken myToken; - address public hyvm; - address owner; - address ZERO_ADDRESS = address(0); - address user = address(0x1231231231231231231231231231231231231231); - uint256 balance = 100 * 1e18; - - // ===== Set up ===== - function setUp() public { - owner = address(this); - myToken = new MyToken(); - // send some tokens to me - vm.prank(owner); - myToken.mint(owner, balance); - - // set deploy VM & set verifier contract - hyvm = HuffDeployer.deploy("HyVM"); - } - - // TODO test ownership... once an owner is defined, must not be able to change owner, - // TODO once there is an owner, the owner must be able to change verifier - // TODO once there is an owner, someone else cannot change the verifier - // TODO write tests for all kind of calls (as time of writing, only CALL & STATICCALL are tested, but we must also test DELEGATECALL & CALLCODE) - - function setVerifier(IHyVMCallVerifier verifier) public { - bytes memory code = Utils.setVerifierBytecode(hyvm, address(verifier)); - (bool success,) = hyvm.delegatecall(code); - assert(success); - } - - function testTransfer() public { - // allow all calls - setVerifier(new VerifyAllCalls()); - (bool success,) = performTransfer(); - - assertEq(success, true); - - console.log("checking that user has received tokens"); - assertEq(myToken.balanceOf(user), balance / 2); - assertEq(myToken.balanceOf(owner), balance / 2); - } - - function testContractVerifierOK() public { - setVerifier(new VerifyOnlyCallsTo(address(myToken))); - (bool success,) = performTransfer(); - - assertEq(success, true); - - console.log("checking that user has received tokens"); - assertEq(myToken.balanceOf(user), balance / 2); - assertEq(myToken.balanceOf(owner), balance / 2); - } - - // function testContractVerifierKO() public { - // setVerifier(new VerifyOnlyCallsTo(address(this))); - // (bool success, ) = performTransfer(); - - // assertEq(success, false); // expecting revert - - // // check that balances have failed to update - // assertEq(myToken.balanceOf(user), 0); - // assertEq(myToken.balanceOf(owner), balance); - // } - - function testBalanceOfVerifierOK() public { - setVerifier(new OnlyAllowExchengesWith(user)); - (bool success,) = performTransfer(); - - assertEq(success, true); - - console.log("checking that user has received tokens"); - assertEq(myToken.balanceOf(user), balance / 2); - assertEq(myToken.balanceOf(owner), balance / 2); - } - - // function testBalanceOfVerifierKO() public { - // setVerifier(new OnlyAllowExchengesWith(user)); - // (bool success, ) = performTransfer(); - - // assertEq(success, false); // expecting revert - - // // check that balances have failed to update - // assertEq(myToken.balanceOf(user), 0); - // assertEq(myToken.balanceOf(owner), balance); - // } - - function performTransfer() public returns (bool success, bytes memory result) { - // regenerate this using 👉 solc --optimize --bin test/calls/SendERC20.sol - bytes memory bytecode = - hex"608060405234801561001057600080fd5b506040516370a0823160e01b8152306004820152600090731234567890abcdef1234567890abcdef12345678906370a0823190602401602060405180830381865afa158015610063573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100879190610139565b9050731234567890abcdef1234567890abcdef1234567863a9059cbb7312312312312312312312312312312312312312316100c3600285610152565b6040516001600160e01b031960e085901b1681526001600160a01b03909216600483015260248201526044016020604051808303816000875af115801561010e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101329190610174565b505061019d565b60006020828403121561014b57600080fd5b5051919050565b60008261016f57634e487b7160e01b600052601260045260246000fd5b500490565b60006020828403121561018657600080fd5b8151801515811461019657600080fd5b9392505050565b60bd806101ab6000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c8063d4b83992146037578063fc0c546a14606d575b600080fd5b605173123123123123123123123123123123123123123181565b6040516001600160a01b03909116815260200160405180910390f35b6051731234567890abcdef1234567890abcdef123456788156fea2646970667358221220aa090d14556401ee765966a7d0ee083f18232d32081f3f1a20c5a8dd4d0b2d1864736f6c634300080f0033"; - - // just replace the dummy addresses by ours - bytecode = Utils.replace(bytecode, 0x1234567890AbcdEF1234567890aBcdef12345678, address(myToken)); - bytecode = Utils.replace(bytecode, 0x1231231231231231231231231231231231231231, user); - console.log("Token & user: "); - console.log(address(myToken)); - console.log(user); - - console.log("checking that user has 0 tokens"); - assertEq(myToken.balanceOf(user), 0); - assertEq(myToken.balanceOf(owner), balance); - - // execute - console.log(" => starting execution"); - return hyvm.delegatecall(bytecode); - } -} diff --git a/test/verifiers/IHyVMCallVerifier.sol b/test/verifiers/IHyVMCallVerifier.sol deleted file mode 100644 index ffabd5f..0000000 --- a/test/verifiers/IHyVMCallVerifier.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.16; - -/** - * @dev A call verifier for HyVM - */ -interface IHyVMCallVerifier { - function verifyCall( - uint8 opcode, - address callContract, - uint256 valueSent, - bytes memory callDataSlice, - bool truncated - ) external returns (bool); -} diff --git a/test/verifiers/MyToken.sol b/test/verifiers/MyToken.sol deleted file mode 100644 index d200222..0000000 --- a/test/verifiers/MyToken.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.16; - -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; - -contract MyToken is ERC20 { - address public owner; - - constructor() ERC20("My Token", "MTKN") { - owner = msg.sender; - } - - function mint(address account, uint256 amount) public { - require(msg.sender == owner, "Only Owner can mint"); - _mint(account, amount); - } - - function burn(address account, uint256 amount) public { - require(msg.sender == owner, "Only Owner can burn"); - _burn(account, amount); - } -} diff --git a/test/verifiers/SendERC20.sol b/test/verifiers/SendERC20.sol deleted file mode 100644 index 692f818..0000000 --- a/test/verifiers/SendERC20.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity ^0.8.16; - -import {IERC20} from "../utils/interfaces/IERC20.sol"; - -contract SimpleTransfer { - IERC20 public constant token = IERC20(0x1234567890AbcdEF1234567890aBcdef12345678); - address public constant target = address(0x1231231231231231231231231231231231231231); - - constructor() { - uint256 amt = token.balanceOf(address(this)); - token.transfer(target, amt / 2); - } -} diff --git a/test/verifiers/set-verifier b/test/verifiers/set-verifier deleted file mode 100644 index c9a7d9f..0000000 --- a/test/verifiers/set-verifier +++ /dev/null @@ -1,10 +0,0 @@ -# bytecode to set the verifier of a contract - -# push the verifier contract (will be replaced at runtime) -PUSH20 0x1231231231231231231231231231231231231231 - -# push the slot to store (see CALL_VERIFIER_SSLOT in HyVM.huff) -PUSH32 0x58a8a9223af434080477cd2d1a2667aba3ce9f1301007611a4d07a0af708e65a - -# store it -SSTORE