From 9d323e30e65c5f660d4b0bba07a2ea1c733ab987 Mon Sep 17 00:00:00 2001 From: Paul Razvan Berg Date: Tue, 26 Jul 2022 14:13:01 +0300 Subject: [PATCH 01/27] Modularize forge-std (#126) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: unbundle cheats from assertions refactor: new category StdUtils refactor: unbundle Test from Script * Rename "vm" to "vm_cheats" in "Cheats.sol" Mark "vm_cheats" as "private" Instantiate a "vm" in "Test.sol" * refactor: remove deprecated "lowLevelError" refactor: rename "vm_cheats" to just "vm" refactor: rename "vm_std_store" to just "vm" refactor: delete "INT256_MAX" and "UINT256_MAX" revert: redeclare "stdstore" in "Test" * refactor: move "stdErrors" to "Errors.sol" refactor: move "stdMath" to "Math.sol" * Add note about versions in "Errors.sol| Co-authored-by: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> * chore: delete stale delineators in Errors and Math chore: delete stale "using stdStorage for StdStorage" * refactor: modularize assertions and utils docs: add NatSpec tag @dev in "console2" refactor: delete log from "bound" function refactor: move "addressFromLast20Bytes" to "Utils.sol" refactor: move "bound" to "Utils.sol" refactor: move "computeCreateAddress" to "Utils.sol" style: move brackets on same line with "if" and "else" in "bound" * Log bound result with static call to `console.log` Co-authored-by: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> * fix: reintroduce "vm" in "Script.sol" chore: silence compiler warning in "bound" refactor: define console2.log address as constant in "Utils.sol" * test: move "testGenerateCorrectAddress" to "StdUtils.t.sol" * Nit: remove unnecessary "bytes20" casts * style: add white-spaces in "deal" * fix: readd "deployCode" functions with "val" * Add "computeCreate2Address" utility Rename "testGenerateCorrectAddress" to "testGenerateCreateAddress" * refactor: use "console2" in "Utils.sol" * style: end lines and white spaces * test: drop pragma to ">=0.8.0" in "StdError.t.sol" chore: remove comment about "v0.8.10" in "Errors.sol" * refactor: define "vm" and "stdStorage" in "TestBase" feat: add "Components.sol" file which re-exports everything * fix: inherit from DSTest in Test * feat: ScriptBase refactor: delete "TestBase.sol" refactor: move TestBase in "Test.sol" * ♻️ Make assertions virtual * ♻️ Make deployCode virtual * ✨ (Components) Export consoles * ♻️ (Script) Import Vm * ♻️ Import from Components * ♻️ Make bound view Co-authored-by: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> --- src/Assertions.sol | 232 +++++++++++ src/Cheats.sol | 180 +++++++++ src/Components.sol | 12 + src/Errors.sol | 15 + src/Math.sol | 45 +++ src/Script.sol | 40 +- src/Storage.sol | 249 ++++++++++++ src/Test.sol | 763 +---------------------------------- src/Utils.sol | 56 +++ src/console2.sol | 11 +- src/test/Script.t.sol | 12 - src/test/StdAssertions.t.sol | 6 +- src/test/StdCheats.t.sol | 49 +-- src/test/StdError.t.sol | 8 +- src/test/StdMath.t.sol | 4 +- src/test/StdStorage.t.sol | 5 +- src/test/StdUtils.t.sol | 64 +++ 17 files changed, 883 insertions(+), 868 deletions(-) create mode 100644 src/Assertions.sol create mode 100644 src/Cheats.sol create mode 100644 src/Components.sol create mode 100644 src/Errors.sol create mode 100644 src/Math.sol create mode 100644 src/Storage.sol create mode 100644 src/Utils.sol delete mode 100644 src/test/Script.t.sol create mode 100644 src/test/StdUtils.t.sol diff --git a/src/Assertions.sol b/src/Assertions.sol new file mode 100644 index 00000000..a52616a1 --- /dev/null +++ b/src/Assertions.sol @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.0 <0.9.0; + +import "ds-test/test.sol"; +import "./Math.sol"; + +contract Assertions is DSTest { + event log_array(uint256[] val); + event log_array(int256[] val); + event log_array(address[] val); + event log_named_array(string key, uint256[] val); + event log_named_array(string key, int256[] val); + event log_named_array(string key, address[] val); + + function fail(string memory err) internal virtual { + emit log_named_string("Error", err); + fail(); + } + + function assertFalse(bool data) internal virtual { + assertTrue(!data); + } + + function assertFalse(bool data, string memory err) internal virtual { + assertTrue(!data, err); + } + + function assertEq(bool a, bool b) internal virtual { + if (a != b) { + emit log ("Error: a == b not satisfied [bool]"); + emit log_named_string (" Expected", b ? "true" : "false"); + emit log_named_string (" Actual", a ? "true" : "false"); + fail(); + } + } + + function assertEq(bool a, bool b, string memory err) internal virtual { + if (a != b) { + emit log_named_string("Error", err); + assertEq(a, b); + } + } + + function assertEq(bytes memory a, bytes memory b) internal virtual { + assertEq0(a, b); + } + + function assertEq(bytes memory a, bytes memory b, string memory err) internal virtual { + assertEq0(a, b, err); + } + + function assertEq(uint256[] memory a, uint256[] memory b) internal virtual { + if (keccak256(abi.encode(a)) != keccak256(abi.encode(b))) { + emit log("Error: a == b not satisfied [uint[]]"); + emit log_named_array(" Expected", b); + emit log_named_array(" Actual", a); + fail(); + } + } + + function assertEq(int256[] memory a, int256[] memory b) internal virtual { + if (keccak256(abi.encode(a)) != keccak256(abi.encode(b))) { + emit log("Error: a == b not satisfied [int[]]"); + emit log_named_array(" Expected", b); + emit log_named_array(" Actual", a); + fail(); + } + } + + function assertEq(address[] memory a, address[] memory b) internal virtual { + if (keccak256(abi.encode(a)) != keccak256(abi.encode(b))) { + emit log("Error: a == b not satisfied [address[]]"); + emit log_named_array(" Expected", b); + emit log_named_array(" Actual", a); + fail(); + } + } + + function assertEq(uint256[] memory a, uint256[] memory b, string memory err) internal virtual { + if (keccak256(abi.encode(a)) != keccak256(abi.encode(b))) { + emit log_named_string("Error", err); + assertEq(a, b); + } + } + + function assertEq(int256[] memory a, int256[] memory b, string memory err) internal virtual { + if (keccak256(abi.encode(a)) != keccak256(abi.encode(b))) { + emit log_named_string("Error", err); + assertEq(a, b); + } + } + + + function assertEq(address[] memory a, address[] memory b, string memory err) internal virtual { + if (keccak256(abi.encode(a)) != keccak256(abi.encode(b))) { + emit log_named_string("Error", err); + assertEq(a, b); + } + } + + function assertApproxEqAbs( + uint256 a, + uint256 b, + uint256 maxDelta + ) internal virtual { + uint256 delta = stdMath.delta(a, b); + + if (delta > maxDelta) { + emit log ("Error: a ~= b not satisfied [uint]"); + emit log_named_uint (" Expected", b); + emit log_named_uint (" Actual", a); + emit log_named_uint (" Max Delta", maxDelta); + emit log_named_uint (" Delta", delta); + fail(); + } + } + + function assertApproxEqAbs( + uint256 a, + uint256 b, + uint256 maxDelta, + string memory err + ) internal virtual { + uint256 delta = stdMath.delta(a, b); + + if (delta > maxDelta) { + emit log_named_string ("Error", err); + assertApproxEqAbs(a, b, maxDelta); + } + } + + function assertApproxEqAbs( + int256 a, + int256 b, + uint256 maxDelta + ) internal virtual { + uint256 delta = stdMath.delta(a, b); + + if (delta > maxDelta) { + emit log ("Error: a ~= b not satisfied [int]"); + emit log_named_int (" Expected", b); + emit log_named_int (" Actual", a); + emit log_named_uint (" Max Delta", maxDelta); + emit log_named_uint (" Delta", delta); + fail(); + } + } + + function assertApproxEqAbs( + int256 a, + int256 b, + uint256 maxDelta, + string memory err + ) internal virtual { + uint256 delta = stdMath.delta(a, b); + + if (delta > maxDelta) { + emit log_named_string ("Error", err); + assertApproxEqAbs(a, b, maxDelta); + } + } + + function assertApproxEqRel( + uint256 a, + uint256 b, + uint256 maxPercentDelta // An 18 decimal fixed point number, where 1e18 == 100% + ) internal virtual { + if (b == 0) return assertEq(a, b); // If the expected is 0, actual must be too. + + uint256 percentDelta = stdMath.percentDelta(a, b); + + if (percentDelta > maxPercentDelta) { + emit log ("Error: a ~= b not satisfied [uint]"); + emit log_named_uint (" Expected", b); + emit log_named_uint (" Actual", a); + emit log_named_decimal_uint (" Max % Delta", maxPercentDelta, 18); + emit log_named_decimal_uint (" % Delta", percentDelta, 18); + fail(); + } + } + + function assertApproxEqRel( + uint256 a, + uint256 b, + uint256 maxPercentDelta, // An 18 decimal fixed point number, where 1e18 == 100% + string memory err + ) internal virtual { + if (b == 0) return assertEq(a, b); // If the expected is 0, actual must be too. + + uint256 percentDelta = stdMath.percentDelta(a, b); + + if (percentDelta > maxPercentDelta) { + emit log_named_string ("Error", err); + assertApproxEqRel(a, b, maxPercentDelta); + } + } + + function assertApproxEqRel( + int256 a, + int256 b, + uint256 maxPercentDelta + ) internal virtual { + if (b == 0) return assertEq(a, b); // If the expected is 0, actual must be too. + + uint256 percentDelta = stdMath.percentDelta(a, b); + + if (percentDelta > maxPercentDelta) { + emit log ("Error: a ~= b not satisfied [int]"); + emit log_named_int (" Expected", b); + emit log_named_int (" Actual", a); + emit log_named_decimal_uint(" Max % Delta", maxPercentDelta, 18); + emit log_named_decimal_uint(" % Delta", percentDelta, 18); + fail(); + } + } + + function assertApproxEqRel( + int256 a, + int256 b, + uint256 maxPercentDelta, + string memory err + ) internal virtual { + if (b == 0) return assertEq(a, b); // If the expected is 0, actual must be too. + + uint256 percentDelta = stdMath.percentDelta(a, b); + + if (percentDelta > maxPercentDelta) { + emit log_named_string ("Error", err); + assertApproxEqRel(a, b, maxPercentDelta); + } + } +} diff --git a/src/Cheats.sol b/src/Cheats.sol new file mode 100644 index 00000000..def74c57 --- /dev/null +++ b/src/Cheats.sol @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.0 <0.9.0; + +import "./Storage.sol"; +import "./Vm.sol"; + +// Wrappers around Cheats to avoid footguns +contract Cheats { + using stdStorage for StdStorage; + + StdStorage private stdstore; + Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + + // Skip forward or rewind time by the specified number of seconds + function skip(uint256 time) internal virtual { + vm.warp(block.timestamp + time); + } + + function rewind(uint256 time) internal virtual { + vm.warp(block.timestamp - time); + } + + // Setup a prank from an address that has some ether + function hoax(address who) internal virtual { + vm.deal(who, 1 << 128); + vm.prank(who); + } + + function hoax(address who, uint256 give) internal virtual { + vm.deal(who, give); + vm.prank(who); + } + + function hoax(address who, address origin) internal virtual { + vm.deal(who, 1 << 128); + vm.prank(who, origin); + } + + function hoax(address who, address origin, uint256 give) internal virtual { + vm.deal(who, give); + vm.prank(who, origin); + } + + // Start perpetual prank from an address that has some ether + function startHoax(address who) internal virtual { + vm.deal(who, 1 << 128); + vm.startPrank(who); + } + + function startHoax(address who, uint256 give) internal virtual { + vm.deal(who, give); + vm.startPrank(who); + } + + // Start perpetual prank from an address that has some ether + // tx.origin is set to the origin parameter + function startHoax(address who, address origin) internal virtual { + vm.deal(who, 1 << 128); + vm.startPrank(who, origin); + } + + function startHoax(address who, address origin, uint256 give) internal virtual { + vm.deal(who, give); + vm.startPrank(who, origin); + } + + function changePrank(address who) internal virtual { + vm.stopPrank(); + vm.startPrank(who); + } + + // The same as Vm's `deal` + // Use the alternative signature for ERC20 tokens + function deal(address to, uint256 give) internal virtual { + vm.deal(to, give); + } + + // Set the balance of an account for any ERC20 token + // Use the alternative signature to update `totalSupply` + function deal(address token, address to, uint256 give) internal virtual { + deal(token, to, give, false); + } + + function deal(address token, address to, uint256 give, bool adjust) internal virtual { + // get current balance + (, bytes memory balData) = token.call(abi.encodeWithSelector(0x70a08231, to)); + uint256 prevBal = abi.decode(balData, (uint256)); + + // update balance + stdstore + .target(token) + .sig(0x70a08231) + .with_key(to) + .checked_write(give); + + // update total supply + if (adjust) { + (, bytes memory totSupData) = token.call(abi.encodeWithSelector(0x18160ddd)); + uint256 totSup = abi.decode(totSupData, (uint256)); + if (give < prevBal) { + totSup -= (prevBal - give); + } else { + totSup += (give - prevBal); + } + stdstore + .target(token) + .sig(0x18160ddd) + .checked_write(totSup); + } + } + + // Deploy a contract by fetching the contract bytecode from + // the artifacts directory + // e.g. `deployCode(code, abi.encode(arg1,arg2,arg3))` + function deployCode(string memory what, bytes memory args) + internal virtual + returns (address addr) + { + bytes memory bytecode = abi.encodePacked(vm.getCode(what), args); + /// @solidity memory-safe-assembly + assembly { + addr := create(0, add(bytecode, 0x20), mload(bytecode)) + } + + require( + addr != address(0), + "Test deployCode(string,bytes): Deployment failed." + ); + } + + function deployCode(string memory what) + internal virtual + returns (address addr) + { + bytes memory bytecode = vm.getCode(what); + /// @solidity memory-safe-assembly + assembly { + addr := create(0, add(bytecode, 0x20), mload(bytecode)) + } + + require( + addr != address(0), + "Test deployCode(string): Deployment failed." + ); + } + + /// @dev deploy contract with value on construction + function deployCode(string memory what, bytes memory args, uint256 val) + internal virtual + returns (address addr) + { + bytes memory bytecode = abi.encodePacked(vm.getCode(what), args); + /// @solidity memory-safe-assembly + assembly { + addr := create(val, add(bytecode, 0x20), mload(bytecode)) + } + + require( + addr != address(0), + "Test deployCode(string,bytes,uint256): Deployment failed." + ); + } + + function deployCode(string memory what, uint256 val) + internal virtual + returns (address addr) + { + bytes memory bytecode = vm.getCode(what); + /// @solidity memory-safe-assembly + assembly { + addr := create(val, add(bytecode, 0x20), mload(bytecode)) + } + + require( + addr != address(0), + "Test deployCode(string,uint256): Deployment failed." + ); + } +} diff --git a/src/Components.sol b/src/Components.sol new file mode 100644 index 00000000..c5bfa3d5 --- /dev/null +++ b/src/Components.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.0 <0.9.0; + +import "./Assertions.sol"; +import "./Cheats.sol"; +import "./console.sol"; +import "./console2.sol"; +import "./Errors.sol"; +import "./Math.sol"; +import "./Storage.sol"; +import "./Utils.sol"; +import "./Vm.sol"; diff --git a/src/Errors.sol b/src/Errors.sol new file mode 100644 index 00000000..7657862f --- /dev/null +++ b/src/Errors.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +// Panics work for versions >=0.8.0, but we lowered the pragma to make this compatible with Test +pragma solidity >=0.6.0 <0.9.0; + +library stdError { + bytes public constant assertionError = abi.encodeWithSignature("Panic(uint256)", 0x01); + bytes public constant arithmeticError = abi.encodeWithSignature("Panic(uint256)", 0x11); + bytes public constant divisionError = abi.encodeWithSignature("Panic(uint256)", 0x12); + bytes public constant enumConversionError = abi.encodeWithSignature("Panic(uint256)", 0x21); + bytes public constant encodeStorageError = abi.encodeWithSignature("Panic(uint256)", 0x22); + bytes public constant popError = abi.encodeWithSignature("Panic(uint256)", 0x31); + bytes public constant indexOOBError = abi.encodeWithSignature("Panic(uint256)", 0x32); + bytes public constant memOverflowError = abi.encodeWithSignature("Panic(uint256)", 0x41); + bytes public constant zeroVarError = abi.encodeWithSignature("Panic(uint256)", 0x51); +} \ No newline at end of file diff --git a/src/Math.sol b/src/Math.sol new file mode 100644 index 00000000..5fb368e3 --- /dev/null +++ b/src/Math.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.0 <0.9.0; + +library stdMath { + int256 private constant INT256_MIN = -57896044618658097711785492504343953926634992332820282019728792003956564819968; + + function abs(int256 a) internal pure returns (uint256) { + // Required or it will fail when `a = type(int256).min` + if (a == INT256_MIN) { + return 57896044618658097711785492504343953926634992332820282019728792003956564819968; + } + + return uint256(a > 0 ? a : -a); + } + + function delta(uint256 a, uint256 b) internal pure returns (uint256) { + return a > b + ? a - b + : b - a; + } + + function delta(int256 a, int256 b) internal pure returns (uint256) { + // a and b are of the same sign + // this works thanks to two's complement, the left-most bit is the sign bit + if ((a ^ b) > -1) { + return delta(abs(a), abs(b)); + } + + // a and b are of opposite signs + return abs(a) + abs(b); + } + + function percentDelta(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 absDelta = delta(a, b); + + return absDelta * 1e18 / b; + } + + function percentDelta(int256 a, int256 b) internal pure returns (uint256) { + uint256 absDelta = delta(a, b); + uint256 absB = abs(b); + + return absDelta * 1e18 / absB; + } +} diff --git a/src/Script.sol b/src/Script.sol index 9bbd8599..8ebdbd80 100644 --- a/src/Script.sol +++ b/src/Script.sol @@ -1,39 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.6.0 <0.9.0; -import "./Vm.sol"; -import "./console.sol"; -import "./console2.sol"; +import {Cheats, console, console2, stdMath, stdStorage, StdStorage, Utils, Vm} from "./Components.sol"; -abstract contract Script { - bool public IS_SCRIPT = true; - address constant private VM_ADDRESS = - address(bytes20(uint160(uint256(keccak256('hevm cheat code'))))); - - Vm public constant vm = Vm(VM_ADDRESS); - - /// @dev Compute the address a contract will be deployed at for a given deployer address and nonce - /// @notice adapated from Solmate implementation (https://github.com/transmissions11/solmate/blob/main/src/utils/LibRLP.sol) - function computeCreateAddress(address deployer, uint256 nonce) internal pure returns (address) { - // The integer zero is treated as an empty byte string, and as a result it only has a length prefix, 0x80, computed via 0x80 + 0. - // A one byte integer uses its own value as its length prefix, there is no additional "0x80 + length" prefix that comes before it. - if (nonce == 0x00) return addressFromLast20Bytes(keccak256(abi.encodePacked(bytes1(0xd6), bytes1(0x94), deployer, bytes1(0x80)))); - if (nonce <= 0x7f) return addressFromLast20Bytes(keccak256(abi.encodePacked(bytes1(0xd6), bytes1(0x94), deployer, uint8(nonce)))); - - // Nonces greater than 1 byte all follow a consistent encoding scheme, where each value is preceded by a prefix of 0x80 + length. - if (nonce <= 2**8 - 1) return addressFromLast20Bytes(keccak256(abi.encodePacked(bytes1(0xd7), bytes1(0x94), deployer, bytes1(0x81), uint8(nonce)))); - if (nonce <= 2**16 - 1) return addressFromLast20Bytes(keccak256(abi.encodePacked(bytes1(0xd8), bytes1(0x94), deployer, bytes1(0x82), uint16(nonce)))); - if (nonce <= 2**24 - 1) return addressFromLast20Bytes(keccak256(abi.encodePacked(bytes1(0xd9), bytes1(0x94), deployer, bytes1(0x83), uint24(nonce)))); - - // More details about RLP encoding can be found here: https://eth.wiki/fundamentals/rlp - // 0xda = 0xc0 (short RLP prefix) + 0x16 (length of: 0x94 ++ proxy ++ 0x84 ++ nonce) - // 0x94 = 0x80 + 0x14 (0x14 = the length of an address, 20 bytes, in hex) - // 0x84 = 0x80 + 0x04 (0x04 = the bytes length of the nonce, 4 bytes, in hex) - // We assume nobody can have a nonce large enough to require more than 32 bytes. - return addressFromLast20Bytes(keccak256(abi.encodePacked(bytes1(0xda), bytes1(0x94), deployer, bytes1(0x84), uint32(nonce)))); - } +abstract contract ScriptBase { + address internal constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); + Vm internal constant vm = Vm(VM_ADDRESS); +} - function addressFromLast20Bytes(bytes32 bytesValue) internal pure returns (address) { - return address(uint160(uint256(bytesValue))); - } +abstract contract Script is ScriptBase, Cheats, Utils { + bool public IS_SCRIPT = true; } diff --git a/src/Storage.sol b/src/Storage.sol new file mode 100644 index 00000000..7e4e260b --- /dev/null +++ b/src/Storage.sol @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.0 <0.9.0; + +import "./Vm.sol"; + +struct StdStorage { + mapping (address => mapping(bytes4 => mapping(bytes32 => uint256))) slots; + mapping (address => mapping(bytes4 => mapping(bytes32 => bool))) finds; + + bytes32[] _keys; + bytes4 _sig; + uint256 _depth; + address _target; + bytes32 _set; +} + +library stdStorage { + event SlotFound(address who, bytes4 fsig, bytes32 keysHash, uint slot); + event WARNING_UninitedSlot(address who, uint slot); + + Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + function sigs( + string memory sigStr + ) + internal + pure + returns (bytes4) + { + return bytes4(keccak256(bytes(sigStr))); + } + + /// @notice find an arbitrary storage slot given a function sig, input data, address of the contract and a value to check against + // slot complexity: + // if flat, will be bytes32(uint256(uint)); + // if map, will be keccak256(abi.encode(key, uint(slot))); + // if deep map, will be keccak256(abi.encode(key1, keccak256(abi.encode(key0, uint(slot))))); + // if map struct, will be bytes32(uint256(keccak256(abi.encode(key1, keccak256(abi.encode(key0, uint(slot)))))) + structFieldDepth); + function find( + StdStorage storage self + ) + internal + returns (uint256) + { + address who = self._target; + bytes4 fsig = self._sig; + uint256 field_depth = self._depth; + bytes32[] memory ins = self._keys; + + // calldata to test against + if (self.finds[who][fsig][keccak256(abi.encodePacked(ins, field_depth))]) { + return self.slots[who][fsig][keccak256(abi.encodePacked(ins, field_depth))]; + } + bytes memory cald = abi.encodePacked(fsig, flatten(ins)); + vm.record(); + bytes32 fdat; + { + (, bytes memory rdat) = who.staticcall(cald); + fdat = bytesToBytes32(rdat, 32*field_depth); + } + + (bytes32[] memory reads, ) = vm.accesses(address(who)); + if (reads.length == 1) { + bytes32 curr = vm.load(who, reads[0]); + if (curr == bytes32(0)) { + emit WARNING_UninitedSlot(who, uint256(reads[0])); + } + if (fdat != curr) { + require(false, "stdStorage find(StdStorage): Packed slot. This would cause dangerous overwriting and currently isn't supported."); + } + emit SlotFound(who, fsig, keccak256(abi.encodePacked(ins, field_depth)), uint256(reads[0])); + self.slots[who][fsig][keccak256(abi.encodePacked(ins, field_depth))] = uint256(reads[0]); + self.finds[who][fsig][keccak256(abi.encodePacked(ins, field_depth))] = true; + } else if (reads.length > 1) { + for (uint256 i = 0; i < reads.length; i++) { + bytes32 prev = vm.load(who, reads[i]); + if (prev == bytes32(0)) { + emit WARNING_UninitedSlot(who, uint256(reads[i])); + } + // store + vm.store(who, reads[i], bytes32(hex"1337")); + bool success; + bytes memory rdat; + { + (success, rdat) = who.staticcall(cald); + fdat = bytesToBytes32(rdat, 32*field_depth); + } + + if (success && fdat == bytes32(hex"1337")) { + // we found which of the slots is the actual one + emit SlotFound(who, fsig, keccak256(abi.encodePacked(ins, field_depth)), uint256(reads[i])); + self.slots[who][fsig][keccak256(abi.encodePacked(ins, field_depth))] = uint256(reads[i]); + self.finds[who][fsig][keccak256(abi.encodePacked(ins, field_depth))] = true; + vm.store(who, reads[i], prev); + break; + } + vm.store(who, reads[i], prev); + } + } else { + require(false, "stdStorage find(StdStorage): No storage use detected for target."); + } + + require(self.finds[who][fsig][keccak256(abi.encodePacked(ins, field_depth))], "stdStorage find(StdStorage): Slot(s) not found."); + + delete self._target; + delete self._sig; + delete self._keys; + delete self._depth; + + return self.slots[who][fsig][keccak256(abi.encodePacked(ins, field_depth))]; + } + + function target(StdStorage storage self, address _target) internal returns (StdStorage storage) { + self._target = _target; + return self; + } + + function sig(StdStorage storage self, bytes4 _sig) internal returns (StdStorage storage) { + self._sig = _sig; + return self; + } + + function sig(StdStorage storage self, string memory _sig) internal returns (StdStorage storage) { + self._sig = sigs(_sig); + return self; + } + + function with_key(StdStorage storage self, address who) internal returns (StdStorage storage) { + self._keys.push(bytes32(uint256(uint160(who)))); + return self; + } + + function with_key(StdStorage storage self, uint256 amt) internal returns (StdStorage storage) { + self._keys.push(bytes32(amt)); + return self; + } + function with_key(StdStorage storage self, bytes32 key) internal returns (StdStorage storage) { + self._keys.push(key); + return self; + } + + function depth(StdStorage storage self, uint256 _depth) internal returns (StdStorage storage) { + self._depth = _depth; + return self; + } + + function checked_write(StdStorage storage self, address who) internal { + checked_write(self, bytes32(uint256(uint160(who)))); + } + + function checked_write(StdStorage storage self, uint256 amt) internal { + checked_write(self, bytes32(amt)); + } + + function checked_write(StdStorage storage self, bool write) internal { + bytes32 t; + /// @solidity memory-safe-assembly + assembly { + t := write + } + checked_write(self, t); + } + + function checked_write( + StdStorage storage self, + bytes32 set + ) internal { + address who = self._target; + bytes4 fsig = self._sig; + uint256 field_depth = self._depth; + bytes32[] memory ins = self._keys; + + bytes memory cald = abi.encodePacked(fsig, flatten(ins)); + if (!self.finds[who][fsig][keccak256(abi.encodePacked(ins, field_depth))]) { + find(self); + } + bytes32 slot = bytes32(self.slots[who][fsig][keccak256(abi.encodePacked(ins, field_depth))]); + + bytes32 fdat; + { + (, bytes memory rdat) = who.staticcall(cald); + fdat = bytesToBytes32(rdat, 32*field_depth); + } + bytes32 curr = vm.load(who, slot); + + if (fdat != curr) { + require(false, "stdStorage find(StdStorage): Packed slot. This would cause dangerous overwriting and currently isn't supported."); + } + vm.store(who, slot, set); + delete self._target; + delete self._sig; + delete self._keys; + delete self._depth; + } + + function read(StdStorage storage self) private returns (bytes memory) { + address t = self._target; + uint256 s = find(self); + return abi.encode(vm.load(t, bytes32(s))); + } + + function read_bytes32(StdStorage storage self) internal returns (bytes32) { + return abi.decode(read(self), (bytes32)); + } + + + function read_bool(StdStorage storage self) internal returns (bool) { + int256 v = read_int(self); + if (v == 0) return false; + if (v == 1) return true; + revert("stdStorage read_bool(StdStorage): Cannot decode. Make sure you are reading a bool."); + } + + function read_address(StdStorage storage self) internal returns (address) { + return abi.decode(read(self), (address)); + } + + function read_uint(StdStorage storage self) internal returns (uint256) { + return abi.decode(read(self), (uint256)); + } + + function read_int(StdStorage storage self) internal returns (int256) { + return abi.decode(read(self), (int256)); + } + + function bytesToBytes32(bytes memory b, uint offset) public pure returns (bytes32) { + bytes32 out; + + uint256 max = b.length > 32 ? 32 : b.length; + for (uint i = 0; i < max; i++) { + out |= bytes32(b[offset + i] & 0xFF) >> (i * 8); + } + return out; + } + + function flatten(bytes32[] memory b) private pure returns (bytes memory) + { + bytes memory result = new bytes(b.length * 32); + for (uint256 i = 0; i < b.length; i++) { + bytes32 k = b[i]; + /// @solidity memory-safe-assembly + assembly { + mstore(add(result, add(32, mul(32, i))), k) + } + } + + return result; + } +} diff --git a/src/Test.sol b/src/Test.sol index da549594..e6f8aa7b 100644 --- a/src/Test.sol +++ b/src/Test.sol @@ -1,767 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.6.0 <0.9.0; -import "./Script.sol"; +import "./Components.sol"; import "ds-test/test.sol"; -// Wrappers around Cheatcodes to avoid footguns -abstract contract Test is DSTest, Script { - using stdStorage for StdStorage; - - uint256 internal constant UINT256_MAX = - 115792089237316195423570985008687907853269984665640564039457584007913129639935; +abstract contract TestBase { + address internal constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); StdStorage internal stdstore; - - /*////////////////////////////////////////////////////////////////////////// - STD-LOGS - //////////////////////////////////////////////////////////////////////////*/ - - event log_array(uint256[] val); - event log_array(int256[] val); - event log_array(address[] val); - event log_named_array(string key, uint256[] val); - event log_named_array(string key, int256[] val); - event log_named_array(string key, address[] val); - - /*////////////////////////////////////////////////////////////////////////// - STD-CHEATS - //////////////////////////////////////////////////////////////////////////*/ - - // Skip forward or rewind time by the specified number of seconds - function skip(uint256 time) internal { - vm.warp(block.timestamp + time); - } - - function rewind(uint256 time) internal { - vm.warp(block.timestamp - time); - } - - // Setup a prank from an address that has some ether - function hoax(address who) internal { - vm.deal(who, 1 << 128); - vm.prank(who); - } - - function hoax(address who, uint256 give) internal { - vm.deal(who, give); - vm.prank(who); - } - - function hoax(address who, address origin) internal { - vm.deal(who, 1 << 128); - vm.prank(who, origin); - } - - function hoax(address who, address origin, uint256 give) internal { - vm.deal(who, give); - vm.prank(who, origin); - } - - // Start perpetual prank from an address that has some ether - function startHoax(address who) internal { - vm.deal(who, 1 << 128); - vm.startPrank(who); - } - - function startHoax(address who, uint256 give) internal { - vm.deal(who, give); - vm.startPrank(who); - } - - // Start perpetual prank from an address that has some ether - // tx.origin is set to the origin parameter - function startHoax(address who, address origin) internal { - vm.deal(who, 1 << 128); - vm.startPrank(who, origin); - } - - function startHoax(address who, address origin, uint256 give) internal { - vm.deal(who, give); - vm.startPrank(who, origin); - } - - function changePrank(address who) internal { - vm.stopPrank(); - vm.startPrank(who); - } - - // DEPRECATED: Use `deal` instead - function tip(address token, address to, uint256 give) internal { - emit log_named_string("WARNING", "Test tip(address,address,uint256): The `tip` stdcheat has been deprecated. Use `deal` instead."); - stdstore - .target(token) - .sig(0x70a08231) - .with_key(to) - .checked_write(give); - } - - // The same as Vm's `deal` - // Use the alternative signature for ERC20 tokens - function deal(address to, uint256 give) internal { - vm.deal(to, give); - } - - // Set the balance of an account for any ERC20 token - // Use the alternative signature to update `totalSupply` - function deal(address token, address to, uint256 give) internal { - deal(token, to, give, false); - } - - function deal(address token, address to, uint256 give, bool adjust) internal { - // get current balance - (, bytes memory balData) = token.call(abi.encodeWithSelector(0x70a08231, to)); - uint256 prevBal = abi.decode(balData, (uint256)); - - // update balance - stdstore - .target(token) - .sig(0x70a08231) - .with_key(to) - .checked_write(give); - - // update total supply - if(adjust){ - (, bytes memory totSupData) = token.call(abi.encodeWithSelector(0x18160ddd)); - uint256 totSup = abi.decode(totSupData, (uint256)); - if(give < prevBal) { - totSup -= (prevBal - give); - } else { - totSup += (give - prevBal); - } - stdstore - .target(token) - .sig(0x18160ddd) - .checked_write(totSup); - } - } - - function bound(uint256 x, uint256 min, uint256 max) internal virtual returns (uint256 result) { - require(min <= max, "Test bound(uint256,uint256,uint256): Max is less than min."); - - uint256 size = max - min; - - if (size == 0) - { - result = min; - } - else if (size == UINT256_MAX) - { - result = x; - } - else - { - ++size; // make `max` inclusive - uint256 mod = x % size; - result = min + mod; - } - - emit log_named_uint("Bound Result", result); - } - - // Deploy a contract by fetching the contract bytecode from - // the artifacts directory - // e.g. `deployCode(code, abi.encode(arg1,arg2,arg3))` - function deployCode(string memory what, bytes memory args) - internal - returns (address addr) - { - bytes memory bytecode = abi.encodePacked(vm.getCode(what), args); - /// @solidity memory-safe-assembly - assembly { - addr := create(0, add(bytecode, 0x20), mload(bytecode)) - } - - require( - addr != address(0), - "Test deployCode(string,bytes): Deployment failed." - ); - } - - function deployCode(string memory what) - internal - returns (address addr) - { - bytes memory bytecode = vm.getCode(what); - /// @solidity memory-safe-assembly - assembly { - addr := create(0, add(bytecode, 0x20), mload(bytecode)) - } - - require( - addr != address(0), - "Test deployCode(string): Deployment failed." - ); - } - - /// deploy contract with value on construction - function deployCode(string memory what, bytes memory args, uint256 val) - internal - returns (address addr) - { - bytes memory bytecode = abi.encodePacked(vm.getCode(what), args); - /// @solidity memory-safe-assembly - assembly { - addr := create(val, add(bytecode, 0x20), mload(bytecode)) - } - - require( - addr != address(0), - "Test deployCode(string,bytes,uint256): Deployment failed." - ); - } - - function deployCode(string memory what, uint256 val) - internal - returns (address addr) - { - bytes memory bytecode = vm.getCode(what); - /// @solidity memory-safe-assembly - assembly { - addr := create(val, add(bytecode, 0x20), mload(bytecode)) - } - - require( - addr != address(0), - "Test deployCode(string,uint256): Deployment failed." - ); - } - - /*////////////////////////////////////////////////////////////////////////// - STD-ASSERTIONS - //////////////////////////////////////////////////////////////////////////*/ - - function fail(string memory err) internal virtual { - emit log_named_string("Error", err); - fail(); - } - - function assertFalse(bool data) internal virtual { - assertTrue(!data); - } - - function assertFalse(bool data, string memory err) internal virtual { - assertTrue(!data, err); - } - - function assertEq(bool a, bool b) internal { - if (a != b) { - emit log ("Error: a == b not satisfied [bool]"); - emit log_named_string (" Expected", b ? "true" : "false"); - emit log_named_string (" Actual", a ? "true" : "false"); - fail(); - } - } - - function assertEq(bool a, bool b, string memory err) internal { - if (a != b) { - emit log_named_string("Error", err); - assertEq(a, b); - } - } - - function assertEq(bytes memory a, bytes memory b) internal { - assertEq0(a, b); - } - - function assertEq(bytes memory a, bytes memory b, string memory err) internal { - assertEq0(a, b, err); - } - - function assertEq(uint256[] memory a, uint256[] memory b) internal { - if (keccak256(abi.encode(a)) != keccak256(abi.encode(b))) { - emit log("Error: a == b not satisfied [uint[]]"); - emit log_named_array(" Expected", b); - emit log_named_array(" Actual", a); - fail(); - } - } - - function assertEq(int256[] memory a, int256[] memory b) internal { - if (keccak256(abi.encode(a)) != keccak256(abi.encode(b))) { - emit log("Error: a == b not satisfied [int[]]"); - emit log_named_array(" Expected", b); - emit log_named_array(" Actual", a); - fail(); - } - } - - function assertEq(address[] memory a, address[] memory b) internal { - if (keccak256(abi.encode(a)) != keccak256(abi.encode(b))) { - emit log("Error: a == b not satisfied [address[]]"); - emit log_named_array(" Expected", b); - emit log_named_array(" Actual", a); - fail(); - } - } - - function assertEq(uint256[] memory a, uint256[] memory b, string memory err) internal { - if (keccak256(abi.encode(a)) != keccak256(abi.encode(b))) { - emit log_named_string("Error", err); - assertEq(a, b); - } - } - - function assertEq(int256[] memory a, int256[] memory b, string memory err) internal { - if (keccak256(abi.encode(a)) != keccak256(abi.encode(b))) { - emit log_named_string("Error", err); - assertEq(a, b); - } - } - - - function assertEq(address[] memory a, address[] memory b, string memory err) internal { - if (keccak256(abi.encode(a)) != keccak256(abi.encode(b))) { - emit log_named_string("Error", err); - assertEq(a, b); - } - } - - function assertApproxEqAbs( - uint256 a, - uint256 b, - uint256 maxDelta - ) internal virtual { - uint256 delta = stdMath.delta(a, b); - - if (delta > maxDelta) { - emit log ("Error: a ~= b not satisfied [uint]"); - emit log_named_uint (" Expected", b); - emit log_named_uint (" Actual", a); - emit log_named_uint (" Max Delta", maxDelta); - emit log_named_uint (" Delta", delta); - fail(); - } - } - - function assertApproxEqAbs( - uint256 a, - uint256 b, - uint256 maxDelta, - string memory err - ) internal virtual { - uint256 delta = stdMath.delta(a, b); - - if (delta > maxDelta) { - emit log_named_string ("Error", err); - assertApproxEqAbs(a, b, maxDelta); - } - } - - function assertApproxEqAbs( - int256 a, - int256 b, - uint256 maxDelta - ) internal virtual { - uint256 delta = stdMath.delta(a, b); - - if (delta > maxDelta) { - emit log ("Error: a ~= b not satisfied [int]"); - emit log_named_int (" Expected", b); - emit log_named_int (" Actual", a); - emit log_named_uint (" Max Delta", maxDelta); - emit log_named_uint (" Delta", delta); - fail(); - } - } - - function assertApproxEqAbs( - int256 a, - int256 b, - uint256 maxDelta, - string memory err - ) internal virtual { - uint256 delta = stdMath.delta(a, b); - - if (delta > maxDelta) { - emit log_named_string ("Error", err); - assertApproxEqAbs(a, b, maxDelta); - } - } - - function assertApproxEqRel( - uint256 a, - uint256 b, - uint256 maxPercentDelta // An 18 decimal fixed point number, where 1e18 == 100% - ) internal virtual { - if (b == 0) return assertEq(a, b); // If the expected is 0, actual must be too. - - uint256 percentDelta = stdMath.percentDelta(a, b); - - if (percentDelta > maxPercentDelta) { - emit log ("Error: a ~= b not satisfied [uint]"); - emit log_named_uint (" Expected", b); - emit log_named_uint (" Actual", a); - emit log_named_decimal_uint (" Max % Delta", maxPercentDelta, 18); - emit log_named_decimal_uint (" % Delta", percentDelta, 18); - fail(); - } - } - - function assertApproxEqRel( - uint256 a, - uint256 b, - uint256 maxPercentDelta, // An 18 decimal fixed point number, where 1e18 == 100% - string memory err - ) internal virtual { - if (b == 0) return assertEq(a, b); // If the expected is 0, actual must be too. - - uint256 percentDelta = stdMath.percentDelta(a, b); - - if (percentDelta > maxPercentDelta) { - emit log_named_string ("Error", err); - assertApproxEqRel(a, b, maxPercentDelta); - } - } - - function assertApproxEqRel( - int256 a, - int256 b, - uint256 maxPercentDelta - ) internal virtual { - if (b == 0) return assertEq(a, b); // If the expected is 0, actual must be too. - - uint256 percentDelta = stdMath.percentDelta(a, b); - - if (percentDelta > maxPercentDelta) { - emit log ("Error: a ~= b not satisfied [int]"); - emit log_named_int (" Expected", b); - emit log_named_int (" Actual", a); - emit log_named_decimal_uint(" Max % Delta", maxPercentDelta, 18); - emit log_named_decimal_uint(" % Delta", percentDelta, 18); - fail(); - } - } - - function assertApproxEqRel( - int256 a, - int256 b, - uint256 maxPercentDelta, - string memory err - ) internal virtual { - if (b == 0) return assertEq(a, b); // If the expected is 0, actual must be too. - - uint256 percentDelta = stdMath.percentDelta(a, b); - - if (percentDelta > maxPercentDelta) { - emit log_named_string ("Error", err); - assertApproxEqRel(a, b, maxPercentDelta); - } - } -} - -/*////////////////////////////////////////////////////////////////////////// - STD-ERRORS -//////////////////////////////////////////////////////////////////////////*/ - -library stdError { - bytes public constant assertionError = abi.encodeWithSignature("Panic(uint256)", 0x01); - bytes public constant arithmeticError = abi.encodeWithSignature("Panic(uint256)", 0x11); - bytes public constant divisionError = abi.encodeWithSignature("Panic(uint256)", 0x12); - bytes public constant enumConversionError = abi.encodeWithSignature("Panic(uint256)", 0x21); - bytes public constant encodeStorageError = abi.encodeWithSignature("Panic(uint256)", 0x22); - bytes public constant popError = abi.encodeWithSignature("Panic(uint256)", 0x31); - bytes public constant indexOOBError = abi.encodeWithSignature("Panic(uint256)", 0x32); - bytes public constant memOverflowError = abi.encodeWithSignature("Panic(uint256)", 0x41); - bytes public constant zeroVarError = abi.encodeWithSignature("Panic(uint256)", 0x51); - // DEPRECATED: Use Vm's `expectRevert` without any arguments instead - bytes public constant lowLevelError = bytes(""); // `0x` -} - -/*////////////////////////////////////////////////////////////////////////// - STD-STORAGE -//////////////////////////////////////////////////////////////////////////*/ - -struct StdStorage { - mapping (address => mapping(bytes4 => mapping(bytes32 => uint256))) slots; - mapping (address => mapping(bytes4 => mapping(bytes32 => bool))) finds; - - bytes32[] _keys; - bytes4 _sig; - uint256 _depth; - address _target; - bytes32 _set; + Vm internal constant vm = Vm(VM_ADDRESS); } -library stdStorage { - event SlotFound(address who, bytes4 fsig, bytes32 keysHash, uint slot); - event WARNING_UninitedSlot(address who, uint slot); - - uint256 private constant UINT256_MAX = 115792089237316195423570985008687907853269984665640564039457584007913129639935; - int256 private constant INT256_MAX = 57896044618658097711785492504343953926634992332820282019728792003956564819967; - - Vm private constant vm_std_store = Vm(address(uint160(uint256(keccak256('hevm cheat code'))))); - - function sigs( - string memory sigStr - ) - internal - pure - returns (bytes4) - { - return bytes4(keccak256(bytes(sigStr))); - } - - /// @notice find an arbitrary storage slot given a function sig, input data, address of the contract and a value to check against - // slot complexity: - // if flat, will be bytes32(uint256(uint)); - // if map, will be keccak256(abi.encode(key, uint(slot))); - // if deep map, will be keccak256(abi.encode(key1, keccak256(abi.encode(key0, uint(slot))))); - // if map struct, will be bytes32(uint256(keccak256(abi.encode(key1, keccak256(abi.encode(key0, uint(slot)))))) + structFieldDepth); - function find( - StdStorage storage self - ) - internal - returns (uint256) - { - address who = self._target; - bytes4 fsig = self._sig; - uint256 field_depth = self._depth; - bytes32[] memory ins = self._keys; - - // calldata to test against - if (self.finds[who][fsig][keccak256(abi.encodePacked(ins, field_depth))]) { - return self.slots[who][fsig][keccak256(abi.encodePacked(ins, field_depth))]; - } - bytes memory cald = abi.encodePacked(fsig, flatten(ins)); - vm_std_store.record(); - bytes32 fdat; - { - (, bytes memory rdat) = who.staticcall(cald); - fdat = bytesToBytes32(rdat, 32*field_depth); - } - - (bytes32[] memory reads, ) = vm_std_store.accesses(address(who)); - if (reads.length == 1) { - bytes32 curr = vm_std_store.load(who, reads[0]); - if (curr == bytes32(0)) { - emit WARNING_UninitedSlot(who, uint256(reads[0])); - } - if (fdat != curr) { - require(false, "stdStorage find(StdStorage): Packed slot. This would cause dangerous overwriting and currently isn't supported."); - } - emit SlotFound(who, fsig, keccak256(abi.encodePacked(ins, field_depth)), uint256(reads[0])); - self.slots[who][fsig][keccak256(abi.encodePacked(ins, field_depth))] = uint256(reads[0]); - self.finds[who][fsig][keccak256(abi.encodePacked(ins, field_depth))] = true; - } else if (reads.length > 1) { - for (uint256 i = 0; i < reads.length; i++) { - bytes32 prev = vm_std_store.load(who, reads[i]); - if (prev == bytes32(0)) { - emit WARNING_UninitedSlot(who, uint256(reads[i])); - } - // store - vm_std_store.store(who, reads[i], bytes32(hex"1337")); - bool success; - bytes memory rdat; - { - (success, rdat) = who.staticcall(cald); - fdat = bytesToBytes32(rdat, 32*field_depth); - } - - if (success && fdat == bytes32(hex"1337")) { - // we found which of the slots is the actual one - emit SlotFound(who, fsig, keccak256(abi.encodePacked(ins, field_depth)), uint256(reads[i])); - self.slots[who][fsig][keccak256(abi.encodePacked(ins, field_depth))] = uint256(reads[i]); - self.finds[who][fsig][keccak256(abi.encodePacked(ins, field_depth))] = true; - vm_std_store.store(who, reads[i], prev); - break; - } - vm_std_store.store(who, reads[i], prev); - } - } else { - require(false, "stdStorage find(StdStorage): No storage use detected for target."); - } - - require(self.finds[who][fsig][keccak256(abi.encodePacked(ins, field_depth))], "stdStorage find(StdStorage): Slot(s) not found."); - - delete self._target; - delete self._sig; - delete self._keys; - delete self._depth; - - return self.slots[who][fsig][keccak256(abi.encodePacked(ins, field_depth))]; - } - - function target(StdStorage storage self, address _target) internal returns (StdStorage storage) { - self._target = _target; - return self; - } - - function sig(StdStorage storage self, bytes4 _sig) internal returns (StdStorage storage) { - self._sig = _sig; - return self; - } - - function sig(StdStorage storage self, string memory _sig) internal returns (StdStorage storage) { - self._sig = sigs(_sig); - return self; - } - - function with_key(StdStorage storage self, address who) internal returns (StdStorage storage) { - self._keys.push(bytes32(uint256(uint160(who)))); - return self; - } - - function with_key(StdStorage storage self, uint256 amt) internal returns (StdStorage storage) { - self._keys.push(bytes32(amt)); - return self; - } - function with_key(StdStorage storage self, bytes32 key) internal returns (StdStorage storage) { - self._keys.push(key); - return self; - } - - function depth(StdStorage storage self, uint256 _depth) internal returns (StdStorage storage) { - self._depth = _depth; - return self; - } - - function checked_write(StdStorage storage self, address who) internal { - checked_write(self, bytes32(uint256(uint160(who)))); - } - - function checked_write(StdStorage storage self, uint256 amt) internal { - checked_write(self, bytes32(amt)); - } - - function checked_write(StdStorage storage self, bool write) internal { - bytes32 t; - /// @solidity memory-safe-assembly - assembly { - t := write - } - checked_write(self, t); - } - - function checked_write( - StdStorage storage self, - bytes32 set - ) internal { - address who = self._target; - bytes4 fsig = self._sig; - uint256 field_depth = self._depth; - bytes32[] memory ins = self._keys; - - bytes memory cald = abi.encodePacked(fsig, flatten(ins)); - if (!self.finds[who][fsig][keccak256(abi.encodePacked(ins, field_depth))]) { - find(self); - } - bytes32 slot = bytes32(self.slots[who][fsig][keccak256(abi.encodePacked(ins, field_depth))]); - - bytes32 fdat; - { - (, bytes memory rdat) = who.staticcall(cald); - fdat = bytesToBytes32(rdat, 32*field_depth); - } - bytes32 curr = vm_std_store.load(who, slot); - - if (fdat != curr) { - require(false, "stdStorage find(StdStorage): Packed slot. This would cause dangerous overwriting and currently isn't supported."); - } - vm_std_store.store(who, slot, set); - delete self._target; - delete self._sig; - delete self._keys; - delete self._depth; - } - - function read(StdStorage storage self) private returns (bytes memory) { - address t = self._target; - uint256 s = find(self); - return abi.encode(vm_std_store.load(t, bytes32(s))); - } - - function read_bytes32(StdStorage storage self) internal returns (bytes32) { - return abi.decode(read(self), (bytes32)); - } - - - function read_bool(StdStorage storage self) internal returns (bool) { - int256 v = read_int(self); - if (v == 0) return false; - if (v == 1) return true; - revert("stdStorage read_bool(StdStorage): Cannot decode. Make sure you are reading a bool."); - } - - function read_address(StdStorage storage self) internal returns (address) { - return abi.decode(read(self), (address)); - } - - function read_uint(StdStorage storage self) internal returns (uint256) { - return abi.decode(read(self), (uint256)); - } - - function read_int(StdStorage storage self) internal returns (int256) { - return abi.decode(read(self), (int256)); - } - - function bytesToBytes32(bytes memory b, uint offset) public pure returns (bytes32) { - bytes32 out; - - uint256 max = b.length > 32 ? 32 : b.length; - for (uint i = 0; i < max; i++) { - out |= bytes32(b[offset + i] & 0xFF) >> (i * 8); - } - return out; - } - - function flatten(bytes32[] memory b) private pure returns (bytes memory) - { - bytes memory result = new bytes(b.length * 32); - for (uint256 i = 0; i < b.length; i++) { - bytes32 k = b[i]; - /// @solidity memory-safe-assembly - assembly { - mstore(add(result, add(32, mul(32, i))), k) - } - } - - return result; - } -} - -/*////////////////////////////////////////////////////////////////////////// - STD-MATH -//////////////////////////////////////////////////////////////////////////*/ - -library stdMath { - int256 private constant INT256_MIN = -57896044618658097711785492504343953926634992332820282019728792003956564819968; - - function abs(int256 a) internal pure returns (uint256) { - // Required or it will fail when `a = type(int256).min` - if (a == INT256_MIN) - return 57896044618658097711785492504343953926634992332820282019728792003956564819968; - - return uint256(a > 0 ? a : -a); - } - - function delta(uint256 a, uint256 b) internal pure returns (uint256) { - return a > b - ? a - b - : b - a; - } - - function delta(int256 a, int256 b) internal pure returns (uint256) { - // a and b are of the same sign - // this works thanks to two's complement, the left-most bit is the sign bit - if ((a ^ b) > -1) { - return delta(abs(a), abs(b)); - } - - // a and b are of opposite signs - return abs(a) + abs(b); - } - - function percentDelta(uint256 a, uint256 b) internal pure returns (uint256) { - uint256 absDelta = delta(a, b); - - return absDelta * 1e18 / b; - } - - function percentDelta(int256 a, int256 b) internal pure returns (uint256) { - uint256 absDelta = delta(a, b); - uint256 absB = abs(b); - - return absDelta * 1e18 / absB; - } -} +abstract contract Test is TestBase, DSTest, Assertions, Cheats, Utils {} diff --git a/src/Utils.sol b/src/Utils.sol new file mode 100644 index 00000000..0694f568 --- /dev/null +++ b/src/Utils.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.0 <0.9.0; + +import "./console2.sol"; + +contract Utils { + uint256 internal constant UINT256_MAX = + 115792089237316195423570985008687907853269984665640564039457584007913129639935; + + function bound(uint256 x, uint256 min, uint256 max) internal view virtual returns (uint256 result) { + require(min <= max, "Test bound(uint256,uint256,uint256): Max is less than min."); + + uint256 size = max - min; + + if (size == 0) { + result = min; + } else if (size == UINT256_MAX) { + result = x; + } else { + ++size; // make `max` inclusive + uint256 mod = x % size; + result = min + mod; + } + + console2.log("Bound Result", result); + } + + /// @dev Compute the address a contract will be deployed at for a given deployer address and nonce + /// @notice adapated from Solmate implementation (https://github.com/Rari-Capital/solmate/blob/main/src/utils/LibRLP.sol) + function computeCreateAddress(address deployer, uint256 nonce) internal pure virtual returns (address) { + // The integer zero is treated as an empty byte string, and as a result it only has a length prefix, 0x80, computed via 0x80 + 0. + // A one byte integer uses its own value as its length prefix, there is no additional "0x80 + length" prefix that comes before it. + if (nonce == 0x00) return addressFromLast20Bytes(keccak256(abi.encodePacked(bytes1(0xd6), bytes1(0x94), deployer, bytes1(0x80)))); + if (nonce <= 0x7f) return addressFromLast20Bytes(keccak256(abi.encodePacked(bytes1(0xd6), bytes1(0x94), deployer, uint8(nonce)))); + + // Nonces greater than 1 byte all follow a consistent encoding scheme, where each value is preceded by a prefix of 0x80 + length. + if (nonce <= 2**8 - 1) return addressFromLast20Bytes(keccak256(abi.encodePacked(bytes1(0xd7), bytes1(0x94), deployer, bytes1(0x81), uint8(nonce)))); + if (nonce <= 2**16 - 1) return addressFromLast20Bytes(keccak256(abi.encodePacked(bytes1(0xd8), bytes1(0x94), deployer, bytes1(0x82), uint16(nonce)))); + if (nonce <= 2**24 - 1) return addressFromLast20Bytes(keccak256(abi.encodePacked(bytes1(0xd9), bytes1(0x94), deployer, bytes1(0x83), uint24(nonce)))); + + // More details about RLP encoding can be found here: https://eth.wiki/fundamentals/rlp + // 0xda = 0xc0 (short RLP prefix) + 0x16 (length of: 0x94 ++ proxy ++ 0x84 ++ nonce) + // 0x94 = 0x80 + 0x14 (0x14 = the length of an address, 20 bytes, in hex) + // 0x84 = 0x80 + 0x04 (0x04 = the bytes length of the nonce, 4 bytes, in hex) + // We assume nobody can have a nonce large enough to require more than 32 bytes. + return addressFromLast20Bytes(keccak256(abi.encodePacked(bytes1(0xda), bytes1(0x94), deployer, bytes1(0x84), uint32(nonce)))); + } + + function computeCreate2Address(bytes32 salt, bytes32 initcodeHash, address deployer) internal pure returns (address) { + return addressFromLast20Bytes(keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, initcodeHash))); + } + + function addressFromLast20Bytes(bytes32 bytesValue) internal pure virtual returns (address) { + return address(uint160(uint256(bytesValue))); + } +} \ No newline at end of file diff --git a/src/console2.sol b/src/console2.sol index 2edfda5b..cdde3d71 100644 --- a/src/console2.sol +++ b/src/console2.sol @@ -1,12 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.4.22 <0.9.0; -// The orignal console.sol uses `int` and `uint` for computing function selectors, but it should -// use `int256` and `uint256`. This modified version fixes that. This version is recommended -// over `console.sol` if you don't need compatibility with Hardhat as the logs will show up in -// forge stack traces. If you do need compatibility with Hardhat, you must use `console.sol`. -// Reference: https://github.com/NomicFoundation/hardhat/issues/2178 - +/// @dev The orignal console.sol uses `int` and `uint` for computing function selectors, but it should +/// use `int256` and `uint256`. This modified version fixes that. This version is recommended +/// over `console.sol` if you don't need compatibility with Hardhat as the logs will show up in +/// forge stack traces. If you do need compatibility with Hardhat, you must use `console.sol`. +/// Reference: https://github.com/NomicFoundation/hardhat/issues/2178 library console2 { address constant CONSOLE_ADDRESS = address(0x000000000000000000636F6e736F6c652e6c6f67); diff --git a/src/test/Script.t.sol b/src/test/Script.t.sol deleted file mode 100644 index 595df7ba..00000000 --- a/src/test/Script.t.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.7.0 <0.9.0; - -import "../Test.sol"; - -contract ScriptTest is Test -{ - function testGenerateCorrectAddress() external { - address creation = computeCreateAddress(0x6C9FC64A53c1b71FB3f9Af64d1ae3A4931A5f4E9, 14); - assertEq(creation, 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45); - } -} \ No newline at end of file diff --git a/src/test/StdAssertions.t.sol b/src/test/StdAssertions.t.sol index db90720a..beee6d23 100644 --- a/src/test/StdAssertions.t.sol +++ b/src/test/StdAssertions.t.sol @@ -3,8 +3,7 @@ pragma solidity >=0.7.0 <0.9.0; import "../Test.sol"; -contract StdAssertionsTest is Test -{ +contract StdAssertionsTest is Test { string constant CUSTOM_ERROR = "guh!"; bool constant EXPECT_PASS = false; @@ -443,8 +442,7 @@ contract StdAssertionsTest is Test } -contract TestTest is Test -{ +contract TestTest is Test { modifier expectFailure(bool expectFail) { bool preState = vm.load(HEVM_ADDRESS, bytes32("failed")) != bytes32(0x00); _; diff --git a/src/test/StdCheats.t.sol b/src/test/StdCheats.t.sol index 2b49e9e7..c891fedb 100644 --- a/src/test/StdCheats.t.sol +++ b/src/test/StdCheats.t.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.7.0 <0.9.0; +import "../Cheats.sol"; import "../Test.sol"; contract StdCheatsTest is Test { @@ -86,48 +87,6 @@ contract StdCheatsTest is Test { assertEq(barToken.totalSupply(), 10000e18); } - function testBound() public { - assertEq(bound(5, 0, 4), 0); - assertEq(bound(0, 69, 69), 69); - assertEq(bound(0, 68, 69), 68); - assertEq(bound(10, 150, 190), 160); - assertEq(bound(300, 2800, 3200), 3100); - assertEq(bound(9999, 1337, 6666), 6006); - } - - function testCannotBoundMaxLessThanMin() public { - vm.expectRevert(bytes("Test bound(uint256,uint256,uint256): Max is less than min.")); - bound(5, 100, 10); - } - - function testBound( - uint256 num, - uint256 min, - uint256 max - ) public { - if (min > max) (min, max) = (max, min); - - uint256 bounded = bound(num, min, max); - - assertGe(bounded, min); - assertLe(bounded, max); - } - - function testBoundUint256Max() public { - assertEq(bound(0, type(uint256).max - 1, type(uint256).max), type(uint256).max - 1); - assertEq(bound(1, type(uint256).max - 1, type(uint256).max), type(uint256).max); - } - - function testCannotBoundMaxLessThanMin( - uint256 num, - uint256 min, - uint256 max - ) public { - vm.assume(min > max); - vm.expectRevert(bytes("Test bound(uint256,uint256,uint256): Max is less than min.")); - bound(num, min, max); - } - function testDeployCode() public { address deployed = deployCode("StdCheats.t.sol:StdCheatsTest", bytes("")); assertEq(string(getCode(deployed)), string(getCode(address(this)))); @@ -138,19 +97,19 @@ contract StdCheatsTest is Test { assertEq(string(getCode(deployed)), string(getCode(address(this)))); } - // We need that payable constructor in order to send ETH on construction + // We need the payable constructor in order to send ETH on construction constructor() payable {} function testDeployCodeVal() public { address deployed = deployCode("StdCheats.t.sol:StdCheatsTest", bytes(""), 1 ether); assertEq(string(getCode(deployed)), string(getCode(address(this)))); - assertEq(deployed.balance, 1 ether); + assertEq(deployed.balance, 1 ether); } function testDeployCodeValNoArgs() public { address deployed = deployCode("StdCheats.t.sol:StdCheatsTest", 1 ether); assertEq(string(getCode(deployed)), string(getCode(address(this)))); - assertEq(deployed.balance, 1 ether); + assertEq(deployed.balance, 1 ether); } // We need this so we can call "this.deployCode" rather than "deployCode" directly diff --git a/src/test/StdError.t.sol b/src/test/StdError.t.sol index 0d6601e4..56bcf2a3 100644 --- a/src/test/StdError.t.sol +++ b/src/test/StdError.t.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.10 <0.9.0; +pragma solidity >=0.8.0 <0.9.0; +import "../Errors.sol"; import "../Test.sol"; contract StdErrorsTest is Test { @@ -59,11 +60,6 @@ contract StdErrorsTest is Test { vm.expectRevert(stdError.zeroVarError); test.intern(); } - - function testExpectLowLvl() public { - vm.expectRevert(stdError.lowLevelError); - test.someArr(0); - } } contract ErrorsTest { diff --git a/src/test/StdMath.t.sol b/src/test/StdMath.t.sol index 9d09b810..23189047 100644 --- a/src/test/StdMath.t.sol +++ b/src/test/StdMath.t.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.0 <0.9.0; +import "../Math.sol"; import "../Test.sol"; -contract StdMathTest is Test -{ +contract StdMathTest is Test { function testGetAbs() external { assertEq(stdMath.abs(-50), 50); assertEq(stdMath.abs(50), 50); diff --git a/src/test/StdStorage.t.sol b/src/test/StdStorage.t.sol index 6e238d04..1b828f8c 100644 --- a/src/test/StdStorage.t.sol +++ b/src/test/StdStorage.t.sol @@ -1,12 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.7.0 <0.9.0; +import "../Storage.sol"; import "../Test.sol"; contract StdStorageTest is Test { using stdStorage for StdStorage; - StorageTest test; + StorageTest internal test; function setUp() public { test = new StorageTest(); @@ -296,7 +297,7 @@ contract StorageTest { uint256 two = (1<<128) | 1; map_packed[msg.sender] = two; - map_packed[address(bytes20(uint160(1337)))] = 1<<128; + map_packed[address(uint160(1337))] = 1<<128; } function read_struct_upper(address who) public view returns (uint256) { diff --git a/src/test/StdUtils.t.sol b/src/test/StdUtils.t.sol new file mode 100644 index 00000000..9e825692 --- /dev/null +++ b/src/test/StdUtils.t.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.7.0 <0.9.0; + +import "../Test.sol"; + +contract StdUtilsTest is Test { + function testBound() public { + assertEq(bound(5, 0, 4), 0); + assertEq(bound(0, 69, 69), 69); + assertEq(bound(0, 68, 69), 68); + assertEq(bound(10, 150, 190), 160); + assertEq(bound(300, 2800, 3200), 3100); + assertEq(bound(9999, 1337, 6666), 6006); + } + + function testBound( + uint256 num, + uint256 min, + uint256 max + ) public { + if (min > max) (min, max) = (max, min); + + uint256 bounded = bound(num, min, max); + + assertGe(bounded, min); + assertLe(bounded, max); + } + + function testBoundUint256Max() public { + assertEq(bound(0, type(uint256).max - 1, type(uint256).max), type(uint256).max - 1); + assertEq(bound(1, type(uint256).max - 1, type(uint256).max), type(uint256).max); + } + + + function testCannotBoundMaxLessThanMin() public { + vm.expectRevert(bytes("Test bound(uint256,uint256,uint256): Max is less than min.")); + bound(5, 100, 10); + } + + function testCannotBoundMaxLessThanMin( + uint256 num, + uint256 min, + uint256 max + ) public { + vm.assume(min > max); + vm.expectRevert(bytes("Test bound(uint256,uint256,uint256): Max is less than min.")); + bound(num, min, max); + } + + function testGenerateCreateAddress() external { + address deployer = 0x6C9FC64A53c1b71FB3f9Af64d1ae3A4931A5f4E9; + uint256 nonce = 14; + address createAddress = computeCreateAddress(deployer, nonce); + assertEq(createAddress, 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45); + } + + function testGenerateCreate2Address() external { + bytes32 salt = bytes32(uint256(31415)); + bytes32 initcodeHash = keccak256(abi.encode(0x6080)); + address deployer = 0x6C9FC64A53c1b71FB3f9Af64d1ae3A4931A5f4E9; + address create2Address = computeCreate2Address(salt, initcodeHash, deployer); + assertEq(create2Address, 0xB147a5d25748fda14b463EB04B111027C290f4d3); + } +} \ No newline at end of file From aa2e9a3f240e48ce801d54d68bccc6ac0e2b8ca7 Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Sun, 14 Aug 2022 21:56:26 +0200 Subject: [PATCH 02/27] feat: make `Script` safer (#147) * feat: add `stdStorageSafe` * test(cheats): fix tests `deployCode` tests started failing after 01c60f9 * refactor: make components `abstract` * feat: add `CheatsSafe` * feat: add `VmSafe` * refactor: update `Script` --- src/Assertions.sol | 2 +- src/Cheats.sol | 142 ++++++++++++++++++++------------------- src/Script.sol | 8 ++- src/Storage.sol | 141 +++++++++++++++++++++++++++++++------- src/Utils.sol | 2 +- src/Vm.sol | 102 ++++++++++++++++++++++++++++ src/test/StdCheats.t.sol | 21 +++--- 7 files changed, 309 insertions(+), 109 deletions(-) diff --git a/src/Assertions.sol b/src/Assertions.sol index a52616a1..b6c7758e 100644 --- a/src/Assertions.sol +++ b/src/Assertions.sol @@ -4,7 +4,7 @@ pragma solidity >=0.6.0 <0.9.0; import "ds-test/test.sol"; import "./Math.sol"; -contract Assertions is DSTest { +abstract contract Assertions is DSTest { event log_array(uint256[] val); event log_array(int256[] val); event log_array(address[] val); diff --git a/src/Cheats.sol b/src/Cheats.sol index def74c57..4324123d 100644 --- a/src/Cheats.sol +++ b/src/Cheats.sol @@ -4,8 +4,80 @@ pragma solidity >=0.6.0 <0.9.0; import "./Storage.sol"; import "./Vm.sol"; +abstract contract CheatsSafe { + VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code"))))); + + // Deploy a contract by fetching the contract bytecode from + // the artifacts directory + // e.g. `deployCode(code, abi.encode(arg1,arg2,arg3))` + function deployCode(string memory what, bytes memory args) + internal virtual + returns (address addr) + { + bytes memory bytecode = abi.encodePacked(vm.getCode(what), args); + /// @solidity memory-safe-assembly + assembly { + addr := create(0, add(bytecode, 0x20), mload(bytecode)) + } + + require( + addr != address(0), + "Test deployCode(string,bytes): Deployment failed." + ); + } + + function deployCode(string memory what) + internal virtual + returns (address addr) + { + bytes memory bytecode = vm.getCode(what); + /// @solidity memory-safe-assembly + assembly { + addr := create(0, add(bytecode, 0x20), mload(bytecode)) + } + + require( + addr != address(0), + "Test deployCode(string): Deployment failed." + ); + } + + /// @dev deploy contract with value on construction + function deployCode(string memory what, bytes memory args, uint256 val) + internal virtual + returns (address addr) + { + bytes memory bytecode = abi.encodePacked(vm.getCode(what), args); + /// @solidity memory-safe-assembly + assembly { + addr := create(val, add(bytecode, 0x20), mload(bytecode)) + } + + require( + addr != address(0), + "Test deployCode(string,bytes,uint256): Deployment failed." + ); + } + + function deployCode(string memory what, uint256 val) + internal virtual + returns (address addr) + { + bytes memory bytecode = vm.getCode(what); + /// @solidity memory-safe-assembly + assembly { + addr := create(val, add(bytecode, 0x20), mload(bytecode)) + } + + require( + addr != address(0), + "Test deployCode(string,uint256): Deployment failed." + ); + } +} + // Wrappers around Cheats to avoid footguns -contract Cheats { +abstract contract Cheats is CheatsSafe { using stdStorage for StdStorage; StdStorage private stdstore; @@ -109,72 +181,4 @@ contract Cheats { .checked_write(totSup); } } - - // Deploy a contract by fetching the contract bytecode from - // the artifacts directory - // e.g. `deployCode(code, abi.encode(arg1,arg2,arg3))` - function deployCode(string memory what, bytes memory args) - internal virtual - returns (address addr) - { - bytes memory bytecode = abi.encodePacked(vm.getCode(what), args); - /// @solidity memory-safe-assembly - assembly { - addr := create(0, add(bytecode, 0x20), mload(bytecode)) - } - - require( - addr != address(0), - "Test deployCode(string,bytes): Deployment failed." - ); - } - - function deployCode(string memory what) - internal virtual - returns (address addr) - { - bytes memory bytecode = vm.getCode(what); - /// @solidity memory-safe-assembly - assembly { - addr := create(0, add(bytecode, 0x20), mload(bytecode)) - } - - require( - addr != address(0), - "Test deployCode(string): Deployment failed." - ); - } - - /// @dev deploy contract with value on construction - function deployCode(string memory what, bytes memory args, uint256 val) - internal virtual - returns (address addr) - { - bytes memory bytecode = abi.encodePacked(vm.getCode(what), args); - /// @solidity memory-safe-assembly - assembly { - addr := create(val, add(bytecode, 0x20), mload(bytecode)) - } - - require( - addr != address(0), - "Test deployCode(string,bytes,uint256): Deployment failed." - ); - } - - function deployCode(string memory what, uint256 val) - internal virtual - returns (address addr) - { - bytes memory bytecode = vm.getCode(what); - /// @solidity memory-safe-assembly - assembly { - addr := create(val, add(bytecode, 0x20), mload(bytecode)) - } - - require( - addr != address(0), - "Test deployCode(string,uint256): Deployment failed." - ); - } } diff --git a/src/Script.sol b/src/Script.sol index 8ebdbd80..5a351105 100644 --- a/src/Script.sol +++ b/src/Script.sol @@ -1,13 +1,15 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.6.0 <0.9.0; -import {Cheats, console, console2, stdMath, stdStorage, StdStorage, Utils, Vm} from "./Components.sol"; +import {CheatsSafe, console, console2, stdMath, stdStorageSafe, StdStorage, Utils, VmSafe} from "./Components.sol"; abstract contract ScriptBase { address internal constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); - Vm internal constant vm = Vm(VM_ADDRESS); + + StdStorage internal stdstore; + VmSafe internal constant vm = VmSafe(VM_ADDRESS); } -abstract contract Script is ScriptBase, Cheats, Utils { +abstract contract Script is ScriptBase, CheatsSafe, Utils { bool public IS_SCRIPT = true; } diff --git a/src/Storage.sol b/src/Storage.sol index 7e4e260b..170d357a 100644 --- a/src/Storage.sol +++ b/src/Storage.sol @@ -14,7 +14,7 @@ struct StdStorage { bytes32 _set; } -library stdStorage { +library stdStorageSafe { event SlotFound(address who, bytes4 fsig, bytes32 keysHash, uint slot); event WARNING_UninitedSlot(address who, uint slot); @@ -144,6 +144,116 @@ library stdStorage { return self; } + function read(StdStorage storage self) private returns (bytes memory) { + address t = self._target; + uint256 s = find(self); + return abi.encode(vm.load(t, bytes32(s))); + } + + function read_bytes32(StdStorage storage self) internal returns (bytes32) { + return abi.decode(read(self), (bytes32)); + } + + + function read_bool(StdStorage storage self) internal returns (bool) { + int256 v = read_int(self); + if (v == 0) return false; + if (v == 1) return true; + revert("stdStorage read_bool(StdStorage): Cannot decode. Make sure you are reading a bool."); + } + + function read_address(StdStorage storage self) internal returns (address) { + return abi.decode(read(self), (address)); + } + + function read_uint(StdStorage storage self) internal returns (uint256) { + return abi.decode(read(self), (uint256)); + } + + function read_int(StdStorage storage self) internal returns (int256) { + return abi.decode(read(self), (int256)); + } + + function bytesToBytes32(bytes memory b, uint offset) public pure returns (bytes32) { + bytes32 out; + + uint256 max = b.length > 32 ? 32 : b.length; + for (uint i = 0; i < max; i++) { + out |= bytes32(b[offset + i] & 0xFF) >> (i * 8); + } + return out; + } + + function flatten(bytes32[] memory b) private pure returns (bytes memory) + { + bytes memory result = new bytes(b.length * 32); + for (uint256 i = 0; i < b.length; i++) { + bytes32 k = b[i]; + /// @solidity memory-safe-assembly + assembly { + mstore(add(result, add(32, mul(32, i))), k) + } + } + + return result; + } +} + +library stdStorage { + Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + function sigs( + string memory sigStr + ) + internal + pure + returns (bytes4) + { + return stdStorageSafe.sigs(sigStr); + } + + /// @notice find an arbitrary storage slot given a function sig, input data, address of the contract and a value to check against + // slot complexity: + // if flat, will be bytes32(uint256(uint)); + // if map, will be keccak256(abi.encode(key, uint(slot))); + // if deep map, will be keccak256(abi.encode(key1, keccak256(abi.encode(key0, uint(slot))))); + // if map struct, will be bytes32(uint256(keccak256(abi.encode(key1, keccak256(abi.encode(key0, uint(slot)))))) + structFieldDepth); + function find( + StdStorage storage self + ) + internal + returns (uint256) + { + return stdStorageSafe.find(self); + } + + function target(StdStorage storage self, address _target) internal returns (StdStorage storage) { + return stdStorageSafe.target(self, _target); + } + + function sig(StdStorage storage self, bytes4 _sig) internal returns (StdStorage storage) { + return stdStorageSafe.sig(self, _sig); + } + + function sig(StdStorage storage self, string memory _sig) internal returns (StdStorage storage) { + return stdStorageSafe.sig(self, _sig); + } + + function with_key(StdStorage storage self, address who) internal returns (StdStorage storage) { + return stdStorageSafe.with_key(self, who); + } + + function with_key(StdStorage storage self, uint256 amt) internal returns (StdStorage storage) { + return stdStorageSafe.with_key(self, amt); + } + function with_key(StdStorage storage self, bytes32 key) internal returns (StdStorage storage) { + return stdStorageSafe.with_key(self, key); + } + + function depth(StdStorage storage self, uint256 _depth) internal returns (StdStorage storage) { + return stdStorageSafe.depth(self, _depth); + } + function checked_write(StdStorage storage self, address who) internal { checked_write(self, bytes32(uint256(uint160(who)))); } @@ -193,46 +303,31 @@ library stdStorage { delete self._depth; } - function read(StdStorage storage self) private returns (bytes memory) { - address t = self._target; - uint256 s = find(self); - return abi.encode(vm.load(t, bytes32(s))); - } - function read_bytes32(StdStorage storage self) internal returns (bytes32) { - return abi.decode(read(self), (bytes32)); + return stdStorageSafe.read_bytes32(self); } - function read_bool(StdStorage storage self) internal returns (bool) { - int256 v = read_int(self); - if (v == 0) return false; - if (v == 1) return true; - revert("stdStorage read_bool(StdStorage): Cannot decode. Make sure you are reading a bool."); + return stdStorageSafe.read_bool(self); } function read_address(StdStorage storage self) internal returns (address) { - return abi.decode(read(self), (address)); + return stdStorageSafe.read_address(self); } function read_uint(StdStorage storage self) internal returns (uint256) { - return abi.decode(read(self), (uint256)); + return stdStorageSafe.read_uint(self); } function read_int(StdStorage storage self) internal returns (int256) { - return abi.decode(read(self), (int256)); + return stdStorageSafe.read_int(self); } function bytesToBytes32(bytes memory b, uint offset) public pure returns (bytes32) { - bytes32 out; - - uint256 max = b.length > 32 ? 32 : b.length; - for (uint i = 0; i < max; i++) { - out |= bytes32(b[offset + i] & 0xFF) >> (i * 8); - } - return out; + return stdStorageSafe.bytesToBytes32(b, offset); } + // Private function so needs to be copied over function flatten(bytes32[] memory b) private pure returns (bytes memory) { bytes memory result = new bytes(b.length * 32); diff --git a/src/Utils.sol b/src/Utils.sol index 0694f568..f29c9324 100644 --- a/src/Utils.sol +++ b/src/Utils.sol @@ -3,7 +3,7 @@ pragma solidity >=0.6.0 <0.9.0; import "./console2.sol"; -contract Utils { +abstract contract Utils { uint256 internal constant UINT256_MAX = 115792089237316195423570985008687907853269984665640564039457584007913129639935; diff --git a/src/Vm.sol b/src/Vm.sol index 6479ca7e..39273236 100644 --- a/src/Vm.sol +++ b/src/Vm.sol @@ -2,6 +2,108 @@ pragma solidity >=0.6.0 <0.9.0; pragma experimental ABIEncoderV2; +interface VmSafe { + struct Log { + bytes32[] topics; + bytes data; + } + + // Loads a storage slot from an address (who, slot) + function load(address,bytes32) external returns (bytes32); + // Signs data, (privateKey, digest) => (v, r, s) + function sign(uint256,bytes32) external returns (uint8,bytes32,bytes32); + // Gets the address for a given private key, (privateKey) => (address) + function addr(uint256) external returns (address); + // Gets the nonce of an account + function getNonce(address) external returns (uint64); + // Performs a foreign function call via the terminal, (stringInputs) => (result) + function ffi(string[] calldata) external returns (bytes memory); + // Sets environment variables, (name, value) + function setEnv(string calldata, string calldata) external; + // Reads environment variables, (name) => (value) + function envBool(string calldata) external returns (bool); + function envUint(string calldata) external returns (uint256); + function envInt(string calldata) external returns (int256); + function envAddress(string calldata) external returns (address); + function envBytes32(string calldata) external returns (bytes32); + function envString(string calldata) external returns (string memory); + function envBytes(string calldata) external returns (bytes memory); + // Reads environment variables as arrays, (name, delim) => (value[]) + function envBool(string calldata, string calldata) external returns (bool[] memory); + function envUint(string calldata, string calldata) external returns (uint256[] memory); + function envInt(string calldata, string calldata) external returns (int256[] memory); + function envAddress(string calldata, string calldata) external returns (address[] memory); + function envBytes32(string calldata, string calldata) external returns (bytes32[] memory); + function envString(string calldata, string calldata) external returns (string[] memory); + function envBytes(string calldata, string calldata) external returns (bytes[] memory); + // Expects an error on next call + function expectRevert(bytes calldata) external; + function expectRevert(bytes4) external; + function expectRevert() external; + // Records all storage reads and writes + function record() external; + // Gets all accessed reads and write slot from a recording session, for a given address + function accesses(address) external returns (bytes32[] memory reads, bytes32[] memory writes); + // Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData). + // Call this function, then emit an event, then call a function. Internally after the call, we check if + // logs were emitted in the expected order with the expected topics and data (as specified by the booleans) + function expectEmit(bool,bool,bool,bool) external; + function expectEmit(bool,bool,bool,bool,address) external; + // Expects a call to an address with the specified calldata. + // Calldata can either be a strict or a partial match + function expectCall(address,bytes calldata) external; + // Expects a call to an address with the specified msg.value and calldata + function expectCall(address,uint256,bytes calldata) external; + // Gets the code from an artifact file. Takes in the relative path to the json file + function getCode(string calldata) external returns (bytes memory); + // Labels an address in call traces + function label(address, string calldata) external; + // Using the address that calls the test contract, has the next call (at this call depth only) create a transaction that can later be signed and sent onchain + function broadcast() external; + // Has the next call (at this call depth only) create a transaction with the address provided as the sender that can later be signed and sent onchain + function broadcast(address) external; + // Using the address that calls the test contract, has all subsequent calls (at this call depth only) create transactions that can later be signed and sent onchain + function startBroadcast() external; + // Has all subsequent calls (at this call depth only) create transactions that can later be signed and sent onchain + function startBroadcast(address) external; + // Stops collecting onchain transactions + function stopBroadcast() external; + // Reads the entire content of file to string, (path) => (data) + function readFile(string calldata) external returns (string memory); + // Reads next line of file to string, (path) => (line) + function readLine(string calldata) external returns (string memory); + // Writes data to file, creating a file if it does not exist, and entirely replacing its contents if it does. + // (path, data) => () + function writeFile(string calldata, string calldata) external; + // Writes line to file, creating a file if it does not exist. + // (path, data) => () + function writeLine(string calldata, string calldata) external; + // Closes file for reading, resetting the offset and allowing to read it from beginning with readLine. + // (path) => () + function closeFile(string calldata) external; + // Removes file. This cheatcode will revert in the following situations, but is not limited to just these cases: + // - Path points to a directory. + // - The file doesn't exist. + // - The user lacks permissions to remove the file. + // (path) => () + function removeFile(string calldata) external; + // Convert values to a string, (value) => (stringified value) + function toString(address) external returns(string memory); + function toString(bytes calldata) external returns(string memory); + function toString(bytes32) external returns(string memory); + function toString(bool) external returns(string memory); + function toString(uint256) external returns(string memory); + function toString(int256) external returns(string memory); + // Record all the transaction logs + function recordLogs() external; + // Gets all the recorded logs, () => (logs) + function getRecordedLogs() external returns (Log[] memory); + // Derive a private key from a provided mnenomic string (or mnenomic file path) at the derivation path m/44'/60'/0'/0/{index} + function deriveKey(string calldata, uint32) external returns (uint256); + // Derive a private key from a provided mnenomic string (or mnenomic file path) at the derivation path {path}{index} + function deriveKey(string calldata, string calldata, uint32) external returns (uint256); +} + interface Vm { struct Log { bytes32[] topics; diff --git a/src/test/StdCheats.t.sol b/src/test/StdCheats.t.sol index c891fedb..57103944 100644 --- a/src/test/StdCheats.t.sol +++ b/src/test/StdCheats.t.sol @@ -88,27 +88,24 @@ contract StdCheatsTest is Test { } function testDeployCode() public { - address deployed = deployCode("StdCheats.t.sol:StdCheatsTest", bytes("")); - assertEq(string(getCode(deployed)), string(getCode(address(this)))); + address deployed = deployCode("StdCheats.t.sol:Bar", bytes("")); + assertEq(string(getCode(deployed)), string(getCode(address(test)))); } function testDeployCodeNoArgs() public { - address deployed = deployCode("StdCheats.t.sol:StdCheatsTest"); - assertEq(string(getCode(deployed)), string(getCode(address(this)))); + address deployed = deployCode("StdCheats.t.sol:Bar"); + assertEq(string(getCode(deployed)), string(getCode(address(test)))); } - // We need the payable constructor in order to send ETH on construction - constructor() payable {} - function testDeployCodeVal() public { - address deployed = deployCode("StdCheats.t.sol:StdCheatsTest", bytes(""), 1 ether); - assertEq(string(getCode(deployed)), string(getCode(address(this)))); + address deployed = deployCode("StdCheats.t.sol:Bar", bytes(""), 1 ether); + assertEq(string(getCode(deployed)), string(getCode(address(test)))); assertEq(deployed.balance, 1 ether); } function testDeployCodeValNoArgs() public { - address deployed = deployCode("StdCheats.t.sol:StdCheatsTest", 1 ether); - assertEq(string(getCode(deployed)), string(getCode(address(this)))); + address deployed = deployCode("StdCheats.t.sol:Bar", 1 ether); + assertEq(string(getCode(deployed)), string(getCode(address(test)))); assertEq(deployed.balance, 1 ether); } @@ -141,7 +138,7 @@ contract StdCheatsTest is Test { } contract Bar { - constructor() { + constructor() payable { /// `DEAL` STDCHEAT totalSupply = 10000e18; balanceOf[address(this)] = totalSupply; From da9be771d247a0db3ae243ebb480a8d165f22dca Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Wed, 24 Aug 2022 01:20:07 +0200 Subject: [PATCH 03/27] docs: add license info (#156) --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 67dc160b..f940dde3 100644 --- a/README.md +++ b/README.md @@ -244,3 +244,7 @@ import "forge-std/console.sol"; ... console.log(someValue); ``` + +## License + +Forge Standard Library is offered under either [MIT](LICENSE-MIT) or [Apache 2.0](LICENSE-APACHE) license. \ No newline at end of file From faa759321d3e73fbd8c70ef1c5e87ce38d8a6b56 Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Wed, 7 Sep 2022 23:49:36 +0200 Subject: [PATCH 04/27] feat: rebrand components (#157) * feat: rebrand components Rename to Std * fix: StdErrors -> StdError * chore: remove `.DS_Store` --- .gitignore | 2 +- src/Components.sol | 12 ++++++------ src/Script.sol | 4 ++-- src/{Assertions.sol => StdAssertions.sol} | 4 ++-- src/{Cheats.sol => StdCheats.sol} | 8 ++++---- src/{Errors.sol => StdError.sol} | 0 src/{Math.sol => StdMath.sol} | 0 src/{Storage.sol => StdStorage.sol} | 6 ------ src/{Utils.sol => StdUtils.sol} | 2 +- src/Test.sol | 2 +- src/test/StdCheats.t.sol | 2 +- src/test/StdError.t.sol | 2 +- src/test/StdMath.t.sol | 2 +- src/test/StdStorage.t.sol | 2 +- 14 files changed, 21 insertions(+), 27 deletions(-) rename src/{Assertions.sol => StdAssertions.sol} (99%) rename src/{Cheats.sol => StdCheats.sol} (97%) rename src/{Errors.sol => StdError.sol} (100%) rename src/{Math.sol => StdMath.sol} (100%) rename src/{Storage.sol => StdStorage.sol} (95%) rename src/{Utils.sol => StdUtils.sol} (99%) diff --git a/.gitignore b/.gitignore index 999e4a77..756106d3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ cache/ out/ .vscode -.idea \ No newline at end of file +.idea diff --git a/src/Components.sol b/src/Components.sol index c5bfa3d5..e6efce46 100644 --- a/src/Components.sol +++ b/src/Components.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.6.0 <0.9.0; -import "./Assertions.sol"; -import "./Cheats.sol"; import "./console.sol"; import "./console2.sol"; -import "./Errors.sol"; -import "./Math.sol"; -import "./Storage.sol"; -import "./Utils.sol"; +import "./StdAssertions.sol"; +import "./StdCheats.sol"; +import "./StdError.sol"; +import "./StdMath.sol"; +import "./StdStorage.sol"; +import "./StdUtils.sol"; import "./Vm.sol"; diff --git a/src/Script.sol b/src/Script.sol index 5a351105..4788afd7 100644 --- a/src/Script.sol +++ b/src/Script.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.6.0 <0.9.0; -import {CheatsSafe, console, console2, stdMath, stdStorageSafe, StdStorage, Utils, VmSafe} from "./Components.sol"; +import {StdCheatsSafe, console, console2, stdMath, stdStorageSafe, StdStorage, StdUtils, VmSafe} from "./Components.sol"; abstract contract ScriptBase { address internal constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); @@ -10,6 +10,6 @@ abstract contract ScriptBase { VmSafe internal constant vm = VmSafe(VM_ADDRESS); } -abstract contract Script is ScriptBase, CheatsSafe, Utils { +abstract contract Script is ScriptBase, StdCheatsSafe, StdUtils { bool public IS_SCRIPT = true; } diff --git a/src/Assertions.sol b/src/StdAssertions.sol similarity index 99% rename from src/Assertions.sol rename to src/StdAssertions.sol index b6c7758e..60f47083 100644 --- a/src/Assertions.sol +++ b/src/StdAssertions.sol @@ -2,9 +2,9 @@ pragma solidity >=0.6.0 <0.9.0; import "ds-test/test.sol"; -import "./Math.sol"; +import "./StdMath.sol"; -abstract contract Assertions is DSTest { +abstract contract StdAssertions is DSTest { event log_array(uint256[] val); event log_array(int256[] val); event log_array(address[] val); diff --git a/src/Cheats.sol b/src/StdCheats.sol similarity index 97% rename from src/Cheats.sol rename to src/StdCheats.sol index 4324123d..f7319c81 100644 --- a/src/Cheats.sol +++ b/src/StdCheats.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.6.0 <0.9.0; -import "./Storage.sol"; +import "./StdStorage.sol"; import "./Vm.sol"; -abstract contract CheatsSafe { +abstract contract StdCheatsSafe { VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code"))))); // Deploy a contract by fetching the contract bytecode from @@ -76,8 +76,8 @@ abstract contract CheatsSafe { } } -// Wrappers around Cheats to avoid footguns -abstract contract Cheats is CheatsSafe { +// Wrappers around cheatcodes to avoid footguns +abstract contract StdCheats is StdCheatsSafe { using stdStorage for StdStorage; StdStorage private stdstore; diff --git a/src/Errors.sol b/src/StdError.sol similarity index 100% rename from src/Errors.sol rename to src/StdError.sol diff --git a/src/Math.sol b/src/StdMath.sol similarity index 100% rename from src/Math.sol rename to src/StdMath.sol diff --git a/src/Storage.sol b/src/StdStorage.sol similarity index 95% rename from src/Storage.sol rename to src/StdStorage.sol index 170d357a..1a2e5fd3 100644 --- a/src/Storage.sol +++ b/src/StdStorage.sol @@ -212,12 +212,6 @@ library stdStorage { return stdStorageSafe.sigs(sigStr); } - /// @notice find an arbitrary storage slot given a function sig, input data, address of the contract and a value to check against - // slot complexity: - // if flat, will be bytes32(uint256(uint)); - // if map, will be keccak256(abi.encode(key, uint(slot))); - // if deep map, will be keccak256(abi.encode(key1, keccak256(abi.encode(key0, uint(slot))))); - // if map struct, will be bytes32(uint256(keccak256(abi.encode(key1, keccak256(abi.encode(key0, uint(slot)))))) + structFieldDepth); function find( StdStorage storage self ) diff --git a/src/Utils.sol b/src/StdUtils.sol similarity index 99% rename from src/Utils.sol rename to src/StdUtils.sol index f29c9324..fc38f766 100644 --- a/src/Utils.sol +++ b/src/StdUtils.sol @@ -3,7 +3,7 @@ pragma solidity >=0.6.0 <0.9.0; import "./console2.sol"; -abstract contract Utils { +abstract contract StdUtils { uint256 internal constant UINT256_MAX = 115792089237316195423570985008687907853269984665640564039457584007913129639935; diff --git a/src/Test.sol b/src/Test.sol index e6f8aa7b..e66e368b 100644 --- a/src/Test.sol +++ b/src/Test.sol @@ -11,4 +11,4 @@ abstract contract TestBase { Vm internal constant vm = Vm(VM_ADDRESS); } -abstract contract Test is TestBase, DSTest, Assertions, Cheats, Utils {} +abstract contract Test is TestBase, DSTest, StdAssertions, StdCheats, StdUtils {} diff --git a/src/test/StdCheats.t.sol b/src/test/StdCheats.t.sol index 57103944..d8059287 100644 --- a/src/test/StdCheats.t.sol +++ b/src/test/StdCheats.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.7.0 <0.9.0; -import "../Cheats.sol"; +import "../StdCheats.sol"; import "../Test.sol"; contract StdCheatsTest is Test { diff --git a/src/test/StdError.t.sol b/src/test/StdError.t.sol index 56bcf2a3..fd989428 100644 --- a/src/test/StdError.t.sol +++ b/src/test/StdError.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.0 <0.9.0; -import "../Errors.sol"; +import "../StdError.sol"; import "../Test.sol"; contract StdErrorsTest is Test { diff --git a/src/test/StdMath.t.sol b/src/test/StdMath.t.sol index 23189047..6182f3d8 100644 --- a/src/test/StdMath.t.sol +++ b/src/test/StdMath.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.0 <0.9.0; -import "../Math.sol"; +import "../StdMath.sol"; import "../Test.sol"; contract StdMathTest is Test { diff --git a/src/test/StdStorage.t.sol b/src/test/StdStorage.t.sol index 1b828f8c..c5be92dc 100644 --- a/src/test/StdStorage.t.sol +++ b/src/test/StdStorage.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.7.0 <0.9.0; -import "../Storage.sol"; +import "../StdStorage.sol"; import "../Test.sol"; contract StdStorageTest is Test { From 8421aec399c6bf595a48887798cfb2b447284484 Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Thu, 22 Sep 2022 12:33:26 +0200 Subject: [PATCH 05/27] fix: use `ABIEncoderV2` --- src/Components.sol | 1 + src/Script.sol | 2 +- src/StdCheats.sol | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Components.sol b/src/Components.sol index 77c450ea..6c20fff2 100644 --- a/src/Components.sol +++ b/src/Components.sol @@ -6,6 +6,7 @@ import "./console2.sol"; import "./StdAssertions.sol"; import "./StdCheats.sol"; import "./StdError.sol"; +import "./StdJson.sol"; import "./StdMath.sol"; import "./StdStorage.sol"; import "./StdUtils.sol"; diff --git a/src/Script.sol b/src/Script.sol index aba2aa56..dc46fc29 100644 --- a/src/Script.sol +++ b/src/Script.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.6.2 <0.9.0; -import {StdCheatsSafe, console, console2, stdMath, stdStorageSafe, StdStorage, StdUtils, VmSafe} from "./Components.sol"; +import {StdCheatsSafe, console, console2, stdJson, stdMath, stdStorageSafe, StdStorage, StdUtils, VmSafe} from "./Components.sol"; abstract contract ScriptBase { address internal constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); diff --git a/src/StdCheats.sol b/src/StdCheats.sol index 6e086228..f75b0f49 100644 --- a/src/StdCheats.sol +++ b/src/StdCheats.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; import "./StdStorage.sol"; import "./Vm.sol"; From d7de20b20b52aa33df8688c0ff8709e85e894265 Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Thu, 22 Sep 2022 12:45:19 +0200 Subject: [PATCH 06/27] test: correct test name --- src/test/StdAssertions.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/StdAssertions.t.sol b/src/test/StdAssertions.t.sol index f0e7319b..9fd6d785 100644 --- a/src/test/StdAssertions.t.sol +++ b/src/test/StdAssertions.t.sol @@ -300,7 +300,7 @@ contract StdAssertionsTest is Test { ASSERT_EQ(UINT) //////////////////////////////////////////////////////////////////////////*/ - function testAssertions() public { + function testAssertEqUint() public { assertEqUint(uint8(1), uint128(1)); assertEqUint(uint64(2), uint64(2)); } From 9aeffc035103407e22543c3e4865c0b4f133ac5b Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Sat, 1 Oct 2022 13:45:38 +0200 Subject: [PATCH 07/27] fix: add `CommonBase` --- src/Common.sol | 11 +++++++++++ src/Script.sol | 10 ++++------ src/Test.sol | 8 ++------ 3 files changed, 17 insertions(+), 12 deletions(-) create mode 100644 src/Common.sol diff --git a/src/Common.sol b/src/Common.sol new file mode 100644 index 00000000..4bef9e5d --- /dev/null +++ b/src/Common.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.2 <0.9.0; + +import {StdStorage, Vm} from "./Components.sol"; + +abstract contract CommonBase { + address internal constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); + + StdStorage internal stdstore; + Vm internal constant vm = Vm(VM_ADDRESS); +} \ No newline at end of file diff --git a/src/Script.sol b/src/Script.sol index dc46fc29..c4947c63 100644 --- a/src/Script.sol +++ b/src/Script.sol @@ -1,13 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.6.2 <0.9.0; -import {StdCheatsSafe, console, console2, stdJson, stdMath, stdStorageSafe, StdStorage, StdUtils, VmSafe} from "./Components.sol"; +import {CommonBase} from "./Common.sol"; +import {console, console2, StdCheatsSafe, stdJson, stdMath, stdStorageSafe, StdStorage, StdUtils, VmSafe} from "./Components.sol"; -abstract contract ScriptBase { - address internal constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); - - StdStorage internal stdstore; - VmSafe internal constant vm = VmSafe(VM_ADDRESS); +abstract contract ScriptBase is CommonBase { + VmSafe internal constant vmSafe = VmSafe(VM_ADDRESS); } abstract contract Script is ScriptBase, StdCheatsSafe, StdUtils { diff --git a/src/Test.sol b/src/Test.sol index 07daa75d..e099c727 100644 --- a/src/Test.sol +++ b/src/Test.sol @@ -1,14 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.6.2 <0.9.0; +import {CommonBase} from "./Common.sol"; import "./Components.sol"; import "ds-test/test.sol"; -abstract contract TestBase { - address internal constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); - - StdStorage internal stdstore; - Vm internal constant vm = Vm(VM_ADDRESS); -} +abstract contract TestBase is CommonBase {} abstract contract Test is TestBase, DSTest, StdAssertions, StdCheats, StdUtils {} From f21ef1a4bbc4248e4400befb5d449bfe39259cec Mon Sep 17 00:00:00 2001 From: Matt Solomon Date: Thu, 6 Oct 2022 08:29:17 -0700 Subject: [PATCH 08/27] refactor: move test dir to root --- {src/test => test}/StdAssertions.t.sol | 2 +- {src/test => test}/StdCheats.t.sol | 16 ++++++++-------- {src/test => test}/StdError.t.sol | 4 ++-- {src/test => test}/StdMath.t.sol | 4 ++-- {src/test => test}/StdStorage.t.sol | 8 ++++---- {src/test => test}/StdUtils.t.sol | 2 +- {src/test => test}/fixtures/broadcast.log.json | 0 7 files changed, 18 insertions(+), 18 deletions(-) rename {src/test => test}/StdAssertions.t.sol (99%) rename {src/test => test}/StdCheats.t.sol (94%) rename {src/test => test}/StdError.t.sol (98%) rename {src/test => test}/StdMath.t.sol (99%) rename {src/test => test}/StdStorage.t.sol (99%) rename {src/test => test}/StdUtils.t.sol (98%) rename {src/test => test}/fixtures/broadcast.log.json (100%) diff --git a/src/test/StdAssertions.t.sol b/test/StdAssertions.t.sol similarity index 99% rename from src/test/StdAssertions.t.sol rename to test/StdAssertions.t.sol index 9fd6d785..1d243791 100644 --- a/src/test/StdAssertions.t.sol +++ b/test/StdAssertions.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.7.0 <0.9.0; -import "../Test.sol"; +import "src/Test.sol"; contract StdAssertionsTest is Test { string constant CUSTOM_ERROR = "guh!"; diff --git a/src/test/StdCheats.t.sol b/test/StdCheats.t.sol similarity index 94% rename from src/test/StdCheats.t.sol rename to test/StdCheats.t.sol index cd346da3..612d74ff 100644 --- a/src/test/StdCheats.t.sol +++ b/test/StdCheats.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.7.0 <0.9.0; -import "../StdCheats.sol"; -import "../Test.sol"; -import "../StdJson.sol"; +import "src/StdCheats.sol"; +import "src/Test.sol"; +import "src/StdJson.sol"; contract StdCheatsTest is Test { Bar test; @@ -169,7 +169,7 @@ contract StdCheatsTest is Test { function testParseJsonTxDetail() public { string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/src/test/fixtures/broadcast.log.json"); + string memory path = string.concat(root, "/test/fixtures/broadcast.log.json"); string memory json = vm.readFile(path); bytes memory transactionDetails = json.parseRaw(".transactions[0].tx"); RawTx1559Detail memory rawTxDetail = abi.decode(transactionDetails, (RawTx1559Detail)); @@ -185,20 +185,20 @@ contract StdCheatsTest is Test { function testReadEIP1559Transaction() public { string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/src/test/fixtures/broadcast.log.json"); + string memory path = string.concat(root, "/test/fixtures/broadcast.log.json"); uint256 index = 0; Tx1559 memory transaction = readTx1559(path, index); } function testReadEIP1559Transactions() public { string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/src/test/fixtures/broadcast.log.json"); + string memory path = string.concat(root, "/test/fixtures/broadcast.log.json"); Tx1559[] memory transactions = readTx1559s(path); } function testReadReceipt() public { string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/src/test/fixtures/broadcast.log.json"); + string memory path = string.concat(root, "/test/fixtures/broadcast.log.json"); uint index = 5; Receipt memory receipt = readReceipt(path, index); assertEq(receipt.logsBloom, @@ -207,7 +207,7 @@ contract StdCheatsTest is Test { function testReadReceipts() public { string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/src/test/fixtures/broadcast.log.json"); + string memory path = string.concat(root, "/test/fixtures/broadcast.log.json"); Receipt[] memory receipts = readReceipts(path); } diff --git a/src/test/StdError.t.sol b/test/StdError.t.sol similarity index 98% rename from src/test/StdError.t.sol rename to test/StdError.t.sol index fd989428..67c127ab 100644 --- a/src/test/StdError.t.sol +++ b/test/StdError.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.0 <0.9.0; -import "../StdError.sol"; -import "../Test.sol"; +import "src/StdError.sol"; +import "src/Test.sol"; contract StdErrorsTest is Test { ErrorsTest test; diff --git a/src/test/StdMath.t.sol b/test/StdMath.t.sol similarity index 99% rename from src/test/StdMath.t.sol rename to test/StdMath.t.sol index 6182f3d8..143fcc4d 100644 --- a/src/test/StdMath.t.sol +++ b/test/StdMath.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.0 <0.9.0; -import "../StdMath.sol"; -import "../Test.sol"; +import "src/StdMath.sol"; +import "src/Test.sol"; contract StdMathTest is Test { function testGetAbs() external { diff --git a/src/test/StdStorage.t.sol b/test/StdStorage.t.sol similarity index 99% rename from src/test/StdStorage.t.sol rename to test/StdStorage.t.sol index c5be92dc..b17baadc 100644 --- a/src/test/StdStorage.t.sol +++ b/test/StdStorage.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.7.0 <0.9.0; -import "../StdStorage.sol"; -import "../Test.sol"; +import "src/StdStorage.sol"; +import "src/Test.sol"; contract StdStorageTest is Test { using stdStorage for StdStorage; @@ -213,7 +213,7 @@ contract StdStorageTest is Test { function testFailStorageNativePack() public { stdstore.target(address(test)).sig(test.tA.selector).find(); stdstore.target(address(test)).sig(test.tB.selector).find(); - + // these both would fail stdstore.target(address(test)).sig(test.tC.selector).find(); stdstore.target(address(test)).sig(test.tD.selector).find(); @@ -274,7 +274,7 @@ contract StorageTest { bool public tC = false; - uint248 public tD = 1; + uint248 public tD = 1; struct UnpackedStruct { diff --git a/src/test/StdUtils.t.sol b/test/StdUtils.t.sol similarity index 98% rename from src/test/StdUtils.t.sol rename to test/StdUtils.t.sol index 9e825692..f5954813 100644 --- a/src/test/StdUtils.t.sol +++ b/test/StdUtils.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.7.0 <0.9.0; -import "../Test.sol"; +import "src/Test.sol"; contract StdUtilsTest is Test { function testBound() public { diff --git a/src/test/fixtures/broadcast.log.json b/test/fixtures/broadcast.log.json similarity index 100% rename from src/test/fixtures/broadcast.log.json rename to test/fixtures/broadcast.log.json From ece89a08de8d8767da3e931ab8ef4540647e8661 Mon Sep 17 00:00:00 2001 From: Matt Solomon Date: Thu, 6 Oct 2022 08:33:49 -0700 Subject: [PATCH 09/27] Revert "refactor: move test dir to root" This reverts commit f21ef1a4bbc4248e4400befb5d449bfe39259cec. --- {test => src/test}/StdAssertions.t.sol | 2 +- {test => src/test}/StdCheats.t.sol | 16 ++++++++-------- {test => src/test}/StdError.t.sol | 4 ++-- {test => src/test}/StdMath.t.sol | 4 ++-- {test => src/test}/StdStorage.t.sol | 8 ++++---- {test => src/test}/StdUtils.t.sol | 2 +- {test => src/test}/fixtures/broadcast.log.json | 0 7 files changed, 18 insertions(+), 18 deletions(-) rename {test => src/test}/StdAssertions.t.sol (99%) rename {test => src/test}/StdCheats.t.sol (94%) rename {test => src/test}/StdError.t.sol (98%) rename {test => src/test}/StdMath.t.sol (99%) rename {test => src/test}/StdStorage.t.sol (99%) rename {test => src/test}/StdUtils.t.sol (98%) rename {test => src/test}/fixtures/broadcast.log.json (100%) diff --git a/test/StdAssertions.t.sol b/src/test/StdAssertions.t.sol similarity index 99% rename from test/StdAssertions.t.sol rename to src/test/StdAssertions.t.sol index 1d243791..9fd6d785 100644 --- a/test/StdAssertions.t.sol +++ b/src/test/StdAssertions.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.7.0 <0.9.0; -import "src/Test.sol"; +import "../Test.sol"; contract StdAssertionsTest is Test { string constant CUSTOM_ERROR = "guh!"; diff --git a/test/StdCheats.t.sol b/src/test/StdCheats.t.sol similarity index 94% rename from test/StdCheats.t.sol rename to src/test/StdCheats.t.sol index 612d74ff..cd346da3 100644 --- a/test/StdCheats.t.sol +++ b/src/test/StdCheats.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.7.0 <0.9.0; -import "src/StdCheats.sol"; -import "src/Test.sol"; -import "src/StdJson.sol"; +import "../StdCheats.sol"; +import "../Test.sol"; +import "../StdJson.sol"; contract StdCheatsTest is Test { Bar test; @@ -169,7 +169,7 @@ contract StdCheatsTest is Test { function testParseJsonTxDetail() public { string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/test/fixtures/broadcast.log.json"); + string memory path = string.concat(root, "/src/test/fixtures/broadcast.log.json"); string memory json = vm.readFile(path); bytes memory transactionDetails = json.parseRaw(".transactions[0].tx"); RawTx1559Detail memory rawTxDetail = abi.decode(transactionDetails, (RawTx1559Detail)); @@ -185,20 +185,20 @@ contract StdCheatsTest is Test { function testReadEIP1559Transaction() public { string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/test/fixtures/broadcast.log.json"); + string memory path = string.concat(root, "/src/test/fixtures/broadcast.log.json"); uint256 index = 0; Tx1559 memory transaction = readTx1559(path, index); } function testReadEIP1559Transactions() public { string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/test/fixtures/broadcast.log.json"); + string memory path = string.concat(root, "/src/test/fixtures/broadcast.log.json"); Tx1559[] memory transactions = readTx1559s(path); } function testReadReceipt() public { string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/test/fixtures/broadcast.log.json"); + string memory path = string.concat(root, "/src/test/fixtures/broadcast.log.json"); uint index = 5; Receipt memory receipt = readReceipt(path, index); assertEq(receipt.logsBloom, @@ -207,7 +207,7 @@ contract StdCheatsTest is Test { function testReadReceipts() public { string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/test/fixtures/broadcast.log.json"); + string memory path = string.concat(root, "/src/test/fixtures/broadcast.log.json"); Receipt[] memory receipts = readReceipts(path); } diff --git a/test/StdError.t.sol b/src/test/StdError.t.sol similarity index 98% rename from test/StdError.t.sol rename to src/test/StdError.t.sol index 67c127ab..fd989428 100644 --- a/test/StdError.t.sol +++ b/src/test/StdError.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.0 <0.9.0; -import "src/StdError.sol"; -import "src/Test.sol"; +import "../StdError.sol"; +import "../Test.sol"; contract StdErrorsTest is Test { ErrorsTest test; diff --git a/test/StdMath.t.sol b/src/test/StdMath.t.sol similarity index 99% rename from test/StdMath.t.sol rename to src/test/StdMath.t.sol index 143fcc4d..6182f3d8 100644 --- a/test/StdMath.t.sol +++ b/src/test/StdMath.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.0 <0.9.0; -import "src/StdMath.sol"; -import "src/Test.sol"; +import "../StdMath.sol"; +import "../Test.sol"; contract StdMathTest is Test { function testGetAbs() external { diff --git a/test/StdStorage.t.sol b/src/test/StdStorage.t.sol similarity index 99% rename from test/StdStorage.t.sol rename to src/test/StdStorage.t.sol index b17baadc..c5be92dc 100644 --- a/test/StdStorage.t.sol +++ b/src/test/StdStorage.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.7.0 <0.9.0; -import "src/StdStorage.sol"; -import "src/Test.sol"; +import "../StdStorage.sol"; +import "../Test.sol"; contract StdStorageTest is Test { using stdStorage for StdStorage; @@ -213,7 +213,7 @@ contract StdStorageTest is Test { function testFailStorageNativePack() public { stdstore.target(address(test)).sig(test.tA.selector).find(); stdstore.target(address(test)).sig(test.tB.selector).find(); - + // these both would fail stdstore.target(address(test)).sig(test.tC.selector).find(); stdstore.target(address(test)).sig(test.tD.selector).find(); @@ -274,7 +274,7 @@ contract StorageTest { bool public tC = false; - uint248 public tD = 1; + uint248 public tD = 1; struct UnpackedStruct { diff --git a/test/StdUtils.t.sol b/src/test/StdUtils.t.sol similarity index 98% rename from test/StdUtils.t.sol rename to src/test/StdUtils.t.sol index f5954813..9e825692 100644 --- a/test/StdUtils.t.sol +++ b/src/test/StdUtils.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.7.0 <0.9.0; -import "src/Test.sol"; +import "../Test.sol"; contract StdUtilsTest is Test { function testBound() public { diff --git a/test/fixtures/broadcast.log.json b/src/test/fixtures/broadcast.log.json similarity index 100% rename from test/fixtures/broadcast.log.json rename to src/test/fixtures/broadcast.log.json From cc55404cdea8416962364c7fbcb03dbc41dd5fa8 Mon Sep 17 00:00:00 2001 From: Matt Solomon Date: Fri, 7 Oct 2022 14:16:28 -0700 Subject: [PATCH 10/27] refactor: move test dir to root, update ci accordingly --- .github/workflows/tests.yml | 10 ++++++---- {src/test => test}/StdAssertions.t.sol | 2 +- {src/test => test}/StdCheats.t.sol | 16 ++++++++-------- {src/test => test}/StdError.t.sol | 4 ++-- {src/test => test}/StdMath.t.sol | 4 ++-- {src/test => test}/StdStorage.t.sol | 8 ++++---- {src/test => test}/StdUtils.t.sol | 2 +- {src/test => test}/fixtures/broadcast.log.json | 0 8 files changed, 24 insertions(+), 22 deletions(-) rename {src/test => test}/StdAssertions.t.sol (99%) rename {src/test => test}/StdCheats.t.sol (94%) rename {src/test => test}/StdError.t.sol (98%) rename {src/test => test}/StdMath.t.sol (99%) rename {src/test => test}/StdStorage.t.sol (99%) rename {src/test => test}/StdUtils.t.sol (98%) rename {src/test => test}/fixtures/broadcast.log.json (100%) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7689a844..37b899f1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,11 +17,13 @@ jobs: - name: Install dependencies run: forge install + - name: Run tests run: forge test -vvv + - name: Build Test with older solc versions run: | - forge build --contracts src/Test.sol --use solc:0.8.0 - forge build --contracts src/Test.sol --use solc:0.7.6 - forge build --contracts src/Test.sol --use solc:0.7.0 - forge build --contracts src/Test.sol --use solc:0.6.2 + forge build --skip test --use solc:0.8.0 + forge build --skip test --use solc:0.7.6 + forge build --skip test --use solc:0.7.0 + forge build --skip test --use solc:0.6.2 diff --git a/src/test/StdAssertions.t.sol b/test/StdAssertions.t.sol similarity index 99% rename from src/test/StdAssertions.t.sol rename to test/StdAssertions.t.sol index 9fd6d785..1d243791 100644 --- a/src/test/StdAssertions.t.sol +++ b/test/StdAssertions.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.7.0 <0.9.0; -import "../Test.sol"; +import "src/Test.sol"; contract StdAssertionsTest is Test { string constant CUSTOM_ERROR = "guh!"; diff --git a/src/test/StdCheats.t.sol b/test/StdCheats.t.sol similarity index 94% rename from src/test/StdCheats.t.sol rename to test/StdCheats.t.sol index cd346da3..612d74ff 100644 --- a/src/test/StdCheats.t.sol +++ b/test/StdCheats.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.7.0 <0.9.0; -import "../StdCheats.sol"; -import "../Test.sol"; -import "../StdJson.sol"; +import "src/StdCheats.sol"; +import "src/Test.sol"; +import "src/StdJson.sol"; contract StdCheatsTest is Test { Bar test; @@ -169,7 +169,7 @@ contract StdCheatsTest is Test { function testParseJsonTxDetail() public { string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/src/test/fixtures/broadcast.log.json"); + string memory path = string.concat(root, "/test/fixtures/broadcast.log.json"); string memory json = vm.readFile(path); bytes memory transactionDetails = json.parseRaw(".transactions[0].tx"); RawTx1559Detail memory rawTxDetail = abi.decode(transactionDetails, (RawTx1559Detail)); @@ -185,20 +185,20 @@ contract StdCheatsTest is Test { function testReadEIP1559Transaction() public { string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/src/test/fixtures/broadcast.log.json"); + string memory path = string.concat(root, "/test/fixtures/broadcast.log.json"); uint256 index = 0; Tx1559 memory transaction = readTx1559(path, index); } function testReadEIP1559Transactions() public { string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/src/test/fixtures/broadcast.log.json"); + string memory path = string.concat(root, "/test/fixtures/broadcast.log.json"); Tx1559[] memory transactions = readTx1559s(path); } function testReadReceipt() public { string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/src/test/fixtures/broadcast.log.json"); + string memory path = string.concat(root, "/test/fixtures/broadcast.log.json"); uint index = 5; Receipt memory receipt = readReceipt(path, index); assertEq(receipt.logsBloom, @@ -207,7 +207,7 @@ contract StdCheatsTest is Test { function testReadReceipts() public { string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/src/test/fixtures/broadcast.log.json"); + string memory path = string.concat(root, "/test/fixtures/broadcast.log.json"); Receipt[] memory receipts = readReceipts(path); } diff --git a/src/test/StdError.t.sol b/test/StdError.t.sol similarity index 98% rename from src/test/StdError.t.sol rename to test/StdError.t.sol index fd989428..67c127ab 100644 --- a/src/test/StdError.t.sol +++ b/test/StdError.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.0 <0.9.0; -import "../StdError.sol"; -import "../Test.sol"; +import "src/StdError.sol"; +import "src/Test.sol"; contract StdErrorsTest is Test { ErrorsTest test; diff --git a/src/test/StdMath.t.sol b/test/StdMath.t.sol similarity index 99% rename from src/test/StdMath.t.sol rename to test/StdMath.t.sol index 6182f3d8..143fcc4d 100644 --- a/src/test/StdMath.t.sol +++ b/test/StdMath.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.0 <0.9.0; -import "../StdMath.sol"; -import "../Test.sol"; +import "src/StdMath.sol"; +import "src/Test.sol"; contract StdMathTest is Test { function testGetAbs() external { diff --git a/src/test/StdStorage.t.sol b/test/StdStorage.t.sol similarity index 99% rename from src/test/StdStorage.t.sol rename to test/StdStorage.t.sol index c5be92dc..b17baadc 100644 --- a/src/test/StdStorage.t.sol +++ b/test/StdStorage.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.7.0 <0.9.0; -import "../StdStorage.sol"; -import "../Test.sol"; +import "src/StdStorage.sol"; +import "src/Test.sol"; contract StdStorageTest is Test { using stdStorage for StdStorage; @@ -213,7 +213,7 @@ contract StdStorageTest is Test { function testFailStorageNativePack() public { stdstore.target(address(test)).sig(test.tA.selector).find(); stdstore.target(address(test)).sig(test.tB.selector).find(); - + // these both would fail stdstore.target(address(test)).sig(test.tC.selector).find(); stdstore.target(address(test)).sig(test.tD.selector).find(); @@ -274,7 +274,7 @@ contract StorageTest { bool public tC = false; - uint248 public tD = 1; + uint248 public tD = 1; struct UnpackedStruct { diff --git a/src/test/StdUtils.t.sol b/test/StdUtils.t.sol similarity index 98% rename from src/test/StdUtils.t.sol rename to test/StdUtils.t.sol index 9e825692..f5954813 100644 --- a/src/test/StdUtils.t.sol +++ b/test/StdUtils.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.7.0 <0.9.0; -import "../Test.sol"; +import "src/Test.sol"; contract StdUtilsTest is Test { function testBound() public { diff --git a/src/test/fixtures/broadcast.log.json b/test/fixtures/broadcast.log.json similarity index 100% rename from src/test/fixtures/broadcast.log.json rename to test/fixtures/broadcast.log.json From 4206aa3e36206e020a58725763057137b062fb2b Mon Sep 17 00:00:00 2001 From: Matt Solomon Date: Mon, 10 Oct 2022 18:47:19 -0500 Subject: [PATCH 11/27] style: configure and run forge fmt --- foundry.toml | 12 +++ src/Common.sol | 2 +- src/Script.sol | 12 ++- src/StdAssertions.sol | 90 +++++++------------- src/StdCheats.sol | 172 +++++++++++++-------------------------- src/StdError.sol | 2 +- src/StdJson.sol | 79 ++++-------------- src/StdMath.sol | 4 +- src/StdStorage.sol | 84 ++++++++----------- src/StdUtils.sol | 21 +++-- src/Vm.sol | 57 ++++++------- test/StdAssertions.t.sol | 112 +++++++++++-------------- test/StdCheats.t.sol | 32 +++++--- test/StdError.t.sol | 6 +- test/StdMath.t.sol | 167 +++++++++++++++++++------------------ test/StdStorage.t.sol | 121 ++++++++++----------------- test/StdUtils.t.sol | 15 +--- 17 files changed, 403 insertions(+), 585 deletions(-) diff --git a/foundry.toml b/foundry.toml index 507b8bb9..6fde36d9 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,2 +1,14 @@ [profile.default] fs_permissions = [{ access = "read-write", path = "./"}] + +[fmt] +# These are all the `forge fmt` defaults. +line_length = 120 +tab_width = 4 +bracket_spacing = false +int_types = 'long' +multiline_func_header = 'attributes_first' +quote_style = 'double' +number_underscore = 'preserve' +single_line_statement_blocks = 'preserve' +ignore = ["src/console.sol", "src/console2.sol"] \ No newline at end of file diff --git a/src/Common.sol b/src/Common.sol index 4bef9e5d..f5e3906d 100644 --- a/src/Common.sol +++ b/src/Common.sol @@ -8,4 +8,4 @@ abstract contract CommonBase { StdStorage internal stdstore; Vm internal constant vm = Vm(VM_ADDRESS); -} \ No newline at end of file +} diff --git a/src/Script.sol b/src/Script.sol index c4947c63..569ce872 100644 --- a/src/Script.sol +++ b/src/Script.sol @@ -2,7 +2,17 @@ pragma solidity >=0.6.2 <0.9.0; import {CommonBase} from "./Common.sol"; -import {console, console2, StdCheatsSafe, stdJson, stdMath, stdStorageSafe, StdStorage, StdUtils, VmSafe} from "./Components.sol"; +import { + console, + console2, + StdCheatsSafe, + stdJson, + stdMath, + StdStorage, + stdStorageSafe, + StdUtils, + VmSafe +} from "./Components.sol"; abstract contract ScriptBase is CommonBase { VmSafe internal constant vmSafe = VmSafe(VM_ADDRESS); diff --git a/src/StdAssertions.sol b/src/StdAssertions.sol index 9d75d2d2..439366e2 100644 --- a/src/StdAssertions.sol +++ b/src/StdAssertions.sol @@ -27,9 +27,9 @@ abstract contract StdAssertions is DSTest { function assertEq(bool a, bool b) internal virtual { if (a != b) { - emit log ("Error: a == b not satisfied [bool]"); - emit log_named_string (" Expected", b ? "true" : "false"); - emit log_named_string (" Actual", a ? "true" : "false"); + emit log("Error: a == b not satisfied [bool]"); + emit log_named_string(" Expected", b ? "true" : "false"); + emit log_named_string(" Actual", a ? "true" : "false"); fail(); } } @@ -90,7 +90,6 @@ abstract contract StdAssertions is DSTest { } } - function assertEq(address[] memory a, address[] memory b, string memory err) internal virtual { if (keccak256(abi.encode(a)) != keccak256(abi.encode(b))) { emit log_named_string("Error", err); @@ -103,64 +102,46 @@ abstract contract StdAssertions is DSTest { assertEq(uint256(a), uint256(b)); } - function assertApproxEqAbs( - uint256 a, - uint256 b, - uint256 maxDelta - ) internal virtual { + function assertApproxEqAbs(uint256 a, uint256 b, uint256 maxDelta) internal virtual { uint256 delta = stdMath.delta(a, b); if (delta > maxDelta) { - emit log ("Error: a ~= b not satisfied [uint]"); - emit log_named_uint (" Expected", b); - emit log_named_uint (" Actual", a); - emit log_named_uint (" Max Delta", maxDelta); - emit log_named_uint (" Delta", delta); + emit log("Error: a ~= b not satisfied [uint]"); + emit log_named_uint(" Expected", b); + emit log_named_uint(" Actual", a); + emit log_named_uint(" Max Delta", maxDelta); + emit log_named_uint(" Delta", delta); fail(); } } - function assertApproxEqAbs( - uint256 a, - uint256 b, - uint256 maxDelta, - string memory err - ) internal virtual { + function assertApproxEqAbs(uint256 a, uint256 b, uint256 maxDelta, string memory err) internal virtual { uint256 delta = stdMath.delta(a, b); if (delta > maxDelta) { - emit log_named_string ("Error", err); + emit log_named_string("Error", err); assertApproxEqAbs(a, b, maxDelta); } } - function assertApproxEqAbs( - int256 a, - int256 b, - uint256 maxDelta - ) internal virtual { + function assertApproxEqAbs(int256 a, int256 b, uint256 maxDelta) internal virtual { uint256 delta = stdMath.delta(a, b); if (delta > maxDelta) { - emit log ("Error: a ~= b not satisfied [int]"); - emit log_named_int (" Expected", b); - emit log_named_int (" Actual", a); - emit log_named_uint (" Max Delta", maxDelta); - emit log_named_uint (" Delta", delta); + emit log("Error: a ~= b not satisfied [int]"); + emit log_named_int(" Expected", b); + emit log_named_int(" Actual", a); + emit log_named_uint(" Max Delta", maxDelta); + emit log_named_uint(" Delta", delta); fail(); } } - function assertApproxEqAbs( - int256 a, - int256 b, - uint256 maxDelta, - string memory err - ) internal virtual { + function assertApproxEqAbs(int256 a, int256 b, uint256 maxDelta, string memory err) internal virtual { uint256 delta = stdMath.delta(a, b); if (delta > maxDelta) { - emit log_named_string ("Error", err); + emit log_named_string("Error", err); assertApproxEqAbs(a, b, maxDelta); } } @@ -175,11 +156,11 @@ abstract contract StdAssertions is DSTest { uint256 percentDelta = stdMath.percentDelta(a, b); if (percentDelta > maxPercentDelta) { - emit log ("Error: a ~= b not satisfied [uint]"); - emit log_named_uint (" Expected", b); - emit log_named_uint (" Actual", a); - emit log_named_decimal_uint (" Max % Delta", maxPercentDelta, 18); - emit log_named_decimal_uint (" % Delta", percentDelta, 18); + emit log("Error: a ~= b not satisfied [uint]"); + emit log_named_uint(" Expected", b); + emit log_named_uint(" Actual", a); + emit log_named_decimal_uint(" Max % Delta", maxPercentDelta, 18); + emit log_named_decimal_uint(" % Delta", percentDelta, 18); fail(); } } @@ -195,42 +176,33 @@ abstract contract StdAssertions is DSTest { uint256 percentDelta = stdMath.percentDelta(a, b); if (percentDelta > maxPercentDelta) { - emit log_named_string ("Error", err); + emit log_named_string("Error", err); assertApproxEqRel(a, b, maxPercentDelta); } } - function assertApproxEqRel( - int256 a, - int256 b, - uint256 maxPercentDelta - ) internal virtual { + function assertApproxEqRel(int256 a, int256 b, uint256 maxPercentDelta) internal virtual { if (b == 0) return assertEq(a, b); // If the expected is 0, actual must be too. uint256 percentDelta = stdMath.percentDelta(a, b); if (percentDelta > maxPercentDelta) { - emit log ("Error: a ~= b not satisfied [int]"); - emit log_named_int (" Expected", b); - emit log_named_int (" Actual", a); + emit log("Error: a ~= b not satisfied [int]"); + emit log_named_int(" Expected", b); + emit log_named_int(" Actual", a); emit log_named_decimal_uint(" Max % Delta", maxPercentDelta, 18); emit log_named_decimal_uint(" % Delta", percentDelta, 18); fail(); } } - function assertApproxEqRel( - int256 a, - int256 b, - uint256 maxPercentDelta, - string memory err - ) internal virtual { + function assertApproxEqRel(int256 a, int256 b, uint256 maxPercentDelta, string memory err) internal virtual { if (b == 0) return assertEq(a, b, err); // If the expected is 0, actual must be too. uint256 percentDelta = stdMath.percentDelta(a, b); if (percentDelta > maxPercentDelta) { - emit log_named_string ("Error", err); + emit log_named_string("Error", err); assertApproxEqRel(a, b, maxPercentDelta); } } diff --git a/src/StdCheats.sol b/src/StdCheats.sol index f75b0f49..e7b62555 100644 --- a/src/StdCheats.sol +++ b/src/StdCheats.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.6.2 <0.9.0; + pragma experimental ABIEncoderV2; import "./StdStorage.sol"; @@ -8,11 +9,11 @@ import "./Vm.sol"; abstract contract StdCheatsSafe { VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code"))))); - // Data structures to parse Transaction objects from the broadcast artifact - // that conform to EIP1559. The Raw structs is what is parsed from the JSON - // and then converted to the one that is used by the user for better UX. + // Data structures to parse Transaction objects from the broadcast artifact + // that conform to EIP1559. The Raw structs is what is parsed from the JSON + // and then converted to the one that is used by the user for better UX. - struct RawTx1559 { + struct RawTx1559 { string[] arguments; address contractAddress; string contractName; @@ -57,11 +58,11 @@ abstract contract StdCheatsSafe { uint256 value; } - // Data structures to parse Transaction objects from the broadcast artifact - // that DO NOT conform to EIP1559. The Raw structs is what is parsed from the JSON - // and then converted to the one that is used by the user for better UX. + // Data structures to parse Transaction objects from the broadcast artifact + // that DO NOT conform to EIP1559. The Raw structs is what is parsed from the JSON + // and then converted to the one that is used by the user for better UX. - struct TxLegacy{ + struct TxLegacy { string[] arguments; address contractAddress; string contractName; @@ -71,7 +72,7 @@ abstract contract StdCheatsSafe { TxDetailLegacy transaction; } - struct TxDetailLegacy{ + struct TxDetailLegacy { AccessList[] accessList; uint256 chainId; bytes data; @@ -89,7 +90,7 @@ abstract contract StdCheatsSafe { uint256 value; } - struct AccessList{ + struct AccessList { address accessAddress; bytes32[] storageKeys; } @@ -185,10 +186,7 @@ abstract contract StdCheatsSafe { string value; } - function readEIP1559ScriptArtifact(string memory path) - internal virtual - returns(EIP1559ScriptArtifact memory) - { + function readEIP1559ScriptArtifact(string memory path) internal virtual returns (EIP1559ScriptArtifact memory) { string memory data = vm.readFile(path); bytes memory parsedData = vm.parseJson(data); RawEIP1559ScriptArtifact memory rawArtifact = abi.decode(parsedData, (RawEIP1559ScriptArtifact)); @@ -203,33 +201,29 @@ abstract contract StdCheatsSafe { return artifact; } - function rawToConvertedEIPTx1559s(RawTx1559[] memory rawTxs) - internal virtual pure - returns (Tx1559[] memory) - { + function rawToConvertedEIPTx1559s(RawTx1559[] memory rawTxs) internal pure virtual returns (Tx1559[] memory) { Tx1559[] memory txs = new Tx1559[](rawTxs.length); - for (uint i; i < rawTxs.length; i++) { + for (uint256 i; i < rawTxs.length; i++) { txs[i] = rawToConvertedEIPTx1559(rawTxs[i]); } return txs; } - function rawToConvertedEIPTx1559(RawTx1559 memory rawTx) - internal virtual pure - returns (Tx1559 memory) - { + function rawToConvertedEIPTx1559(RawTx1559 memory rawTx) internal pure virtual returns (Tx1559 memory) { Tx1559 memory transaction; transaction.arguments = rawTx.arguments; transaction.contractName = rawTx.contractName; transaction.functionSig = rawTx.functionSig; - transaction.hash= rawTx.hash; + transaction.hash = rawTx.hash; transaction.txDetail = rawToConvertedEIP1559Detail(rawTx.txDetail); - transaction.opcode= rawTx.opcode; + transaction.opcode = rawTx.opcode; return transaction; } function rawToConvertedEIP1559Detail(RawTx1559Detail memory rawDetail) - internal virtual pure + internal + pure + virtual returns (Tx1559Detail memory) { Tx1559Detail memory txDetail; @@ -242,78 +236,55 @@ abstract contract StdCheatsSafe { txDetail.gas = bytesToUint(rawDetail.gas); txDetail.accessList = rawDetail.accessList; return txDetail; - } - function readTx1559s(string memory path) - internal virtual - returns (Tx1559[] memory) - { + function readTx1559s(string memory path) internal virtual returns (Tx1559[] memory) { string memory deployData = vm.readFile(path); - bytes memory parsedDeployData = - vm.parseJson(deployData, ".transactions"); + bytes memory parsedDeployData = vm.parseJson(deployData, ".transactions"); RawTx1559[] memory rawTxs = abi.decode(parsedDeployData, (RawTx1559[])); return rawToConvertedEIPTx1559s(rawTxs); } - - function readTx1559(string memory path, uint256 index) - internal virtual - returns (Tx1559 memory) - { + function readTx1559(string memory path, uint256 index) internal virtual returns (Tx1559 memory) { string memory deployData = vm.readFile(path); - string memory key = string(abi.encodePacked(".transactions[",vm.toString(index), "]")); - bytes memory parsedDeployData = - vm.parseJson(deployData, key); + string memory key = string(abi.encodePacked(".transactions[", vm.toString(index), "]")); + bytes memory parsedDeployData = vm.parseJson(deployData, key); RawTx1559 memory rawTx = abi.decode(parsedDeployData, (RawTx1559)); return rawToConvertedEIPTx1559(rawTx); } - // Analogous to readTransactions, but for receipts. - function readReceipts(string memory path) - internal virtual - returns (Receipt[] memory) - { + function readReceipts(string memory path) internal virtual returns (Receipt[] memory) { string memory deployData = vm.readFile(path); bytes memory parsedDeployData = vm.parseJson(deployData, ".receipts"); RawReceipt[] memory rawReceipts = abi.decode(parsedDeployData, (RawReceipt[])); return rawToConvertedReceipts(rawReceipts); } - function readReceipt(string memory path, uint index) - internal virtual - returns (Receipt memory) - { + function readReceipt(string memory path, uint256 index) internal virtual returns (Receipt memory) { string memory deployData = vm.readFile(path); - string memory key = string(abi.encodePacked(".receipts[",vm.toString(index), "]")); + string memory key = string(abi.encodePacked(".receipts[", vm.toString(index), "]")); bytes memory parsedDeployData = vm.parseJson(deployData, key); RawReceipt memory rawReceipt = abi.decode(parsedDeployData, (RawReceipt)); return rawToConvertedReceipt(rawReceipt); } - function rawToConvertedReceipts(RawReceipt[] memory rawReceipts) - internal virtual pure - returns(Receipt[] memory) - { + function rawToConvertedReceipts(RawReceipt[] memory rawReceipts) internal pure virtual returns (Receipt[] memory) { Receipt[] memory receipts = new Receipt[](rawReceipts.length); - for (uint i; i < rawReceipts.length; i++) { + for (uint256 i; i < rawReceipts.length; i++) { receipts[i] = rawToConvertedReceipt(rawReceipts[i]); } return receipts; } - function rawToConvertedReceipt(RawReceipt memory rawReceipt) - internal virtual pure - returns(Receipt memory) - { + function rawToConvertedReceipt(RawReceipt memory rawReceipt) internal pure virtual returns (Receipt memory) { Receipt memory receipt; receipt.blockHash = rawReceipt.blockHash; receipt.to = rawReceipt.to; receipt.from = rawReceipt.from; receipt.contractAddress = rawReceipt.contractAddress; receipt.effectiveGasPrice = bytesToUint(rawReceipt.effectiveGasPrice); - receipt.cumulativeGasUsed= bytesToUint(rawReceipt.cumulativeGasUsed); + receipt.cumulativeGasUsed = bytesToUint(rawReceipt.cumulativeGasUsed); receipt.gasUsed = bytesToUint(rawReceipt.gasUsed); receipt.status = bytesToUint(rawReceipt.status); receipt.transactionIndex = bytesToUint(rawReceipt.transactionIndex); @@ -325,11 +296,13 @@ abstract contract StdCheatsSafe { } function rawToConvertedReceiptLogs(RawReceiptLog[] memory rawLogs) - internal virtual pure + internal + pure + virtual returns (ReceiptLog[] memory) { ReceiptLog[] memory logs = new ReceiptLog[](rawLogs.length); - for (uint i; i < rawLogs.length; i++) { + for (uint256 i; i < rawLogs.length; i++) { logs[i].logAddress = rawLogs[i].logAddress; logs[i].blockHash = rawLogs[i].blockHash; logs[i].blockNumber = bytesToUint(rawLogs[i].blockNumber); @@ -341,99 +314,78 @@ abstract contract StdCheatsSafe { logs[i].removed = rawLogs[i].removed; } return logs; - } // Deploy a contract by fetching the contract bytecode from // the artifacts directory // e.g. `deployCode(code, abi.encode(arg1,arg2,arg3))` - function deployCode(string memory what, bytes memory args) - internal virtual - returns (address addr) - { + function deployCode(string memory what, bytes memory args) internal virtual returns (address addr) { bytes memory bytecode = abi.encodePacked(vm.getCode(what), args); /// @solidity memory-safe-assembly assembly { addr := create(0, add(bytecode, 0x20), mload(bytecode)) } - require( - addr != address(0), - "Test deployCode(string,bytes): Deployment failed." - ); + require(addr != address(0), "Test deployCode(string,bytes): Deployment failed."); } - function deployCode(string memory what) - internal virtual - returns (address addr) - { + function deployCode(string memory what) internal virtual returns (address addr) { bytes memory bytecode = vm.getCode(what); /// @solidity memory-safe-assembly assembly { addr := create(0, add(bytecode, 0x20), mload(bytecode)) } - require( - addr != address(0), - "Test deployCode(string): Deployment failed." - ); + require(addr != address(0), "Test deployCode(string): Deployment failed."); } /// @dev deploy contract with value on construction - function deployCode(string memory what, bytes memory args, uint256 val) - internal virtual - returns (address addr) - { + function deployCode(string memory what, bytes memory args, uint256 val) internal virtual returns (address addr) { bytes memory bytecode = abi.encodePacked(vm.getCode(what), args); /// @solidity memory-safe-assembly assembly { addr := create(val, add(bytecode, 0x20), mload(bytecode)) } - require( - addr != address(0), - "Test deployCode(string,bytes,uint256): Deployment failed." - ); + require(addr != address(0), "Test deployCode(string,bytes,uint256): Deployment failed."); } - function deployCode(string memory what, uint256 val) - internal virtual - returns (address addr) - { + function deployCode(string memory what, uint256 val) internal virtual returns (address addr) { bytes memory bytecode = vm.getCode(what); /// @solidity memory-safe-assembly assembly { addr := create(val, add(bytecode, 0x20), mload(bytecode)) } - require( - addr != address(0), - "Test deployCode(string,uint256): Deployment failed." - ); + require(addr != address(0), "Test deployCode(string,uint256): Deployment failed."); } // creates a labeled address and the corresponding private key - function makeAddrAndKey(string memory name) internal virtual returns(address addr, uint256 privateKey) { + function makeAddrAndKey(string memory name) internal virtual returns (address addr, uint256 privateKey) { privateKey = uint256(keccak256(abi.encodePacked(name))); addr = vm.addr(privateKey); vm.label(addr, name); } // creates a labeled address - function makeAddr(string memory name) internal virtual returns(address addr) { + function makeAddr(string memory name) internal virtual returns (address addr) { (addr,) = makeAddrAndKey(name); } - function deriveRememberKey(string memory mnemonic, uint32 index) internal virtual returns (address who, uint256 privateKey) { + function deriveRememberKey(string memory mnemonic, uint32 index) + internal + virtual + returns (address who, uint256 privateKey) + { privateKey = vm.deriveKey(mnemonic, index); who = vm.rememberKey(privateKey); } - function bytesToUint(bytes memory b) private pure returns (uint256){ - uint256 number; - for (uint i=0; i < b.length; i++) { - number = number + uint(uint8(b[i]))*(2**(8*(b.length-(i+1)))); - } + function bytesToUint(bytes memory b) private pure returns (uint256) { + uint256 number; + for (uint256 i = 0; i < b.length; i++) { + number = number + uint256(uint8(b[i])) * (2 ** (8 * (b.length - (i + 1)))); + } return number; } } @@ -445,7 +397,6 @@ abstract contract StdCheats is StdCheatsSafe { StdStorage private stdstore; Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); - // Skip forward or rewind time by the specified number of seconds function skip(uint256 time) internal virtual { vm.warp(block.timestamp + time); @@ -522,11 +473,7 @@ abstract contract StdCheats is StdCheatsSafe { uint256 prevBal = abi.decode(balData, (uint256)); // update balance - stdstore - .target(token) - .sig(0x70a08231) - .with_key(to) - .checked_write(give); + stdstore.target(token).sig(0x70a08231).with_key(to).checked_write(give); // update total supply if (adjust) { @@ -537,10 +484,7 @@ abstract contract StdCheats is StdCheatsSafe { } else { totSup += (give - prevBal); } - stdstore - .target(token) - .sig(0x18160ddd) - .checked_write(totSup); + stdstore.target(token).sig(0x18160ddd).checked_write(totSup); } } } diff --git a/src/StdError.sol b/src/StdError.sol index 7631f3b9..a302191f 100644 --- a/src/StdError.sol +++ b/src/StdError.sol @@ -12,4 +12,4 @@ library stdError { bytes public constant indexOOBError = abi.encodeWithSignature("Panic(uint256)", 0x32); bytes public constant memOverflowError = abi.encodeWithSignature("Panic(uint256)", 0x41); bytes public constant zeroVarError = abi.encodeWithSignature("Panic(uint256)", 0x51); -} \ No newline at end of file +} diff --git a/src/StdJson.sol b/src/StdJson.sol index 2d335010..f5fd7074 100644 --- a/src/StdJson.sol +++ b/src/StdJson.sol @@ -1,118 +1,71 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.6.0 <0.9.0; + pragma experimental ABIEncoderV2; import "./Vm.sol"; // Helpers for parsing keys into types. library stdJson { - VmSafe private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); - function parseRaw(string memory json, string memory key) - internal - returns (bytes memory) - { + function parseRaw(string memory json, string memory key) internal returns (bytes memory) { return vm.parseJson(json, key); } - function readUint(string memory json, string memory key) - internal - returns (uint256) - { + function readUint(string memory json, string memory key) internal returns (uint256) { return abi.decode(vm.parseJson(json, key), (uint256)); } - function readUintArray(string memory json, string memory key) - internal - returns (uint256[] memory) - { + function readUintArray(string memory json, string memory key) internal returns (uint256[] memory) { return abi.decode(vm.parseJson(json, key), (uint256[])); } - function readInt(string memory json, string memory key) - internal - returns (int256) - { + function readInt(string memory json, string memory key) internal returns (int256) { return abi.decode(vm.parseJson(json, key), (int256)); } - function readIntArray(string memory json, string memory key) - internal - returns (int256[] memory) - { + function readIntArray(string memory json, string memory key) internal returns (int256[] memory) { return abi.decode(vm.parseJson(json, key), (int256[])); } - function readBytes32(string memory json, string memory key) - internal - returns (bytes32) - { + function readBytes32(string memory json, string memory key) internal returns (bytes32) { return abi.decode(vm.parseJson(json, key), (bytes32)); } - function readBytes32Array(string memory json, string memory key) - internal - returns (bytes32[] memory) - { + function readBytes32Array(string memory json, string memory key) internal returns (bytes32[] memory) { return abi.decode(vm.parseJson(json, key), (bytes32[])); } - function readString(string memory json, string memory key) - internal - returns (string memory) - { + function readString(string memory json, string memory key) internal returns (string memory) { return abi.decode(vm.parseJson(json, key), (string)); } - function readStringArray(string memory json, string memory key) - internal - returns (string[] memory) - { + function readStringArray(string memory json, string memory key) internal returns (string[] memory) { return abi.decode(vm.parseJson(json, key), (string[])); } - function readAddress(string memory json, string memory key) - internal - returns (address) - { + function readAddress(string memory json, string memory key) internal returns (address) { return abi.decode(vm.parseJson(json, key), (address)); } - function readAddressArray(string memory json, string memory key) - internal - returns (address[] memory) - { + function readAddressArray(string memory json, string memory key) internal returns (address[] memory) { return abi.decode(vm.parseJson(json, key), (address[])); } - function readBool(string memory json, string memory key) - internal - returns (bool) - { + function readBool(string memory json, string memory key) internal returns (bool) { return abi.decode(vm.parseJson(json, key), (bool)); } - function readBoolArray(string memory json, string memory key) - internal - returns (bool[] memory) - { + function readBoolArray(string memory json, string memory key) internal returns (bool[] memory) { return abi.decode(vm.parseJson(json, key), (bool[])); } - function readBytes(string memory json, string memory key) - internal - returns (bytes memory) - { + function readBytes(string memory json, string memory key) internal returns (bytes memory) { return abi.decode(vm.parseJson(json, key), (bytes)); } - function readBytesArray(string memory json, string memory key) - internal - returns (bytes[] memory) - { + function readBytesArray(string memory json, string memory key) internal returns (bytes[] memory) { return abi.decode(vm.parseJson(json, key), (bytes[])); } - - } diff --git a/src/StdMath.sol b/src/StdMath.sol index 2da1bd77..459523bd 100644 --- a/src/StdMath.sol +++ b/src/StdMath.sol @@ -14,9 +14,7 @@ library stdMath { } function delta(uint256 a, uint256 b) internal pure returns (uint256) { - return a > b - ? a - b - : b - a; + return a > b ? a - b : b - a; } function delta(int256 a, int256 b) internal pure returns (uint256) { diff --git a/src/StdStorage.sol b/src/StdStorage.sol index 97cb7636..89710e8d 100644 --- a/src/StdStorage.sol +++ b/src/StdStorage.sol @@ -4,9 +4,8 @@ pragma solidity >=0.6.2 <0.9.0; import "./Vm.sol"; struct StdStorage { - mapping (address => mapping(bytes4 => mapping(bytes32 => uint256))) slots; - mapping (address => mapping(bytes4 => mapping(bytes32 => bool))) finds; - + mapping(address => mapping(bytes4 => mapping(bytes32 => uint256))) slots; + mapping(address => mapping(bytes4 => mapping(bytes32 => bool))) finds; bytes32[] _keys; bytes4 _sig; uint256 _depth; @@ -15,18 +14,12 @@ struct StdStorage { } library stdStorageSafe { - event SlotFound(address who, bytes4 fsig, bytes32 keysHash, uint slot); - event WARNING_UninitedSlot(address who, uint slot); + event SlotFound(address who, bytes4 fsig, bytes32 keysHash, uint256 slot); + event WARNING_UninitedSlot(address who, uint256 slot); Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); - function sigs( - string memory sigStr - ) - internal - pure - returns (bytes4) - { + function sigs(string memory sigStr) internal pure returns (bytes4) { return bytes4(keccak256(bytes(sigStr))); } @@ -36,12 +29,7 @@ library stdStorageSafe { // if map, will be keccak256(abi.encode(key, uint(slot))); // if deep map, will be keccak256(abi.encode(key1, keccak256(abi.encode(key0, uint(slot))))); // if map struct, will be bytes32(uint256(keccak256(abi.encode(key1, keccak256(abi.encode(key0, uint(slot)))))) + structFieldDepth); - function find( - StdStorage storage self - ) - internal - returns (uint256) - { + function find(StdStorage storage self) internal returns (uint256) { address who = self._target; bytes4 fsig = self._sig; uint256 field_depth = self._depth; @@ -56,17 +44,20 @@ library stdStorageSafe { bytes32 fdat; { (, bytes memory rdat) = who.staticcall(cald); - fdat = bytesToBytes32(rdat, 32*field_depth); + fdat = bytesToBytes32(rdat, 32 * field_depth); } - (bytes32[] memory reads, ) = vm.accesses(address(who)); + (bytes32[] memory reads,) = vm.accesses(address(who)); if (reads.length == 1) { bytes32 curr = vm.load(who, reads[0]); if (curr == bytes32(0)) { emit WARNING_UninitedSlot(who, uint256(reads[0])); } if (fdat != curr) { - require(false, "stdStorage find(StdStorage): Packed slot. This would cause dangerous overwriting and currently isn't supported."); + require( + false, + "stdStorage find(StdStorage): Packed slot. This would cause dangerous overwriting and currently isn't supported." + ); } emit SlotFound(who, fsig, keccak256(abi.encodePacked(ins, field_depth)), uint256(reads[0])); self.slots[who][fsig][keccak256(abi.encodePacked(ins, field_depth))] = uint256(reads[0]); @@ -83,7 +74,7 @@ library stdStorageSafe { bytes memory rdat; { (success, rdat) = who.staticcall(cald); - fdat = bytesToBytes32(rdat, 32*field_depth); + fdat = bytesToBytes32(rdat, 32 * field_depth); } if (success && fdat == bytes32(hex"1337")) { @@ -100,7 +91,10 @@ library stdStorageSafe { require(false, "stdStorage find(StdStorage): No storage use detected for target."); } - require(self.finds[who][fsig][keccak256(abi.encodePacked(ins, field_depth))], "stdStorage find(StdStorage): Slot(s) not found."); + require( + self.finds[who][fsig][keccak256(abi.encodePacked(ins, field_depth))], + "stdStorage find(StdStorage): Slot(s) not found." + ); delete self._target; delete self._sig; @@ -134,6 +128,7 @@ library stdStorageSafe { self._keys.push(bytes32(amt)); return self; } + function with_key(StdStorage storage self, bytes32 key) internal returns (StdStorage storage) { self._keys.push(key); return self; @@ -154,7 +149,6 @@ library stdStorageSafe { return abi.decode(read(self), (bytes32)); } - function read_bool(StdStorage storage self) internal returns (bool) { int256 v = read_int(self); if (v == 0) return false; @@ -174,18 +168,17 @@ library stdStorageSafe { return abi.decode(read(self), (int256)); } - function bytesToBytes32(bytes memory b, uint offset) private pure returns (bytes32) { + function bytesToBytes32(bytes memory b, uint256 offset) private pure returns (bytes32) { bytes32 out; uint256 max = b.length > 32 ? 32 : b.length; - for (uint i = 0; i < max; i++) { + for (uint256 i = 0; i < max; i++) { out |= bytes32(b[offset + i] & 0xFF) >> (i * 8); } return out; } - function flatten(bytes32[] memory b) private pure returns (bytes memory) - { + function flatten(bytes32[] memory b) private pure returns (bytes memory) { bytes memory result = new bytes(b.length * 32); for (uint256 i = 0; i < b.length; i++) { bytes32 k = b[i]; @@ -202,22 +195,11 @@ library stdStorageSafe { library stdStorage { Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); - function sigs( - string memory sigStr - ) - internal - pure - returns (bytes4) - { + function sigs(string memory sigStr) internal pure returns (bytes4) { return stdStorageSafe.sigs(sigStr); } - function find( - StdStorage storage self - ) - internal - returns (uint256) - { + function find(StdStorage storage self) internal returns (uint256) { return stdStorageSafe.find(self); } @@ -240,6 +222,7 @@ library stdStorage { function with_key(StdStorage storage self, uint256 amt) internal returns (StdStorage storage) { return stdStorageSafe.with_key(self, amt); } + function with_key(StdStorage storage self, bytes32 key) internal returns (StdStorage storage) { return stdStorageSafe.with_key(self, key); } @@ -265,10 +248,7 @@ library stdStorage { checked_write(self, t); } - function checked_write( - StdStorage storage self, - bytes32 set - ) internal { + function checked_write(StdStorage storage self, bytes32 set) internal { address who = self._target; bytes4 fsig = self._sig; uint256 field_depth = self._depth; @@ -283,12 +263,15 @@ library stdStorage { bytes32 fdat; { (, bytes memory rdat) = who.staticcall(cald); - fdat = bytesToBytes32(rdat, 32*field_depth); + fdat = bytesToBytes32(rdat, 32 * field_depth); } bytes32 curr = vm.load(who, slot); if (fdat != curr) { - require(false, "stdStorage find(StdStorage): Packed slot. This would cause dangerous overwriting and currently isn't supported."); + require( + false, + "stdStorage find(StdStorage): Packed slot. This would cause dangerous overwriting and currently isn't supported." + ); } vm.store(who, slot, set); delete self._target; @@ -318,19 +301,18 @@ library stdStorage { } // Private function so needs to be copied over - function bytesToBytes32(bytes memory b, uint offset) private pure returns (bytes32) { + function bytesToBytes32(bytes memory b, uint256 offset) private pure returns (bytes32) { bytes32 out; uint256 max = b.length > 32 ? 32 : b.length; - for (uint i = 0; i < max; i++) { + for (uint256 i = 0; i < max; i++) { out |= bytes32(b[offset + i] & 0xFF) >> (i * 8); } return out; } // Private function so needs to be copied over - function flatten(bytes32[] memory b) private pure returns (bytes memory) - { + function flatten(bytes32[] memory b) private pure returns (bytes memory) { bytes memory result = new bytes(b.length * 32); for (uint256 i = 0; i < b.length; i++) { bytes32 k = b[i]; diff --git a/src/StdUtils.sol b/src/StdUtils.sol index 71bda431..cec6c38b 100644 --- a/src/StdUtils.sol +++ b/src/StdUtils.sol @@ -21,36 +21,45 @@ abstract contract StdUtils { uint256 mod = x % size; result = min + mod; } - + console2.log("Bound Result", result); } /// @dev Compute the address a contract will be deployed at for a given deployer address and nonce /// @notice adapated from Solmate implementation (https://github.com/Rari-Capital/solmate/blob/main/src/utils/LibRLP.sol) function computeCreateAddress(address deployer, uint256 nonce) internal pure virtual returns (address) { + // forgefmt: disable-start // The integer zero is treated as an empty byte string, and as a result it only has a length prefix, 0x80, computed via 0x80 + 0. // A one byte integer uses its own value as its length prefix, there is no additional "0x80 + length" prefix that comes before it. - if (nonce == 0x00) return addressFromLast20Bytes(keccak256(abi.encodePacked(bytes1(0xd6), bytes1(0x94), deployer, bytes1(0x80)))); - if (nonce <= 0x7f) return addressFromLast20Bytes(keccak256(abi.encodePacked(bytes1(0xd6), bytes1(0x94), deployer, uint8(nonce)))); + if (nonce == 0x00) return addressFromLast20Bytes(keccak256(abi.encodePacked(bytes1(0xd6), bytes1(0x94), deployer, bytes1(0x80)))); + if (nonce <= 0x7f) return addressFromLast20Bytes(keccak256(abi.encodePacked(bytes1(0xd6), bytes1(0x94), deployer, uint8(nonce)))); // Nonces greater than 1 byte all follow a consistent encoding scheme, where each value is preceded by a prefix of 0x80 + length. if (nonce <= 2**8 - 1) return addressFromLast20Bytes(keccak256(abi.encodePacked(bytes1(0xd7), bytes1(0x94), deployer, bytes1(0x81), uint8(nonce)))); if (nonce <= 2**16 - 1) return addressFromLast20Bytes(keccak256(abi.encodePacked(bytes1(0xd8), bytes1(0x94), deployer, bytes1(0x82), uint16(nonce)))); if (nonce <= 2**24 - 1) return addressFromLast20Bytes(keccak256(abi.encodePacked(bytes1(0xd9), bytes1(0x94), deployer, bytes1(0x83), uint24(nonce)))); + // forgefmt: disable-end // More details about RLP encoding can be found here: https://eth.wiki/fundamentals/rlp // 0xda = 0xc0 (short RLP prefix) + 0x16 (length of: 0x94 ++ proxy ++ 0x84 ++ nonce) // 0x94 = 0x80 + 0x14 (0x14 = the length of an address, 20 bytes, in hex) // 0x84 = 0x80 + 0x04 (0x04 = the bytes length of the nonce, 4 bytes, in hex) // We assume nobody can have a nonce large enough to require more than 32 bytes. - return addressFromLast20Bytes(keccak256(abi.encodePacked(bytes1(0xda), bytes1(0x94), deployer, bytes1(0x84), uint32(nonce)))); + return addressFromLast20Bytes( + keccak256(abi.encodePacked(bytes1(0xda), bytes1(0x94), deployer, bytes1(0x84), uint32(nonce))) + ); } - function computeCreate2Address(bytes32 salt, bytes32 initcodeHash, address deployer) internal pure virtual returns (address) { + function computeCreate2Address(bytes32 salt, bytes32 initcodeHash, address deployer) + internal + pure + virtual + returns (address) + { return addressFromLast20Bytes(keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, initcodeHash))); } function addressFromLast20Bytes(bytes32 bytesValue) private pure returns (address) { return address(uint160(uint256(bytesValue))); } -} \ No newline at end of file +} diff --git a/src/Vm.sol b/src/Vm.sol index 56b0f7cd..798ac8fd 100644 --- a/src/Vm.sol +++ b/src/Vm.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.6.2 <0.9.0; + pragma experimental ABIEncoderV2; interface VmSafe { @@ -9,9 +10,9 @@ interface VmSafe { } // Loads a storage slot from an address (who, slot) - function load(address,bytes32) external returns (bytes32); + function load(address, bytes32) external returns (bytes32); // Signs data, (privateKey, digest) => (v, r, s) - function sign(uint256,bytes32) external returns (uint8,bytes32,bytes32); + function sign(uint256, bytes32) external returns (uint8, bytes32, bytes32); // Gets the address for a given private key, (privateKey) => (address) function addr(uint256) external returns (address); // Gets the nonce of an account @@ -87,12 +88,12 @@ interface VmSafe { // (path) => () function removeFile(string calldata) external; // Convert values to a string, (value) => (stringified value) - function toString(address) external returns(string memory); - function toString(bytes calldata) external returns(string memory); - function toString(bytes32) external returns(string memory); - function toString(bool) external returns(string memory); - function toString(uint256) external returns(string memory); - function toString(int256) external returns(string memory); + function toString(address) external returns (string memory); + function toString(bytes calldata) external returns (string memory); + function toString(bytes32) external returns (string memory); + function toString(bool) external returns (string memory); + function toString(uint256) external returns (string memory); + function toString(int256) external returns (string memory); // Convert values from a string, (string) => (parsed value) function parseBytes(string calldata) external returns (bytes memory); function parseAddress(string calldata) external returns (address); @@ -113,10 +114,10 @@ interface VmSafe { // Given a string of JSON, return the ABI-encoded value of provided key // (stringified json, key) => (ABI-encoded data) // Read the note below! - function parseJson(string calldata, string calldata) external returns(bytes memory); + function parseJson(string calldata, string calldata) external returns (bytes memory); // Given a string of JSON, return it as ABI-encoded, (stringified json, key) => (ABI-encoded data) // Read the note below! - function parseJson(string calldata) external returns(bytes memory); + function parseJson(string calldata) external returns (bytes memory); // Note: // ---- // In case the returned value is a JSON object, it's encoded as a ABI-encoded tuple. As JSON objects @@ -145,7 +146,7 @@ interface Vm is VmSafe { // Sets block.chainid function chainId(uint256) external; // Stores a value to an address' storage slot, (who, slot, value) - function store(address,bytes32,bytes32) external; + function store(address, bytes32, bytes32) external; // Sets the nonce of an account; must be higher than the current nonce of the account function setNonce(address, uint64) external; // Sets the *next* call's msg.sender to be the input address @@ -153,9 +154,9 @@ interface Vm is VmSafe { // Sets all subsequent calls' msg.sender to be the input address until `stopPrank` is called function startPrank(address) external; // Sets the *next* call's msg.sender to be the input address, and the tx.origin to be the second input - function prank(address,address) external; + function prank(address, address) external; // Sets all subsequent calls' msg.sender to be the input address until `stopPrank` is called, and the tx.origin to be the second input - function startPrank(address,address) external; + function startPrank(address, address) external; // Resets subsequent calls' msg.sender to be `address(this)` function stopPrank() external; // Sets an address' balance, (who, newBalance) @@ -169,23 +170,23 @@ interface Vm is VmSafe { // Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData). // Call this function, then emit an event, then call a function. Internally after the call, we check if // logs were emitted in the expected order with the expected topics and data (as specified by the booleans) - function expectEmit(bool,bool,bool,bool) external; - function expectEmit(bool,bool,bool,bool,address) external; + function expectEmit(bool, bool, bool, bool) external; + function expectEmit(bool, bool, bool, bool, address) external; // Mocks a call to an address, returning specified data. // Calldata can either be strict or a partial match, e.g. if you only // pass a Solidity selector to the expected calldata, then the entire Solidity // function will be mocked. - function mockCall(address,bytes calldata,bytes calldata) external; + function mockCall(address, bytes calldata, bytes calldata) external; // Mocks a call to an address with a specific msg.value, returning specified data. // Calldata match takes precedence over msg.value in case of ambiguity. - function mockCall(address,uint256,bytes calldata,bytes calldata) external; + function mockCall(address, uint256, bytes calldata, bytes calldata) external; // Clears all mocked calls function clearMockedCalls() external; // Expects a call to an address with the specified calldata. // Calldata can either be a strict or a partial match - function expectCall(address,bytes calldata) external; + function expectCall(address, bytes calldata) external; // Expects a call to an address with the specified msg.value and calldata - function expectCall(address,uint256,bytes calldata) external; + function expectCall(address, uint256, bytes calldata) external; // If the condition is false, discard this run's fuzz inputs and generate new ones function assume(bool) external; // Sets block.coinbase (who) @@ -193,28 +194,28 @@ interface Vm is VmSafe { // Snapshot the current state of the evm. // Returns the id of the snapshot that was created. // To revert a snapshot use `revertTo` - function snapshot() external returns(uint256); + function snapshot() external returns (uint256); // Revert the state of the evm to a previous snapshot // Takes the snapshot id to revert to. // This deletes the snapshot and all snapshots taken after the given snapshot id. - function revertTo(uint256) external returns(bool); + function revertTo(uint256) external returns (bool); // Creates a new fork with the given endpoint and block and returns the identifier of the fork - function createFork(string calldata,uint256) external returns(uint256); + function createFork(string calldata, uint256) external returns (uint256); // Creates a new fork with the given endpoint and the _latest_ block and returns the identifier of the fork - function createFork(string calldata) external returns(uint256); + function createFork(string calldata) external returns (uint256); // Creates a new fork with the given endpoint and at the block the given transaction was mined in, and replays all transaction mined in the block before the transaction function createFork(string calldata, bytes32) external returns (uint256); // Creates _and_ also selects a new fork with the given endpoint and block and returns the identifier of the fork - function createSelectFork(string calldata,uint256) external returns(uint256); + function createSelectFork(string calldata, uint256) external returns (uint256); // Creates _and_ also selects new fork with the given endpoint and at the block the given transaction was mined in, and replays all transaction mined in the block before the transaction function createSelectFork(string calldata, bytes32) external returns (uint256); // Creates _and_ also selects a new fork with the given endpoint and the latest block and returns the identifier of the fork - function createSelectFork(string calldata) external returns(uint256); + function createSelectFork(string calldata) external returns (uint256); // Takes a fork identifier created by `createFork` and sets the corresponding forked state as active. function selectFork(uint256) external; /// Returns the currently active fork /// Reverts if no fork is currently active - function activeFork() external returns(uint256); + function activeFork() external returns (uint256); // Updates the currently active fork to given block number // This is similar to `roll` but for the currently active fork function rollFork(uint256) external; @@ -243,7 +244,7 @@ interface Vm is VmSafe { // Fetches the given transaction from the given fork and executes it on the current state function transact(uint256 forkId, bytes32 txHash) external; // Returns the RPC url for the given alias - function rpcUrl(string calldata) external returns(string memory); + function rpcUrl(string calldata) external returns (string memory); // Returns all rpc urls and their aliases `[alias, url][]` - function rpcUrls() external returns(string[2][] memory); + function rpcUrls() external returns (string[2][] memory); } diff --git a/test/StdAssertions.t.sol b/test/StdAssertions.t.sol index 1d243791..6ebd3a4a 100644 --- a/test/StdAssertions.t.sol +++ b/test/StdAssertions.t.sol @@ -166,7 +166,6 @@ contract StdAssertionsTest is Test { t._assertEq(a, b, EXPECT_FAIL); } - function testAssertEq_AddressArr_FailEl(address e1) public { vm.assume(e1 != address(0)); address[] memory a = new address[](3); @@ -204,7 +203,6 @@ contract StdAssertionsTest is Test { t._assertEq(a, b, CUSTOM_ERROR, EXPECT_FAIL); } - function testAssertEq_AddressArrErr_FailEl(address e1) public { vm.assume(e1 != address(0)); address[] memory a = new address[](3); @@ -447,7 +445,6 @@ contract StdAssertionsTest is Test { } } - contract TestTest is Test { modifier expectFailure(bool expectFail) { bool preState = vm.load(HEVM_ADDRESS, bytes32("failed")) != bytes32(0x00); @@ -492,11 +489,10 @@ contract TestTest is Test { assertEq(a, b); } - function _assertEq(bytes memory a, - bytes memory b, - string memory err, - bool expectFail - ) external expectFailure(expectFail) { + function _assertEq(bytes memory a, bytes memory b, string memory err, bool expectFail) + external + expectFailure(expectFail) + { assertEq(a, b, err); } @@ -512,92 +508,80 @@ contract TestTest is Test { assertEq(a, b); } - function _assertEq(uint256[] memory a, uint256[] memory b, string memory err, bool expectFail) external expectFailure(expectFail) { + function _assertEq(uint256[] memory a, uint256[] memory b, string memory err, bool expectFail) + external + expectFailure(expectFail) + { assertEq(a, b, err); } - function _assertEq(int256[] memory a, int256[] memory b, string memory err, bool expectFail) external expectFailure(expectFail) { + function _assertEq(int256[] memory a, int256[] memory b, string memory err, bool expectFail) + external + expectFailure(expectFail) + { assertEq(a, b, err); } - function _assertEq(address[] memory a, address[] memory b, string memory err, bool expectFail) external expectFailure(expectFail) { + function _assertEq(address[] memory a, address[] memory b, string memory err, bool expectFail) + external + expectFailure(expectFail) + { assertEq(a, b, err); } - - function _assertApproxEqAbs( - uint256 a, - uint256 b, - uint256 maxDelta, - bool expectFail - ) external expectFailure(expectFail) { + function _assertApproxEqAbs(uint256 a, uint256 b, uint256 maxDelta, bool expectFail) + external + expectFailure(expectFail) + { assertApproxEqAbs(a, b, maxDelta); } - function _assertApproxEqAbs( - uint256 a, - uint256 b, - uint256 maxDelta, - string memory err, - bool expectFail - ) external expectFailure(expectFail) { + function _assertApproxEqAbs(uint256 a, uint256 b, uint256 maxDelta, string memory err, bool expectFail) + external + expectFailure(expectFail) + { assertApproxEqAbs(a, b, maxDelta, err); } - function _assertApproxEqAbs( - int256 a, - int256 b, - uint256 maxDelta, - bool expectFail - ) external expectFailure(expectFail) { + function _assertApproxEqAbs(int256 a, int256 b, uint256 maxDelta, bool expectFail) + external + expectFailure(expectFail) + { assertApproxEqAbs(a, b, maxDelta); } - function _assertApproxEqAbs( - int256 a, - int256 b, - uint256 maxDelta, - string memory err, - bool expectFail - ) external expectFailure(expectFail) { + function _assertApproxEqAbs(int256 a, int256 b, uint256 maxDelta, string memory err, bool expectFail) + external + expectFailure(expectFail) + { assertApproxEqAbs(a, b, maxDelta, err); } - function _assertApproxEqRel( - uint256 a, - uint256 b, - uint256 maxPercentDelta, - bool expectFail - ) external expectFailure(expectFail) { + function _assertApproxEqRel(uint256 a, uint256 b, uint256 maxPercentDelta, bool expectFail) + external + expectFailure(expectFail) + { assertApproxEqRel(a, b, maxPercentDelta); } - function _assertApproxEqRel( - uint256 a, - uint256 b, - uint256 maxPercentDelta, - string memory err, - bool expectFail - ) external expectFailure(expectFail) { + function _assertApproxEqRel(uint256 a, uint256 b, uint256 maxPercentDelta, string memory err, bool expectFail) + external + expectFailure(expectFail) + { assertApproxEqRel(a, b, maxPercentDelta, err); } - function _assertApproxEqRel( - int256 a, - int256 b, - uint256 maxPercentDelta, - bool expectFail - ) external expectFailure(expectFail) { + function _assertApproxEqRel(int256 a, int256 b, uint256 maxPercentDelta, bool expectFail) + external + expectFailure(expectFail) + { assertApproxEqRel(a, b, maxPercentDelta); } - function _assertApproxEqRel( - int256 a, - int256 b, - uint256 maxPercentDelta, - string memory err, - bool expectFail - ) external expectFailure(expectFail) { + function _assertApproxEqRel(int256 a, int256 b, uint256 maxPercentDelta, string memory err, bool expectFail) + external + expectFailure(expectFail) + { assertApproxEqRel(a, b, maxPercentDelta, err); } } diff --git a/test/StdCheats.t.sol b/test/StdCheats.t.sol index 612d74ff..4ae4174b 100644 --- a/test/StdCheats.t.sol +++ b/test/StdCheats.t.sol @@ -68,7 +68,7 @@ contract StdCheatsTest is Test { } function testMakeAddrEquivalence() public { - (address addr, ) = makeAddrAndKey("1337"); + (address addr,) = makeAddrAndKey("1337"); assertEq(makeAddr("1337"), addr); } @@ -161,10 +161,10 @@ contract StdCheatsTest is Test { } function testBytesToUint() public { - assertEq(3, bytesToUint_test(hex'03')); - assertEq(2, bytesToUint_test(hex'02')); - assertEq(255, bytesToUint_test(hex'ff')); - assertEq(29625, bytesToUint_test(hex'73b9')); + assertEq(3, bytesToUint_test(hex"03")); + assertEq(2, bytesToUint_test(hex"02")); + assertEq(255, bytesToUint_test(hex"ff")); + assertEq(29625, bytesToUint_test(hex"73b9")); } function testParseJsonTxDetail() public { @@ -176,7 +176,10 @@ contract StdCheatsTest is Test { Tx1559Detail memory txDetail = rawToConvertedEIP1559Detail(rawTxDetail); assertEq(txDetail.from, 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); assertEq(txDetail.to, 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512); - assertEq(txDetail.data, hex'23e99187000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000013370000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000004'); + assertEq( + txDetail.data, + hex"23e99187000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000013370000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000004" + ); assertEq(txDetail.nonce, 3); assertEq(txDetail.txType, 2); assertEq(txDetail.gas, 29625); @@ -199,10 +202,12 @@ contract StdCheatsTest is Test { function testReadReceipt() public { string memory root = vm.projectRoot(); string memory path = string.concat(root, "/test/fixtures/broadcast.log.json"); - uint index = 5; + uint256 index = 5; Receipt memory receipt = readReceipt(path, index); - assertEq(receipt.logsBloom, - hex"00000000000800000000000000000010000000000000000000000000000180000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100"); + assertEq( + receipt.logsBloom, + hex"00000000000800000000000000000010000000000000000000000000000180000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100" + ); } function testReadReceipts() public { @@ -213,8 +218,8 @@ contract StdCheatsTest is Test { function bytesToUint_test(bytes memory b) private pure returns (uint256) { uint256 number; - for (uint i=0; i < b.length; i++) { - number = number + uint(uint8(b[i]))*(2**(8*(b.length-(i+1)))); + for (uint256 i = 0; i < b.length; i++) { + number = number + uint256(uint8(b[i])) * (2 ** (8 * (b.length - (i + 1)))); } return number; } @@ -231,17 +236,19 @@ contract Bar { function bar(address expectedSender) public payable { require(msg.sender == expectedSender, "!prank"); } + function origin(address expectedSender) public payable { require(msg.sender == expectedSender, "!prank"); require(tx.origin == expectedSender, "!prank"); } + function origin(address expectedSender, address expectedOrigin) public payable { require(msg.sender == expectedSender, "!prank"); require(tx.origin == expectedOrigin, "!prank"); } /// `DEAL` STDCHEAT - mapping (address => uint256) public balanceOf; + mapping(address => uint256) public balanceOf; uint256 public totalSupply; } @@ -250,4 +257,3 @@ contract RevertingContract { revert(); } } - diff --git a/test/StdError.t.sol b/test/StdError.t.sol index 67c127ab..d9abc495 100644 --- a/test/StdError.t.sol +++ b/test/StdError.t.sol @@ -63,9 +63,7 @@ contract StdErrorsTest is Test { } contract ErrorsTest { - enum T { - T1 - } + enum T {T1} uint256[] public someArr; bytes someBytes; @@ -108,7 +106,7 @@ contract ErrorsTest { } function mem() public pure { - uint256 l = 2**256 / 32; + uint256 l = 2 ** 256 / 32; new uint256[](l); } diff --git a/test/StdMath.t.sol b/test/StdMath.t.sol index 143fcc4d..19f753c0 100644 --- a/test/StdMath.t.sol +++ b/test/StdMath.t.sol @@ -6,10 +6,10 @@ import "src/Test.sol"; contract StdMathTest is Test { function testGetAbs() external { - assertEq(stdMath.abs(-50), 50); - assertEq(stdMath.abs(50), 50); - assertEq(stdMath.abs(-1337), 1337); - assertEq(stdMath.abs(0), 0); + assertEq(stdMath.abs(-50), 50); + assertEq(stdMath.abs(50), 50); + assertEq(stdMath.abs(-1337), 1337); + assertEq(stdMath.abs(0), 0); assertEq(stdMath.abs(type(int256).min), (type(uint256).max >> 1) + 1); assertEq(stdMath.abs(type(int256).max), (type(uint256).max >> 1)); @@ -24,21 +24,21 @@ contract StdMathTest is Test { } function testGetDelta_Uint() external { - assertEq(stdMath.delta(uint256(0), uint256(0)), 0); - assertEq(stdMath.delta(uint256(0), uint256(1337)), 1337); - assertEq(stdMath.delta(uint256(0), type(uint64).max), type(uint64).max); - assertEq(stdMath.delta(uint256(0), type(uint128).max), type(uint128).max); - assertEq(stdMath.delta(uint256(0), type(uint256).max), type(uint256).max); - - assertEq(stdMath.delta(0, uint256(0)), 0); - assertEq(stdMath.delta(1337, uint256(0)), 1337); - assertEq(stdMath.delta(type(uint64).max, uint256(0)), type(uint64).max); - assertEq(stdMath.delta(type(uint128).max, uint256(0)), type(uint128).max); - assertEq(stdMath.delta(type(uint256).max, uint256(0)), type(uint256).max); - - assertEq(stdMath.delta(1337, uint256(1337)), 0); - assertEq(stdMath.delta(type(uint256).max, type(uint256).max), 0); - assertEq(stdMath.delta(5000, uint256(1250)), 3750); + assertEq(stdMath.delta(uint256(0), uint256(0)), 0); + assertEq(stdMath.delta(uint256(0), uint256(1337)), 1337); + assertEq(stdMath.delta(uint256(0), type(uint64).max), type(uint64).max); + assertEq(stdMath.delta(uint256(0), type(uint128).max), type(uint128).max); + assertEq(stdMath.delta(uint256(0), type(uint256).max), type(uint256).max); + + assertEq(stdMath.delta(0, uint256(0)), 0); + assertEq(stdMath.delta(1337, uint256(0)), 1337); + assertEq(stdMath.delta(type(uint64).max, uint256(0)), type(uint64).max); + assertEq(stdMath.delta(type(uint128).max, uint256(0)), type(uint128).max); + assertEq(stdMath.delta(type(uint256).max, uint256(0)), type(uint256).max); + + assertEq(stdMath.delta(1337, uint256(1337)), 0); + assertEq(stdMath.delta(type(uint256).max, type(uint256).max), 0); + assertEq(stdMath.delta(5000, uint256(1250)), 3750); } function testGetDelta_Uint_Fuzz(uint256 a, uint256 b) external { @@ -55,43 +55,41 @@ contract StdMathTest is Test { } function testGetDelta_Int() external { - assertEq(stdMath.delta(int256(0), int256(0)), 0); - assertEq(stdMath.delta(int256(0), int256(1337)), 1337); - assertEq(stdMath.delta(int256(0), type(int64).max), type(uint64).max >> 1); - assertEq(stdMath.delta(int256(0), type(int128).max), type(uint128).max >> 1); - assertEq(stdMath.delta(int256(0), type(int256).max), type(uint256).max >> 1); - - assertEq(stdMath.delta(0, int256(0)), 0); - assertEq(stdMath.delta(1337, int256(0)), 1337); - assertEq(stdMath.delta(type(int64).max, int256(0)), type(uint64).max >> 1); - assertEq(stdMath.delta(type(int128).max, int256(0)), type(uint128).max >> 1); - assertEq(stdMath.delta(type(int256).max, int256(0)), type(uint256).max >> 1); - - assertEq(stdMath.delta(-0, int256(0)), 0); - assertEq(stdMath.delta(-1337, int256(0)), 1337); - assertEq(stdMath.delta(type(int64).min, int256(0)), (type(uint64).max >> 1) + 1); - assertEq(stdMath.delta(type(int128).min, int256(0)), (type(uint128).max >> 1) + 1); - assertEq(stdMath.delta(type(int256).min, int256(0)), (type(uint256).max >> 1) + 1); - - assertEq(stdMath.delta(int256(0), -0), 0); - assertEq(stdMath.delta(int256(0), -1337), 1337); - assertEq(stdMath.delta(int256(0), type(int64).min), (type(uint64).max >> 1) + 1); - assertEq(stdMath.delta(int256(0), type(int128).min), (type(uint128).max >> 1) + 1); - assertEq(stdMath.delta(int256(0), type(int256).min), (type(uint256).max >> 1) + 1); - - assertEq(stdMath.delta(1337, int256(1337)), 0); - assertEq(stdMath.delta(type(int256).max, type(int256).max), 0); - assertEq(stdMath.delta(type(int256).min, type(int256).min), 0); - assertEq(stdMath.delta(type(int256).min, type(int256).max), type(uint256).max); - assertEq(stdMath.delta(5000, int256(1250)), 3750); + assertEq(stdMath.delta(int256(0), int256(0)), 0); + assertEq(stdMath.delta(int256(0), int256(1337)), 1337); + assertEq(stdMath.delta(int256(0), type(int64).max), type(uint64).max >> 1); + assertEq(stdMath.delta(int256(0), type(int128).max), type(uint128).max >> 1); + assertEq(stdMath.delta(int256(0), type(int256).max), type(uint256).max >> 1); + + assertEq(stdMath.delta(0, int256(0)), 0); + assertEq(stdMath.delta(1337, int256(0)), 1337); + assertEq(stdMath.delta(type(int64).max, int256(0)), type(uint64).max >> 1); + assertEq(stdMath.delta(type(int128).max, int256(0)), type(uint128).max >> 1); + assertEq(stdMath.delta(type(int256).max, int256(0)), type(uint256).max >> 1); + + assertEq(stdMath.delta(-0, int256(0)), 0); + assertEq(stdMath.delta(-1337, int256(0)), 1337); + assertEq(stdMath.delta(type(int64).min, int256(0)), (type(uint64).max >> 1) + 1); + assertEq(stdMath.delta(type(int128).min, int256(0)), (type(uint128).max >> 1) + 1); + assertEq(stdMath.delta(type(int256).min, int256(0)), (type(uint256).max >> 1) + 1); + + assertEq(stdMath.delta(int256(0), -0), 0); + assertEq(stdMath.delta(int256(0), -1337), 1337); + assertEq(stdMath.delta(int256(0), type(int64).min), (type(uint64).max >> 1) + 1); + assertEq(stdMath.delta(int256(0), type(int128).min), (type(uint128).max >> 1) + 1); + assertEq(stdMath.delta(int256(0), type(int256).min), (type(uint256).max >> 1) + 1); + + assertEq(stdMath.delta(1337, int256(1337)), 0); + assertEq(stdMath.delta(type(int256).max, type(int256).max), 0); + assertEq(stdMath.delta(type(int256).min, type(int256).min), 0); + assertEq(stdMath.delta(type(int256).min, type(int256).max), type(uint256).max); + assertEq(stdMath.delta(5000, int256(1250)), 3750); } function testGetDelta_Int_Fuzz(int256 a, int256 b) external { uint256 absA = getAbs(a); uint256 absB = getAbs(b); - uint256 absDelta = absA > absB - ? absA - absB - : absB - absA; + uint256 absDelta = absA > absB ? absA - absB : absB - absA; uint256 manualDelta; if ((a >= 0 && b >= 0) || (a < 0 && b < 0)) { @@ -108,17 +106,17 @@ contract StdMathTest is Test { } function testGetPercentDelta_Uint() external { - assertEq(stdMath.percentDelta(uint256(0), uint256(1337)), 1e18); - assertEq(stdMath.percentDelta(uint256(0), type(uint64).max), 1e18); - assertEq(stdMath.percentDelta(uint256(0), type(uint128).max), 1e18); - assertEq(stdMath.percentDelta(uint256(0), type(uint192).max), 1e18); - - assertEq(stdMath.percentDelta(1337, uint256(1337)), 0); - assertEq(stdMath.percentDelta(type(uint192).max, type(uint192).max), 0); - assertEq(stdMath.percentDelta(0, uint256(2500)), 1e18); - assertEq(stdMath.percentDelta(2500, uint256(2500)), 0); - assertEq(stdMath.percentDelta(5000, uint256(2500)), 1e18); - assertEq(stdMath.percentDelta(7500, uint256(2500)), 2e18); + assertEq(stdMath.percentDelta(uint256(0), uint256(1337)), 1e18); + assertEq(stdMath.percentDelta(uint256(0), type(uint64).max), 1e18); + assertEq(stdMath.percentDelta(uint256(0), type(uint128).max), 1e18); + assertEq(stdMath.percentDelta(uint256(0), type(uint192).max), 1e18); + + assertEq(stdMath.percentDelta(1337, uint256(1337)), 0); + assertEq(stdMath.percentDelta(type(uint192).max, type(uint192).max), 0); + assertEq(stdMath.percentDelta(0, uint256(2500)), 1e18); + assertEq(stdMath.percentDelta(2500, uint256(2500)), 0); + assertEq(stdMath.percentDelta(5000, uint256(2500)), 1e18); + assertEq(stdMath.percentDelta(7500, uint256(2500)), 2e18); vm.expectRevert(stdError.divisionError); stdMath.percentDelta(uint256(1), 0); @@ -140,25 +138,25 @@ contract StdMathTest is Test { } function testGetPercentDelta_Int() external { - assertEq(stdMath.percentDelta(int256(0), int256(1337)), 1e18); - assertEq(stdMath.percentDelta(int256(0), -1337), 1e18); - assertEq(stdMath.percentDelta(int256(0), type(int64).min), 1e18); - assertEq(stdMath.percentDelta(int256(0), type(int128).min), 1e18); - assertEq(stdMath.percentDelta(int256(0), type(int192).min), 1e18); - assertEq(stdMath.percentDelta(int256(0), type(int64).max), 1e18); - assertEq(stdMath.percentDelta(int256(0), type(int128).max), 1e18); - assertEq(stdMath.percentDelta(int256(0), type(int192).max), 1e18); - - assertEq(stdMath.percentDelta(1337, int256(1337)), 0); - assertEq(stdMath.percentDelta(type(int192).max, type(int192).max), 0); - assertEq(stdMath.percentDelta(type(int192).min, type(int192).min), 0); - - assertEq(stdMath.percentDelta(type(int192).min, type(int192).max), 2e18); // rounds the 1 wei diff down - assertEq(stdMath.percentDelta(type(int192).max, type(int192).min), 2e18 - 1); // rounds the 1 wei diff down - assertEq(stdMath.percentDelta(0, int256(2500)), 1e18); - assertEq(stdMath.percentDelta(2500, int256(2500)), 0); - assertEq(stdMath.percentDelta(5000, int256(2500)), 1e18); - assertEq(stdMath.percentDelta(7500, int256(2500)), 2e18); + assertEq(stdMath.percentDelta(int256(0), int256(1337)), 1e18); + assertEq(stdMath.percentDelta(int256(0), -1337), 1e18); + assertEq(stdMath.percentDelta(int256(0), type(int64).min), 1e18); + assertEq(stdMath.percentDelta(int256(0), type(int128).min), 1e18); + assertEq(stdMath.percentDelta(int256(0), type(int192).min), 1e18); + assertEq(stdMath.percentDelta(int256(0), type(int64).max), 1e18); + assertEq(stdMath.percentDelta(int256(0), type(int128).max), 1e18); + assertEq(stdMath.percentDelta(int256(0), type(int192).max), 1e18); + + assertEq(stdMath.percentDelta(1337, int256(1337)), 0); + assertEq(stdMath.percentDelta(type(int192).max, type(int192).max), 0); + assertEq(stdMath.percentDelta(type(int192).min, type(int192).min), 0); + + assertEq(stdMath.percentDelta(type(int192).min, type(int192).max), 2e18); // rounds the 1 wei diff down + assertEq(stdMath.percentDelta(type(int192).max, type(int192).min), 2e18 - 1); // rounds the 1 wei diff down + assertEq(stdMath.percentDelta(0, int256(2500)), 1e18); + assertEq(stdMath.percentDelta(2500, int256(2500)), 0); + assertEq(stdMath.percentDelta(5000, int256(2500)), 1e18); + assertEq(stdMath.percentDelta(7500, int256(2500)), 2e18); vm.expectRevert(stdError.divisionError); stdMath.percentDelta(int256(1), 0); @@ -168,9 +166,7 @@ contract StdMathTest is Test { vm.assume(b != 0); uint256 absA = getAbs(a); uint256 absB = getAbs(b); - uint256 absDelta = absA > absB - ? absA - absB - : absB - absA; + uint256 absDelta = absA > absB ? absA - absB : absB - absA; uint256 manualDelta; if ((a >= 0 && b >= 0) || (a < 0 && b < 0)) { @@ -192,8 +188,9 @@ contract StdMathTest is Test { //////////////////////////////////////////////////////////////////////////*/ function getAbs(int256 a) private pure returns (uint256) { - if (a < 0) + if (a < 0) { return a == type(int256).min ? uint256(type(int256).max) + 1 : uint256(-a); + } return uint256(a); } diff --git a/test/StdStorage.t.sol b/test/StdStorage.t.sol index b17baadc..96752cb5 100644 --- a/test/StdStorage.t.sol +++ b/test/StdStorage.t.sol @@ -32,112 +32,75 @@ contract StdStorageTest is Test { } function testStorageMapStructA() public { - uint256 slot = stdstore - .target(address(test)) - .sig(test.map_struct.selector) - .with_key(address(this)) - .depth(0) - .find(); + uint256 slot = + stdstore.target(address(test)).sig(test.map_struct.selector).with_key(address(this)).depth(0).find(); assertEq(uint256(keccak256(abi.encode(address(this), 4))), slot); } function testStorageMapStructB() public { - uint256 slot = stdstore - .target(address(test)) - .sig(test.map_struct.selector) - .with_key(address(this)) - .depth(1) - .find(); - assertEq(uint256(keccak256(abi.encode(address(this), 4))) + 1, slot); + uint256 slot = + stdstore.target(address(test)).sig(test.map_struct.selector).with_key(address(this)).depth(1).find(); + assertEq(uint256(keccak256(abi.encode(address(this), 4))) + 1, slot); } function testStorageDeepMap() public { - uint256 slot = stdstore - .target(address(test)) - .sig(test.deep_map.selector) - .with_key(address(this)) - .with_key(address(this)) - .find(); - assertEq(uint256(keccak256(abi.encode(address(this), keccak256(abi.encode(address(this), uint(5)))))), slot); + uint256 slot = stdstore.target(address(test)).sig(test.deep_map.selector).with_key(address(this)).with_key( + address(this) + ).find(); + assertEq(uint256(keccak256(abi.encode(address(this), keccak256(abi.encode(address(this), uint256(5)))))), slot); } function testStorageCheckedWriteDeepMap() public { - stdstore - .target(address(test)) - .sig(test.deep_map.selector) - .with_key(address(this)) - .with_key(address(this)) + stdstore.target(address(test)).sig(test.deep_map.selector).with_key(address(this)).with_key(address(this)) .checked_write(100); assertEq(100, test.deep_map(address(this), address(this))); } function testStorageDeepMapStructA() public { - uint256 slot = stdstore - .target(address(test)) - .sig(test.deep_map_struct.selector) - .with_key(address(this)) - .with_key(address(this)) - .depth(0) - .find(); - assertEq(bytes32(uint256(keccak256(abi.encode(address(this), keccak256(abi.encode(address(this), uint(6)))))) + 0), bytes32(slot)); + uint256 slot = stdstore.target(address(test)).sig(test.deep_map_struct.selector).with_key(address(this)) + .with_key(address(this)).depth(0).find(); + assertEq( + bytes32(uint256(keccak256(abi.encode(address(this), keccak256(abi.encode(address(this), uint256(6)))))) + 0), + bytes32(slot) + ); } function testStorageDeepMapStructB() public { - uint256 slot = stdstore - .target(address(test)) - .sig(test.deep_map_struct.selector) - .with_key(address(this)) - .with_key(address(this)) - .depth(1) - .find(); - assertEq(bytes32(uint256(keccak256(abi.encode(address(this), keccak256(abi.encode(address(this), uint(6)))))) + 1), bytes32(slot)); + uint256 slot = stdstore.target(address(test)).sig(test.deep_map_struct.selector).with_key(address(this)) + .with_key(address(this)).depth(1).find(); + assertEq( + bytes32(uint256(keccak256(abi.encode(address(this), keccak256(abi.encode(address(this), uint256(6)))))) + 1), + bytes32(slot) + ); } function testStorageCheckedWriteDeepMapStructA() public { - stdstore - .target(address(test)) - .sig(test.deep_map_struct.selector) - .with_key(address(this)) - .with_key(address(this)) - .depth(0) - .checked_write(100); + stdstore.target(address(test)).sig(test.deep_map_struct.selector).with_key(address(this)).with_key( + address(this) + ).depth(0).checked_write(100); (uint256 a, uint256 b) = test.deep_map_struct(address(this), address(this)); assertEq(100, a); assertEq(0, b); } function testStorageCheckedWriteDeepMapStructB() public { - stdstore - .target(address(test)) - .sig(test.deep_map_struct.selector) - .with_key(address(this)) - .with_key(address(this)) - .depth(1) - .checked_write(100); + stdstore.target(address(test)).sig(test.deep_map_struct.selector).with_key(address(this)).with_key( + address(this) + ).depth(1).checked_write(100); (uint256 a, uint256 b) = test.deep_map_struct(address(this), address(this)); assertEq(0, a); assertEq(100, b); } function testStorageCheckedWriteMapStructA() public { - stdstore - .target(address(test)) - .sig(test.map_struct.selector) - .with_key(address(this)) - .depth(0) - .checked_write(100); + stdstore.target(address(test)).sig(test.map_struct.selector).with_key(address(this)).depth(0).checked_write(100); (uint256 a, uint256 b) = test.map_struct(address(this)); assertEq(a, 100); assertEq(b, 0); } function testStorageCheckedWriteMapStructB() public { - stdstore - .target(address(test)) - .sig(test.map_struct.selector) - .with_key(address(this)) - .depth(1) - .checked_write(100); + stdstore.target(address(test)).sig(test.map_struct.selector).with_key(address(this)).depth(1).checked_write(100); (uint256 a, uint256 b) = test.map_struct(address(this)); assertEq(a, 0); assertEq(b, 100); @@ -161,7 +124,7 @@ contract StdStorageTest is Test { } function testStorageCheckedWriteStructB() public { - stdstore.target(address(test)).sig(test.basic.selector).depth(1).checked_write(100); + stdstore.target(address(test)).sig(test.basic.selector).depth(1).checked_write(100); (uint256 a, uint256 b) = test.basic(); assertEq(a, 1337); assertEq(b, 100); @@ -169,12 +132,12 @@ contract StdStorageTest is Test { function testStorageMapAddrFound() public { uint256 slot = stdstore.target(address(test)).sig(test.map_addr.selector).with_key(address(this)).find(); - assertEq(uint256(keccak256(abi.encode(address(this), uint(1)))), slot); + assertEq(uint256(keccak256(abi.encode(address(this), uint256(1)))), slot); } function testStorageMapUintFound() public { uint256 slot = stdstore.target(address(test)).sig(test.map_uint.selector).with_key(100).find(); - assertEq(uint256(keccak256(abi.encode(100, uint(2)))), slot); + assertEq(uint256(keccak256(abi.encode(100, uint256(2)))), slot); } function testStorageCheckedWriteMapUint() public { @@ -194,14 +157,17 @@ contract StdStorageTest is Test { function testFailStorageCheckedWriteMapPacked() public { // expect PackedSlot error but not external call so cant expectRevert - stdstore.target(address(test)).sig(test.read_struct_lower.selector).with_key(address(uint160(1337))).checked_write(100); + stdstore.target(address(test)).sig(test.read_struct_lower.selector).with_key(address(uint160(1337))) + .checked_write(100); } function testStorageCheckedWriteMapPackedSuccess() public { uint256 full = test.map_packed(address(1337)); // keep upper 128, set lower 128 to 1337 full = (full & (uint256((1 << 128) - 1) << 128)) | 1337; - stdstore.target(address(test)).sig(test.map_packed.selector).with_key(address(uint160(1337))).checked_write(full); + stdstore.target(address(test)).sig(test.map_packed.selector).with_key(address(uint160(1337))).checked_write( + full + ); assertEq(1337, test.read_struct_lower(address(1337))); } @@ -272,11 +238,9 @@ contract StorageTest { uint248 public tA; bool public tB; - bool public tC = false; uint248 public tD = 1; - struct UnpackedStruct { uint256 a; uint256 b; @@ -290,14 +254,11 @@ contract StorageTest { bool public tH = true; constructor() { - basic = UnpackedStruct({ - a: 1337, - b: 1337 - }); + basic = UnpackedStruct({a: 1337, b: 1337}); - uint256 two = (1<<128) | 1; + uint256 two = (1 << 128) | 1; map_packed[msg.sender] = two; - map_packed[address(uint160(1337))] = 1<<128; + map_packed[address(uint160(1337))] = 1 << 128; } function read_struct_upper(address who) public view returns (uint256) { diff --git a/test/StdUtils.t.sol b/test/StdUtils.t.sol index f5954813..8e071385 100644 --- a/test/StdUtils.t.sol +++ b/test/StdUtils.t.sol @@ -13,11 +13,7 @@ contract StdUtilsTest is Test { assertEq(bound(9999, 1337, 6666), 6006); } - function testBound( - uint256 num, - uint256 min, - uint256 max - ) public { + function testBound(uint256 num, uint256 min, uint256 max) public { if (min > max) (min, max) = (max, min); uint256 bounded = bound(num, min, max); @@ -31,17 +27,12 @@ contract StdUtilsTest is Test { assertEq(bound(1, type(uint256).max - 1, type(uint256).max), type(uint256).max); } - function testCannotBoundMaxLessThanMin() public { vm.expectRevert(bytes("Test bound(uint256,uint256,uint256): Max is less than min.")); bound(5, 100, 10); } - function testCannotBoundMaxLessThanMin( - uint256 num, - uint256 min, - uint256 max - ) public { + function testCannotBoundMaxLessThanMin(uint256 num, uint256 min, uint256 max) public { vm.assume(min > max); vm.expectRevert(bytes("Test bound(uint256,uint256,uint256): Max is less than min.")); bound(num, min, max); @@ -61,4 +52,4 @@ contract StdUtilsTest is Test { address create2Address = computeCreate2Address(salt, initcodeHash, deployer); assertEq(create2Address, 0xB147a5d25748fda14b463EB04B111027C290f4d3); } -} \ No newline at end of file +} From d24318dbe1723082649cf0425e4d5704f229831a Mon Sep 17 00:00:00 2001 From: Matt Solomon Date: Mon, 10 Oct 2022 18:50:31 -0500 Subject: [PATCH 12/27] ci: split into jobs and add fmt job --- .github/workflows/{tests.yml => ci.yml} | 40 ++++++++++++++++++++----- 1 file changed, 33 insertions(+), 7 deletions(-) rename .github/workflows/{tests.yml => ci.yml} (51%) diff --git a/.github/workflows/tests.yml b/.github/workflows/ci.yml similarity index 51% rename from .github/workflows/tests.yml rename to .github/workflows/ci.yml index 37b899f1..2721e2a5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/ci.yml @@ -2,13 +2,11 @@ name: Tests on: [push, pull_request] jobs: - check: + build: name: Foundry project runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - with: - submodules: recursive + - uses: actions/checkout@v3 - name: Install Foundry uses: onbjerg/foundry-toolchain@v1 @@ -18,12 +16,40 @@ jobs: - name: Install dependencies run: forge install - - name: Run tests - run: forge test -vvv - - name: Build Test with older solc versions run: | forge build --skip test --use solc:0.8.0 forge build --skip test --use solc:0.7.6 forge build --skip test --use solc:0.7.0 forge build --skip test --use solc:0.6.2 + + test: + name: Foundry project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Install Foundry + uses: onbjerg/foundry-toolchain@v1 + with: + version: nightly + + - name: Install dependencies + run: forge install + + - name: Run tests + run: forge test -vvv + + fmt: + name: forge fmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Install Foundry + uses: onbjerg/foundry-toolchain@v1 + with: + version: nightly + + - name: Check formatting + run: forge fmt --check \ No newline at end of file From ffd9c8ead8eadbf1a26fb2a4047341c2fc93528e Mon Sep 17 00:00:00 2001 From: Matt Solomon Date: Mon, 10 Oct 2022 18:52:16 -0500 Subject: [PATCH 13/27] ci: update name and triggers --- .github/workflows/ci.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2721e2a5..be00042e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,11 @@ -name: Tests -on: [push, pull_request] +name: CI +on: + workflow_dispatch: + pull_request: + push: + branches: + - main + jobs: build: From dc8d830d3af6f67758f793ce42572270fb0f41fb Mon Sep 17 00:00:00 2001 From: Matt Solomon Date: Mon, 10 Oct 2022 18:53:44 -0500 Subject: [PATCH 14/27] ci: remove name field --- .github/workflows/ci.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index be00042e..fd8c46f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,15 +1,15 @@ name: CI + on: workflow_dispatch: pull_request: push: branches: - - main + - master jobs: build: - name: Foundry project runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -30,7 +30,6 @@ jobs: forge build --skip test --use solc:0.6.2 test: - name: Foundry project runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -47,7 +46,6 @@ jobs: run: forge test -vvv fmt: - name: forge fmt runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 From 16f109d1ffc95cfd3a9beec6cea077cc274cec4c Mon Sep 17 00:00:00 2001 From: Matt Solomon Date: Fri, 14 Oct 2022 09:34:13 -0500 Subject: [PATCH 15/27] feat: better bound, ref https://github.com/foundry-rs/forge-std/issues/188 --- src/StdUtils.sol | 30 +++++++++++++++++++++--------- test/StdUtils.t.sol | 23 +++++++++++++++++++++++ 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/src/StdUtils.sol b/src/StdUtils.sol index cec6c38b..0164b443 100644 --- a/src/StdUtils.sol +++ b/src/StdUtils.sol @@ -10,17 +10,29 @@ abstract contract StdUtils { function bound(uint256 x, uint256 min, uint256 max) internal view virtual returns (uint256 result) { require(min <= max, "Test bound(uint256,uint256,uint256): Max is less than min."); + // If x is between min and max, return x directly. This is to ensure that dictionary values + // do not get shifted if the min is nonzero. More info: https://github.com/foundry-rs/forge-std/issues/188 + if (x >= min && x <= max) return x; + + // Handle degenerate cases. uint256 size = max - min; + if (size == 0) return min; + if (size == UINT256_MAX) return x; + + // If the value is 0,1,2 wrap that to min, min+1, min+2. Similarly, for the MAX_UINT side. + // This helps ensure coverage of the min/max values. It does introduce a small bias, however + // uint256 is so large that this is insignificant. + if (x == 0) return min; + if (x == 1 && size >= 1) return min + 1; + if (x == 2 && size >= 2) return min + 2; + if (x == UINT256_MAX) return max; + if (x == UINT256_MAX - 1 && size >= 1) return max - 1; + if (x == UINT256_MAX - 2 && size >= 2) return max - 2; - if (size == 0) { - result = min; - } else if (size == UINT256_MAX) { - result = x; - } else { - ++size; // make `max` inclusive - uint256 mod = x % size; - result = min + mod; - } + // Otherwise, wrap x into the range [min, max], i.e. the range is inclusive. + ++size; // make `max` inclusive + uint256 mod = x % size; + result = min + mod; console2.log("Bound Result", result); } diff --git a/test/StdUtils.t.sol b/test/StdUtils.t.sol index 8e071385..1f885029 100644 --- a/test/StdUtils.t.sol +++ b/test/StdUtils.t.sol @@ -11,6 +11,27 @@ contract StdUtilsTest is Test { assertEq(bound(10, 150, 190), 160); assertEq(bound(300, 2800, 3200), 3100); assertEq(bound(9999, 1337, 6666), 6006); + + // Don't wrap values within range. + assertEq(bound(51, 50, 150), 51); + assertEq(bound(51, 50, 150), bound(bound(51, 50, 150), 50, 150)); + assertEq(bound(149, 50, 150), 149); + assertEq(bound(149, 50, 150), bound(bound(149, 50, 150), 50, 150)); + + // 0, 1, 2 and MAX_UINT, MAX_UINT-1, and MAX_UINT-2 should map to min/max. + assertEq(bound(0, 50, 150), 50); + assertEq(bound(1, 50, 150), 51); + assertEq(bound(2, 50, 150), 52); + assertEq(bound(type(uint256).max, 50, 150), 150); + assertEq(bound(type(uint256).max - 1, 50, 150), 149); + assertEq(bound(type(uint256).max - 2, 50, 150), 148); + + assertEq(bound(0, 50, 51), 50); + assertEq(bound(1, 50, 51), 51); + assertEq(bound(2, 50, 51), 50); + assertEq(bound(type(uint256).max, 50, 51), 51); + assertEq(bound(type(uint256).max - 1, 50, 51), 50); + assertEq(bound(type(uint256).max - 2, 50, 51), 51); } function testBound(uint256 num, uint256 min, uint256 max) public { @@ -20,6 +41,8 @@ contract StdUtilsTest is Test { assertGe(bounded, min); assertLe(bounded, max); + assertEq(bounded, bound(bounded, min, max)); + if (num >= min && num <= max) assertEq(bounded, num); } function testBoundUint256Max() public { From 1959a93b85e752c23415db1f5635f3473d03325f Mon Sep 17 00:00:00 2001 From: Matt Solomon Date: Fri, 14 Oct 2022 15:54:20 -0500 Subject: [PATCH 16/27] fix: bound logs + remove unneeded line --- src/StdUtils.sol | 21 ++++++++++++--------- test/StdUtils.t.sol | 4 ++-- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/StdUtils.sol b/src/StdUtils.sol index 0164b443..cfd5f29b 100644 --- a/src/StdUtils.sol +++ b/src/StdUtils.sol @@ -7,33 +7,36 @@ abstract contract StdUtils { uint256 internal constant UINT256_MAX = 115792089237316195423570985008687907853269984665640564039457584007913129639935; - function bound(uint256 x, uint256 min, uint256 max) internal view virtual returns (uint256 result) { - require(min <= max, "Test bound(uint256,uint256,uint256): Max is less than min."); + function _bound(uint256 x, uint256 min, uint256 max) internal view virtual returns (uint256 result) { + require(min <= max, "StdUtils bound(uint256,uint256,uint256): Max is less than min."); // If x is between min and max, return x directly. This is to ensure that dictionary values // do not get shifted if the min is nonzero. More info: https://github.com/foundry-rs/forge-std/issues/188 if (x >= min && x <= max) return x; // Handle degenerate cases. + // The `if (size == UINT256_MAX) return x` case is covered by the above line. uint256 size = max - min; if (size == 0) return min; - if (size == UINT256_MAX) return x; + ++size; // make `max` inclusive // If the value is 0,1,2 wrap that to min, min+1, min+2. Similarly, for the MAX_UINT side. // This helps ensure coverage of the min/max values. It does introduce a small bias, however // uint256 is so large that this is insignificant. if (x == 0) return min; - if (x == 1 && size >= 1) return min + 1; - if (x == 2 && size >= 2) return min + 2; + if (x == 1 && size > 1) return min + 1; + if (x == 2 && size > 2) return min + 2; if (x == UINT256_MAX) return max; - if (x == UINT256_MAX - 1 && size >= 1) return max - 1; - if (x == UINT256_MAX - 2 && size >= 2) return max - 2; + if (x == UINT256_MAX - 1 && size > 1) return max - 1; + if (x == UINT256_MAX - 2 && size > 2) return max - 2; // Otherwise, wrap x into the range [min, max], i.e. the range is inclusive. - ++size; // make `max` inclusive uint256 mod = x % size; - result = min + mod; + return min + mod; + } + function bound(uint256 x, uint256 min, uint256 max) internal view virtual returns (uint256 result) { + result = _bound(x, min, max); console2.log("Bound Result", result); } diff --git a/test/StdUtils.t.sol b/test/StdUtils.t.sol index 1f885029..75d41e26 100644 --- a/test/StdUtils.t.sol +++ b/test/StdUtils.t.sol @@ -51,13 +51,13 @@ contract StdUtilsTest is Test { } function testCannotBoundMaxLessThanMin() public { - vm.expectRevert(bytes("Test bound(uint256,uint256,uint256): Max is less than min.")); + vm.expectRevert(bytes("StdUtils bound(uint256,uint256,uint256): Max is less than min.")); bound(5, 100, 10); } function testCannotBoundMaxLessThanMin(uint256 num, uint256 min, uint256 max) public { vm.assume(min > max); - vm.expectRevert(bytes("Test bound(uint256,uint256,uint256): Max is less than min.")); + vm.expectRevert(bytes("StdUtils bound(uint256,uint256,uint256): Max is less than min.")); bound(num, min, max); } From 5269c766add32c52c279e79f09cb80fe748a32f7 Mon Sep 17 00:00:00 2001 From: Matt Solomon Date: Sat, 15 Oct 2022 10:16:42 -0500 Subject: [PATCH 17/27] fix: update require strings --- src/StdCheats.sol | 8 ++++---- test/StdCheats.t.sol | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/StdCheats.sol b/src/StdCheats.sol index e7b62555..bd9fa3c5 100644 --- a/src/StdCheats.sol +++ b/src/StdCheats.sol @@ -326,7 +326,7 @@ abstract contract StdCheatsSafe { addr := create(0, add(bytecode, 0x20), mload(bytecode)) } - require(addr != address(0), "Test deployCode(string,bytes): Deployment failed."); + require(addr != address(0), "StdCheats deployCode(string,bytes): Deployment failed."); } function deployCode(string memory what) internal virtual returns (address addr) { @@ -336,7 +336,7 @@ abstract contract StdCheatsSafe { addr := create(0, add(bytecode, 0x20), mload(bytecode)) } - require(addr != address(0), "Test deployCode(string): Deployment failed."); + require(addr != address(0), "StdCheats deployCode(string): Deployment failed."); } /// @dev deploy contract with value on construction @@ -347,7 +347,7 @@ abstract contract StdCheatsSafe { addr := create(val, add(bytecode, 0x20), mload(bytecode)) } - require(addr != address(0), "Test deployCode(string,bytes,uint256): Deployment failed."); + require(addr != address(0), "StdCheats deployCode(string,bytes,uint256): Deployment failed."); } function deployCode(string memory what, uint256 val) internal virtual returns (address addr) { @@ -357,7 +357,7 @@ abstract contract StdCheatsSafe { addr := create(val, add(bytecode, 0x20), mload(bytecode)) } - require(addr != address(0), "Test deployCode(string,uint256): Deployment failed."); + require(addr != address(0), "StdCheats deployCode(string,uint256): Deployment failed."); } // creates a labeled address and the corresponding private key diff --git a/test/StdCheats.t.sol b/test/StdCheats.t.sol index 4ae4174b..28270c02 100644 --- a/test/StdCheats.t.sol +++ b/test/StdCheats.t.sol @@ -131,7 +131,7 @@ contract StdCheatsTest is Test { } function testDeployCodeFail() public { - vm.expectRevert(bytes("Test deployCode(string): Deployment failed.")); + vm.expectRevert(bytes("StdCheats deployCode(string): Deployment failed.")); this.deployCodeHelper("StdCheats.t.sol:RevertingContract"); } From 87c9ac8478e5a74956adfca8d54aaa7ab49cdef6 Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Sat, 15 Oct 2022 17:46:56 +0200 Subject: [PATCH 18/27] refactor: clean up `Test` and `Script` - do not forge fmt Components import - do not import Safe Components in `Test` --- src/Script.sol | 13 ++----------- src/Test.sol | 3 ++- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/Script.sol b/src/Script.sol index 569ce872..d051169b 100644 --- a/src/Script.sol +++ b/src/Script.sol @@ -2,17 +2,8 @@ pragma solidity >=0.6.2 <0.9.0; import {CommonBase} from "./Common.sol"; -import { - console, - console2, - StdCheatsSafe, - stdJson, - stdMath, - StdStorage, - stdStorageSafe, - StdUtils, - VmSafe -} from "./Components.sol"; +// forgefmt: disable-next-line +import {console, console2, StdCheatsSafe, stdJson, stdMath, StdStorage, stdStorageSafe, StdUtils, VmSafe} from "./Components.sol"; abstract contract ScriptBase is CommonBase { VmSafe internal constant vmSafe = VmSafe(VM_ADDRESS); diff --git a/src/Test.sol b/src/Test.sol index e099c727..2a96ede1 100644 --- a/src/Test.sol +++ b/src/Test.sol @@ -2,8 +2,9 @@ pragma solidity >=0.6.2 <0.9.0; import {CommonBase} from "./Common.sol"; -import "./Components.sol"; import "ds-test/test.sol"; +// forgefmt: disable-next-line +import {console, console2, StdAssertions, StdCheats, stdError, stdJson, stdMath, StdStorage, stdStorage, StdUtils, Vm} from "./Components.sol"; abstract contract TestBase is CommonBase {} From 4d2879e2b2f018faa65a0880e58755fa85e39445 Mon Sep 17 00:00:00 2001 From: Matt Solomon Date: Sat, 15 Oct 2022 11:18:53 -0500 Subject: [PATCH 19/27] fix: udpate bound to match forge's uint edge bias strategy --- src/StdUtils.sol | 4 +++- test/StdUtils.t.sol | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/StdUtils.sol b/src/StdUtils.sol index cfd5f29b..1a1ffef2 100644 --- a/src/StdUtils.sol +++ b/src/StdUtils.sol @@ -20,15 +20,17 @@ abstract contract StdUtils { if (size == 0) return min; ++size; // make `max` inclusive - // If the value is 0,1,2 wrap that to min, min+1, min+2. Similarly, for the MAX_UINT side. + // If the value is 0,1,2,3 wrap that to min, min+1, min+2, min+3. Similarly, for the MAX_UINT side. // This helps ensure coverage of the min/max values. It does introduce a small bias, however // uint256 is so large that this is insignificant. if (x == 0) return min; if (x == 1 && size > 1) return min + 1; if (x == 2 && size > 2) return min + 2; + if (x == 3 && size > 3) return min + 3; if (x == UINT256_MAX) return max; if (x == UINT256_MAX - 1 && size > 1) return max - 1; if (x == UINT256_MAX - 2 && size > 2) return max - 2; + if (x == UINT256_MAX - 3 && size > 3) return max - 3; // Otherwise, wrap x into the range [min, max], i.e. the range is inclusive. uint256 mod = x % size; diff --git a/test/StdUtils.t.sol b/test/StdUtils.t.sol index 75d41e26..524dd2de 100644 --- a/test/StdUtils.t.sol +++ b/test/StdUtils.t.sol @@ -22,9 +22,11 @@ contract StdUtilsTest is Test { assertEq(bound(0, 50, 150), 50); assertEq(bound(1, 50, 150), 51); assertEq(bound(2, 50, 150), 52); + assertEq(bound(3, 50, 150), 53); assertEq(bound(type(uint256).max, 50, 150), 150); assertEq(bound(type(uint256).max - 1, 50, 150), 149); assertEq(bound(type(uint256).max - 2, 50, 150), 148); + assertEq(bound(type(uint256).max - 3, 50, 150), 147); assertEq(bound(0, 50, 51), 50); assertEq(bound(1, 50, 51), 51); From be7916cfc644e37c72524226f719bffea9cf5fa4 Mon Sep 17 00:00:00 2001 From: Matt Solomon Date: Sat, 15 Oct 2022 09:43:25 -0700 Subject: [PATCH 20/27] feat: add interfaces (#193) * chore: update function visibility * feat: add interfaces * fix: fix import * style: consistent spec style * chore: fix find/replace issue Co-authored-by: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> * chore: update comments Co-authored-by: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Co-authored-by: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> --- src/StdUtils.sol | 2 +- src/interfaces/IERC1155.sol | 104 ++++++++++++++++++++ src/interfaces/IERC165.sol | 11 +++ src/interfaces/IERC20.sol | 42 ++++++++ src/interfaces/IERC4626.sol | 189 ++++++++++++++++++++++++++++++++++++ src/interfaces/IERC721.sol | 163 +++++++++++++++++++++++++++++++ 6 files changed, 510 insertions(+), 1 deletion(-) create mode 100644 src/interfaces/IERC1155.sol create mode 100644 src/interfaces/IERC165.sol create mode 100644 src/interfaces/IERC20.sol create mode 100644 src/interfaces/IERC4626.sol create mode 100644 src/interfaces/IERC721.sol diff --git a/src/StdUtils.sol b/src/StdUtils.sol index 1a1ffef2..2c24ea53 100644 --- a/src/StdUtils.sol +++ b/src/StdUtils.sol @@ -7,7 +7,7 @@ abstract contract StdUtils { uint256 internal constant UINT256_MAX = 115792089237316195423570985008687907853269984665640564039457584007913129639935; - function _bound(uint256 x, uint256 min, uint256 max) internal view virtual returns (uint256 result) { + function _bound(uint256 x, uint256 min, uint256 max) internal pure virtual returns (uint256 result) { require(min <= max, "StdUtils bound(uint256,uint256,uint256): Max is less than min."); // If x is between min and max, return x directly. This is to ensure that dictionary values diff --git a/src/interfaces/IERC1155.sol b/src/interfaces/IERC1155.sol new file mode 100644 index 00000000..692b7441 --- /dev/null +++ b/src/interfaces/IERC1155.sol @@ -0,0 +1,104 @@ +pragma solidity >=0.6.2; + +import "src/interfaces/IERC165.sol"; + +/// @title ERC-1155 Multi Token Standard +/// @dev See https://eips.ethereum.org/EIPS/eip-1155 +/// Note: The ERC-165 identifier for this interface is 0xd9b67a26. +interface ERC1155 is IERC165 { + /// @dev + /// - Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see "Safe Transfer Rules" section of the standard). + /// - The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender). + /// - The `_from` argument MUST be the address of the holder whose balance is decreased. + /// - The `_to` argument MUST be the address of the recipient whose balance is increased. + /// - The `_id` argument MUST be the token type being transferred. + /// - The `_value` argument MUST be the number of tokens the holder balance is decreased by and match what the recipient balance is increased by. + /// - When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address). + /// - When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address). + event TransferSingle( + address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value + ); + + /// @dev + /// - Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see "Safe Transfer Rules" section of the standard). + /// - The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender). + /// - The `_from` argument MUST be the address of the holder whose balance is decreased. + /// - The `_to` argument MUST be the address of the recipient whose balance is increased. + /// - The `_ids` argument MUST be the list of tokens being transferred. + /// - The `_values` argument MUST be the list of number of tokens (matching the list and order of tokens specified in _ids) the holder balance is decreased by and match what the recipient balance is increased by. + /// - When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address). + /// - When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address). + event TransferBatch( + address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values + ); + + /// @dev MUST emit when approval for a second party/operator address to manage all tokens for an owner address is enabled or disabled (absence of an event assumes disabled). + event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); + + /// @dev MUST emit when the URI is updated for a token ID. URIs are defined in RFC 3986. + /// The URI MUST point to a JSON file that conforms to the "ERC-1155 Metadata URI JSON Schema". + event URI(string _value, uint256 indexed _id); + + /// @notice Transfers `_value` amount of an `_id` from the `_from` address to the `_to` address specified (with safety call). + /// @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see "Approval" section of the standard). + /// - MUST revert if `_to` is the zero address. + /// - MUST revert if balance of holder for token `_id` is lower than the `_value` sent. + /// - MUST revert on any other error. + /// - MUST emit the `TransferSingle` event to reflect the balance change (see "Safe Transfer Rules" section of the standard). + /// - After the above conditions are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call `onERC1155Received` on `_to` and act appropriately (see "Safe Transfer Rules" section of the standard). + /// @param _from Source address + /// @param _to Target address + /// @param _id ID of the token type + /// @param _value Transfer amount + /// @param _data Additional data with no specified format, MUST be sent unaltered in call to `onERC1155Received` on `_to` + function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external; + + /// @notice Transfers `_values` amount(s) of `_ids` from the `_from` address to the `_to` address specified (with safety call). + /// @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see "Approval" section of the standard). + /// - MUST revert if `_to` is the zero address. + /// - MUST revert if length of `_ids` is not the same as length of `_values`. + /// - MUST revert if any of the balance(s) of the holder(s) for token(s) in `_ids` is lower than the respective amount(s) in `_values` sent to the recipient. + /// - MUST revert on any other error. + /// - MUST emit `TransferSingle` or `TransferBatch` event(s) such that all the balance changes are reflected (see "Safe Transfer Rules" section of the standard). + /// - Balance changes and events MUST follow the ordering of the arrays (_ids[0]/_values[0] before _ids[1]/_values[1], etc). + /// - After the above conditions for the transfer(s) in the batch are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call the relevant `ERC1155TokenReceiver` hook(s) on `_to` and act appropriately (see "Safe Transfer Rules" section of the standard). + /// @param _from Source address + /// @param _to Target address + /// @param _ids IDs of each token type (order and length must match _values array) + /// @param _values Transfer amounts per token type (order and length must match _ids array) + /// @param _data Additional data with no specified format, MUST be sent unaltered in call to the `ERC1155TokenReceiver` hook(s) on `_to` + function safeBatchTransferFrom( + address _from, + address _to, + uint256[] calldata _ids, + uint256[] calldata _values, + bytes calldata _data + ) external; + + /// @notice Get the balance of an account's tokens. + /// @param _owner The address of the token holder + /// @param _id ID of the token + /// @return The _owner's balance of the token type requested + function balanceOf(address _owner, uint256 _id) external view returns (uint256); + + /// @notice Get the balance of multiple account/token pairs + /// @param _owners The addresses of the token holders + /// @param _ids ID of the tokens + /// @return The _owner's balance of the token types requested (i.e. balance for each (owner, id) pair) + function balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids) + external + view + returns (uint256[] memory); + + /// @notice Enable or disable approval for a third party ("operator") to manage all of the caller's tokens. + /// @dev MUST emit the ApprovalForAll event on success. + /// @param _operator Address to add to the set of authorized operators + /// @param _approved True if the operator is approved, false to revoke approval + function setApprovalForAll(address _operator, bool _approved) external; + + /// @notice Queries the approval status of an operator for a given owner. + /// @param _owner The owner of the tokens + /// @param _operator Address of authorized operator + /// @return True if the operator is approved, false if not + function isApprovedForAll(address _owner, address _operator) external view returns (bool); +} diff --git a/src/interfaces/IERC165.sol b/src/interfaces/IERC165.sol new file mode 100644 index 00000000..05e8d8d9 --- /dev/null +++ b/src/interfaces/IERC165.sol @@ -0,0 +1,11 @@ +pragma solidity >=0.6.2; + +interface IERC165 { + /// @notice Query if a contract implements an interface + /// @param interfaceID The interface identifier, as specified in ERC-165 + /// @dev Interface identification is specified in ERC-165. This function + /// uses less than 30,000 gas. + /// @return `true` if the contract implements `interfaceID` and + /// `interfaceID` is not 0xffffffff, `false` otherwise + function supportsInterface(bytes4 interfaceID) external view returns (bool); +} diff --git a/src/interfaces/IERC20.sol b/src/interfaces/IERC20.sol new file mode 100644 index 00000000..95b279ee --- /dev/null +++ b/src/interfaces/IERC20.sol @@ -0,0 +1,42 @@ +pragma solidity >=0.6.2; + +/// @dev Interface of the ERC20 standard as defined in the EIP. +/// @dev This includes the optional name, symbol, and decimals metadata. +interface IERC20 { + /// @dev Emitted when `value` tokens are moved from one account (`from`) to another (`to`). + event Transfer(address indexed from, address indexed to, uint256 value); + + /// @dev Emitted when the allowance of a `spender` for an `owner` is set, where `value` + /// is the new allowance. + event Approval(address indexed owner, address indexed spender, uint256 value); + + /// @notice Returns the amount of tokens in existence. + function totalSupply() external view returns (uint256); + + /// @notice Returns the amount of tokens owned by `account`. + function balanceOf(address account) external view returns (uint256); + + /// @notice Moves `amount` tokens from the caller's account to `to`. + function transfer(address to, uint256 amount) external returns (bool); + + /// @notice Returns the remaining number of tokens that `spender` is allowed + /// to spend on behalf of `owner` + function allowance(address owner, address spender) external view returns (uint256); + + /// @notice Sets `amount` as the allowance of `spender` over the caller's tokens. + /// @dev Be aware of front-running risks: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + function approve(address spender, uint256 amount) external returns (bool); + + /// @notice Moves `amount` tokens from `from` to `to` using the allowance mechanism. + /// `amount` is then deducted from the caller's allowance. + function transferFrom(address from, address to, uint256 amount) external returns (bool); + + /// @notice Returns the name of the token. + function name() external view returns (string memory); + + /// @notice Returns the symbol of the token. + function symbol() external view returns (string memory); + + /// @notice Returns the decimals places of the token. + function decimals() external view returns (uint8); +} diff --git a/src/interfaces/IERC4626.sol b/src/interfaces/IERC4626.sol new file mode 100644 index 00000000..07afb522 --- /dev/null +++ b/src/interfaces/IERC4626.sol @@ -0,0 +1,189 @@ +pragma solidity >=0.6.2; + +import "src/interfaces/IERC20.sol"; + +/// @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in +/// https://eips.ethereum.org/EIPS/eip-4626 +interface IERC4626 is IERC20 { + event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); + + event Withdraw( + address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares + ); + + /// @notice Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. + /// @dev + /// - MUST be an ERC-20 token contract. + /// - MUST NOT revert. + function asset() external view returns (address assetTokenAddress); + + /// @notice Returns the total amount of the underlying asset that is “managed” by Vault. + /// @dev + /// - SHOULD include any compounding that occurs from yield. + /// - MUST be inclusive of any fees that are charged against assets in the Vault. + /// - MUST NOT revert. + function totalAssets() external view returns (uint256 totalManagedAssets); + + /// @notice Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal + /// scenario where all the conditions are met. + /// @dev + /// - MUST NOT be inclusive of any fees that are charged against assets in the Vault. + /// - MUST NOT show any variations depending on the caller. + /// - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. + /// - MUST NOT revert. + /// + /// NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the + /// “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and + /// from. + function convertToShares(uint256 assets) external view returns (uint256 shares); + + /// @notice Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal + /// scenario where all the conditions are met. + /// @dev + /// - MUST NOT be inclusive of any fees that are charged against assets in the Vault. + /// - MUST NOT show any variations depending on the caller. + /// - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. + /// - MUST NOT revert. + /// + /// NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the + /// “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and + /// from. + function convertToAssets(uint256 shares) external view returns (uint256 assets); + + /// @notice Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver, + /// through a deposit call. + /// @dev + /// - MUST return a limited value if receiver is subject to some deposit limit. + /// - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. + /// - MUST NOT revert. + function maxDeposit(address receiver) external view returns (uint256 maxAssets); + + /// @notice Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given + /// current on-chain conditions. + /// @dev + /// - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit + /// call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called + /// in the same transaction. + /// - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the + /// deposit would be accepted, regardless if the user has enough tokens approved, etc. + /// - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. + /// - MUST NOT revert. + /// + /// NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in + /// share price or some other type of condition, meaning the depositor will lose assets by depositing. + function previewDeposit(uint256 assets) external view returns (uint256 shares); + + /// @notice Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens. + /// @dev + /// - MUST emit the Deposit event. + /// - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + /// deposit execution, and are accounted for during deposit. + /// - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not + /// approving enough underlying tokens to the Vault contract, etc). + /// + /// NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + function deposit(uint256 assets, address receiver) external returns (uint256 shares); + + /// @notice Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. + /// @dev + /// - MUST return a limited value if receiver is subject to some mint limit. + /// - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. + /// - MUST NOT revert. + function maxMint(address receiver) external view returns (uint256 maxShares); + + /// @notice Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given + /// current on-chain conditions. + /// @dev + /// - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call + /// in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the + /// same transaction. + /// - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint + /// would be accepted, regardless if the user has enough tokens approved, etc. + /// - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. + /// - MUST NOT revert. + /// + /// NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in + /// share price or some other type of condition, meaning the depositor will lose assets by minting. + function previewMint(uint256 shares) external view returns (uint256 assets); + + /// @notice Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens. + /// @dev + /// - MUST emit the Deposit event. + /// - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint + /// execution, and are accounted for during mint. + /// - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not + /// approving enough underlying tokens to the Vault contract, etc). + /// + /// NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + function mint(uint256 shares, address receiver) external returns (uint256 assets); + + /// @notice Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the + /// Vault, through a withdraw call. + /// @dev + /// - MUST return a limited value if owner is subject to some withdrawal limit or timelock. + /// - MUST NOT revert. + function maxWithdraw(address owner) external view returns (uint256 maxAssets); + + /// @notice Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, + /// given current on-chain conditions. + /// @dev + /// - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw + /// call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if + /// called + /// in the same transaction. + /// - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though + /// the withdrawal would be accepted, regardless if the user has enough shares, etc. + /// - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. + /// - MUST NOT revert. + /// + /// NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in + /// share price or some other type of condition, meaning the depositor will lose assets by depositing. + function previewWithdraw(uint256 assets) external view returns (uint256 shares); + + /// @notice Burns shares from owner and sends exactly assets of underlying tokens to receiver. + /// @dev + /// - MUST emit the Withdraw event. + /// - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + /// withdraw execution, and are accounted for during withdraw. + /// - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner + /// not having enough shares, etc). + /// + /// Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. + /// Those methods should be performed separately. + function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); + + /// @notice Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, + /// through a redeem call. + /// @dev + /// - MUST return a limited value if owner is subject to some withdrawal limit or timelock. + /// - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock. + /// - MUST NOT revert. + function maxRedeem(address owner) external view returns (uint256 maxShares); + + /// @notice Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, + /// given current on-chain conditions. + /// @dev + /// - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call + /// in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the + /// same transaction. + /// - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the + /// redemption would be accepted, regardless if the user has enough shares, etc. + /// - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. + /// - MUST NOT revert. + /// + /// NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in + /// share price or some other type of condition, meaning the depositor will lose assets by redeeming. + function previewRedeem(uint256 shares) external view returns (uint256 assets); + + /// @notice Burns exactly shares from owner and sends assets of underlying tokens to receiver. + /// @dev + /// - MUST emit the Withdraw event. + /// - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + /// redeem execution, and are accounted for during redeem. + /// - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner + /// not having enough shares, etc). + /// + /// NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed. + /// Those methods should be performed separately. + function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); +} diff --git a/src/interfaces/IERC721.sol b/src/interfaces/IERC721.sol new file mode 100644 index 00000000..954468c7 --- /dev/null +++ b/src/interfaces/IERC721.sol @@ -0,0 +1,163 @@ +pragma solidity >=0.6.2; + +import "src/interfaces/IERC165.sol"; + +/// @title ERC-721 Non-Fungible Token Standard +/// @dev See https://eips.ethereum.org/EIPS/eip-721 +/// Note: the ERC-165 identifier for this interface is 0x80ac58cd. +interface IERC721 is IERC165 { + /// @dev This emits when ownership of any NFT changes by any mechanism. + /// This event emits when NFTs are created (`from` == 0) and destroyed + /// (`to` == 0). Exception: during contract creation, any number of NFTs + /// may be created and assigned without emitting Transfer. At the time of + /// any transfer, the approved address for that NFT (if any) is reset to none. + event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); + + /// @dev This emits when the approved address for an NFT is changed or + /// reaffirmed. The zero address indicates there is no approved address. + /// When a Transfer event emits, this also indicates that the approved + /// address for that NFT (if any) is reset to none. + event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId); + + /// @dev This emits when an operator is enabled or disabled for an owner. + /// The operator can manage all NFTs of the owner. + event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); + + /// @notice Count all NFTs assigned to an owner + /// @dev NFTs assigned to the zero address are considered invalid, and this + /// function throws for queries about the zero address. + /// @param _owner An address for whom to query the balance + /// @return The number of NFTs owned by `_owner`, possibly zero + function balanceOf(address _owner) external view returns (uint256); + + /// @notice Find the owner of an NFT + /// @dev NFTs assigned to zero address are considered invalid, and queries + /// about them do throw. + /// @param _tokenId The identifier for an NFT + /// @return The address of the owner of the NFT + function ownerOf(uint256 _tokenId) external view returns (address); + + /// @notice Transfers the ownership of an NFT from one address to another address + /// @dev Throws unless `msg.sender` is the current owner, an authorized + /// operator, or the approved address for this NFT. Throws if `_from` is + /// not the current owner. Throws if `_to` is the zero address. Throws if + /// `_tokenId` is not a valid NFT. When transfer is complete, this function + /// checks if `_to` is a smart contract (code size > 0). If so, it calls + /// `onERC721Received` on `_to` and throws if the return value is not + /// `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`. + /// @param _from The current owner of the NFT + /// @param _to The new owner + /// @param _tokenId The NFT to transfer + /// @param data Additional data with no specified format, sent in call to `_to` + function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes calldata data) external payable; + + /// @notice Transfers the ownership of an NFT from one address to another address + /// @dev This works identically to the other function with an extra data parameter, + /// except this function just sets data to "". + /// @param _from The current owner of the NFT + /// @param _to The new owner + /// @param _tokenId The NFT to transfer + function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable; + + /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE + /// TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE + /// THEY MAY BE PERMANENTLY LOST + /// @dev Throws unless `msg.sender` is the current owner, an authorized + /// operator, or the approved address for this NFT. Throws if `_from` is + /// not the current owner. Throws if `_to` is the zero address. Throws if + /// `_tokenId` is not a valid NFT. + /// @param _from The current owner of the NFT + /// @param _to The new owner + /// @param _tokenId The NFT to transfer + function transferFrom(address _from, address _to, uint256 _tokenId) external payable; + + /// @notice Change or reaffirm the approved address for an NFT + /// @dev The zero address indicates there is no approved address. + /// Throws unless `msg.sender` is the current NFT owner, or an authorized + /// operator of the current owner. + /// @param _approved The new approved NFT controller + /// @param _tokenId The NFT to approve + function approve(address _approved, uint256 _tokenId) external payable; + + /// @notice Enable or disable approval for a third party ("operator") to manage + /// all of `msg.sender`'s assets + /// @dev Emits the ApprovalForAll event. The contract MUST allow + /// multiple operators per owner. + /// @param _operator Address to add to the set of authorized operators + /// @param _approved True if the operator is approved, false to revoke approval + function setApprovalForAll(address _operator, bool _approved) external; + + /// @notice Get the approved address for a single NFT + /// @dev Throws if `_tokenId` is not a valid NFT. + /// @param _tokenId The NFT to find the approved address for + /// @return The approved address for this NFT, or the zero address if there is none + function getApproved(uint256 _tokenId) external view returns (address); + + /// @notice Query if an address is an authorized operator for another address + /// @param _owner The address that owns the NFTs + /// @param _operator The address that acts on behalf of the owner + /// @return True if `_operator` is an approved operator for `_owner`, false otherwise + function isApprovedForAll(address _owner, address _operator) external view returns (bool); +} + +/// @dev Note: the ERC-165 identifier for this interface is 0x150b7a02. +interface IERC721TokenReceiver { + /// @notice Handle the receipt of an NFT + /// @dev The ERC721 smart contract calls this function on the recipient + /// after a `transfer`. This function MAY throw to revert and reject the + /// transfer. Return of other than the magic value MUST result in the + /// transaction being reverted. + /// Note: the contract address is always the message sender. + /// @param _operator The address which called `safeTransferFrom` function + /// @param _from The address which previously owned the token + /// @param _tokenId The NFT identifier which is being transferred + /// @param _data Additional data with no specified format + /// @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` + /// unless throwing + function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes calldata _data) + external + returns (bytes4); +} + +/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension +/// @dev See https://eips.ethereum.org/EIPS/eip-721 +/// Note: the ERC-165 identifier for this interface is 0x5b5e139f. +interface IERC721Metadata is IERC721 { + /// @notice A descriptive name for a collection of NFTs in this contract + function name() external view returns (string memory _name); + + /// @notice An abbreviated name for NFTs in this contract + function symbol() external view returns (string memory _symbol); + + /// @notice A distinct Uniform Resource Identifier (URI) for a given asset. + /// @dev Throws if `_tokenId` is not a valid NFT. URIs are defined in RFC + /// 3986. The URI may point to a JSON file that conforms to the "ERC721 + /// Metadata JSON Schema". + function tokenURI(uint256 _tokenId) external view returns (string memory); +} + +/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension +/// @dev See https://eips.ethereum.org/EIPS/eip-721 +/// Note: the ERC-165 identifier for this interface is 0x780e9d63. +interface IERC721Enumerable is IERC721 { + /// @notice Count NFTs tracked by this contract + /// @return A count of valid NFTs tracked by this contract, where each one of + /// them has an assigned and queryable owner not equal to the zero address + function totalSupply() external view returns (uint256); + + /// @notice Enumerate valid NFTs + /// @dev Throws if `_index` >= `totalSupply()`. + /// @param _index A counter less than `totalSupply()` + /// @return The token identifier for the `_index`th NFT, + /// (sort order not specified) + function tokenByIndex(uint256 _index) external view returns (uint256); + + /// @notice Enumerate NFTs assigned to an owner + /// @dev Throws if `_index` >= `balanceOf(_owner)` or if + /// `_owner` is the zero address, representing invalid NFTs. + /// @param _owner An address where we are interested in NFTs owned by them + /// @param _index A counter less than `balanceOf(_owner)` + /// @return The token identifier for the `_index`th NFT assigned to `_owner`, + /// (sort order not specified) + function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256); +} From 7d14c38b8db4ab578af347e779dddbb6fa02735e Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Fri, 21 Oct 2022 22:08:40 +0200 Subject: [PATCH 21/27] feat: reimplement `bound` w/ even distribution --- src/StdUtils.sol | 39 ++++++++++++++++++++------------------- test/StdUtils.t.sol | 29 ++++++++++++----------------- 2 files changed, 32 insertions(+), 36 deletions(-) diff --git a/src/StdUtils.sol b/src/StdUtils.sol index 2c24ea53..f1b0bd12 100644 --- a/src/StdUtils.sol +++ b/src/StdUtils.sol @@ -7,34 +7,35 @@ abstract contract StdUtils { uint256 internal constant UINT256_MAX = 115792089237316195423570985008687907853269984665640564039457584007913129639935; - function _bound(uint256 x, uint256 min, uint256 max) internal pure virtual returns (uint256 result) { + function _bound(uint256 x, uint256 min, uint256 max) internal pure virtual returns (uint256) { require(min <= max, "StdUtils bound(uint256,uint256,uint256): Max is less than min."); // If x is between min and max, return x directly. This is to ensure that dictionary values // do not get shifted if the min is nonzero. More info: https://github.com/foundry-rs/forge-std/issues/188 if (x >= min && x <= max) return x; - // Handle degenerate cases. - // The `if (size == UINT256_MAX) return x` case is covered by the above line. - uint256 size = max - min; - if (size == 0) return min; - ++size; // make `max` inclusive + uint256 size = max - min + 1; - // If the value is 0,1,2,3 wrap that to min, min+1, min+2, min+3. Similarly, for the MAX_UINT side. - // This helps ensure coverage of the min/max values. It does introduce a small bias, however - // uint256 is so large that this is insignificant. - if (x == 0) return min; - if (x == 1 && size > 1) return min + 1; - if (x == 2 && size > 2) return min + 2; - if (x == 3 && size > 3) return min + 3; - if (x == UINT256_MAX) return max; - if (x == UINT256_MAX - 1 && size > 1) return max - 1; - if (x == UINT256_MAX - 2 && size > 2) return max - 2; - if (x == UINT256_MAX - 3 && size > 3) return max - 3; + // If the value is 0, 1, 2, 3, warp that to min, min+1, min+2, min+3. Similarly for the UINT256_MAX side. + // This helps ensure coverage of the min/max values. + if (x <= 3 && size > x) return min + x; + if (x >= UINT256_MAX - 3 && size > UINT256_MAX - x) return max - (UINT256_MAX - x); // Otherwise, wrap x into the range [min, max], i.e. the range is inclusive. - uint256 mod = x % size; - return min + mod; + if (x > max) { + uint256 diff = x - max; + uint256 rem = diff % size; + if (rem == 0) return max; + uint256 result = min + rem - 1; + return result; + } + if (x < max) { + uint256 diff = min - x; + uint256 rem = diff % size; + if (rem == 0) return min; + uint256 result = max - rem + 1; + return result; + } } function bound(uint256 x, uint256 min, uint256 max) internal view virtual returns (uint256 result) { diff --git a/test/StdUtils.t.sol b/test/StdUtils.t.sol index 524dd2de..17c77aee 100644 --- a/test/StdUtils.t.sol +++ b/test/StdUtils.t.sol @@ -8,17 +8,19 @@ contract StdUtilsTest is Test { assertEq(bound(5, 0, 4), 0); assertEq(bound(0, 69, 69), 69); assertEq(bound(0, 68, 69), 68); - assertEq(bound(10, 150, 190), 160); - assertEq(bound(300, 2800, 3200), 3100); - assertEq(bound(9999, 1337, 6666), 6006); + assertEq(bound(10, 150, 190), 174); + assertEq(bound(300, 2800, 3200), 3107); + assertEq(bound(9999, 1337, 6666), 4669); + } - // Don't wrap values within range. + function testBound_WithinRange() public { assertEq(bound(51, 50, 150), 51); assertEq(bound(51, 50, 150), bound(bound(51, 50, 150), 50, 150)); assertEq(bound(149, 50, 150), 149); assertEq(bound(149, 50, 150), bound(bound(149, 50, 150), 50, 150)); + } - // 0, 1, 2 and MAX_UINT, MAX_UINT-1, and MAX_UINT-2 should map to min/max. + function testBound_EdgeCoverage() public { assertEq(bound(0, 50, 150), 50); assertEq(bound(1, 50, 150), 51); assertEq(bound(2, 50, 150), 52); @@ -27,24 +29,17 @@ contract StdUtilsTest is Test { assertEq(bound(type(uint256).max - 1, 50, 150), 149); assertEq(bound(type(uint256).max - 2, 50, 150), 148); assertEq(bound(type(uint256).max - 3, 50, 150), 147); - - assertEq(bound(0, 50, 51), 50); - assertEq(bound(1, 50, 51), 51); - assertEq(bound(2, 50, 51), 50); - assertEq(bound(type(uint256).max, 50, 51), 51); - assertEq(bound(type(uint256).max - 1, 50, 51), 50); - assertEq(bound(type(uint256).max - 2, 50, 51), 51); } function testBound(uint256 num, uint256 min, uint256 max) public { if (min > max) (min, max) = (max, min); - uint256 bounded = bound(num, min, max); + uint256 result = bound(num, min, max); - assertGe(bounded, min); - assertLe(bounded, max); - assertEq(bounded, bound(bounded, min, max)); - if (num >= min && num <= max) assertEq(bounded, num); + assertGe(result, min); + assertLe(result, max); + assertEq(result, bound(result, min, max)); + if (num >= min && num <= max) assertEq(result, num); } function testBoundUint256Max() public { From 14d78ea299c599337ca42eb0898146be34cd8cde Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Fri, 21 Oct 2022 22:22:31 +0200 Subject: [PATCH 22/27] build: rename step --- .github/workflows/ci.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fd8c46f2..9971ff9e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,6 @@ on: branches: - master - jobs: build: runs-on: ubuntu-latest @@ -22,7 +21,7 @@ jobs: - name: Install dependencies run: forge install - - name: Build Test with older solc versions + - name: Check backward compatibility run: | forge build --skip test --use solc:0.8.0 forge build --skip test --use solc:0.7.6 @@ -56,4 +55,4 @@ jobs: version: nightly - name: Check formatting - run: forge fmt --check \ No newline at end of file + run: forge fmt --check From 6ca63a3e1fd08207efed171357c60220008a93f0 Mon Sep 17 00:00:00 2001 From: Drake Evans <31104161+DrakeEvans@users.noreply.github.com> Date: Wed, 26 Oct 2022 15:17:35 -0400 Subject: [PATCH 23/27] Add memory-safe notation so that compiling via-ir can optimize effectively (#196) --- src/console2.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/console2.sol b/src/console2.sol index cdde3d71..3627e148 100644 --- a/src/console2.sol +++ b/src/console2.sol @@ -12,6 +12,7 @@ library console2 { function _sendLogPayload(bytes memory payload) private view { uint256 payloadLength = payload.length; address consoleAddress = CONSOLE_ADDRESS; + /// @solidity memory-safe-assembly assembly { let payloadStart := add(payload, 32) let r := staticcall(gas(), consoleAddress, payloadStart, payloadLength, 0, 0) From d369d2ab246a15d655592842f6c87b693b4f2ee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ekkusu=20=E3=82=BC=E3=83=AD?= <94782988+ZeroEkkusu@users.noreply.github.com> Date: Fri, 28 Oct 2022 20:48:48 +0200 Subject: [PATCH 24/27] test(bound): add even distribution test (#197) --- src/StdUtils.sol | 11 ++++------- test/StdUtils.t.sol | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/StdUtils.sol b/src/StdUtils.sol index f1b0bd12..ca12afb8 100644 --- a/src/StdUtils.sol +++ b/src/StdUtils.sol @@ -7,7 +7,7 @@ abstract contract StdUtils { uint256 internal constant UINT256_MAX = 115792089237316195423570985008687907853269984665640564039457584007913129639935; - function _bound(uint256 x, uint256 min, uint256 max) internal pure virtual returns (uint256) { + function _bound(uint256 x, uint256 min, uint256 max) internal pure virtual returns (uint256 result) { require(min <= max, "StdUtils bound(uint256,uint256,uint256): Max is less than min."); // If x is between min and max, return x directly. This is to ensure that dictionary values @@ -26,15 +26,12 @@ abstract contract StdUtils { uint256 diff = x - max; uint256 rem = diff % size; if (rem == 0) return max; - uint256 result = min + rem - 1; - return result; - } - if (x < max) { + result = min + rem - 1; + } else if (x < max) { uint256 diff = min - x; uint256 rem = diff % size; if (rem == 0) return min; - uint256 result = max - rem + 1; - return result; + result = max - rem + 1; } } diff --git a/test/StdUtils.t.sol b/test/StdUtils.t.sol index 17c77aee..f838fedc 100644 --- a/test/StdUtils.t.sol +++ b/test/StdUtils.t.sol @@ -31,6 +31,22 @@ contract StdUtilsTest is Test { assertEq(bound(type(uint256).max - 3, 50, 150), 147); } + function testBound_DistributionIsEven(uint256 min, uint256 size) public { + size = size % 100 + 1; + min = bound(min, UINT256_MAX / 2, UINT256_MAX / 2 + size); + uint256 max = min + size - 1; + uint256 result; + + for (uint256 i = 1; i <= size * 4; ++i) { + // x > max + result = bound(max + i, min, max); + assertEq(result, min + (i - 1) % size); + // x < min + result = bound(min - i, min, max); + assertEq(result, max - (i - 1) % size); + } + } + function testBound(uint256 num, uint256 min, uint256 max) public { if (min > max) (min, max) = (max, min); From 72cdd70ae608e32711b4bcf3bbcdafc9eddc3c56 Mon Sep 17 00:00:00 2001 From: Matt Solomon Date: Fri, 28 Oct 2022 17:12:07 -0400 Subject: [PATCH 25/27] feat: add `assumeNoPrecompiles` (#195) * refactor: use fully-qualified paths instead of relative paths * chore: fix typo * feat: start adding StdChains * feat: start adding assumeNoPrecompiles * feat: add chains * feat: add precompiles/predeploys * Revert "refactor: use fully-qualified paths instead of relative paths" This reverts commit bb2579e36b0275a8e6d331c09ed3dd35a24ca7e8. * refactor: use relative paths for compatibility with solc <0.6.9 (no --base-path flag) * refactor: make assumeNoPrecompiles virtual * refactor: no more constructor warning from StdChains * fix: move stdChains into StdCheats, fix constructor initalization order, move cheats into VmSafe that can be safely used --- foundry.toml | 7 +++ src/StdCheats.sol | 129 +++++++++++++++++++++++++++++++++++++++ src/Vm.sol | 14 +++-- src/console2.sol | 2 +- test/StdAssertions.t.sol | 2 +- test/StdCheats.t.sol | 27 +++++++- test/StdError.t.sol | 4 +- test/StdMath.t.sol | 4 +- test/StdStorage.t.sol | 4 +- test/StdUtils.t.sol | 7 ++- 10 files changed, 182 insertions(+), 18 deletions(-) diff --git a/foundry.toml b/foundry.toml index 6fde36d9..4f93dbfb 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,6 +1,13 @@ [profile.default] fs_permissions = [{ access = "read-write", path = "./"}] +[rpc_endpoints] +# We intentionally use both dashes and underscores in the key names to ensure both are supported. +# The RPC URLs below match the StdChains URLs but append a trailing slash for testing. +mainnet = "https://api.mycryptoapi.com/eth/" +optimism_goerli = "https://goerli.optimism.io/" +arbitrum-one-goerli = "https://goerli-rollup.arbitrum.io/rpc/" + [fmt] # These are all the `forge fmt` defaults. line_length = 120 diff --git a/src/StdCheats.sol b/src/StdCheats.sol index bd9fa3c5..240db21d 100644 --- a/src/StdCheats.sol +++ b/src/StdCheats.sol @@ -9,6 +9,45 @@ import "./Vm.sol"; abstract contract StdCheatsSafe { VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code"))))); + /// @dev To hide constructor warnings across solc versions due to different constructor visibility requirements and + /// syntaxes, we put the constructor in a private method and assign an unused return value to a variable. This + /// forces the method to run during construction, but without declaring an explicit constructor. + uint256 private CONSTRUCTOR = _constructor(); + + struct Chain { + // The chain name, using underscores as the separator to match `foundry.toml` conventions. + string name; + // The chain's Chain ID. + uint256 chainId; + // A default RPC endpoint for this chain. + // NOTE: This default RPC URL is included for convenience to facilitate quick tests and + // experimentation. Do not use this RPC URL for production test suites, CI, or other heavy + // usage as you will be throttled and this is a disservice to others who need this endpoint. + string rpcUrl; + } + + struct Chains { + Chain Anvil; + Chain Hardhat; + Chain Mainnet; + Chain Goerli; + Chain Sepolia; + Chain Optimism; + Chain OptimismGoerli; + Chain ArbitrumOne; + Chain ArbitrumOneGoerli; + Chain ArbitrumNova; + Chain Polygon; + Chain PolygonMumbai; + Chain Avalanche; + Chain AvalancheFuji; + Chain BnbSmartChain; + Chain BnbSmartChainTestnet; + Chain GnosisChain; + } + + Chains stdChains; + // Data structures to parse Transaction objects from the broadcast artifact // that conform to EIP1559. The Raw structs is what is parsed from the JSON // and then converted to the one that is used by the user for better UX. @@ -186,6 +225,96 @@ abstract contract StdCheatsSafe { string value; } + function _constructor() private returns (uint256) { + // Initialize `stdChains` with the defaults. + stdChains = Chains({ + Anvil: Chain("Anvil", 31337, "http://127.0.0.1:8545"), + Hardhat: Chain("Hardhat", 31337, "http://127.0.0.1:8545"), + Mainnet: Chain("Mainnet", 1, "https://api.mycryptoapi.com/eth"), + Goerli: Chain("Goerli", 5, "https://goerli.infura.io/v3/84842078b09946638c03157f83405213"), // Default Infura key from ethers.js: https://github.com/ethers-io/ethers.js/blob/c80fcddf50a9023486e9f9acb1848aba4c19f7b6/packages/providers/src.ts/infura-provider.ts + Sepolia: Chain("Sepolia", 11155111, "https://rpc.sepolia.dev"), + Optimism: Chain("Optimism", 10, "https://mainnet.optimism.io"), + OptimismGoerli: Chain("OptimismGoerli", 420, "https://goerli.optimism.io"), + ArbitrumOne: Chain("ArbitrumOne", 42161, "https://arb1.arbitrum.io/rpc"), + ArbitrumOneGoerli: Chain("ArbitrumOneGoerli", 421613, "https://goerli-rollup.arbitrum.io/rpc"), + ArbitrumNova: Chain("ArbitrumNova", 42170, "https://nova.arbitrum.io/rpc"), + Polygon: Chain("Polygon", 137, "https://polygon-rpc.com"), + PolygonMumbai: Chain("PolygonMumbai", 80001, "https://rpc-mumbai.matic.today"), + Avalanche: Chain("Avalanche", 43114, "https://api.avax.network/ext/bc/C/rpc"), + AvalancheFuji: Chain("AvalancheFuji", 43113, "https://api.avax-test.network/ext/bc/C/rpc"), + BnbSmartChain: Chain("BnbSmartChain", 56, "https://bsc-dataseed1.binance.org"), + BnbSmartChainTestnet: Chain("BnbSmartChainTestnet", 97, "https://data-seed-prebsc-1-s1.binance.org:8545"), + GnosisChain: Chain("GnosisChain", 100, "https://rpc.gnosischain.com") + }); + + // Loop over RPC URLs in the config file to replace the default RPC URLs + (string[2][] memory rpcs) = vm.rpcUrls(); + for (uint256 i = 0; i < rpcs.length; i++) { + (string memory name, string memory rpcUrl) = (rpcs[i][0], rpcs[i][1]); + // forgefmt: disable-start + if (isEqual(name, "anvil")) stdChains.Anvil.rpcUrl = rpcUrl; + else if (isEqual(name, "hardhat")) stdChains.Hardhat.rpcUrl = rpcUrl; + else if (isEqual(name, "mainnet")) stdChains.Mainnet.rpcUrl = rpcUrl; + else if (isEqual(name, "goerli")) stdChains.Goerli.rpcUrl = rpcUrl; + else if (isEqual(name, "sepolia")) stdChains.Sepolia.rpcUrl = rpcUrl; + else if (isEqual(name, "optimism")) stdChains.Optimism.rpcUrl = rpcUrl; + else if (isEqual(name, "optimism_goerli", "optimism-goerli")) stdChains.OptimismGoerli.rpcUrl = rpcUrl; + else if (isEqual(name, "arbitrum_one", "arbitrum-one")) stdChains.ArbitrumOne.rpcUrl = rpcUrl; + else if (isEqual(name, "arbitrum_one_goerli", "arbitrum-one-goerli")) stdChains.ArbitrumOneGoerli.rpcUrl = rpcUrl; + else if (isEqual(name, "arbitrum_nova", "arbitrum-nova")) stdChains.ArbitrumNova.rpcUrl = rpcUrl; + else if (isEqual(name, "polygon")) stdChains.Polygon.rpcUrl = rpcUrl; + else if (isEqual(name, "polygon_mumbai", "polygon-mumbai")) stdChains.PolygonMumbai.rpcUrl = rpcUrl; + else if (isEqual(name, "avalanche")) stdChains.Avalanche.rpcUrl = rpcUrl; + else if (isEqual(name, "avalanche_fuji", "avalanche-fuji")) stdChains.AvalancheFuji.rpcUrl = rpcUrl; + else if (isEqual(name, "bnb_smart_chain", "bnb-smart-chain")) stdChains.BnbSmartChain.rpcUrl = rpcUrl; + else if (isEqual(name, "bnb_smart_chain_testnet", "bnb-smart-chain-testnet")) stdChains.BnbSmartChainTestnet.rpcUrl = rpcUrl; + else if (isEqual(name, "gnosis_chain", "gnosis-chain")) stdChains.GnosisChain.rpcUrl = rpcUrl; + // forgefmt: disable-end + } + return 0; + } + + function isEqual(string memory a, string memory b) private pure returns (bool) { + return keccak256(abi.encode(a)) == keccak256(abi.encode(b)); + } + + function isEqual(string memory a, string memory b, string memory c) private pure returns (bool) { + return keccak256(abi.encode(a)) == keccak256(abi.encode(b)) + || keccak256(abi.encode(a)) == keccak256(abi.encode(c)); + } + + function assumeNoPrecompiles(address addr) internal virtual { + // Assembly required since `block.chainid` was introduced in 0.8.0. + uint256 chainId; + assembly { + chainId := chainid() + } + assumeNoPrecompiles(addr, chainId); + } + + function assumeNoPrecompiles(address addr, uint256 chainId) internal virtual { + // Note: For some chains like Optimism these are technically predeploys (i.e. bytecode placed at a specific + // address), but the same rationale for excluding them applies so we include those too. + + // These should be present on all EVM-compatible chains. + vm.assume(addr < address(0x1) || addr > address(0x9)); + + // forgefmt: disable-start + if (chainId == stdChains.Optimism.chainId || chainId == stdChains.OptimismGoerli.chainId) { + // https://github.com/ethereum-optimism/optimism/blob/eaa371a0184b56b7ca6d9eb9cb0a2b78b2ccd864/op-bindings/predeploys/addresses.go#L6-L21 + vm.assume(addr < address(0x4200000000000000000000000000000000000000) || addr > address(0x4200000000000000000000000000000000000800)); + } else if (chainId == stdChains.ArbitrumOne.chainId || chainId == stdChains.ArbitrumOneGoerli.chainId) { + // https://developer.arbitrum.io/useful-addresses#arbitrum-precompiles-l2-same-on-all-arb-chains + vm.assume(addr < address(0x0000000000000000000000000000000000000064) || addr > address(0x0000000000000000000000000000000000000068)); + } else if (chainId == stdChains.Avalanche.chainId || chainId == stdChains.AvalancheFuji.chainId) { + // https://github.com/ava-labs/subnet-evm/blob/47c03fd007ecaa6de2c52ea081596e0a88401f58/precompile/params.go#L18-L59 + vm.assume(addr < address(0x0100000000000000000000000000000000000000) || addr > address(0x01000000000000000000000000000000000000ff)); + vm.assume(addr < address(0x0200000000000000000000000000000000000000) || addr > address(0x02000000000000000000000000000000000000FF)); + vm.assume(addr < address(0x0300000000000000000000000000000000000000) || addr > address(0x03000000000000000000000000000000000000Ff)); + } + // forgefmt: disable-end + } + function readEIP1559ScriptArtifact(string memory path) internal virtual returns (EIP1559ScriptArtifact memory) { string memory data = vm.readFile(path); bytes memory parsedData = vm.parseJson(data); diff --git a/src/Vm.sol b/src/Vm.sol index 798ac8fd..5a24d1cf 100644 --- a/src/Vm.sol +++ b/src/Vm.sol @@ -132,6 +132,14 @@ interface VmSafe { // struct json = { uint256 a; address b; } // If we defined a json struct with the opposite order, meaning placing the address b first, it would try to // decode the tuple in that order, and thus fail. + + // Returns the RPC url for the given alias + function rpcUrl(string calldata) external returns (string memory); + // Returns all rpc urls and their aliases `[alias, url][]` + function rpcUrls() external returns (string[2][] memory); + + // If the condition is false, discard this run's fuzz inputs and generate new ones. + function assume(bool) external; } interface Vm is VmSafe { @@ -187,8 +195,6 @@ interface Vm is VmSafe { function expectCall(address, bytes calldata) external; // Expects a call to an address with the specified msg.value and calldata function expectCall(address, uint256, bytes calldata) external; - // If the condition is false, discard this run's fuzz inputs and generate new ones - function assume(bool) external; // Sets block.coinbase (who) function coinbase(address) external; // Snapshot the current state of the evm. @@ -243,8 +249,4 @@ interface Vm is VmSafe { function transact(bytes32 txHash) external; // Fetches the given transaction from the given fork and executes it on the current state function transact(uint256 forkId, bytes32 txHash) external; - // Returns the RPC url for the given alias - function rpcUrl(string calldata) external returns (string memory); - // Returns all rpc urls and their aliases `[alias, url][]` - function rpcUrls() external returns (string[2][] memory); } diff --git a/src/console2.sol b/src/console2.sol index 3627e148..8cd6e219 100644 --- a/src/console2.sol +++ b/src/console2.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.4.22 <0.9.0; -/// @dev The orignal console.sol uses `int` and `uint` for computing function selectors, but it should +/// @dev The original console.sol uses `int` and `uint` for computing function selectors, but it should /// use `int256` and `uint256`. This modified version fixes that. This version is recommended /// over `console.sol` if you don't need compatibility with Hardhat as the logs will show up in /// forge stack traces. If you do need compatibility with Hardhat, you must use `console.sol`. diff --git a/test/StdAssertions.t.sol b/test/StdAssertions.t.sol index 6ebd3a4a..4d5827d6 100644 --- a/test/StdAssertions.t.sol +++ b/test/StdAssertions.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.7.0 <0.9.0; -import "src/Test.sol"; +import "../src/Test.sol"; contract StdAssertionsTest is Test { string constant CUSTOM_ERROR = "guh!"; diff --git a/test/StdCheats.t.sol b/test/StdCheats.t.sol index 28270c02..c8a68370 100644 --- a/test/StdCheats.t.sol +++ b/test/StdCheats.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.7.0 <0.9.0; -import "src/StdCheats.sol"; -import "src/Test.sol"; -import "src/StdJson.sol"; +import "../src/StdCheats.sol"; +import "../src/Test.sol"; +import "../src/StdJson.sol"; contract StdCheatsTest is Test { Bar test; @@ -223,6 +223,27 @@ contract StdCheatsTest is Test { } return number; } + + function testChainRpcInitialization() public { + // RPCs specified in `foundry.toml` should be updated. + assertEq(stdChains.Mainnet.rpcUrl, "https://api.mycryptoapi.com/eth/"); + assertEq(stdChains.OptimismGoerli.rpcUrl, "https://goerli.optimism.io/"); + assertEq(stdChains.ArbitrumOneGoerli.rpcUrl, "https://goerli-rollup.arbitrum.io/rpc/"); + + // Other RPCs should remain unchanged. + assertEq(stdChains.Anvil.rpcUrl, "http://127.0.0.1:8545"); + assertEq(stdChains.Hardhat.rpcUrl, "http://127.0.0.1:8545"); + assertEq(stdChains.Sepolia.rpcUrl, "https://rpc.sepolia.dev"); + } + + // Ensure we can connect to the default RPC URL for each chain. + function testRpcs() public { + (string[2][] memory rpcs) = vm.rpcUrls(); + for (uint256 i = 0; i < rpcs.length; i++) { + ( /* string memory name */ , string memory rpcUrl) = (rpcs[i][0], rpcs[i][1]); + vm.createSelectFork(rpcUrl); + } + } } contract Bar { diff --git a/test/StdError.t.sol b/test/StdError.t.sol index d9abc495..ccd3efac 100644 --- a/test/StdError.t.sol +++ b/test/StdError.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.0 <0.9.0; -import "src/StdError.sol"; -import "src/Test.sol"; +import "../src/StdError.sol"; +import "../src/Test.sol"; contract StdErrorsTest is Test { ErrorsTest test; diff --git a/test/StdMath.t.sol b/test/StdMath.t.sol index 19f753c0..95037ea5 100644 --- a/test/StdMath.t.sol +++ b/test/StdMath.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.0 <0.9.0; -import "src/StdMath.sol"; -import "src/Test.sol"; +import "../src/StdMath.sol"; +import "../src/Test.sol"; contract StdMathTest is Test { function testGetAbs() external { diff --git a/test/StdStorage.t.sol b/test/StdStorage.t.sol index 96752cb5..d4c563a0 100644 --- a/test/StdStorage.t.sol +++ b/test/StdStorage.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.7.0 <0.9.0; -import "src/StdStorage.sol"; -import "src/Test.sol"; +import "../src/StdStorage.sol"; +import "../src/Test.sol"; contract StdStorageTest is Test { using stdStorage for StdStorage; diff --git a/test/StdUtils.t.sol b/test/StdUtils.t.sol index f838fedc..4782d82b 100644 --- a/test/StdUtils.t.sol +++ b/test/StdUtils.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.7.0 <0.9.0; -import "src/Test.sol"; +import "../src/Test.sol"; contract StdUtilsTest is Test { function testBound() public { @@ -88,4 +88,9 @@ contract StdUtilsTest is Test { address create2Address = computeCreate2Address(salt, initcodeHash, deployer); assertEq(create2Address, 0xB147a5d25748fda14b463EB04B111027C290f4d3); } + + function testAssumeNoPrecompilesL1(address addr) external { + assumeNoPrecompiles(addr, stdChains.Mainnet.chainId); + assertTrue(addr < address(1) || addr > address(9)); + } } From 4fba2c449b5fb1155bdca60c014df73f5133c78c Mon Sep 17 00:00:00 2001 From: Pascal Marco Caversaccio Date: Mon, 31 Oct 2022 15:48:59 +0000 Subject: [PATCH 26/27] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20update=20ds-test=20(?= =?UTF-8?q?#200)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ♻️ update ds-test Signed-off-by: Pascal Marco Caversaccio * ♻️ use relative path for ds-test imports Signed-off-by: Pascal Marco Caversaccio Signed-off-by: Pascal Marco Caversaccio --- lib/ds-test | 2 +- src/StdAssertions.sol | 2 +- src/Test.sol | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/ds-test b/lib/ds-test index 9310e879..cd98eff2 160000 --- a/lib/ds-test +++ b/lib/ds-test @@ -1 +1 @@ -Subproject commit 9310e879db8ba3ea6d5c6489a579118fd264a3f5 +Subproject commit cd98eff28324bfac652e63a239a60632a761790b diff --git a/src/StdAssertions.sol b/src/StdAssertions.sol index 439366e2..6926809b 100644 --- a/src/StdAssertions.sol +++ b/src/StdAssertions.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.6.2 <0.9.0; -import "ds-test/test.sol"; +import "../lib/ds-test/src/test.sol"; import "./StdMath.sol"; abstract contract StdAssertions is DSTest { diff --git a/src/Test.sol b/src/Test.sol index 2a96ede1..09eca870 100644 --- a/src/Test.sol +++ b/src/Test.sol @@ -2,7 +2,7 @@ pragma solidity >=0.6.2 <0.9.0; import {CommonBase} from "./Common.sol"; -import "ds-test/test.sol"; +import "../lib/ds-test/src/test.sol"; // forgefmt: disable-next-line import {console, console2, StdAssertions, StdCheats, stdError, stdJson, stdMath, StdStorage, stdStorage, StdUtils, Vm} from "./Components.sol"; From 80b66ff40d9471e4331720f426e4ae177218bdd4 Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Mon, 31 Oct 2022 17:17:40 +0100 Subject: [PATCH 27/27] refactor: move `UINT256_MAX` to `CommonBase` --- src/Common.sol | 2 ++ src/StdUtils.sol | 2 +- test/StdCheats.t.sol | 3 +++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Common.sol b/src/Common.sol index f5e3906d..a29b4847 100644 --- a/src/Common.sol +++ b/src/Common.sol @@ -5,6 +5,8 @@ import {StdStorage, Vm} from "./Components.sol"; abstract contract CommonBase { address internal constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); + uint256 internal constant UINT256_MAX = + 115792089237316195423570985008687907853269984665640564039457584007913129639935; StdStorage internal stdstore; Vm internal constant vm = Vm(VM_ADDRESS); diff --git a/src/StdUtils.sol b/src/StdUtils.sol index ca12afb8..b52fa230 100644 --- a/src/StdUtils.sol +++ b/src/StdUtils.sol @@ -4,7 +4,7 @@ pragma solidity >=0.6.2 <0.9.0; import "./console2.sol"; abstract contract StdUtils { - uint256 internal constant UINT256_MAX = + uint256 private constant UINT256_MAX = 115792089237316195423570985008687907853269984665640564039457584007913129639935; function _bound(uint256 x, uint256 min, uint256 max) internal pure virtual returns (uint256 result) { diff --git a/test/StdCheats.t.sol b/test/StdCheats.t.sol index c8a68370..0580c2d7 100644 --- a/test/StdCheats.t.sol +++ b/test/StdCheats.t.sol @@ -191,12 +191,14 @@ contract StdCheatsTest is Test { string memory path = string.concat(root, "/test/fixtures/broadcast.log.json"); uint256 index = 0; Tx1559 memory transaction = readTx1559(path, index); + transaction; } function testReadEIP1559Transactions() public { string memory root = vm.projectRoot(); string memory path = string.concat(root, "/test/fixtures/broadcast.log.json"); Tx1559[] memory transactions = readTx1559s(path); + transactions; } function testReadReceipt() public { @@ -214,6 +216,7 @@ contract StdCheatsTest is Test { string memory root = vm.projectRoot(); string memory path = string.concat(root, "/test/fixtures/broadcast.log.json"); Receipt[] memory receipts = readReceipts(path); + receipts; } function bytesToUint_test(bytes memory b) private pure returns (uint256) {