├── .gitignore ├── test ├── 0_Template.t.sol ├── 9_consoleFmt.t.sol ├── 7_payablePrecompiles.t.sol ├── 5_forkId.t.sol ├── 6_expectRevertBug.t.sol ├── 8_usdcBlacklist.t.sol ├── 3_create3.t.sol ├── 2_OzGovernorStorageLayout.sol ├── 10_loopCounterInitialization.t.sol ├── 11_constantVsImmutable.t.sol ├── 1_Dispatcher.t.sol ├── 12_LowLeverRevertCauses.t.sol └── 4_InvariantNonceGoUp.t.sol ├── .gitmodules ├── foundry.toml ├── newTest.sh ├── LICENSE ├── script └── 1_mineSalt.s.sol ├── README.md └── data ├── storage-OzTimelockControllerUpgradeable.txt └── storage-OzGovernorCountingSimpleUpgradeable.txt /.gitignore: -------------------------------------------------------------------------------- 1 | cache/ 2 | out/ 3 | 4 | 5 | .vscode 6 | .idea -------------------------------------------------------------------------------- /test/0_Template.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | contract TestX is Test { 7 | function test_X() external {} 8 | } 9 | -------------------------------------------------------------------------------- /test/9_consoleFmt.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | contract Test9 is Test { 7 | function test_consoleFmt() external view { 8 | uint256 x = 1e18; 9 | console2.log("x is %s", x); 10 | console2.log("x is %i", x); 11 | console2.log("x is %x", x); 12 | console2.log("x is %e", x); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/7_payablePrecompiles.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | contract Test7 is Test { 7 | function test_payablePrecompiles() external { 8 | (bool success,) = payable(address(8)).call{value: 1 ether}(""); 9 | require(success, "call failed"); 10 | 11 | payable(address(8)).transfer(1 ether); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/5_forkId.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | contract Test5 is Test { 7 | function test_forkId() external { 8 | uint256 forkId1 = vm.createSelectFork(getChain(1).rpcUrl); 9 | uint256 forkId2 = vm.createSelectFork(getChain(1).rpcUrl); 10 | uint256 forkId3 = vm.createSelectFork(getChain(10).rpcUrl); 11 | 12 | assertEq(forkId1, 0, "forkId1"); 13 | assertEq(forkId2, 1, "forkId2"); 14 | assertEq(forkId3, 2, "forkId3"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/solmate"] 2 | path = lib/solmate 3 | url = https://github.com/transmissions11/solmate 4 | [submodule "lib/openzeppelin-contracts"] 5 | path = lib/openzeppelin-contracts 6 | url = https://github.com/OpenZeppelin/openzeppelin-contracts 7 | [submodule "lib/openzeppelin-contracts-upgradeable"] 8 | path = lib/openzeppelin-contracts-upgradeable 9 | url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable 10 | [submodule "lib/forge-std"] 11 | path = lib/forge-std 12 | url = https://github.com/foundry-rs/forge-std 13 | branch = v1 14 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | fs_permissions = [{ access = "read-write", path = "./" }] 3 | optimizer = true 4 | optimizer-runs = 200 5 | solc-version = "0.8.15" 6 | verbosity = 3 7 | 8 | [fmt] 9 | bracket_spacing = false 10 | int_types = "long" 11 | line_length = 100 12 | multiline_func_header = "attributes_first" 13 | number_underscore = "thousands" 14 | quote_style = "double" 15 | single_line_statement_blocks = "single" 16 | tab_width = 2 17 | wrap_comments = true 18 | 19 | [rpc_endpoints] 20 | arbitrum = "${ARBITRUM_ONE_RPC_URL}" 21 | goerli = "${GOERLI_RPC_URL}" 22 | mainnet = "${MAINNET_RPC_URL}" 23 | optimism = "${OPTIMISM_RPC_URL}" 24 | polygon = "${POLYGON_RPC_URL}" 25 | sepolia = "${SEPOLIA_RPC_URL}" 26 | -------------------------------------------------------------------------------- /test/6_expectRevertBug.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | contract Foo { 7 | function mightRevert(bool shouldRevert) external pure { 8 | if (shouldRevert) revert(); 9 | } 10 | } 11 | 12 | contract Test6 is Test { 13 | function mightRevert(bool shouldRevert) internal pure { 14 | if (shouldRevert) revert(); 15 | } 16 | 17 | function test_expectRevertBug() external { 18 | vm.expectRevert(); 19 | mightRevert(true); // Execution halts here. 20 | 21 | console2.log("this never executes"); 22 | } 23 | 24 | function test_expectRevertNormal() external { 25 | Foo foo = new Foo(); 26 | vm.expectRevert(); 27 | foo.mightRevert(true); 28 | console2.log("this executes"); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/8_usdcBlacklist.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "forge-std/Test.sol"; 5 | import {IERC20} from "forge-std/interfaces/IERC20.sol"; 6 | 7 | // Apparently in https://github.com/foundry-rs/forge-std/issues/380, the fuzzer was able to 8 | // generate blacklisted addresses for USDC. This test attempts to reproduce that. 9 | contract Test8 is Test { 10 | IERC20 usdc = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); 11 | 12 | function setUp() public { 13 | vm.createSelectFork(vm.envString("MAINNET_RPC_URL"), 17_330_951); 14 | } 15 | 16 | function test_usdcBlacklist(address holder, address spender) external { 17 | vm.assume(holder != address(0) && spender != address(0)); 18 | vm.prank(holder); 19 | usdc.approve(spender, 1); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/3_create3.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "forge-std/Test.sol"; 5 | import "solmate/utils/CREATE3.sol"; 6 | import "solmate/test/utils/mocks/MockERC20.sol"; 7 | 8 | contract Test3 is Test { 9 | function test_create3() public { 10 | bytes32 salt = keccak256(bytes("A salt!")); 11 | 12 | MockERC20 deployed = MockERC20( 13 | CREATE3.deploy( 14 | salt, 15 | abi.encodePacked(type(MockERC20).creationCode, abi.encode("Mock Token", "MOCK", 18)), 16 | 0 17 | ) 18 | ); 19 | 20 | assertEq(address(deployed), CREATE3.getDeployed(salt)); 21 | assertEq(deployed.name(), "Mock Token"); 22 | assertEq(deployed.symbol(), "MOCK"); 23 | assertEq(deployed.decimals(), 18); 24 | } 25 | 26 | function run() external { 27 | test_create3(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /newTest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | num=0 4 | for file in $(ls test/*.sol) ; do 5 | base=$(basename $file .sol) 6 | # get chars of basename before underscore 7 | 8 | prefix=$(echo $base | cut -d_ -f1) 9 | # if prefix is a number 10 | if [[ $prefix =~ ^[0-9]+$ ]] ; then 11 | # get the number 12 | new_num=$prefix 13 | fi 14 | 15 | # compare new_num to num 16 | if [ $new_num -gt $num ] ; then 17 | num=$new_num 18 | fi 19 | done 20 | num=$((num+1)) 21 | 22 | if [[ $1 ]] ; then 23 | newTest=$1 24 | else 25 | # get input for new test 26 | echo "Enter name of new test: " 27 | read newTest 28 | fi 29 | 30 | # Replace spaces with _ 31 | newTest=${newTest// /_} 32 | newfile="test/${num}_${newTest}.t.sol" 33 | 34 | cat test/0_Template.t.sol \ 35 | | sed "s/TestX/Test$num/g" \ 36 | | sed "s/test_X/test_$newTest/g" \ 37 | > $newfile 38 | 39 | echo 40 | echo New test created: ${newTest} 41 | echo 42 | echo "To run tests:" 43 | echo "forge test --mc Test${num}" 44 | 45 | code . ${newfile} 46 | -------------------------------------------------------------------------------- /test/2_OzGovernorStorageLayout.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "forge-std/Test.sol"; 5 | import 6 | "openzeppelin-contracts-upgradeable/contracts/governance/extensions/GovernorCountingSimpleUpgradeable.sol"; 7 | import "openzeppelin-contracts-upgradeable/contracts/governance/TimelockControllerUpgradeable.sol"; 8 | 9 | contract OzGovernorCountingSimpleUpgradeable is GovernorCountingSimpleUpgradeable { 10 | function _getVotes(address, uint256, bytes memory) internal pure override returns (uint256) { 11 | return 0; 12 | } 13 | 14 | function quorum(uint256) public pure override returns (uint256) { 15 | return 0; 16 | } 17 | 18 | function votingDelay() public pure override returns (uint256) { 19 | return 0; 20 | } 21 | 22 | function votingPeriod() public pure override returns (uint256) { 23 | return 0; 24 | } 25 | } 26 | 27 | contract OzTimelockControllerUpgradeable is TimelockControllerUpgradeable {} 28 | 29 | contract Test2 is Test { 30 | function test_OzGovernorStorageLayout() external {} 31 | } 32 | -------------------------------------------------------------------------------- /test/10_loopCounterInitialization.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Does not initializing the loop counter save gas? It does not. 3 | pragma solidity ^0.8.15; 4 | 5 | import "forge-std/Test.sol"; 6 | 7 | contract Foo1 { 8 | function bar(uint256 x, uint256 y) public view returns (uint256) { 9 | uint256 startGas = gasleft(); 10 | for (uint256 i = 0; i < y; i++) { 11 | x += i; 12 | } 13 | console2.log("Foo1", startGas - gasleft()); 14 | return x; 15 | } 16 | } 17 | 18 | contract Foo2 { 19 | function bar(uint256 x, uint256 y) public view returns (uint256) { 20 | uint256 startGas = gasleft(); 21 | for (uint256 i; i < y; i++) { 22 | x += i; 23 | } 24 | console2.log("Foo2", startGas - gasleft()); 25 | return x; 26 | } 27 | } 28 | 29 | contract Test10 is Test { 30 | Foo1 foo1; 31 | Foo2 foo2; 32 | 33 | function setUp() public { 34 | foo1 = new Foo1(); 35 | foo2 = new Foo2(); 36 | } 37 | 38 | function test_Gas(uint8 x, uint8 y) public view { 39 | foo1.bar(x, y); 40 | foo2.bar(x, y); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Maurelian 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /script/1_mineSalt.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import {console2, Script} from "forge-std/Script.sol"; 5 | 6 | contract Hello { 7 | constructor(uint256 x, uint256 y) { 8 | // do nothing 9 | } 10 | } 11 | 12 | contract Script1 is Script { 13 | function run() public noGasMetering { 14 | bytes memory constructorArgs = abi.encode(uint256(100), address(5)); 15 | bytes32 bytecodeHash = keccak256(abi.encodePacked(type(Hello).creationCode, constructorArgs)); 16 | 17 | bytes32 salt = mineSalt(bytecodeHash); 18 | Hello hello = new Hello{salt: salt}(100, 5); 19 | console2.log("hello: ", address(hello)); 20 | } 21 | 22 | function mineSalt(bytes32 bytecodeHash) internal pure returns (bytes32 salt) { 23 | uint256 targetStart = uint16(bytes2(hex"1234")); 24 | uint256 targetEnd = uint16(bytes2(hex"5678")); 25 | 26 | uint256 i; 27 | address addr; 28 | 29 | while (uint16(uint160(addr) >> 144) != targetStart || uint16(uint160(addr)) != targetEnd) { 30 | salt = bytes32(i); 31 | addr = computeCreate2Address(salt, bytecodeHash, CREATE2_FACTORY); 32 | i += 1; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/11_constantVsImmutable.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | contract Test11 is Test { 7 | uint256 public gas; 8 | 9 | bytes32 public immutable MANAGER_ROLE_IMMUT; 10 | bytes32 public constant MANAGER_ROLE_CONST = keccak256("MANAGER_ROLE"); 11 | 12 | mapping(bytes32 => mapping(address => bool)) public roles; 13 | 14 | constructor() { 15 | MANAGER_ROLE_IMMUT = keccak256("MANAGER_ROLE"); 16 | roles[MANAGER_ROLE_IMMUT][msg.sender] = true; 17 | roles[MANAGER_ROLE_CONST][msg.sender] = true; 18 | } 19 | 20 | function hasRole(bytes32 role, address account) public view returns (bool) { 21 | return roles[role][account]; 22 | } 23 | 24 | function test_immutableCheck() external { 25 | gas = gasleft(); 26 | require(hasRole(MANAGER_ROLE_IMMUT, msg.sender), "Caller is not in manager role"); // 24408 gas 27 | console2.log(gas -= gasleft()); 28 | } 29 | 30 | function test_constantCheck() external { 31 | gas = gasleft(); 32 | require(hasRole(MANAGER_ROLE_CONST, msg.sender), "Caller is not in manager role"); // 24419 gas 33 | console2.log(gas -= gasleft()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/1_Dispatcher.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.15; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | // An example of how you might use this sandbox 7 | contract Test1 is Test { 8 | // This contract is a useful case study for exposing the use of the binary search in solidity's 9 | // dispatcher. If there are 4 functions or less, the search is just linear, above that threshold 10 | // binary search is used. 11 | // The functions in this contract are ordered by the value of their function selectors from lowest 12 | // to highest. 13 | // Try running `forge test -m test_4` with and without the last function commented out. 14 | // You'll see that adding the 5th function actually makes test_4() cheaper, because instead of 15 | // being the 4th selector in the search, it becomes the second function after the initial 16 | // branch check is performed. 17 | 18 | // selector: 663bc990 19 | function test_1() public {} 20 | 21 | // selector: 899eb49c 22 | function test_2() public {} 23 | 24 | // selector: b9e2fa35 25 | function test_3() public {} 26 | 27 | // selector: e5935e3a 28 | function test_4() public {} 29 | 30 | // selector: ed94943a 31 | // function test_5aaaaaaaaaaaaaaaaaa() public {} 32 | } 33 | -------------------------------------------------------------------------------- /test/12_LowLeverRevertCauses.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | contract Setter { 7 | uint public x; 8 | 9 | function set(uint _x) public { 10 | x = _x; 11 | } 12 | 13 | } 14 | 15 | interface ISetter1 { 16 | // Intentionally incorrectly marked as view 17 | function set(uint _x) external view; 18 | } 19 | 20 | interface ISetter2 { 21 | // Intentionally incorrectly marked as payable 22 | function set(uint _x) external payable; 23 | } 24 | 25 | interface ISetter3 { 26 | // Intentionally adds non-existent function 27 | function foo() external; 28 | } 29 | 30 | contract Test12 is Test { 31 | // Good 32 | function test_EthBalanceTooLow() external { 33 | address alice = makeAddr("alice"); 34 | vm.prank(alice); 35 | payable(address(123)).transfer(1 ether); 36 | } 37 | 38 | // Good 39 | function test_StateChangeWileStatic() external { 40 | ISetter1 setter = ISetter1(address(new Setter())); 41 | setter.set(1); 42 | } 43 | 44 | // Bad 45 | function test_FunctionNotPayable() external { 46 | ISetter2 setter = ISetter2(address(new Setter())); 47 | setter.set{value: 1 ether}(1); 48 | } 49 | 50 | // Bad 51 | function test_ContractCannotReceive() external { 52 | Setter setter = Setter(payable(address(new Setter()))); 53 | // Test passes, but trace does not show revert reason 54 | (bool ok, ) = address(setter).call{value: 1 ether}(""); 55 | assertEq(ok, false); 56 | } 57 | 58 | // Bad 59 | function test_AddressHasNoCode() external { 60 | address setter = makeAddr("setter"); 61 | Setter(setter).set(1); 62 | } 63 | 64 | // Bad 65 | function test_FunctionDoesNotExist() external { 66 | ISetter3 setter = ISetter3(address(new Setter())); 67 | setter.foo(); 68 | } 69 | 70 | // Bad 71 | function test_AddressAlreadyHasCode() external { 72 | new Setter{salt: bytes32(0)}(); 73 | new Setter{salt: bytes32(0)}(); 74 | } 75 | } 76 | 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Just a simple [forge](https://book.getfoundry.sh/forge/) based repo for playing around with and 2 | understanding solidity toy code. 3 | 4 | See how other's are using it: 5 | - [maurelian's branch](https://github.com/maurelian/solidity-sandbox/tree/maurelian) 6 | - [devtooligan's fork](https://github.com/devtooligan/solidity-sandbox) 7 | 8 | ## Conventions 9 | 10 | The whole purpose of this repo is to make it fast and easy to test stuff, and then keep the test code for 11 | future reference. Specific test contracts can be chosen for testing. Replace `` with full or partial contract name: 12 | 13 | `forge test --match-contract ` or `forge test --mc ` 14 | 15 | 16 | ## Creating a new test 17 | 18 | I don't want to have to think about avoiding contract naming collision, so each new test file 19 | is prefixed with a number, and all the contract names in that file have that same number as a suffix. 20 | 21 | There's now a script to generate a new file with a contract and empty test function. Just run the following replacing `` with the name of your test (file naming conventions apply, avoid spaces). Or omit the test name to invoke _interactive mode_. 22 | 23 | ```sh 24 | ./newTest.sh 25 | ``` 26 | 27 | ## Yul code 28 | 29 | Occasionally it's helpful to generate the Yul intermediate representation to understand what's 30 | happening underneath the hood. In that case, I'll just use a command like the following to 31 | put the IR into the `./ir` dir. Using a `.sol` extension gives pretty decent syntax highlighting 32 | for readability. 33 | 34 | ``` 35 | forge inspect Target16 ir >! ir/bytesArgLenCheck16.yul.sol 36 | ``` 37 | 38 | Yul code can be compiled with `solc --strict-assembly`. 39 | 40 | 41 | ## Advanced Installation Tip 42 | You can create a bash function that will change directories and call newTest. 43 | Add it to your `.bashrc` file so you can call `scratch` from anywhere! 44 | 45 | ```bash 46 | scratch() { 47 | cd 48 | bash newTest.sh $1 49 | } 50 | ``` 51 | -------------------------------------------------------------------------------- /data/storage-OzTimelockControllerUpgradeable.txt: -------------------------------------------------------------------------------- 1 | +---------------+--------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------------+ 2 | | Name | Type | Slot | Offset | Bytes | Contract | 3 | +===========================================================================================================================================================================+ 4 | | _initialized | uint8 | 0 | 0 | 1 | test/2_OzGovernorStorageLayout.sol:OzTimelockControllerUpgradeable | 5 | |---------------+--------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------------| 6 | | _initializing | bool | 0 | 1 | 1 | test/2_OzGovernorStorageLayout.sol:OzTimelockControllerUpgradeable | 7 | |---------------+--------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------------| 8 | | __gap | uint256[50] | 1 | 0 | 1600 | test/2_OzGovernorStorageLayout.sol:OzTimelockControllerUpgradeable | 9 | |---------------+--------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------------| 10 | | __gap | uint256[50] | 51 | 0 | 1600 | test/2_OzGovernorStorageLayout.sol:OzTimelockControllerUpgradeable | 11 | |---------------+--------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------------| 12 | | _roles | mapping(bytes32 => struct AccessControlUpgradeable.RoleData) | 101 | 0 | 32 | test/2_OzGovernorStorageLayout.sol:OzTimelockControllerUpgradeable | 13 | |---------------+--------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------------| 14 | | __gap | uint256[49] | 102 | 0 | 1568 | test/2_OzGovernorStorageLayout.sol:OzTimelockControllerUpgradeable | 15 | |---------------+--------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------------| 16 | | _timestamps | mapping(bytes32 => uint256) | 151 | 0 | 32 | test/2_OzGovernorStorageLayout.sol:OzTimelockControllerUpgradeable | 17 | |---------------+--------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------------| 18 | | _minDelay | uint256 | 152 | 0 | 32 | test/2_OzGovernorStorageLayout.sol:OzTimelockControllerUpgradeable | 19 | |---------------+--------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------------| 20 | | __gap | uint256[48] | 153 | 0 | 1536 | test/2_OzGovernorStorageLayout.sol:OzTimelockControllerUpgradeable | 21 | +---------------+--------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------------+ 22 | -------------------------------------------------------------------------------- /data/storage-OzGovernorCountingSimpleUpgradeable.txt: -------------------------------------------------------------------------------- 1 | +-----------------+---------------------------------------------------------------------------+------+--------+-------+------------------------------------------------------------------------+ 2 | | Name | Type | Slot | Offset | Bytes | Contract | 3 | +==============================================================================================================================================================================================+ 4 | | _initialized | uint8 | 0 | 0 | 1 | test/2_OzGovernorStorageLayout.sol:OzGovernorCountingSimpleUpgradeable | 5 | |-----------------+---------------------------------------------------------------------------+------+--------+-------+------------------------------------------------------------------------| 6 | | _initializing | bool | 0 | 1 | 1 | test/2_OzGovernorStorageLayout.sol:OzGovernorCountingSimpleUpgradeable | 7 | |-----------------+---------------------------------------------------------------------------+------+--------+-------+------------------------------------------------------------------------| 8 | | __gap | uint256[50] | 1 | 0 | 1600 | test/2_OzGovernorStorageLayout.sol:OzGovernorCountingSimpleUpgradeable | 9 | |-----------------+---------------------------------------------------------------------------+------+--------+-------+------------------------------------------------------------------------| 10 | | __gap | uint256[50] | 51 | 0 | 1600 | test/2_OzGovernorStorageLayout.sol:OzGovernorCountingSimpleUpgradeable | 11 | |-----------------+---------------------------------------------------------------------------+------+--------+-------+------------------------------------------------------------------------| 12 | | _HASHED_NAME | bytes32 | 101 | 0 | 32 | test/2_OzGovernorStorageLayout.sol:OzGovernorCountingSimpleUpgradeable | 13 | |-----------------+---------------------------------------------------------------------------+------+--------+-------+------------------------------------------------------------------------| 14 | | _HASHED_VERSION | bytes32 | 102 | 0 | 32 | test/2_OzGovernorStorageLayout.sol:OzGovernorCountingSimpleUpgradeable | 15 | |-----------------+---------------------------------------------------------------------------+------+--------+-------+------------------------------------------------------------------------| 16 | | __gap | uint256[50] | 103 | 0 | 1600 | test/2_OzGovernorStorageLayout.sol:OzGovernorCountingSimpleUpgradeable | 17 | |-----------------+---------------------------------------------------------------------------+------+--------+-------+------------------------------------------------------------------------| 18 | | __gap | uint256[50] | 153 | 0 | 1600 | test/2_OzGovernorStorageLayout.sol:OzGovernorCountingSimpleUpgradeable | 19 | |-----------------+---------------------------------------------------------------------------+------+--------+-------+------------------------------------------------------------------------| 20 | | _name | string | 203 | 0 | 32 | test/2_OzGovernorStorageLayout.sol:OzGovernorCountingSimpleUpgradeable | 21 | |-----------------+---------------------------------------------------------------------------+------+--------+-------+------------------------------------------------------------------------| 22 | | _proposals | mapping(uint256 => struct GovernorUpgradeable.ProposalCore) | 204 | 0 | 32 | test/2_OzGovernorStorageLayout.sol:OzGovernorCountingSimpleUpgradeable | 23 | |-----------------+---------------------------------------------------------------------------+------+--------+-------+------------------------------------------------------------------------| 24 | | _governanceCall | struct DoubleEndedQueueUpgradeable.Bytes32Deque | 205 | 0 | 64 | test/2_OzGovernorStorageLayout.sol:OzGovernorCountingSimpleUpgradeable | 25 | |-----------------+---------------------------------------------------------------------------+------+--------+-------+------------------------------------------------------------------------| 26 | | __gap | uint256[46] | 207 | 0 | 1472 | test/2_OzGovernorStorageLayout.sol:OzGovernorCountingSimpleUpgradeable | 27 | |-----------------+---------------------------------------------------------------------------+------+--------+-------+------------------------------------------------------------------------| 28 | | _proposalVotes | mapping(uint256 => struct GovernorCountingSimpleUpgradeable.ProposalVote) | 253 | 0 | 32 | test/2_OzGovernorStorageLayout.sol:OzGovernorCountingSimpleUpgradeable | 29 | |-----------------+---------------------------------------------------------------------------+------+--------+-------+------------------------------------------------------------------------| 30 | | __gap | uint256[49] | 254 | 0 | 1568 | test/2_OzGovernorStorageLayout.sol:OzGovernorCountingSimpleUpgradeable | 31 | +-----------------+---------------------------------------------------------------------------+------+--------+-------+------------------------------------------------------------------------+ 32 | -------------------------------------------------------------------------------- /test/4_InvariantNonceGoUp.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | /** 7 | * @dev WARNING: While this example technically "works" it's not actually 8 | * correct because state is not yet committed between calls in an invariant 9 | * test. This is being tracked here: https://github.com/foundry-rs/foundry/issues/3005. 10 | * Additional invariant test issues that will improve the examples in this file 11 | * once implemented are: 12 | * - https://github.com/foundry-rs/foundry/issues/2962 13 | * - https://github.com/foundry-rs/foundry/issues/2963 14 | * - https://github.com/foundry-rs/foundry/issues/2985 15 | * - https://github.com/foundry-rs/foundry/issues/2986 16 | * Reading through the above will give context on what currently is and is not 17 | * supported. 18 | * 19 | * @dev Invariant testing works as follows: 20 | * 1. Run `setUp()` and save off resulting state. 21 | * 2. for (i = 0; i < numberOfInvariantRuns; i++) { 22 | * 0. Set global state to post `setUp()` state. 23 | * 1. Check that the invariant holds. 24 | * 2. Choose random contract + random non-view calldata to call on that 25 | * contract. (More info below on how to control what "random" means). 26 | * 3. If `fail_on_revert = true` and call reverts, invariant failed. 27 | * 4. If `fail_on_revert = false` and call reverts, continue. 28 | * 5. Check invariant. 29 | * 6. Repeat steps 2-5 `depth` number of times. 30 | * } 31 | * 32 | * @dev This file demonstrates a simple invariant test of making sure a 33 | * contract's nonce always increases and how you can customize the invariant 34 | * testing behavior. For more complex invariant testing you'll likely want to 35 | * use "Actor-Based Invariant Testing". More info on this pattern and examples 36 | * can be found here: https://github.com/foundry-rs/book/issues/497#issuecomment-1208473205 37 | * 38 | * @dev There are `[profile.default.fuzz]` and `[profile.default.invariant]` 39 | * sections of the `foundry.toml` file you can use to customize behavior. The 40 | * defaults are shown below. More info here: 41 | * - https://book.getfoundry.sh/config/ 42 | * - https://book.getfoundry.sh/reference/config/testing#fuzz 43 | * - https://book.getfoundry.sh/reference/config/testing#invariant 44 | * 45 | * [profile.default.fuzz] 46 | * runs = 256 # The number of test cases ran for each property test. 47 | * max_global_rejects = 65536 # When using `vm.assume`, each failed check is a reject. A test fails 48 | * when it exceeds this number of rejections. 49 | * dictionary_weight = 40 # Use values collected from your contracts 40% of the time, random 50 | * 60% of the time. 51 | * include_storage = true # Collect values from contract storage and add them to the 52 | * dictionary. 53 | * include_push_bytes = true # Collect PUSH bytes from the contract code and add them to the 54 | * dictionary. 55 | * 56 | * [profile.default.invariant] 57 | * runs = 256 # The number of runs for each invariant test. 58 | * depth = 15 # The number of calls executed to attempt to break invariants in one 59 | * run. 60 | * fail_on_revert = false # Fails the invariant test if a revert occurs. 61 | * call_override = false # Allows overriding an unsafe external call when running invariant 62 | * tests, e.g. reentrancy checks (this feature is still a WIP). 63 | * dictionary_weight = 80 # Use values collected from your contracts 80% of the time, random 20% 64 | * of the time. 65 | * include_storage = true # Collect values from contract storage and add them to the dictionary. 66 | * include_push_bytes = true # Collect PUSH bytes from the contract code and add them to the 67 | * dictionary. 68 | */ 69 | contract NonceCounter { 70 | // Invariant must hold before any calls are made, so we start this at 1 to ensure `1 > 0` and 71 | // avoid the need for int256s to start `lastNonce` at -1. Once 72 | // https://github.com/foundry-rs/foundry/issues/2985 73 | // is implemented we can start this at zero and conditionally skip the check on the first run. 74 | uint256 public nonce = 1; 75 | 76 | function increment() public { 77 | nonce++; 78 | } 79 | 80 | // Comment out this function and the invariant test will pass. 81 | function decrement() public { 82 | nonce--; 83 | } 84 | } 85 | 86 | contract Test4 is Test { 87 | NonceCounter nonceCounter; // Contract under test. 88 | uint256 lastNonce; // Last nonce seen, this is what we test against. 89 | 90 | function setUp() public { 91 | nonceCounter = new NonceCounter(); 92 | } 93 | 94 | // There are lots of ways to customize the sender, target contracts, and 95 | // function selectors called, which you would do here. In this example we have 96 | // a simple test and don't need that, so forge automatically uses any non-test 97 | // contract created during `setUp`. For reference, here are the available 98 | // methods you can use to customize things. 99 | // 100 | // By default, there's a 10% chance a random address is used as the sender or 101 | // a 90% of using an address from the dictionary. When using the below to 102 | // customize the senders, there's an 80% chance that one from the list is 103 | // selected. The remaining 20% will either be a random address (10%) or from 104 | // the dictionary (90%). 105 | // 106 | // function targetSenders() public returns (address[] memory); 107 | // function excludeSenders() public returns (address[] memory); 108 | // 109 | // By default, each contract is chosen with an equal probability and from 110 | // there a random function selector is chosen. To customize the target 111 | // contracts and function selectors (listed in order of priority if there's 112 | // clashes) you can use the below methods: 113 | // 114 | // struct FuzzAbiSelector { 115 | // string contractAbi; // "Contract1","src/Contract2.sol:Contract2" 116 | // bytes4[] selectors; 117 | // } 118 | // function targetArtifactSelectors() public returns (FuzzAbiSelector1[] memory); 119 | // 120 | // struct FuzzSelector { 121 | // address addr; 122 | // bytes4[] selectors; 123 | // } 124 | // function targetSelectors() public returns (FuzzSelector[] memory); 125 | // 126 | // function excludeContracts() public returns (address[] memory); 127 | // function excludeArtifacts() public returns (string[] memory); // 128 | // ["Contract1","src/Contract2.sol:Contract2"] 129 | // function targetContracts() public returns (address[] memory); 130 | // function targetArtifacts() public returns (string[] memory); // 131 | // ["Contract1","src/Contract2.sol:Contract2"] 132 | 133 | function invariant_NonceGoUp() external { 134 | console2.log("lastNonce: ", lastNonce); 135 | uint256 currentNonce = nonceCounter.nonce(); 136 | console2.log("currentNonce: ", currentNonce); 137 | require(currentNonce > lastNonce, "Invariant violated: Nonce did not increase"); 138 | lastNonce = currentNonce; 139 | } 140 | } 141 | --------------------------------------------------------------------------------