From 17656a2fa5453f495d8c1302a0cedded912457eb 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: Mon, 31 Oct 2022 20:49:32 +0100 Subject: [PATCH] feat: forge-std v1.0.0 (#184) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Modularize forge-std (#126) * 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> * 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` * docs: add license info (#156) * feat: rebrand components (#157) * feat: rebrand components Rename to Std * fix: StdErrors -> StdError * chore: remove `.DS_Store` * fix: use `ABIEncoderV2` * test: correct test name * fix: add `CommonBase` * refactor: move test dir to root * Revert "refactor: move test dir to root" This reverts commit f21ef1a4bbc4248e4400befb5d449bfe39259cec. * refactor: move test dir to root, update ci accordingly * style: configure and run forge fmt * ci: split into jobs and add fmt job * ci: update name and triggers * ci: remove name field * feat: better bound, ref https://github.com/foundry-rs/forge-std/issues/188 * fix: bound logs + remove unneeded line * fix: update require strings * refactor: clean up `Test` and `Script` - do not forge fmt Components import - do not import Safe Components in `Test` * fix: udpate bound to match forge's uint edge bias strategy * 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> * feat: reimplement `bound` w/ even distribution * build: rename step * Add memory-safe notation so that compiling via-ir can optimize effectively (#196) * test(bound): add even distribution test (#197) * 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 * ♻️ update ds-test (#200) * ♻️ 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 * refactor: move `UINT256_MAX` to `CommonBase` Signed-off-by: Pascal Marco Caversaccio Co-authored-by: Paul Razvan Berg Co-authored-by: Matt Solomon Co-authored-by: Drake Evans <31104161+DrakeEvans@users.noreply.github.com> Co-authored-by: Pascal Marco Caversaccio --- .github/workflows/ci.yml | 58 + .github/workflows/tests.yml | 27 - .gitignore | 2 +- LICENSE-APACHE | 2 +- LICENSE-MIT | 2 +- README.md | 10 +- foundry.toml | 19 + lib/ds-test | 2 +- package.json | 6 +- src/Common.sol | 13 + src/Components.sol | 13 + src/Script.sol | 48 +- src/StdAssertions.sol | 209 +++ src/StdCheats.sol | 619 +++++++++ src/StdError.sol | 15 + src/StdJson.sol | 81 +- src/StdMath.sol | 43 + src/StdStorage.sol | 327 +++++ src/StdUtils.sol | 80 ++ src/Test.sol | 1141 +---------------- src/Vm.sol | 230 ++-- src/console2.sol | 12 +- 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 +++ src/test/Script.t.sol | 20 - src/test/StdMath.t.sol | 200 --- {src/test => test}/StdAssertions.t.sol | 145 +-- {src/test => test}/StdCheats.t.sol | 151 +-- {src/test => test}/StdError.t.sol | 16 +- test/StdMath.t.sol | 197 +++ {src/test => test}/StdStorage.t.sol | 130 +- test/StdUtils.t.sol | 96 ++ .../test => test}/fixtures/broadcast.log.json | 0 36 files changed, 2553 insertions(+), 1870 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/tests.yml create mode 100644 src/Common.sol create mode 100644 src/Components.sol create mode 100644 src/StdAssertions.sol create mode 100644 src/StdCheats.sol create mode 100644 src/StdError.sol create mode 100644 src/StdMath.sol create mode 100644 src/StdStorage.sol create mode 100644 src/StdUtils.sol 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 delete mode 100644 src/test/Script.t.sol delete mode 100644 src/test/StdMath.t.sol rename {src/test => test}/StdAssertions.t.sol (89%) rename {src/test => test}/StdCheats.t.sol (64%) rename {src/test => test}/StdError.t.sol (91%) create mode 100644 test/StdMath.t.sol rename {src/test => test}/StdStorage.t.sol (72%) create mode 100644 test/StdUtils.t.sol rename {src/test => test}/fixtures/broadcast.log.json (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..9971ff9e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,58 @@ +name: CI + +on: + workflow_dispatch: + pull_request: + push: + branches: + - master + +jobs: + build: + 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: Check backward compatibility + 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: + 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: + 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 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index 8e86b251..00000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Tests -on: [push, pull_request] - -jobs: - check: - name: Foundry project - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - submodules: recursive - - - name: Install Foundry - uses: onbjerg/foundry-toolchain@v1 - with: - version: nightly - - - 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.0 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/LICENSE-APACHE b/LICENSE-APACHE index 28d22de7..cf01a499 100644 --- a/LICENSE-APACHE +++ b/LICENSE-APACHE @@ -1,4 +1,4 @@ -Copyright Contributors to forge-std +Copyright Contributors to Forge Standard Library Apache License Version 2.0, January 2004 diff --git a/LICENSE-MIT b/LICENSE-MIT index 1538ed39..28f98304 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -1,4 +1,4 @@ -Copyright Contributors to forge-std +Copyright Contributors to Forge Standard Library Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated diff --git a/README.md b/README.md index 67dc160b..cddbd82d 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Forge Standard Library • [![tests](https://github.com/brockelmore/forge-std/actions/workflows/tests.yml/badge.svg)](https://github.com/brockelmore/forge-std/actions/workflows/tests.yml) -Forge Standard Library is a collection of helpful contracts for use with [`forge` and `foundry`](https://github.com/foundry-rs/foundry). It leverages `forge`'s cheatcodes to make writing tests easier and faster, while improving the UX of cheatcodes. +Forge Standard Library is a collection of helpful contracts and libraries for use with [Forge and Foundry](https://github.com/foundry-rs/foundry). It leverages Forge's cheatcodes to make writing tests easier and faster, while improving the UX of cheatcodes. -**Learn how to use Forge Std with the [📖 Foundry Book (Forge Std Guide)](https://book.getfoundry.sh/forge/forge-std.html).** +**Learn how to use Forge-Std with the [📖 Foundry Book (Forge-Std Guide)](https://book.getfoundry.sh/forge/forge-std.html).** ## Install @@ -13,7 +13,7 @@ forge install foundry-rs/forge-std ## Contracts ### stdError -This is a helper contract for errors and reverts. In `forge`, this contract is particularly helpful for the `expectRevert` cheatcode, as it provides all compiler builtin errors. +This is a helper contract for errors and reverts. In Forge, this contract is particularly helpful for the `expectRevert` cheatcode, as it provides all compiler builtin errors. See the contract itself for all error codes. @@ -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 diff --git a/foundry.toml b/foundry.toml index 507b8bb9..4f93dbfb 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,2 +1,21 @@ [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 +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/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/package.json b/package.json index 914a361f..656de98f 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "forge-std", - "version": "0.1.0", - "description": "Forge Standard Library is a collection of helpful contracts for use with forge and foundry", + "version": "1.0.0", + "description": "Forge Standard Library is a collection of helpful contracts and libraries for use with Forge and Foundry.", "homepage": "https://book.getfoundry.sh/forge/forge-std", "bugs": "https://github.com/foundry-rs/forge-std/issues", "license": "(Apache-2.0 OR MIT)", - "author": "Contributors to forge-std", + "author": "Contributors to Forge Standard Library", "files": [ "src/*" ], diff --git a/src/Common.sol b/src/Common.sol new file mode 100644 index 00000000..a29b4847 --- /dev/null +++ b/src/Common.sol @@ -0,0 +1,13 @@ +// 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")))); + uint256 internal constant UINT256_MAX = + 115792089237316195423570985008687907853269984665640564039457584007913129639935; + + StdStorage internal stdstore; + Vm internal constant vm = Vm(VM_ADDRESS); +} diff --git a/src/Components.sol b/src/Components.sol new file mode 100644 index 00000000..6c20fff2 --- /dev/null +++ b/src/Components.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.2 <0.9.0; + +import "./console.sol"; +import "./console2.sol"; +import "./StdAssertions.sol"; +import "./StdCheats.sol"; +import "./StdError.sol"; +import "./StdJson.sol"; +import "./StdMath.sol"; +import "./StdStorage.sol"; +import "./StdUtils.sol"; +import "./Vm.sol"; diff --git a/src/Script.sol b/src/Script.sol index e1e3a513..d051169b 100644 --- a/src/Script.sol +++ b/src/Script.sol @@ -1,44 +1,14 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.6.0 <0.9.0; +pragma solidity >=0.6.2 <0.9.0; -import "./console.sol"; -import "./console2.sol"; -import "./StdJson.sol"; +import {CommonBase} from "./Common.sol"; +// forgefmt: disable-next-line +import {console, console2, StdCheatsSafe, stdJson, stdMath, StdStorage, stdStorageSafe, StdUtils, VmSafe} 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)))); - } - - function addressFromLast20Bytes(bytes32 bytesValue) internal pure returns (address) { - return address(uint160(uint256(bytesValue))); - } +abstract contract ScriptBase is CommonBase { + VmSafe internal constant vmSafe = VmSafe(VM_ADDRESS); +} - function deriveRememberKey(string memory mnemonic, uint32 index) internal returns (address who, uint256 privateKey) { - privateKey = vm.deriveKey(mnemonic, index); - who = vm.rememberKey(privateKey); - } +abstract contract Script is ScriptBase, StdCheatsSafe, StdUtils { + bool public IS_SCRIPT = true; } diff --git a/src/StdAssertions.sol b/src/StdAssertions.sol new file mode 100644 index 00000000..6926809b --- /dev/null +++ b/src/StdAssertions.sol @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.2 <0.9.0; + +import "../lib/ds-test/src/test.sol"; +import "./StdMath.sol"; + +abstract contract StdAssertions 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); + } + } + + // Legacy helper + function assertEqUint(uint256 a, uint256 b) internal virtual { + assertEq(uint256(a), uint256(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, 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); + 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, 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); + assertApproxEqRel(a, b, maxPercentDelta); + } + } +} diff --git a/src/StdCheats.sol b/src/StdCheats.sol new file mode 100644 index 00000000..240db21d --- /dev/null +++ b/src/StdCheats.sol @@ -0,0 +1,619 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.2 <0.9.0; + +pragma experimental ABIEncoderV2; + +import "./StdStorage.sol"; +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. + + struct RawTx1559 { + string[] arguments; + address contractAddress; + string contractName; + // json value name = function + string functionSig; + bytes32 hash; + // json value name = tx + RawTx1559Detail txDetail; + // json value name = type + string opcode; + } + + struct RawTx1559Detail { + AccessList[] accessList; + bytes data; + address from; + bytes gas; + bytes nonce; + address to; + bytes txType; + bytes value; + } + + struct Tx1559 { + string[] arguments; + address contractAddress; + string contractName; + string functionSig; + bytes32 hash; + Tx1559Detail txDetail; + string opcode; + } + + struct Tx1559Detail { + AccessList[] accessList; + bytes data; + address from; + uint256 gas; + uint256 nonce; + address to; + uint256 txType; + 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. + + struct TxLegacy { + string[] arguments; + address contractAddress; + string contractName; + string functionSig; + string hash; + string opcode; + TxDetailLegacy transaction; + } + + struct TxDetailLegacy { + AccessList[] accessList; + uint256 chainId; + bytes data; + address from; + uint256 gas; + uint256 gasPrice; + bytes32 hash; + uint256 nonce; + bytes1 opcode; + bytes32 r; + bytes32 s; + uint256 txType; + address to; + uint8 v; + uint256 value; + } + + struct AccessList { + address accessAddress; + bytes32[] storageKeys; + } + + // Data structures to parse Receipt objects from the broadcast artifact. + // 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 RawReceipt { + bytes32 blockHash; + bytes blockNumber; + address contractAddress; + bytes cumulativeGasUsed; + bytes effectiveGasPrice; + address from; + bytes gasUsed; + RawReceiptLog[] logs; + bytes logsBloom; + bytes status; + address to; + bytes32 transactionHash; + bytes transactionIndex; + } + + struct Receipt { + bytes32 blockHash; + uint256 blockNumber; + address contractAddress; + uint256 cumulativeGasUsed; + uint256 effectiveGasPrice; + address from; + uint256 gasUsed; + ReceiptLog[] logs; + bytes logsBloom; + uint256 status; + address to; + bytes32 transactionHash; + uint256 transactionIndex; + } + + // Data structures to parse the entire broadcast artifact, assuming the + // transactions conform to EIP1559. + + struct EIP1559ScriptArtifact { + string[] libraries; + string path; + string[] pending; + Receipt[] receipts; + uint256 timestamp; + Tx1559[] transactions; + TxReturn[] txReturns; + } + + struct RawEIP1559ScriptArtifact { + string[] libraries; + string path; + string[] pending; + RawReceipt[] receipts; + TxReturn[] txReturns; + uint256 timestamp; + RawTx1559[] transactions; + } + + struct RawReceiptLog { + // json value = address + address logAddress; + bytes32 blockHash; + bytes blockNumber; + bytes data; + bytes logIndex; + bool removed; + bytes32[] topics; + bytes32 transactionHash; + bytes transactionIndex; + bytes transactionLogIndex; + } + + struct ReceiptLog { + // json value = address + address logAddress; + bytes32 blockHash; + uint256 blockNumber; + bytes data; + uint256 logIndex; + bytes32[] topics; + uint256 transactionIndex; + uint256 transactionLogIndex; + bool removed; + } + + struct TxReturn { + string internalType; + 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); + RawEIP1559ScriptArtifact memory rawArtifact = abi.decode(parsedData, (RawEIP1559ScriptArtifact)); + EIP1559ScriptArtifact memory artifact; + artifact.libraries = rawArtifact.libraries; + artifact.path = rawArtifact.path; + artifact.timestamp = rawArtifact.timestamp; + artifact.pending = rawArtifact.pending; + artifact.txReturns = rawArtifact.txReturns; + artifact.receipts = rawToConvertedReceipts(rawArtifact.receipts); + artifact.transactions = rawToConvertedEIPTx1559s(rawArtifact.transactions); + return artifact; + } + + function rawToConvertedEIPTx1559s(RawTx1559[] memory rawTxs) internal pure virtual returns (Tx1559[] memory) { + Tx1559[] memory txs = new Tx1559[](rawTxs.length); + for (uint256 i; i < rawTxs.length; i++) { + txs[i] = rawToConvertedEIPTx1559(rawTxs[i]); + } + return txs; + } + + 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.txDetail = rawToConvertedEIP1559Detail(rawTx.txDetail); + transaction.opcode = rawTx.opcode; + return transaction; + } + + function rawToConvertedEIP1559Detail(RawTx1559Detail memory rawDetail) + internal + pure + virtual + returns (Tx1559Detail memory) + { + Tx1559Detail memory txDetail; + txDetail.data = rawDetail.data; + txDetail.from = rawDetail.from; + txDetail.to = rawDetail.to; + txDetail.nonce = bytesToUint(rawDetail.nonce); + txDetail.txType = bytesToUint(rawDetail.txType); + txDetail.value = bytesToUint(rawDetail.value); + txDetail.gas = bytesToUint(rawDetail.gas); + txDetail.accessList = rawDetail.accessList; + return txDetail; + } + + function readTx1559s(string memory path) internal virtual returns (Tx1559[] memory) { + string memory deployData = vm.readFile(path); + 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) { + string memory deployData = vm.readFile(path); + 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) { + 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, uint256 index) internal virtual returns (Receipt memory) { + string memory deployData = vm.readFile(path); + 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 pure virtual returns (Receipt[] memory) { + Receipt[] memory receipts = new Receipt[](rawReceipts.length); + for (uint256 i; i < rawReceipts.length; i++) { + receipts[i] = rawToConvertedReceipt(rawReceipts[i]); + } + return receipts; + } + + 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.gasUsed = bytesToUint(rawReceipt.gasUsed); + receipt.status = bytesToUint(rawReceipt.status); + receipt.transactionIndex = bytesToUint(rawReceipt.transactionIndex); + receipt.blockNumber = bytesToUint(rawReceipt.blockNumber); + receipt.logs = rawToConvertedReceiptLogs(rawReceipt.logs); + receipt.logsBloom = rawReceipt.logsBloom; + receipt.transactionHash = rawReceipt.transactionHash; + return receipt; + } + + function rawToConvertedReceiptLogs(RawReceiptLog[] memory rawLogs) + internal + pure + virtual + returns (ReceiptLog[] memory) + { + ReceiptLog[] memory logs = new ReceiptLog[](rawLogs.length); + 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); + logs[i].data = rawLogs[i].data; + logs[i].logIndex = bytesToUint(rawLogs[i].logIndex); + logs[i].topics = rawLogs[i].topics; + logs[i].transactionIndex = bytesToUint(rawLogs[i].transactionIndex); + logs[i].transactionLogIndex = bytesToUint(rawLogs[i].transactionLogIndex); + 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) { + 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), "StdCheats 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), "StdCheats 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), "StdCheats 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), "StdCheats 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) { + 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) { + (addr,) = makeAddrAndKey(name); + } + + 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 (uint256 i = 0; i < b.length; i++) { + number = number + uint256(uint8(b[i])) * (2 ** (8 * (b.length - (i + 1)))); + } + return number; + } +} + +// Wrappers around cheatcodes to avoid footguns +abstract contract StdCheats is StdCheatsSafe { + 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); + } + } +} diff --git a/src/StdError.sol b/src/StdError.sol new file mode 100644 index 00000000..a302191f --- /dev/null +++ b/src/StdError.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.2 <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); +} diff --git a/src/StdJson.sol b/src/StdJson.sol index c4ad825d..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"))))); - Vm 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 new file mode 100644 index 00000000..459523bd --- /dev/null +++ b/src/StdMath.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.2 <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/StdStorage.sol b/src/StdStorage.sol new file mode 100644 index 00000000..89710e8d --- /dev/null +++ b/src/StdStorage.sol @@ -0,0 +1,327 @@ +// SPDX-License-Identifier: MIT +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; + bytes32[] _keys; + bytes4 _sig; + uint256 _depth; + address _target; + bytes32 _set; +} + +library stdStorageSafe { + 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) { + 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 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, uint256 offset) private pure returns (bytes32) { + bytes32 out; + + uint256 max = b.length > 32 ? 32 : b.length; + 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) { + 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); + } + + 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)))); + } + + 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_bytes32(StdStorage storage self) internal returns (bytes32) { + return stdStorageSafe.read_bytes32(self); + } + + function read_bool(StdStorage storage self) internal returns (bool) { + return stdStorageSafe.read_bool(self); + } + + function read_address(StdStorage storage self) internal returns (address) { + return stdStorageSafe.read_address(self); + } + + function read_uint(StdStorage storage self) internal returns (uint256) { + return stdStorageSafe.read_uint(self); + } + + function read_int(StdStorage storage self) internal returns (int256) { + return stdStorageSafe.read_int(self); + } + + // Private function so needs to be copied over + function bytesToBytes32(bytes memory b, uint256 offset) private pure returns (bytes32) { + bytes32 out; + + uint256 max = b.length > 32 ? 32 : b.length; + 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) { + 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/StdUtils.sol b/src/StdUtils.sol new file mode 100644 index 00000000..b52fa230 --- /dev/null +++ b/src/StdUtils.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.2 <0.9.0; + +import "./console2.sol"; + +abstract contract StdUtils { + uint256 private constant UINT256_MAX = + 115792089237316195423570985008687907853269984665640564039457584007913129639935; + + 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 + // 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; + + uint256 size = max - min + 1; + + // 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. + if (x > max) { + uint256 diff = x - max; + uint256 rem = diff % size; + if (rem == 0) return max; + result = min + rem - 1; + } else if (x < max) { + uint256 diff = min - x; + uint256 rem = diff % size; + if (rem == 0) return min; + result = max - rem + 1; + } + } + + function bound(uint256 x, uint256 min, uint256 max) internal view virtual returns (uint256 result) { + result = _bound(x, min, max); + 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)))); + + // 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))) + ); + } + + 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))); + } +} diff --git a/src/Test.sol b/src/Test.sol index ef18bb66..09eca870 100644 --- a/src/Test.sol +++ b/src/Test.sol @@ -1,1138 +1,11 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.6.0 <0.9.0; -pragma experimental ABIEncoderV2; +pragma solidity >=0.6.2 <0.9.0; -import "./Script.sol"; -import "ds-test/test.sol"; +import {CommonBase} from "./Common.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"; -// Wrappers around Cheatcodes to avoid footguns -abstract contract Test is DSTest, Script { - using stdStorage for StdStorage; +abstract contract TestBase is CommonBase {} - uint256 internal constant UINT256_MAX = - 115792089237316195423570985008687907853269984665640564039457584007913129639935; - - 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); - } - - // creates a labeled address and the corresponding private key - function makeAddrAndKey(string memory name) internal 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 returns(address addr) { - (addr,) = makeAddrAndKey(name); - } - - // 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 assertEqUint(uint256 a, uint256 b) internal { - assertEq(uint256(a), uint256(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, 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); - 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); - } - } - - /*////////////////////////////////////////////////////////////// - JSON PARSING - //////////////////////////////////////////////////////////////*/ - - // 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 { - string[] arguments; - address contractAddress; - string contractName; - // json value name = function - string functionSig; - bytes32 hash; - // json value name = tx - RawTx1559Detail txDetail; - // json value name = type - string opcode; - } - - struct RawTx1559Detail { - AccessList[] accessList; - bytes data; - address from; - bytes gas; - bytes nonce; - address to; - bytes txType; - bytes value; - } - - struct Tx1559 { - string[] arguments; - address contractAddress; - string contractName; - string functionSig; - bytes32 hash; - Tx1559Detail txDetail; - string opcode; - } - - struct Tx1559Detail { - AccessList[] accessList; - bytes data; - address from; - uint256 gas; - uint256 nonce; - address to; - uint256 txType; - 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. - - struct TxLegacy{ - string[] arguments; - address contractAddress; - string contractName; - string functionSig; - string hash; - string opcode; - TxDetailLegacy transaction; - } - - struct TxDetailLegacy{ - AccessList[] accessList; - uint256 chainId; - bytes data; - address from; - uint256 gas; - uint256 gasPrice; - bytes32 hash; - uint256 nonce; - bytes1 opcode; - bytes32 r; - bytes32 s; - uint256 txType; - address to; - uint8 v; - uint256 value; - } - - struct AccessList{ - address accessAddress; - bytes32[] storageKeys; - } - - // Data structures to parse Receipt objects from the broadcast artifact. - // 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 RawReceipt { - bytes32 blockHash; - bytes blockNumber; - address contractAddress; - bytes cumulativeGasUsed; - bytes effectiveGasPrice; - address from; - bytes gasUsed; - RawReceiptLog[] logs; - bytes logsBloom; - bytes status; - address to; - bytes32 transactionHash; - bytes transactionIndex; - } - - struct Receipt { - bytes32 blockHash; - uint256 blockNumber; - address contractAddress; - uint256 cumulativeGasUsed; - uint256 effectiveGasPrice; - address from; - uint256 gasUsed; - ReceiptLog[] logs; - bytes logsBloom; - uint256 status; - address to; - bytes32 transactionHash; - uint256 transactionIndex; - } - - // Data structures to parse the entire broadcast artifact, assuming the - // transactions conform to EIP1559. - - struct EIP1559ScriptArtifact { - string[] libraries; - string path; - string[] pending; - Receipt[] receipts; - uint256 timestamp; - Tx1559[] transactions; - TxReturn[] txReturns; - } - - struct RawEIP1559ScriptArtifact { - string[] libraries; - string path; - string[] pending; - RawReceipt[] receipts; - TxReturn[] txReturns; - uint256 timestamp; - RawTx1559[] transactions; - } - - struct RawReceiptLog { - // json value = address - address logAddress; - bytes32 blockHash; - bytes blockNumber; - bytes data; - bytes logIndex; - bool removed; - bytes32[] topics; - bytes32 transactionHash; - bytes transactionIndex; - bytes transactionLogIndex; - } - - struct ReceiptLog { - // json value = address - address logAddress; - bytes32 blockHash; - uint256 blockNumber; - bytes data; - uint256 logIndex; - bytes32[] topics; - uint256 transactionIndex; - uint256 transactionLogIndex; - bool removed; - } - - struct TxReturn { - string internalType; - string value; - } - - - function readEIP1559ScriptArtifact(string memory path) - internal - returns(EIP1559ScriptArtifact memory) - { - string memory data = vm.readFile(path); - bytes memory parsedData = vm.parseJson(data); - RawEIP1559ScriptArtifact memory rawArtifact = abi.decode(parsedData, (RawEIP1559ScriptArtifact)); - EIP1559ScriptArtifact memory artifact; - artifact.libraries = rawArtifact.libraries; - artifact.path = rawArtifact.path; - artifact.timestamp = rawArtifact.timestamp; - artifact.pending = rawArtifact.pending; - artifact.txReturns = rawArtifact.txReturns; - artifact.receipts = rawToConvertedReceipts(rawArtifact.receipts); - artifact.transactions = rawToConvertedEIPTx1559s(rawArtifact.transactions); - return artifact; - } - - function rawToConvertedEIPTx1559s(RawTx1559[] memory rawTxs) - internal pure - returns (Tx1559[] memory) - { - Tx1559[] memory txs = new Tx1559[](rawTxs.length); - for (uint i; i < rawTxs.length; i++) { - txs[i] = rawToConvertedEIPTx1559(rawTxs[i]); - } - return txs; - } - - function rawToConvertedEIPTx1559(RawTx1559 memory rawTx) - internal pure - returns (Tx1559 memory) - { - Tx1559 memory transaction; - transaction.arguments = rawTx.arguments; - transaction.contractName = rawTx.contractName; - transaction.functionSig = rawTx.functionSig; - transaction.hash= rawTx.hash; - transaction.txDetail = rawToConvertedEIP1559Detail(rawTx.txDetail); - transaction.opcode= rawTx.opcode; - return transaction; - } - - function rawToConvertedEIP1559Detail(RawTx1559Detail memory rawDetail) - internal pure - returns (Tx1559Detail memory) - { - Tx1559Detail memory txDetail; - txDetail.data = rawDetail.data; - txDetail.from = rawDetail.from; - txDetail.to = rawDetail.to; - txDetail.nonce = bytesToUint(rawDetail.nonce); - txDetail.txType = bytesToUint(rawDetail.txType); - txDetail.value = bytesToUint(rawDetail.value); - txDetail.gas = bytesToUint(rawDetail.gas); - txDetail.accessList = rawDetail.accessList; - return txDetail; - - } - - function readTx1559s(string memory path) - internal - returns (Tx1559[] memory) - { - string memory deployData = vm.readFile(path); - 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 - 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); - RawTx1559 memory rawTx = abi.decode(parsedDeployData, (RawTx1559)); - return rawToConvertedEIPTx1559(rawTx); - } - - - // Analogous to readTransactions, but for receipts. - function readReceipts(string memory path) - internal - 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 - returns (Receipt memory) - { - string memory deployData = vm.readFile(path); - 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 pure - returns(Receipt[] memory) - { - Receipt[] memory receipts = new Receipt[](rawReceipts.length); - for (uint i; i < rawReceipts.length; i++) { - receipts[i] = rawToConvertedReceipt(rawReceipts[i]); - } - return receipts; - } - - function rawToConvertedReceipt(RawReceipt memory rawReceipt) - internal pure - 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.gasUsed = bytesToUint(rawReceipt.gasUsed); - receipt.status = bytesToUint(rawReceipt.status); - receipt.transactionIndex = bytesToUint(rawReceipt.transactionIndex); - receipt.blockNumber = bytesToUint(rawReceipt.blockNumber); - receipt.logs = rawToConvertedReceiptLogs(rawReceipt.logs); - receipt.logsBloom = rawReceipt.logsBloom; - receipt.transactionHash = rawReceipt.transactionHash; - return receipt; - } - - function rawToConvertedReceiptLogs(RawReceiptLog[] memory rawLogs) - internal pure - returns (ReceiptLog[] memory) - { - ReceiptLog[] memory logs = new ReceiptLog[](rawLogs.length); - for (uint i; i < rawLogs.length; i++) { - logs[i].logAddress = rawLogs[i].logAddress; - logs[i].blockHash = rawLogs[i].blockHash; - logs[i].blockNumber = bytesToUint(rawLogs[i].blockNumber); - logs[i].data = rawLogs[i].data; - logs[i].logIndex = bytesToUint(rawLogs[i].logIndex); - logs[i].topics = rawLogs[i].topics; - logs[i].transactionIndex = bytesToUint(rawLogs[i].transactionIndex); - logs[i].transactionLogIndex = bytesToUint(rawLogs[i].transactionLogIndex); - logs[i].removed = rawLogs[i].removed; - } - return logs; - - } - - function bytesToUint(bytes memory b) internal 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)))); - } - return number; - } - -} - -/*////////////////////////////////////////////////////////////////////////// - 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; -} - -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, StdAssertions, StdCheats, StdUtils {} diff --git a/src/Vm.sol b/src/Vm.sol index fa375db0..5a24d1cf 100644 --- a/src/Vm.sol +++ b/src/Vm.sol @@ -1,35 +1,22 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.6.0 <0.9.0; +pragma solidity >=0.6.2 <0.9.0; + pragma experimental ABIEncoderV2; -interface Vm { +interface VmSafe { struct Log { bytes32[] topics; bytes data; } - // Sets block.timestamp (newTimestamp) - function warp(uint256) external; - // Sets block.height (newHeight) - function roll(uint256) external; - // Sets block.basefee (newBasefee) - function fee(uint256) external; - // Sets block.difficulty (newDifficulty) - function difficulty(uint256) external; - // Sets block.chainid - function chainId(uint256) external; // Loads a storage slot from an address (who, slot) - function load(address,bytes32) external returns (bytes32); - // Stores a value to an address' storage slot, (who, slot, value) - function store(address,bytes32,bytes32) external; + 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 function getNonce(address) external returns (uint64); - // Sets the nonce of an account; must be higher than the current nonce of the account - function setNonce(address, uint64) external; // Performs a foreign function call via the terminal, (stringInputs) => (result) function ffi(string[] calldata) external returns (bytes memory); // Sets environment variables, (name, value) @@ -50,58 +37,16 @@ interface Vm { 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); - // Sets the *next* call's msg.sender to be the input address - function prank(address) external; - // 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; - // 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; - // Resets subsequent calls' msg.sender to be `address(this)` - function stopPrank() external; - // Sets an address' balance, (who, newBalance) - function deal(address, uint256) external; - // Sets an address' code, (who, newCode) - function etch(address, bytes calldata) external; - // 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; - // 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; - // 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; - // 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; - // Expects a call to an address with the specified msg.value and calldata - function expectCall(address,uint256,bytes calldata) external; // Gets the _creation_ bytecode from an artifact file. Takes in the relative path to the json file function getCode(string calldata) external returns (bytes memory); // Gets the _deployed_ bytecode from an artifact file. Takes in the relative path to the json file function getDeployedCode(string calldata) external returns (bytes memory); // Labels an address in call traces function label(address, string 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; // 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 @@ -116,7 +61,6 @@ interface Vm { function startBroadcast(uint256) 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 the entire content of file as binary. Path is relative to the project root. (path) => (data) @@ -143,15 +87,13 @@ interface Vm { // - 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); - + 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); @@ -159,37 +101,127 @@ interface Vm { function parseInt(string calldata) external returns (int256); function parseBytes32(string calldata) external returns (bytes32); function parseBool(string calldata) external returns (bool); - // 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); + // Adds a private key to the local forge wallet and returns the address + function rememberKey(uint256) external returns (address); + // 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); + // 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); + // Note: + // ---- + // In case the returned value is a JSON object, it's encoded as a ABI-encoded tuple. As JSON objects + // don't have the notion of ordered, but tuples do, they JSON object is encoded with it's fields ordered in + // ALPHABETICAL ordser. That means that in order to succesfully decode the tuple, we need to define a tuple that + // encodes the fields in the same order, which is alphabetical. In the case of Solidity structs, they are encoded + // as tuples, with the attributes in the order in which they are defined. + // For example: json = { 'a': 1, 'b': 0xa4tb......3xs} + // a: uint256 + // b: address + // To decode that json, we need to define a struct or a tuple as follows: + // 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 { + // Sets block.timestamp (newTimestamp) + function warp(uint256) external; + // Sets block.height (newHeight) + function roll(uint256) external; + // Sets block.basefee (newBasefee) + function fee(uint256) external; + // Sets block.difficulty (newDifficulty) + function difficulty(uint256) external; + // Sets block.chainid + function chainId(uint256) external; + // Stores a value to an address' storage slot, (who, slot, value) + 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 + function prank(address) external; + // 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; + // 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; + // Resets subsequent calls' msg.sender to be `address(this)` + function stopPrank() external; + // Sets an address' balance, (who, newBalance) + function deal(address, uint256) external; + // Sets an address' code, (who, newCode) + function etch(address, bytes calldata) external; + // Expects an error on next call + function expectRevert(bytes calldata) external; + function expectRevert(bytes4) external; + function expectRevert() external; + // 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; + // 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; + // 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; + // 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; + // Expects a call to an address with the specified msg.value and calldata + function expectCall(address, uint256, bytes calldata) external; + // Sets block.coinbase (who) + function coinbase(address) external; // 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; @@ -200,7 +232,6 @@ interface Vm { function rollFork(uint256 forkId, uint256 blockNumber) external; // Updates the given fork to block number of the given transaction and replays all transaction mined before it in the block function rollFork(uint256 forkId, bytes32 transaction) external; - // Marks that the account(s) should use persistent storage across fork swaps in a multifork setup // Meaning, changes made to the state of this account will be kept when switching forks function makePersistent(address) external; @@ -212,51 +243,10 @@ interface Vm { function revokePersistent(address[] calldata) external; // Returns true if the account is marked as persistent function isPersistent(address) external returns (bool); - // In forking mode, explicitly grant the given address cheatcode access function allowCheatcodes(address) external; - // Fetches the given transaction from the active fork and executes it on the current state 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); - - // 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); - // Adds a private key to the local forge wallet and returns the address - function rememberKey(uint256) external returns (address); - - // parseJson - - // 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); - - // 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); - - // Note: - // ---- - // In case the returned value is a JSON object, it's encoded as a ABI-encoded tuple. As JSON objects - // don't have the notion of ordered, but tuples do, they JSON object is encoded with it's fields ordered in - // ALPHABETICAL ordser. That means that in order to succesfully decode the tuple, we need to define a tuple that - // encodes the fields in the same order, which is alphabetical. In the case of Solidity structs, they are encoded - // as tuples, with the attributes in the order in which they are defined. - // For example: json = { 'a': 1, 'b': 0xa4tb......3xs} - // a: uint256 - // b: address - // To decode that json, we need to define a struct or a tuple as follows: - // 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. - } diff --git a/src/console2.sol b/src/console2.sol index 2edfda5b..8cd6e219 100644 --- a/src/console2.sol +++ b/src/console2.sol @@ -1,18 +1,18 @@ // 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 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`. +/// Reference: https://github.com/NomicFoundation/hardhat/issues/2178 library console2 { address constant CONSOLE_ADDRESS = address(0x000000000000000000636F6e736F6c652e6c6f67); 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) 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); +} diff --git a/src/test/Script.t.sol b/src/test/Script.t.sol deleted file mode 100644 index b26db7f4..00000000 --- a/src/test/Script.t.sol +++ /dev/null @@ -1,20 +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); - } - - function testDeriveRememberKey() external { - string memory mnemonic = "test test test test test test test test test test test junk"; - - (address deployer, uint256 privateKey) = deriveRememberKey(mnemonic, 0); - assertEq(deployer, 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); - assertEq(privateKey, 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80); - } -} \ No newline at end of file diff --git a/src/test/StdMath.t.sol b/src/test/StdMath.t.sol deleted file mode 100644 index 9d09b810..00000000 --- a/src/test/StdMath.t.sol +++ /dev/null @@ -1,200 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0 <0.9.0; - -import "../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(type(int256).min), (type(uint256).max >> 1) + 1); - assertEq(stdMath.abs(type(int256).max), (type(uint256).max >> 1)); - } - - function testGetAbs_Fuzz(int256 a) external { - uint256 manualAbs = getAbs(a); - - uint256 abs = stdMath.abs(a); - - assertEq(abs, manualAbs); - } - - 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); - } - - function testGetDelta_Uint_Fuzz(uint256 a, uint256 b) external { - uint256 manualDelta; - if (a > b) { - manualDelta = a - b; - } else { - manualDelta = b - a; - } - - uint256 delta = stdMath.delta(a, b); - - assertEq(delta, manualDelta); - } - - 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); - } - - 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 manualDelta; - if ((a >= 0 && b >= 0) || (a < 0 && b < 0)) { - manualDelta = absDelta; - } - // (a < 0 && b >= 0) || (a >= 0 && b < 0) - else { - manualDelta = absA + absB; - } - - uint256 delta = stdMath.delta(a, b); - - assertEq(delta, manualDelta); - } - - 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); - - vm.expectRevert(stdError.divisionError); - stdMath.percentDelta(uint256(1), 0); - } - - function testGetPercentDelta_Uint_Fuzz(uint192 a, uint192 b) external { - vm.assume(b != 0); - uint256 manualDelta; - if (a > b) { - manualDelta = a - b; - } else { - manualDelta = b - a; - } - - uint256 manualPercentDelta = manualDelta * 1e18 / b; - uint256 percentDelta = stdMath.percentDelta(a, b); - - assertEq(percentDelta, manualPercentDelta); - } - - 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); - - vm.expectRevert(stdError.divisionError); - stdMath.percentDelta(int256(1), 0); - } - - function testGetPercentDelta_Int_Fuzz(int192 a, int192 b) external { - vm.assume(b != 0); - uint256 absA = getAbs(a); - uint256 absB = getAbs(b); - uint256 absDelta = absA > absB - ? absA - absB - : absB - absA; - - uint256 manualDelta; - if ((a >= 0 && b >= 0) || (a < 0 && b < 0)) { - manualDelta = absDelta; - } - // (a < 0 && b >= 0) || (a >= 0 && b < 0) - else { - manualDelta = absA + absB; - } - - uint256 manualPercentDelta = manualDelta * 1e18 / absB; - uint256 percentDelta = stdMath.percentDelta(a, b); - - assertEq(percentDelta, manualPercentDelta); - } - - /*////////////////////////////////////////////////////////////////////////// - HELPERS - //////////////////////////////////////////////////////////////////////////*/ - - function getAbs(int256 a) private pure returns (uint256) { - if (a < 0) - return a == type(int256).min ? uint256(type(int256).max) + 1 : uint256(-a); - - return uint256(a); - } -} diff --git a/src/test/StdAssertions.t.sol b/test/StdAssertions.t.sol similarity index 89% rename from src/test/StdAssertions.t.sol rename to test/StdAssertions.t.sol index 3f26f764..4d5827d6 100644 --- a/src/test/StdAssertions.t.sol +++ b/test/StdAssertions.t.sol @@ -1,10 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.7.0 <0.9.0; -import "../Test.sol"; +import "../src/Test.sol"; -contract StdAssertionsTest is Test -{ +contract StdAssertionsTest is Test { string constant CUSTOM_ERROR = "guh!"; bool constant EXPECT_PASS = false; @@ -12,17 +11,6 @@ contract StdAssertionsTest is Test TestTest t = new TestTest(); - /*////////////////////////////////////////////////////////////////////////// - ASSERT_EQ(UINT) - //////////////////////////////////////////////////////////////////////////*/ - - function testAssertions() public { - assertEqUint(uint32(1), uint32(1)); - assertEqUint(uint64(1), uint64(1)); - assertEqUint(uint96(1), uint96(1)); - assertEqUint(uint128(1), uint128(1)); - } - /*////////////////////////////////////////////////////////////////////////// FAIL(STRING) //////////////////////////////////////////////////////////////////////////*/ @@ -178,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); @@ -216,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); @@ -308,6 +294,20 @@ contract StdAssertionsTest is Test t._assertEq(a, b, CUSTOM_ERROR, EXPECT_FAIL); } + /*////////////////////////////////////////////////////////////////////////// + ASSERT_EQ(UINT) + //////////////////////////////////////////////////////////////////////////*/ + + function testAssertEqUint() public { + assertEqUint(uint8(1), uint128(1)); + assertEqUint(uint64(2), uint64(2)); + } + + function testFailAssertEqUint() public { + assertEqUint(uint64(1), uint96(2)); + assertEqUint(uint160(3), uint160(4)); + } + /*////////////////////////////////////////////////////////////////////////// APPROX_EQ_ABS(UINT) //////////////////////////////////////////////////////////////////////////*/ @@ -445,9 +445,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); _; @@ -491,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); } @@ -511,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/src/test/StdCheats.t.sol b/test/StdCheats.t.sol similarity index 64% rename from src/test/StdCheats.t.sol rename to test/StdCheats.t.sol index 05e240a2..0580c2d7 100644 --- a/src/test/StdCheats.t.sol +++ b/test/StdCheats.t.sol @@ -1,8 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.7.0 <0.9.0; -import "../Test.sol"; -import "../StdJson.sol"; +import "../src/StdCheats.sol"; +import "../src/Test.sol"; +import "../src/StdJson.sol"; contract StdCheatsTest is Test { Bar test; @@ -67,7 +68,7 @@ contract StdCheatsTest is Test { } function testMakeAddrEquivalence() public { - (address addr, ) = makeAddrAndKey("1337"); + (address addr,) = makeAddrAndKey("1337"); assertEq(makeAddr("1337"), addr); } @@ -102,71 +103,26 @@ 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)))); + 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 that 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); + 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)))); - assertEq(deployed.balance, 1 ether); + address deployed = deployCode("StdCheats.t.sol:Bar", 1 ether); + assertEq(string(getCode(deployed)), string(getCode(address(test)))); + assertEq(deployed.balance, 1 ether); } // We need this so we can call "this.deployCode" rather than "deployCode" directly @@ -175,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"); } @@ -196,23 +152,34 @@ contract StdCheatsTest is Test { } } + function testDeriveRememberKey() public { + string memory mnemonic = "test test test test test test test test test test test junk"; + + (address deployer, uint256 privateKey) = deriveRememberKey(mnemonic, 0); + assertEq(deployer, 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); + assertEq(privateKey, 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80); + } + function testBytesToUint() public { - assertEq(3, bytesToUint(hex'03')); - assertEq(2, bytesToUint(hex'02')); - assertEq(255, bytesToUint(hex'ff')); - assertEq(29625, bytesToUint(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 { 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)); 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); @@ -221,36 +188,69 @@ 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); + transaction; } 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); + transactions; } function testReadReceipt() public { string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/src/test/fixtures/broadcast.log.json"); - uint index = 5; + string memory path = string.concat(root, "/test/fixtures/broadcast.log.json"); + uint256 index = 5; Receipt memory receipt = readReceipt(path, index); - assertEq(receipt.logsBloom, - hex"00000000000800000000000000000010000000000000000000000000000180000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100"); + assertEq( + receipt.logsBloom, + hex"00000000000800000000000000000010000000000000000000000000000180000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100" + ); } 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); + receipts; + } + + function bytesToUint_test(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; + } + + 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 { - constructor() { + constructor() payable { /// `DEAL` STDCHEAT totalSupply = 10000e18; balanceOf[address(this)] = totalSupply; @@ -260,17 +260,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; } @@ -279,4 +281,3 @@ contract RevertingContract { revert(); } } - diff --git a/src/test/StdError.t.sol b/test/StdError.t.sol similarity index 91% rename from src/test/StdError.t.sol rename to test/StdError.t.sol index 0d6601e4..ccd3efac 100644 --- a/src/test/StdError.t.sol +++ b/test/StdError.t.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.10 <0.9.0; +pragma solidity >=0.8.0 <0.9.0; -import "../Test.sol"; +import "../src/StdError.sol"; +import "../src/Test.sol"; contract StdErrorsTest is Test { ErrorsTest test; @@ -59,17 +60,10 @@ contract StdErrorsTest is Test { vm.expectRevert(stdError.zeroVarError); test.intern(); } - - function testExpectLowLvl() public { - vm.expectRevert(stdError.lowLevelError); - test.someArr(0); - } } contract ErrorsTest { - enum T { - T1 - } + enum T {T1} uint256[] public someArr; bytes someBytes; @@ -112,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 new file mode 100644 index 00000000..95037ea5 --- /dev/null +++ b/test/StdMath.t.sol @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0 <0.9.0; + +import "../src/StdMath.sol"; +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(type(int256).min), (type(uint256).max >> 1) + 1); + assertEq(stdMath.abs(type(int256).max), (type(uint256).max >> 1)); + } + + function testGetAbs_Fuzz(int256 a) external { + uint256 manualAbs = getAbs(a); + + uint256 abs = stdMath.abs(a); + + assertEq(abs, manualAbs); + } + + 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); + } + + function testGetDelta_Uint_Fuzz(uint256 a, uint256 b) external { + uint256 manualDelta; + if (a > b) { + manualDelta = a - b; + } else { + manualDelta = b - a; + } + + uint256 delta = stdMath.delta(a, b); + + assertEq(delta, manualDelta); + } + + 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); + } + + 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 manualDelta; + if ((a >= 0 && b >= 0) || (a < 0 && b < 0)) { + manualDelta = absDelta; + } + // (a < 0 && b >= 0) || (a >= 0 && b < 0) + else { + manualDelta = absA + absB; + } + + uint256 delta = stdMath.delta(a, b); + + assertEq(delta, manualDelta); + } + + 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); + + vm.expectRevert(stdError.divisionError); + stdMath.percentDelta(uint256(1), 0); + } + + function testGetPercentDelta_Uint_Fuzz(uint192 a, uint192 b) external { + vm.assume(b != 0); + uint256 manualDelta; + if (a > b) { + manualDelta = a - b; + } else { + manualDelta = b - a; + } + + uint256 manualPercentDelta = manualDelta * 1e18 / b; + uint256 percentDelta = stdMath.percentDelta(a, b); + + assertEq(percentDelta, manualPercentDelta); + } + + 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); + + vm.expectRevert(stdError.divisionError); + stdMath.percentDelta(int256(1), 0); + } + + function testGetPercentDelta_Int_Fuzz(int192 a, int192 b) external { + vm.assume(b != 0); + uint256 absA = getAbs(a); + uint256 absB = getAbs(b); + uint256 absDelta = absA > absB ? absA - absB : absB - absA; + + uint256 manualDelta; + if ((a >= 0 && b >= 0) || (a < 0 && b < 0)) { + manualDelta = absDelta; + } + // (a < 0 && b >= 0) || (a >= 0 && b < 0) + else { + manualDelta = absA + absB; + } + + uint256 manualPercentDelta = manualDelta * 1e18 / absB; + uint256 percentDelta = stdMath.percentDelta(a, b); + + assertEq(percentDelta, manualPercentDelta); + } + + /*////////////////////////////////////////////////////////////////////////// + HELPERS + //////////////////////////////////////////////////////////////////////////*/ + + function getAbs(int256 a) private pure returns (uint256) { + if (a < 0) { + return a == type(int256).min ? uint256(type(int256).max) + 1 : uint256(-a); + } + + return uint256(a); + } +} diff --git a/src/test/StdStorage.t.sol b/test/StdStorage.t.sol similarity index 72% rename from src/test/StdStorage.t.sol rename to test/StdStorage.t.sol index 6e238d04..d4c563a0 100644 --- a/src/test/StdStorage.t.sol +++ b/test/StdStorage.t.sol @@ -1,12 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.7.0 <0.9.0; -import "../Test.sol"; +import "../src/StdStorage.sol"; +import "../src/Test.sol"; contract StdStorageTest is Test { using stdStorage for StdStorage; - StorageTest test; + StorageTest internal test; function setUp() public { test = new StorageTest(); @@ -31,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); @@ -160,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); @@ -168,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 { @@ -193,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))); } @@ -212,7 +179,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(); @@ -271,10 +238,8 @@ contract StorageTest { uint248 public tA; bool public tB; - bool public tC = false; - uint248 public tD = 1; - + uint248 public tD = 1; struct UnpackedStruct { uint256 a; @@ -289,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(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/test/StdUtils.t.sol b/test/StdUtils.t.sol new file mode 100644 index 00000000..4782d82b --- /dev/null +++ b/test/StdUtils.t.sol @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.7.0 <0.9.0; + +import "../src/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), 174); + assertEq(bound(300, 2800, 3200), 3107); + assertEq(bound(9999, 1337, 6666), 4669); + } + + 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)); + } + + function testBound_EdgeCoverage() public { + 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); + } + + 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); + + uint256 result = bound(num, min, max); + + assertGe(result, min); + assertLe(result, max); + assertEq(result, bound(result, min, max)); + if (num >= min && num <= max) assertEq(result, num); + } + + 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("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("StdUtils 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); + } + + function testAssumeNoPrecompilesL1(address addr) external { + assumeNoPrecompiles(addr, stdChains.Mainnet.chainId); + assertTrue(addr < address(1) || addr > address(9)); + } +} 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