├── .gitmodules ├── funding.json ├── credits.txt ├── package.json ├── test ├── utils │ ├── forge-std │ │ ├── Script.sol │ │ └── Test.sol │ ├── mocks │ │ ├── MockERC1271Malicious.sol │ │ └── MockERC1271Wallet.sol │ └── TestPlus.sol └── Multicaller.t.sol ├── .gitignore ├── foundry.toml ├── .github ├── pull_request_template.md └── workflows │ ├── ci-all-via-ir.yml │ └── ci.yml ├── LICENSE.txt ├── .gas-snapshot ├── audits ├── 0xth0mas_review.md └── 0xphaze_review.md ├── src ├── LibMulticaller.sol ├── MulticallerWithSender.sol ├── Multicaller.sol ├── MulticallerEtcher.sol └── MulticallerWithSigner.sol ├── README.md └── API.md /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /funding.json: -------------------------------------------------------------------------------- 1 | { 2 | "opRetro": { 3 | "projectId": "0xfc54b4a9c537be25238cba3f05100b733aeee83a3eb3fdae60d651b3b913390c" 4 | } 5 | } -------------------------------------------------------------------------------- /credits.txt: -------------------------------------------------------------------------------- 1 | Many thanks to the following for their reviews and inputs on Multicaller: 2 | 3 | - MathisGD 4 | - DaejunPark 5 | - atarpara 6 | - moodlezoup 7 | - ncitron 8 | - a16z 9 | - soundxyz 10 | - 0age 11 | - philogy 12 | - pcaversaccio 13 | - 0xdineshkumarsm 14 | - z0r0z 15 | - 0xPhaze 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multicaller", 3 | "license": "MIT", 4 | "version": "1.3.2", 5 | "description": "Efficient multicaller contract", 6 | "files": [ 7 | "src/**/*.sol" 8 | ], 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/vectorized/multicaller.git" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/utils/forge-std/Script.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.0 <0.9.0; 3 | 4 | import "./Vm.sol"; 5 | 6 | abstract contract Script { 7 | bool public IS_SCRIPT = true; 8 | 9 | /// @dev `address(bytes20(uint160(uint256(keccak256("hevm cheat code")))))`. 10 | Vm public constant vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # NodeJS files 2 | node_modules/ 3 | coverage 4 | coverage.json 5 | typechain 6 | 7 | # Hardhat files 8 | cache 9 | artifacts 10 | 11 | cache/ 12 | out/ 13 | 14 | # Ignore Environment Variables! 15 | .env 16 | .env.prod 17 | 18 | # Ignore all vscode settings 19 | .vscode/ 20 | 21 | # Ignore flattened files 22 | flattened.txt 23 | 24 | broadcast 25 | 26 | # Coverage 27 | lcov.info 28 | 29 | # Build files for CREATE2 deployments 30 | /multicaller 31 | /multicaller_with_sender 32 | /multicaller_with_signer 33 | .tmp 34 | salts.txt 35 | -------------------------------------------------------------------------------- /test/utils/mocks/MockERC1271Malicious.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | /// @dev WARNING! This mock is strictly intended for testing purposes only. 5 | /// Do NOT copy anything here into production code unless you really know what you are doing. 6 | contract MockERC1271Malicious { 7 | function isValidSignature(bytes32, bytes calldata) external pure returns (bytes4) { 8 | /// @solidity memory-safe-assembly 9 | assembly { 10 | mstore(0, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) 11 | return(0, 32) 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | # Foundry Configuration File 2 | # Default definitions: https://github.com/gakonst/foundry/blob/b7917fa8491aedda4dd6db53fbb206ea233cd531/config/src/lib.rs#L782 3 | # See more config options at: https://github.com/gakonst/foundry/tree/master/config 4 | 5 | # The Default Profile 6 | [profile.default] 7 | solc_version = '0.8.19' 8 | auto_detect_solc = false 9 | optimizer = true 10 | optimizer_runs = 1_000 11 | gas_limit = 100_000_000 # ETH is 30M, but we use a higher value. 12 | remappings = [ 13 | "forge-std=test/utils/forge-std/" 14 | ] 15 | 16 | [fmt] 17 | line_length = 100 # While we allow up to 120, we lint at 100 for readability. 18 | 19 | [profile.default.fuzz] 20 | runs = 256 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | Describe the changes made in your pull request here. 4 | 5 | ## Checklist 6 | 7 | Ensure you completed **all of the steps** below before submitting your pull request: 8 | 9 | - [ ] Ran `forge fmt`? 10 | - [ ] Ran `forge snapshot`? 11 | - [ ] Ran `forge test`? 12 | 13 | _Pull requests with an incomplete checklist will be thrown out._ 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 vectorized.eth. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /.github/workflows/ci-all-via-ir.yml: -------------------------------------------------------------------------------- 1 | name: ci-all-via-ir 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | tests: 8 | name: Forge Testing all via-ir 9 | runs-on: ubuntu-latest 10 | 11 | strategy: 12 | matrix: 13 | profile: [via-ir-0,via-ir-1] 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Install Foundry 19 | uses: foundry-rs/foundry-toolchain@v1 20 | with: 21 | version: nightly 22 | 23 | - name: Install Dependencies 24 | run: forge install 25 | 26 | - name: Run Tests with ${{ matrix.profile }} 27 | run: > 28 | ( [ "${{ matrix.profile }}" = "via-ir-0" ] && 29 | forge test --use 0.8.14 --via-ir && 30 | forge test --use 0.8.15 --via-ir && 31 | forge test --use 0.8.16 --via-ir && 32 | forge test --use 0.8.17 --via-ir && 33 | forge test --use 0.8.13 --via-ir && 34 | forge test --use 0.8.12 --via-ir && 35 | forge test --use 0.8.11 --via-ir && 36 | forge test --use 0.8.10 --via-ir 37 | ) || 38 | ( [ "${{ matrix.profile }}" = "via-ir-1" ] && 39 | forge test --use 0.8.25 --via-ir && 40 | forge test --use 0.8.24 --via-ir && 41 | forge test --use 0.8.23 --via-ir && 42 | forge test --use 0.8.22 --via-ir && 43 | forge test --use 0.8.21 --via-ir && 44 | forge test --use 0.8.20 --via-ir && 45 | forge test --use 0.8.19 --via-ir && 46 | forge test --use 0.8.18 --via-ir 47 | ) 48 | -------------------------------------------------------------------------------- /.gas-snapshot: -------------------------------------------------------------------------------- 1 | MulticallerTest:testMultiCallerWithSignerIncrementNonceSalt(uint256) (runs: 256, μ: 83552, ~: 83533) 2 | MulticallerTest:testMultiCallerWithSignerIncrementNonceSaltWithERC1271(uint256) (runs: 256, μ: 614905, ~: 614953) 3 | MulticallerTest:testMulticallerCdFallback(string) (runs: 256, μ: 187169, ~: 181861) 4 | MulticallerTest:testMulticallerForwardsMessageValue() (gas: 214101) 5 | MulticallerTest:testMulticallerGetNames() (gas: 46531) 6 | MulticallerTest:testMulticallerReentrancyGuard() (gas: 20005) 7 | MulticallerTest:testMulticallerRefund(uint256) (runs: 256, μ: 169652, ~: 171909) 8 | MulticallerTest:testMulticallerReturnDataIsProperlyEncoded() (gas: 38730) 9 | MulticallerTest:testMulticallerReturnDataIsProperlyEncoded(string,string,uint256) (runs: 256, μ: 224746, ~: 212808) 10 | MulticallerTest:testMulticallerReturnDataIsProperlyEncoded(uint256,uint256,uint256,uint256) (runs: 256, μ: 38832, ~: 38832) 11 | MulticallerTest:testMulticallerRevertWithCustomError() (gas: 35431) 12 | MulticallerTest:testMulticallerRevertWithMessage() (gas: 38277) 13 | MulticallerTest:testMulticallerRevertWithMessage(string) (runs: 256, μ: 38933, ~: 38655) 14 | MulticallerTest:testMulticallerRevertWithNothing() (gas: 35278) 15 | MulticallerTest:testMulticallerSenderDoesNotRevertWithoutMulticallerDeployed() (gas: 3485) 16 | MulticallerTest:testMulticallerTargetGetMulticallerSender() (gas: 27557) 17 | MulticallerTest:testMulticallerWithNoData() (gas: 16170) 18 | MulticallerTest:testMulticallerWithSigner(uint256) (runs: 256, μ: 128140, ~: 119223) 19 | MulticallerTest:testMulticallerWithSignerEIP712Domain() (gas: 11361) 20 | MulticallerTest:testMulticallerWithSignerGetMulticallerSigner() (gas: 136483) 21 | MulticallerTest:testMulticallerWithSignerInvalidateNonces(uint256) (runs: 256, μ: 80929, ~: 79415) 22 | MulticallerTest:testMulticallerWithSignerInvalidateNoncesWithERC1271(uint256) (runs: 256, μ: 617130, ~: 616495) 23 | MulticallerTest:testMulticallerWithSignerNonPayableFunctions() (gas: 48848) 24 | MulticallerTest:testMulticallerWithSignerReentrancyGuard() (gas: 124834) 25 | MulticallerTest:testMulticallerWithSignerRevert() (gas: 206198) 26 | MulticallerTest:testMulticallerWithSignerWithERC1271(uint256) (runs: 256, μ: 661007, ~: 651334) 27 | MulticallerTest:testMulticallerWithSignerWithNoData() (gas: 130081) 28 | MulticallerTest:testNastyCalldataRevert() (gas: 3552) 29 | MulticallerTest:testOffsetTrick(uint256,uint256,uint256) (runs: 256, μ: 527, ~: 527) 30 | MulticallerTest:test__codesize() (gas: 48435) 31 | TestPlus:test__codesize() (gas: 840) -------------------------------------------------------------------------------- /audits/0xth0mas_review.md: -------------------------------------------------------------------------------- 1 | Original at: https://gist.github.com/0xth0mas/59ea1f31f16e058da27b0607c4dc24b8 2 | 3 | # General 4 | 5 | No major vulnerabilities were identified. 6 | 7 | # Scope 8 | 9 | Review for [Vectorized/multicaller commit 8c07107](https://github.com/Vectorized/multicaller/commit/8c071078b29d0037a7f01ec6f346776ec7c89948) 10 | 11 | ``` 12 | src 13 | ├── LibMulticaller.sol 14 | └── MulticallerWithSigner.sol 15 | ``` 16 | 17 | --- 18 | 19 | ### Documentation for reentrancy guard bit references incorrect bit location 20 | 21 | **Severity:** Informational 22 | **Likelihood:** N/A 23 | **File:** [MulticallerWithSigner](https://github.com/Vectorized/multicaller/blob/8c071078b29d0037a7f01ec6f346776ec7c89948/src/MulticallerWithSigner.sol#L122) 24 | 25 | The comment in the constructor detailing the data packing of storage slot 0 references `bit 1` as the location for the `MulticallerWithSigner` reentrancy guard while it is stored at bit 0. 26 | 27 | This has no impact to the functionality of `MulticallerWithSigner`. The only potential negative impact would be from a developer forking `MulticallerWithSigner` without understanding the code and making assumptions about the reentrancy guard based on the comment. 28 | 29 | **Mitigation:** 30 | 31 | Update comment to reflect the reentrancy guard at bit 0. 32 | 33 | --- 34 | 35 | ### Potential gas optimization on `resultsOffset` incrementer 36 | 37 | **Severity:** Gas Optimization 38 | **Likelihood:** Unknown 39 | **File:** [MulticallerWithSigner](https://github.com/Vectorized/multicaller/blob/8c071078b29d0037a7f01ec6f346776ec7c89948/src/MulticallerWithSigner.sol#L319) 40 | 41 | Incrementing the `resultsOffset` value with the necessary padding for ABI encoding of results values requires eight operations that are unnecessary for the final iteration of the loop. 42 | 43 | It may be more efficient to move the `resultsOffset` incrementer after the loop end check. Mock tests show a gas savings of 24 gas units for a single call with each additional call in the multicall transaction reducing savings by 6 gas units until it hits a breakeven point at 5 calls and becomes more expensive at 6 calls. 44 | 45 | If the average number of calls included in a multicall transaction to `MulticallerWithSigner` is under 5 it would be beneficial to change the code as follows: 46 | 47 | ##### From - 48 | ```solidity 49 | // Advance the `resultsOffset` by `returndatasize() + 0x20`, 50 | // rounded up to the next multiple of 0x20. 51 | resultsOffset := and(add(add(resultsOffset, returndatasize()), 0x3f), not(0x1f)) 52 | // Advance the `results` pointer. 53 | results := add(results, 0x20) 54 | if eq(results, end) { break } 55 | ``` 56 | 57 | #### To - 58 | ```solidity 59 | // Advance the `results` pointer. 60 | results := add(results, 0x20) 61 | if eq(results, end) { break } 62 | // Advance the `resultsOffset` by `returndatasize() + 0x20`, 63 | // rounded up to the next multiple of 0x20. 64 | resultsOffset := and(add(add(resultsOffset, returndatasize()), 0x3f), not(0x1f)) 65 | ``` -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | paths: 7 | - '**.sol' 8 | - '**.yml' 9 | push: 10 | branches: [main] 11 | paths: 12 | - '**.sol' 13 | - '**.yml' 14 | jobs: 15 | tests: 16 | name: Forge Testing 17 | runs-on: ubuntu-latest 18 | 19 | strategy: 20 | matrix: 21 | profile: [post-cancun,post-cancun-via-ir,solc-past-versions-0,solc-past-versions-1,via-ir,min-solc,min-solc-via-ir,intense] 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | 26 | - name: Install Foundry 27 | uses: foundry-rs/foundry-toolchain@v1 28 | with: 29 | version: nightly 30 | 31 | - name: Install Dependencies 32 | run: forge install 33 | 34 | - name: Run Tests with ${{ matrix.profile }} 35 | run: > 36 | ( [ "${{ matrix.profile }}" = "post-cancun" ] && 37 | forge test --use 0.8.25 --evm-version "cancun" 38 | ) || 39 | ( [ "${{ matrix.profile }}" = "post-cancun-via-ir" ] && 40 | forge test --use 0.8.25 --evm-version "cancun" --via-ir 41 | ) || 42 | ( [ "${{ matrix.profile }}" = "solc-past-versions-0" ] && 43 | forge test --use 0.8.5 --fuzz-runs 16 && 44 | forge test --use 0.8.6 --fuzz-runs 16 && 45 | forge test --use 0.8.7 --fuzz-runs 16 && 46 | forge test --use 0.8.8 --fuzz-runs 16 && 47 | forge test --use 0.8.9 --fuzz-runs 16 && 48 | forge test --use 0.8.10 --fuzz-runs 16 && 49 | forge test --use 0.8.11 --fuzz-runs 16 && 50 | forge test --use 0.8.12 --fuzz-runs 16 51 | ) || 52 | ( [ "${{ matrix.profile }}" = "solc-past-versions-1" ] && 53 | forge test --use 0.8.13 --fuzz-runs 16 && 54 | forge test --use 0.8.14 --fuzz-runs 16 && 55 | forge test --use 0.8.15 --fuzz-runs 16 && 56 | forge test --use 0.8.16 --fuzz-runs 16 && 57 | forge test --use 0.8.17 --fuzz-runs 16 && 58 | forge test --use 0.8.18 --fuzz-runs 16 && 59 | forge test --use 0.8.19 --fuzz-runs 16 && 60 | forge test --use 0.8.20 --fuzz-runs 16 && 61 | forge test --use 0.8.21 --fuzz-runs 16 && 62 | forge test --use 0.8.22 --fuzz-runs 16 && 63 | forge test --use 0.8.23 --fuzz-runs 16 && 64 | forge test --use 0.8.24 --fuzz-runs 16 65 | ) || 66 | ( [ "${{ matrix.profile }}" = "via-ir" ] && 67 | forge test --via-ir 68 | ) || 69 | ( [ "${{ matrix.profile }}" = "min-solc" ] && 70 | forge fmt --check && 71 | forge test --use 0.8.4 72 | ) || 73 | ( [ "${{ matrix.profile }}" = "min-solc-via-ir" ] && 74 | forge test --use 0.8.13 --via-ir 75 | ) || 76 | ( [ "${{ matrix.profile }}" = "intense" ] && 77 | forge test --fuzz-runs 5000 78 | ) 79 | 80 | codespell: 81 | runs-on: ${{ matrix.os }} 82 | strategy: 83 | matrix: 84 | os: 85 | - ubuntu-latest 86 | 87 | steps: 88 | - name: Checkout 89 | uses: actions/checkout@v4 90 | 91 | - name: Run codespell 92 | uses: codespell-project/actions-codespell@v2.0 93 | with: 94 | check_filenames: true 95 | ignore_words_list: usera 96 | skip: ./.git,package-lock.json,ackee-blockchain-solady-report.pdf,EIP712Mock.sol 97 | -------------------------------------------------------------------------------- /src/LibMulticaller.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | /** 5 | * @title LibMulticaller 6 | * @author vectorized.eth 7 | * @notice Library to read the `msg.sender` of the multicaller with sender contract. 8 | * 9 | * @dev Note: 10 | * The functions in this library do NOT guard against reentrancy. 11 | * A single transaction can recurse through different Multicallers 12 | * (e.g. `MulticallerWithSender -> contract -> MulticallerWithSigner -> contract`). 13 | * 14 | * Think of these functions like `msg.sender`. 15 | * 16 | * If your contract `C` can handle reentrancy safely with plain old `msg.sender` 17 | * for any `A -> C -> B -> C`, you should be fine substituting `msg.sender` with these functions. 18 | */ 19 | library LibMulticaller { 20 | /** 21 | * @dev The address of the multicaller contract. 22 | */ 23 | address internal constant MULTICALLER = 0x0000000000002Bdbf1Bf3279983603Ec279CC6dF; 24 | 25 | /** 26 | * @dev The address of the multicaller with sender contract. 27 | */ 28 | address internal constant MULTICALLER_WITH_SENDER = 0x00000000002Fd5Aeb385D324B580FCa7c83823A0; 29 | 30 | /** 31 | * @dev The address of the multicaller with signer contract. 32 | */ 33 | address internal constant MULTICALLER_WITH_SIGNER = 0x000000000000D9ECebf3C23529de49815Dac1c4c; 34 | 35 | /** 36 | * @dev Returns the caller of `aggregateWithSender` on `MULTICALLER_WITH_SENDER`. 37 | */ 38 | function multicallerSender() internal view returns (address result) { 39 | return at(MULTICALLER_WITH_SENDER); 40 | } 41 | 42 | /** 43 | * @dev Returns the signer of `aggregateWithSigner` on `MULTICALLER_WITH_SIGNER`. 44 | */ 45 | function multicallerSigner() internal view returns (address result) { 46 | return at(MULTICALLER_WITH_SIGNER); 47 | } 48 | 49 | /** 50 | * @dev Returns the caller of `aggregateWithSender` on `MULTICALLER_WITH_SENDER`, 51 | * if the current context's `msg.sender` is `MULTICALLER_WITH_SENDER`. 52 | * Otherwise, returns `msg.sender`. 53 | */ 54 | function sender() internal view returns (address result) { 55 | return resolve(MULTICALLER_WITH_SENDER); 56 | } 57 | 58 | /** 59 | * @dev Returns the caller of `aggregateWithSigner` on `MULTICALLER_WITH_SIGNER`, 60 | * if the current context's `msg.sender` is `MULTICALLER_WITH_SIGNER`. 61 | * Otherwise, returns `msg.sender`. 62 | */ 63 | function signer() internal view returns (address) { 64 | return resolve(MULTICALLER_WITH_SIGNER); 65 | } 66 | 67 | /** 68 | * @dev Returns the caller or signer at `a`. 69 | * @param a The multicaller with sender / signer. 70 | */ 71 | function at(address a) internal view returns (address result) { 72 | /// @solidity memory-safe-assembly 73 | assembly { 74 | mstore(0x00, 0x00) 75 | if iszero(staticcall(gas(), a, codesize(), 0x00, 0x00, 0x20)) { 76 | revert(codesize(), codesize()) // For better gas estimation. 77 | } 78 | result := mload(0x00) 79 | } 80 | } 81 | 82 | /** 83 | * @dev Returns the caller or signer at `a`, if the caller is `a`. 84 | * @param a The multicaller with sender / signer. 85 | */ 86 | function resolve(address a) internal view returns (address result) { 87 | /// @solidity memory-safe-assembly 88 | assembly { 89 | mstore(0x00, caller()) 90 | if eq(caller(), a) { 91 | if iszero(staticcall(gas(), a, codesize(), 0x00, 0x00, 0x20)) { 92 | revert(codesize(), codesize()) // For better gas estimation. 93 | } 94 | } 95 | result := mload(0x00) 96 | } 97 | } 98 | 99 | /** 100 | * @dev Returns the caller of `aggregateWithSender` on `MULTICALLER_WITH_SENDER`, 101 | * if the current context's `msg.sender` is `MULTICALLER_WITH_SENDER`. 102 | * Returns the signer of `aggregateWithSigner` on `MULTICALLER_WITH_SIGNER`, 103 | * if the current context's `msg.sender` is `MULTICALLER_WITH_SIGNER`. 104 | * Otherwise, returns `msg.sender`. 105 | */ 106 | function senderOrSigner() internal view returns (address result) { 107 | /// @solidity memory-safe-assembly 108 | assembly { 109 | mstore(0x00, caller()) 110 | let withSender := MULTICALLER_WITH_SENDER 111 | if eq(caller(), withSender) { 112 | if iszero(staticcall(gas(), withSender, codesize(), 0x00, 0x00, 0x20)) { 113 | revert(codesize(), codesize()) // For better gas estimation. 114 | } 115 | } 116 | let withSigner := MULTICALLER_WITH_SIGNER 117 | if eq(caller(), withSigner) { 118 | if iszero(staticcall(gas(), withSigner, codesize(), 0x00, 0x00, 0x20)) { 119 | revert(codesize(), codesize()) // For better gas estimation. 120 | } 121 | } 122 | result := mload(0x00) 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /test/utils/mocks/MockERC1271Wallet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | /// @notice A generic interface for a contract which properly accepts ERC721 tokens. 5 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol) 6 | abstract contract ERC721TokenReceiver { 7 | function onERC721Received(address, address, uint256, bytes calldata) 8 | external 9 | virtual 10 | returns (bytes4) 11 | { 12 | return ERC721TokenReceiver.onERC721Received.selector; 13 | } 14 | } 15 | 16 | /// @notice A generic interface for a contract which properly accepts ERC1155 tokens. 17 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC1155.sol) 18 | abstract contract ERC1155TokenReceiver { 19 | function onERC1155Received(address, address, uint256, uint256, bytes calldata) 20 | external 21 | virtual 22 | returns (bytes4) 23 | { 24 | return ERC1155TokenReceiver.onERC1155Received.selector; 25 | } 26 | 27 | function onERC1155BatchReceived( 28 | address, 29 | address, 30 | uint256[] calldata, 31 | uint256[] calldata, 32 | bytes calldata 33 | ) external virtual returns (bytes4) { 34 | return ERC1155TokenReceiver.onERC1155BatchReceived.selector; 35 | } 36 | } 37 | 38 | library SignatureCheckerLib { 39 | function isValidSignatureNowCalldata(address signer, bytes32 hash, bytes calldata signature) 40 | internal 41 | view 42 | returns (bool isValid) 43 | { 44 | /// @solidity memory-safe-assembly 45 | assembly { 46 | // Clean the upper 96 bits of `signer` in case they are dirty. 47 | for { signer := shr(96, shl(96, signer)) } signer {} { 48 | let m := mload(0x40) 49 | mstore(0x00, hash) 50 | if eq(signature.length, 64) { 51 | let vs := calldataload(add(signature.offset, 0x20)) 52 | mstore(0x20, add(shr(255, vs), 27)) // `v`. 53 | mstore(0x40, calldataload(signature.offset)) // `r`. 54 | mstore(0x60, shr(1, shl(1, vs))) // `s`. 55 | let t := 56 | staticcall( 57 | gas(), // Amount of gas left for the transaction. 58 | 1, // Address of `ecrecover`. 59 | 0x00, // Start of input. 60 | 0x80, // Size of input. 61 | 0x01, // Start of output. 62 | 0x20 // Size of output. 63 | ) 64 | // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. 65 | if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) { 66 | isValid := 1 67 | mstore(0x60, 0) // Restore the zero slot. 68 | mstore(0x40, m) // Restore the free memory pointer. 69 | break 70 | } 71 | } 72 | if eq(signature.length, 65) { 73 | mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`. 74 | calldatacopy(0x40, signature.offset, 0x40) // `r`, `s`. 75 | let t := 76 | staticcall( 77 | gas(), // Amount of gas left for the transaction. 78 | 1, // Address of `ecrecover`. 79 | 0x00, // Start of input. 80 | 0x80, // Size of input. 81 | 0x01, // Start of output. 82 | 0x20 // Size of output. 83 | ) 84 | // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. 85 | if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) { 86 | isValid := 1 87 | mstore(0x60, 0) // Restore the zero slot. 88 | mstore(0x40, m) // Restore the free memory pointer. 89 | break 90 | } 91 | } 92 | mstore(0x60, 0) // Restore the zero slot. 93 | mstore(0x40, m) // Restore the free memory pointer. 94 | 95 | let f := shl(224, 0x1626ba7e) 96 | mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. 97 | mstore(add(m, 0x04), hash) 98 | let d := add(m, 0x24) 99 | mstore(d, 0x40) // The offset of the `signature` in the calldata. 100 | mstore(add(m, 0x44), signature.length) 101 | // Copy the `signature` over. 102 | calldatacopy(add(m, 0x64), signature.offset, signature.length) 103 | // forgefmt: disable-next-item 104 | isValid := and( 105 | // Whether the returndata is the magic value `0x1626ba7e` (left-aligned). 106 | eq(mload(d), f), 107 | // Whether the staticcall does not revert. 108 | // This must be placed at the end of the `and` clause, 109 | // as the arguments are evaluated from right to left. 110 | staticcall( 111 | gas(), // Remaining gas. 112 | signer, // The `signer` address. 113 | m, // Offset of calldata in memory. 114 | add(signature.length, 0x64), // Length of calldata in memory. 115 | d, // Offset of returndata. 116 | 0x20 // Length of returndata to write. 117 | ) 118 | ) 119 | break 120 | } 121 | } 122 | } 123 | } 124 | 125 | /// @dev WARNING! This mock is strictly intended for testing purposes only. 126 | /// Do NOT copy anything here into production code unless you really know what you are doing. 127 | contract MockERC1271Wallet is ERC721TokenReceiver, ERC1155TokenReceiver { 128 | address signer; 129 | 130 | bytes32 private constant _MALLEABILITY_THRESHOLD_PLUS_ONE = 131 | 0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a1; 132 | 133 | constructor(address signer_) { 134 | signer = signer_; 135 | } 136 | 137 | function isValidSignature(bytes32 hash, bytes calldata signature) 138 | external 139 | view 140 | returns (bytes4) 141 | { 142 | return SignatureCheckerLib.isValidSignatureNowCalldata(signer, hash, signature) 143 | ? bytes4(0x1626ba7e) 144 | : bytes4(0); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/MulticallerWithSender.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | /** 5 | * @title MulticallerWithSender 6 | * @author vectorized.eth 7 | * @notice Contract that allows for efficient aggregation of multiple calls 8 | * in a single transaction, while "forwarding" the `msg.sender`. 9 | */ 10 | contract MulticallerWithSender { 11 | // ============================================================= 12 | // ERRORS 13 | // ============================================================= 14 | 15 | /** 16 | * @dev The lengths of the input arrays are not the same. 17 | */ 18 | error ArrayLengthsMismatch(); 19 | 20 | /** 21 | * @dev This function does not support reentrancy. 22 | */ 23 | error Reentrancy(); 24 | 25 | // ============================================================= 26 | // CONSTRUCTOR 27 | // ============================================================= 28 | 29 | constructor() payable { 30 | assembly { 31 | // Throughout this code, we will abuse returndatasize 32 | // in place of zero anywhere before a call to save a bit of gas. 33 | // We will use storage slot zero to store the caller at 34 | // bits [0..159] and reentrancy guard flag at bit 160. 35 | sstore(returndatasize(), shl(160, 1)) 36 | } 37 | } 38 | 39 | // ============================================================= 40 | // AGGREGATION OPERATIONS 41 | // ============================================================= 42 | 43 | /** 44 | * @dev Returns the address that called `aggregateWithSender` on this contract. 45 | * The value is always the zero address outside a transaction. 46 | */ 47 | receive() external payable { 48 | assembly { 49 | mstore(returndatasize(), and(sub(shl(160, 1), 1), sload(returndatasize()))) 50 | return(returndatasize(), 0x20) 51 | } 52 | } 53 | 54 | /** 55 | * @dev Aggregates multiple calls in a single transaction. 56 | * This method will set `sender` to the `msg.sender` temporarily 57 | * for the span of its execution. 58 | * This method does not support reentrancy. 59 | * @param targets An array of addresses to call. 60 | * @param data An array of calldata to forward to the targets. 61 | * @param values How much ETH to forward to each target. 62 | * @return An array of the returndata from each call. 63 | */ 64 | function aggregateWithSender( 65 | address[] calldata targets, 66 | bytes[] calldata data, 67 | uint256[] calldata values 68 | ) external payable returns (bytes[] memory) { 69 | assembly { 70 | if iszero(and(eq(targets.length, data.length), eq(data.length, values.length))) { 71 | // Store the function selector of `ArrayLengthsMismatch()`. 72 | mstore(returndatasize(), 0x3b800a46) 73 | // Revert with (offset, size). 74 | revert(0x1c, 0x04) 75 | } 76 | 77 | if iszero(and(sload(returndatasize()), shl(160, 1))) { 78 | // Store the function selector of `Reentrancy()`. 79 | mstore(returndatasize(), 0xab143c06) 80 | // Revert with (offset, size). 81 | revert(0x1c, 0x04) 82 | } 83 | 84 | mstore(returndatasize(), 0x20) // Store the memory offset of the `results`. 85 | mstore(0x20, data.length) // Store `data.length` into `results`. 86 | // Early return if no data. 87 | if iszero(data.length) { return(returndatasize(), 0x40) } 88 | 89 | // Set the sender slot temporarily for the span of this transaction. 90 | sstore(returndatasize(), caller()) 91 | 92 | let results := 0x40 93 | // Left shift by 5 is equivalent to multiplying by 0x20. 94 | data.length := shl(5, data.length) 95 | // Copy the offsets from calldata into memory. 96 | calldatacopy(results, data.offset, data.length) 97 | // Offset into `results`. 98 | let resultsOffset := data.length 99 | // Pointer to the end of `results`. 100 | // Recycle `data.length` to avoid stack too deep. 101 | data.length := add(results, data.length) 102 | 103 | for {} 1 {} { 104 | // The offset of the current bytes in the calldata. 105 | let o := add(data.offset, mload(results)) 106 | let memPtr := add(resultsOffset, 0x40) 107 | // Copy the current bytes from calldata to the memory. 108 | calldatacopy( 109 | memPtr, 110 | add(o, 0x20), // The offset of the current bytes' bytes. 111 | calldataload(o) // The length of the current bytes. 112 | ) 113 | if iszero( 114 | call( 115 | gas(), // Remaining gas. 116 | calldataload(targets.offset), // Address to call. 117 | calldataload(values.offset), // ETH to send. 118 | memPtr, // Start of input calldata in memory. 119 | calldataload(o), // Size of input calldata. 120 | 0x00, // We will use returndatacopy instead. 121 | 0x00 // We will use returndatacopy instead. 122 | ) 123 | ) { 124 | // Bubble up the revert if the call reverts. 125 | returndatacopy(0x00, 0x00, returndatasize()) 126 | revert(0x00, returndatasize()) 127 | } 128 | // Advance the `targets.offset`. 129 | targets.offset := add(targets.offset, 0x20) 130 | // Advance the `values.offset`. 131 | values.offset := add(values.offset, 0x20) 132 | // Append the current `resultsOffset` into `results`. 133 | mstore(results, resultsOffset) 134 | results := add(results, 0x20) 135 | // Append the returndatasize, and the returndata. 136 | mstore(memPtr, returndatasize()) 137 | returndatacopy(add(memPtr, 0x20), 0x00, returndatasize()) 138 | // Advance the `resultsOffset` by `returndatasize() + 0x20`, 139 | // rounded up to the next multiple of 0x20. 140 | resultsOffset := and(add(add(resultsOffset, returndatasize()), 0x3f), not(0x1f)) 141 | if iszero(lt(results, data.length)) { break } 142 | } 143 | // Restore the `sender` slot. 144 | sstore(0, shl(160, 1)) 145 | // Direct return. 146 | return(0x00, add(resultsOffset, 0x40)) 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Multicaller 2 | 3 | [![NPM][npm-shield]][npm-url] 4 | [![CI][ci-shield]][ci-url] 5 | [![Solidity][solidity-shield]][solidity-ci-url] 6 | 7 | Efficiently call multiple contracts in a single transaction. 8 | 9 | Enables "forwarding" of `msg.sender` to the contracts called. 10 | 11 | ## Deployments 12 | 13 | Please open an issue if you need help to deploy to an EVM chain of your choice. 14 | 15 | - Ethereum 16 | - Multicaller: [`0x0000000000002Bdbf1Bf3279983603Ec279CC6dF`](https://etherscan.io/address/0x0000000000002Bdbf1Bf3279983603Ec279CC6dF) 17 | - MulticallerWithSender: [`0x00000000002Fd5Aeb385D324B580FCa7c83823A0`](https://etherscan.io/address/0x00000000002Fd5Aeb385D324B580FCa7c83823A0) 18 | - MulticallerWithSigner: [`0x000000000000D9ECebf3C23529de49815Dac1c4c`](https://etherscan.io/address/0x000000000000D9ECebf3C23529de49815Dac1c4c) 19 | - Goerli 20 | - Multicaller: [`0x0000000000002Bdbf1Bf3279983603Ec279CC6dF`](https://goerli.etherscan.io/address/0x0000000000002Bdbf1Bf3279983603Ec279CC6dF) 21 | - MulticallerWithSender: [`0x00000000002Fd5Aeb385D324B580FCa7c83823A0`](https://goerli.etherscan.io/address/0x00000000002Fd5Aeb385D324B580FCa7c83823A0) 22 | - MulticallerWithSigner: [`0x000000000000D9ECebf3C23529de49815Dac1c4c`](https://goerli.etherscan.io/address/0x000000000000D9ECebf3C23529de49815Dac1c4c) 23 | - Sepolia 24 | - Multicaller: [`0x0000000000002Bdbf1Bf3279983603Ec279CC6dF`](https://sepolia.etherscan.io/address/0x0000000000002Bdbf1Bf3279983603Ec279CC6dF) 25 | - MulticallerWithSender: [`0x00000000002Fd5Aeb385D324B580FCa7c83823A0`](https://sepolia.etherscan.io/address/0x00000000002Fd5Aeb385D324B580FCa7c83823A0) 26 | - MulticallerWithSigner: [`0x000000000000D9ECebf3C23529de49815Dac1c4c`](https://sepolia.etherscan.io/address/0x000000000000D9ECebf3C23529de49815Dac1c4c) 27 | - Holesky 28 | - Multicaller: [`0x0000000000002Bdbf1Bf3279983603Ec279CC6dF`](https://holesky.etherscan.io/address/0x0000000000002Bdbf1Bf3279983603Ec279CC6dF) 29 | - MulticallerWithSender: [`0x00000000002Fd5Aeb385D324B580FCa7c83823A0`](https://holesky.etherscan.io/address/0x00000000002Fd5Aeb385D324B580FCa7c83823A0) 30 | - MulticallerWithSigner: [`0x000000000000D9ECebf3C23529de49815Dac1c4c`](https://holesky.etherscan.io/address/0x000000000000D9ECebf3C23529de49815Dac1c4c) 31 | - Polygon 32 | - Multicaller: [`0x0000000000002Bdbf1Bf3279983603Ec279CC6dF`](https://polygonscan.com/address/0x0000000000002Bdbf1Bf3279983603Ec279CC6dF) 33 | - MulticallerWithSender: [`0x00000000002Fd5Aeb385D324B580FCa7c83823A0`](https://polygonscan.com/address/0x00000000002Fd5Aeb385D324B580FCa7c83823A0) 34 | - MulticallerWithSigner: [`0x000000000000D9ECebf3C23529de49815Dac1c4c`](https://polygonscan.com/address/0x000000000000D9ECebf3C23529de49815Dac1c4c) 35 | - Mumbai 36 | - Multicaller: [`0x0000000000002Bdbf1Bf3279983603Ec279CC6dF`](https://mumbai.polygonscan.com/address/0x0000000000002Bdbf1Bf3279983603Ec279CC6dF) 37 | - MulticallerWithSender: [`0x00000000002Fd5Aeb385D324B580FCa7c83823A0`](https://mumbai.polygonscan.com/address/0x00000000002Fd5Aeb385D324B580FCa7c83823A0) 38 | - MulticallerWithSigner: [`0x000000000000D9ECebf3C23529de49815Dac1c4c`](https://mumbai.polygonscan.com/address/0x000000000000D9ECebf3C23529de49815Dac1c4c) 39 | - Optimism 40 | - Multicaller: [`0x0000000000002Bdbf1Bf3279983603Ec279CC6dF`](https://optimistic.etherscan.io/address/0x0000000000002Bdbf1Bf3279983603Ec279CC6dF) 41 | - MulticallerWithSender: [`0x00000000002Fd5Aeb385D324B580FCa7c83823A0`](https://optimistic.etherscan.io/address/0x00000000002Fd5Aeb385D324B580FCa7c83823A0) 42 | - MulticallerWithSigner: [`0x000000000000D9ECebf3C23529de49815Dac1c4c`](https://optimistic.etherscan.io/address/0x000000000000D9ECebf3C23529de49815Dac1c4c) 43 | - Arbitrum 44 | - Multicaller: [`0x0000000000002Bdbf1Bf3279983603Ec279CC6dF`](https://arbiscan.io/address/0x0000000000002Bdbf1Bf3279983603Ec279CC6dF) 45 | - MulticallerWithSender: [`0x00000000002Fd5Aeb385D324B580FCa7c83823A0`](https://arbiscan.io/address/0x00000000002Fd5Aeb385D324B580FCa7c83823A0) 46 | - MulticallerWithSigner: [`0x000000000000D9ECebf3C23529de49815Dac1c4c`](https://arbiscan.io/address/0x000000000000D9ECebf3C23529de49815Dac1c4c) 47 | - Base 48 | - Multicaller: [`0x0000000000002Bdbf1Bf3279983603Ec279CC6dF`](https://basescan.org/address/0x0000000000002Bdbf1Bf3279983603Ec279CC6dF) 49 | - MulticallerWithSender: [`0x00000000002Fd5Aeb385D324B580FCa7c83823A0`](https://basescan.org/address/0x00000000002Fd5Aeb385D324B580FCa7c83823A0) 50 | - MulticallerWithSigner: [`0x000000000000D9ECebf3C23529de49815Dac1c4c`](https://basescan.org/address/0x000000000000D9ECebf3C23529de49815Dac1c4c) 51 | - Blast 52 | - Multicaller: [`0x0000000000002Bdbf1Bf3279983603Ec279CC6dF`](https://blastscan.io/address/0x0000000000002Bdbf1Bf3279983603Ec279CC6dF) 53 | - MulticallerWithSender: [`0x00000000002Fd5Aeb385D324B580FCa7c83823A0`](https://blastscan.io/address/0x00000000002Fd5Aeb385D324B580FCa7c83823A0) 54 | - MulticallerWithSigner: [`0x000000000000D9ECebf3C23529de49815Dac1c4c`](https://blastscan.io/address/0x000000000000D9ECebf3C23529de49815Dac1c4c) 55 | 56 | ## Contracts 57 | 58 | ```ml 59 | src 60 | ├─ Multicaller.sol — "The multicaller contract" 61 | ├─ MulticallerWithSender.sol — "The multicaller with sender contract" 62 | ├─ MulticallerWithSigner.sol — "The multicaller with signer contract" 63 | └─ LibMulticaller.sol — "Library to read the multicaller contracts" 64 | ``` 65 | 66 | ## Installation 67 | 68 | You can use the [`src/LibMulticaller.sol`](./src/LibMulticaller.sol) library in your contracts to query the multicaller with sender contract efficiently. 69 | 70 | To install with [**Foundry**](https://github.com/gakonst/foundry): 71 | 72 | ```sh 73 | forge install vectorized/multicaller 74 | ``` 75 | 76 | To install with [**Hardhat**](https://github.com/nomiclabs/hardhat) or [**Truffle**](https://github.com/trufflesuite/truffle): 77 | 78 | ```sh 79 | npm install multicaller 80 | ``` 81 | 82 | ## API 83 | 84 | [The API docs](API.md). 85 | 86 | ## Design 87 | 88 | The contracts are designed with a priority on efficiency and minimalism. 89 | 90 | ## Safety 91 | 92 | We **do not give any warranties** and **will not be liable for any loss** incurred through any use of this codebase. 93 | 94 | Please read the API docs / Natspec, test with your code, and get additional security reviews to make sure that any usage is done safely. 95 | 96 | ## Acknowledgments 97 | 98 | Multicaller is inspired by and directly modified from: 99 | 100 | - [Solady](https://github.com/vectorized/solady) 101 | - [MakerDao's Multicall](https://github.com/makerdao/multicall) 102 | 103 | This project is a public good initiative of [sound.xyz](https://sound.xyz) and Solady. 104 | 105 | We would like to thank our [reviewers and contributors](credits.txt) for their invaluable help. 106 | 107 | [npm-shield]: https://img.shields.io/npm/v/multicaller.svg 108 | [npm-url]: https://www.npmjs.com/package/multicaller 109 | 110 | [ci-shield]: https://img.shields.io/github/actions/workflow/status/vectorized/multicaller/ci.yml?label=build&branch=main 111 | [ci-url]: https://github.com/vectorized/multicaller/actions/workflows/ci.yml 112 | 113 | [solidity-shield]: https://img.shields.io/badge/solidity-%3E=0.8.10%20%3C=0.8.25-aa6746 114 | [solidity-ci-url]: https://github.com/Vectorized/solady/actions/workflows/ci-all-via-ir.yml 115 | -------------------------------------------------------------------------------- /src/Multicaller.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | /** 5 | * @title Multicaller 6 | * @author vectorized.eth 7 | * @notice Contract that allows for efficient aggregation 8 | * of multiple calls in a single transaction. 9 | */ 10 | contract Multicaller { 11 | // ============================================================= 12 | // ERRORS 13 | // ============================================================= 14 | 15 | /** 16 | * @dev The lengths of the input arrays are not the same. 17 | */ 18 | error ArrayLengthsMismatch(); 19 | 20 | // ============================================================= 21 | // AGGREGATION OPERATIONS 22 | // ============================================================= 23 | 24 | /** 25 | * @dev Aggregates multiple calls in a single transaction. 26 | * @param targets An array of addresses to call. 27 | * @param data An array of calldata to forward to the targets. 28 | * @param values How much ETH to forward to each target. 29 | * @param refundTo The address to transfer any remaining ETH in the contract after the calls. 30 | * If `address(0)`, remaining ETH will NOT be refunded. 31 | * If `address(1)`, remaining ETH will be refunded to `msg.sender`. 32 | * If anything else, remaining ETH will be refunded to `refundTo`. 33 | * @return An array of the returndata from each call. 34 | */ 35 | function aggregate( 36 | address[] calldata targets, 37 | bytes[] calldata data, 38 | uint256[] calldata values, 39 | address refundTo 40 | ) external payable returns (bytes[] memory) { 41 | assembly { 42 | if iszero(and(eq(targets.length, data.length), eq(data.length, values.length))) { 43 | // Store the function selector of `ArrayLengthsMismatch()`. 44 | mstore(returndatasize(), 0x3b800a46) 45 | // Revert with (offset, size). 46 | revert(0x1c, 0x04) 47 | } 48 | 49 | let resultsSize := 0x40 50 | 51 | if data.length { 52 | let results := 0x40 53 | // Left shift by 5 is equivalent to multiplying by 0x20. 54 | data.length := shl(5, data.length) 55 | // Copy the offsets from calldata into memory. 56 | calldatacopy(results, data.offset, data.length) 57 | // Offset into `results`. 58 | let resultsOffset := data.length 59 | // Pointer to the end of `results`. 60 | let end := add(results, data.length) 61 | // For deriving the calldata offsets from the `results` pointer. 62 | let valuesOffsetDiff := sub(values.offset, results) 63 | let targetsOffsetDiff := sub(targets.offset, results) 64 | 65 | for {} 1 {} { 66 | // The offset of the current bytes in the calldata. 67 | let o := add(data.offset, mload(results)) 68 | let memPtr := add(resultsOffset, 0x40) 69 | // Copy the current bytes from calldata to the memory. 70 | calldatacopy( 71 | memPtr, 72 | add(o, 0x20), // The offset of the current bytes' bytes. 73 | calldataload(o) // The length of the current bytes. 74 | ) 75 | if iszero( 76 | call( 77 | gas(), // Remaining gas. 78 | calldataload(add(targetsOffsetDiff, results)), // Address to call. 79 | calldataload(add(valuesOffsetDiff, results)), // ETH to send. 80 | memPtr, // Start of input calldata in memory. 81 | calldataload(o), // Size of input calldata. 82 | 0x00, // We will use returndatacopy instead. 83 | 0x00 // We will use returndatacopy instead. 84 | ) 85 | ) { 86 | // Bubble up the revert if the call reverts. 87 | returndatacopy(0x00, 0x00, returndatasize()) 88 | revert(0x00, returndatasize()) 89 | } 90 | // Append the current `resultsOffset` into `results`. 91 | mstore(results, resultsOffset) 92 | // Append the returndatasize, and the returndata. 93 | mstore(memPtr, returndatasize()) 94 | returndatacopy(add(memPtr, 0x20), 0x00, returndatasize()) 95 | // Advance the `resultsOffset` by `returndatasize() + 0x20`, 96 | // rounded up to the next multiple of 0x20. 97 | resultsOffset := and(add(add(resultsOffset, returndatasize()), 0x3f), not(0x1f)) 98 | // Advance the `results` pointer. 99 | results := add(results, 0x20) 100 | if eq(results, end) { break } 101 | } 102 | resultsSize := add(resultsOffset, 0x40) 103 | } 104 | 105 | if refundTo { 106 | // Force transfers all the remaining ETH in the contract to `refundTo`, 107 | // with a gas stipend of 100000, which should be enough for most use cases. 108 | // If sending via a regular call fails, force sends the ETH by 109 | // creating a temporary contract which uses `SELFDESTRUCT` to force send the ETH. 110 | if selfbalance() { 111 | // If `refundTo` is `address(1)`, replace it with the `msg.sender`. 112 | refundTo := xor(refundTo, mul(eq(refundTo, 1), xor(refundTo, caller()))) 113 | // Transfer the ETH and check if it succeeded or not. 114 | if iszero( 115 | call(100000, refundTo, selfbalance(), codesize(), 0x00, codesize(), 0x00) 116 | ) { 117 | mstore(0x00, refundTo) // Store the address in scratch space. 118 | mstore8(0x0b, 0x73) // Opcode `PUSH20`. 119 | mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`. 120 | // We can directly use `SELFDESTRUCT` in the contract creation. 121 | // Compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758 122 | if iszero(create(selfbalance(), 0x0b, 0x16)) { 123 | // Coerce gas estimation to provide enough gas for the `create` above. 124 | revert(codesize(), codesize()) 125 | } 126 | } 127 | } 128 | } 129 | 130 | mstore(0x00, 0x20) // Store the memory offset of the `results`. 131 | mstore(0x20, targets.length) // Store `targets.length` into `results`. 132 | // Direct return. 133 | return(0x00, resultsSize) 134 | } 135 | } 136 | 137 | /** 138 | * @dev For receiving ETH. 139 | * Does nothing and returns nothing. 140 | * Called instead of `fallback()` if the calldatasize is zero. 141 | */ 142 | receive() external payable {} 143 | 144 | /** 145 | * @dev Decompresses the calldata and performs a delegatecall 146 | * with the decompressed calldata to itself. 147 | * 148 | * Accompanying JavaScript library to compress the calldata: 149 | * https://github.com/vectorized/solady/blob/main/js/solady.js 150 | * (See: `LibZip.cdCompress`) 151 | */ 152 | fallback() external payable { 153 | assembly { 154 | // If the calldata starts with the bitwise negation of 155 | // `bytes4(keccak256("aggregate(address[],bytes[],uint256[],address)"))`. 156 | let s := calldataload(returndatasize()) 157 | if eq(shr(224, s), 0x66e0daa0) { 158 | mstore(returndatasize(), not(s)) 159 | let o := 4 160 | for { let i := o } lt(i, calldatasize()) {} { 161 | let c := byte(returndatasize(), calldataload(i)) 162 | i := add(i, 1) 163 | if iszero(c) { 164 | let d := byte(returndatasize(), calldataload(i)) 165 | i := add(i, 1) 166 | // Fill with either 0xff or 0x00. 167 | mstore(o, not(returndatasize())) 168 | if iszero(gt(d, 0x7f)) { codecopy(o, codesize(), add(d, 1)) } 169 | o := add(o, add(and(d, 0x7f), 1)) 170 | continue 171 | } 172 | mstore8(o, c) 173 | o := add(o, 1) 174 | } 175 | let success := delegatecall(gas(), address(), 0x00, o, 0x00, 0x00) 176 | returndatacopy(0x00, 0x00, returndatasize()) 177 | if iszero(success) { revert(0x00, returndatasize()) } 178 | return(0x00, returndatasize()) 179 | } 180 | revert(returndatasize(), returndatasize()) 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # API 2 | 3 | ## Multicaller 4 | 5 | ### Functions 6 | 7 | #### `aggregate` 8 | ```solidity 9 | function aggregate( 10 | address[] calldata targets, 11 | bytes[] calldata data, 12 | uint256[] calldata values, 13 | address refundTo 14 | ) external payable returns (bytes[] memory) 15 | ``` 16 | Aggregates multiple calls in a single transaction. 17 | 18 | Remaining ETH in the contract after the calls can be refunded: 19 | - If `refundTo` is `address(0)`, remaining ETH will NOT be refunded. 20 | - If `refundTo` is `address(1)`, remaining ETH will be refunded to `msg.sender`. 21 | - If `refundTo` is anything else, remaining ETH will be refunded to `refundTo`. 22 | 23 | Returns an array of the returndata from each call. 24 | 25 | #### `receive` 26 | ```solidity 27 | receive() external payable 28 | ``` 29 | 30 | For receiving ETH. 31 | 32 | Does nothing and returns nothing. 33 | 34 | Will be called instead of `fallback()` if the calldatasize is zero. 35 | 36 | #### `fallback` 37 | ```solidity 38 | fallback() external payable 39 | ``` 40 | 41 | Decompresses the calldata and performs a delegatecall with the decompressed calldata to itself. 42 | 43 | Accompanying JavaScript library to compress the calldata: 44 | https://github.com/vectorized/solady/blob/main/js/solady.js 45 | (See: `LibZip.cdCompress`) 46 | 47 | ## MulticallerWithSender 48 | 49 | ### Functions 50 | 51 | #### `aggregateWithSender` 52 | ```solidity 53 | function aggregateWithSender( 54 | address[] calldata targets, 55 | bytes[] calldata data, 56 | uint256[] calldata values 57 | ) external payable returns (bytes[] memory) 58 | ``` 59 | Aggregates multiple calls in a single transaction. 60 | 61 | This method will set the multicaller sender to the `msg.sender` temporarily for the span of its execution. 62 | 63 | This method does NOT support reentrancy. 64 | 65 | This method does NOT refund any excess ETH in the contract. 66 | 67 | Returns an array of the returndata from each call. 68 | 69 | #### `receive` 70 | ```solidity 71 | receive() external payable 72 | ``` 73 | Returns the caller of `aggregateWithSender` on the contract. 74 | 75 | The value is always the zero address outside a transaction. 76 | 77 | ## MulticallerWithSigner 78 | 79 | ### Events 80 | 81 | #### `NoncesInvalidated` 82 | ```solidity 83 | event NoncesInvalidated(address indexed signer, uint256[] nonces) 84 | ``` 85 | 86 | Emitted when the `nonces` of `signer` are invalidated. 87 | 88 | #### `NonceSaltIncremented` 89 | ```solidity 90 | event NonceSaltIncremented(address indexed signer, uint256 newNonceSalt) 91 | ``` 92 | 93 | Emitted when the nonce salt of `signer` is incremented. 94 | 95 | ### Constants 96 | 97 | These EIP-712 constants are made private to save function dispatch gas. 98 | 99 | If you need them in your code, please copy and paste them. 100 | 101 | #### `_AGGREGATE_WITH_SIGNER_TYPEHASH` 102 | ```solidity 103 | bytes32 private constant _AGGREGATE_WITH_SIGNER_TYPEHASH = 104 | 0xfb989fd34c8af81a76f18167f528fc7315f92cacc19a0e63215abd54633f8a28; 105 | ``` 106 | 107 | For EIP-712 signature digest calculation for the `aggregateWithSigner` function. 108 | 109 | `keccak256("AggregateWithSigner(address signer,address[] targets,bytes[] data,uint256[] values,uint256 nonce,uint256 nonceSalt)")`. 110 | 111 | - `signer`: The signer of the signature. 112 | - `targets`: An array of addresses to call. 113 | - `data`: An array of calldata to forward to the targets. 114 | - `values`: How much ETH to forward to each target. 115 | - `nonce`: The nonce for the signature. 116 | - `nonceSalt`: The current nonce salt of the signer. 117 | 118 | #### `_INVALIDATE_NONCES_FOR_SIGNER_TYPEHASH` 119 | ```solidity 120 | bytes32 private constant _INVALIDATE_NONCES_FOR_SIGNER_TYPEHASH = 121 | 0x12b047058eea3df4085cdc159a103d9c100c4e78cfb7029cc39d02cb8b9e48f5; 122 | ``` 123 | 124 | For EIP-712 signature digest calculation for the `invalidateNoncesForSigner` function. 125 | 126 | `keccak256("InvalidateNoncesForSigner(address signer,uint256[] nonces,uint256 nonceSalt)")`. 127 | 128 | - `signer`: The signer of the signature. 129 | - `nonces`: The array of nonces for the signer. 130 | - `nonceSalt`: The current nonce salt of the signer. 131 | 132 | #### `_INCREMENT_NONCE_SALT_FOR_SIGNER_TYPEHASH` 133 | ```solidity 134 | bytes32 private constant _INCREMENT_NONCE_SALT_FOR_SIGNER_TYPEHASH = 135 | 0xfa181078c7d1d4d369301511d3c5611e9367d0cebbf65eefdee9dfc75849c1d3; 136 | ``` 137 | 138 | For EIP-712 signature digest calculation for the `incrementNonceSaltForSigner` function. 139 | 140 | `keccak256("IncrementNonceSaltForSigner(address signer,uint256 nonceSalt)")`. 141 | 142 | - `signer`: The signer of the signature. 143 | - `nonceSalt`: The current nonce salt of the signer. 144 | 145 | #### `_DOMAIN_TYPEHASH` 146 | ```solidity 147 | bytes32 private constant _DOMAIN_TYPEHASH = 148 | 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; 149 | ``` 150 | 151 | For EIP-712 signature digest calculation. 152 | 153 | `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`. 154 | 155 | #### `_NAME_HASH` 156 | ```solidity 157 | bytes32 private constant _NAME_HASH = 158 | 0x301013e8a31863902646dc218ecd889c37491c2967a8104d5ff1cf42af0f9ea4; 159 | ``` 160 | 161 | For EIP-712 signature digest calculation. 162 | 163 | `keccak256("MulticallerWithSigner")`. 164 | 165 | #### `_VERSION_HASH` 166 | ```solidity 167 | bytes32 private constant _VERSION_HASH = 168 | 0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6; 169 | ``` 170 | 171 | For EIP-712 signature digest calculation. 172 | 173 | `keccak256("1")`. 174 | 175 | 176 | ### Functions 177 | 178 | #### `aggregateWithSigner` 179 | ```solidity 180 | function aggregateWithSigner( 181 | address[] calldata targets, 182 | bytes[] calldata data, 183 | uint256[] calldata values, 184 | uint256 nonceSalt, 185 | address signer, 186 | bytes calldata signature 187 | ) external payable returns (bytes[] memory) 188 | ``` 189 | Aggregates multiple calls in a single transaction. 190 | 191 | This method will set the multicaller signer to the `signer` temporarily for the span of its execution. 192 | 193 | This method does NOT support reentrancy. 194 | 195 | This method does NOT refund any excess ETH in the contract. 196 | 197 | Emits a `NoncesInvalidated(signer, [nonce])` event. 198 | 199 | Returns an array of the returndata from each call. 200 | 201 | #### `invalidateNonces` 202 | ```solidity 203 | function invalidateNonces(uint256[] calldata nonces) external 204 | ``` 205 | 206 | Invalidates the `nonces` of `msg.sender`. 207 | 208 | Emits a `NoncesInvalidated(msg.sender, nonces)` event. 209 | 210 | #### `invalidateNoncesForSigner` 211 | ```solidity 212 | function invalidateNoncesForSigner( 213 | uint256[] calldata nonces, 214 | address signer, 215 | bytes calldata signature 216 | ) external 217 | ``` 218 | 219 | Invalidates the `nonces` of `signer`. 220 | 221 | Emits a `NoncesInvalidated(signer, nonces)` event. 222 | 223 | #### `noncesInvalidated` 224 | ```solidity 225 | function noncesInvalidated(address signer, uint256[] calldata nonces) 226 | external 227 | view 228 | returns (bool[] memory) 229 | ``` 230 | 231 | Returns whether each of the `nonces` of `signer` has been invalidated. 232 | 233 | #### `incrementNonceSalt` 234 | ```solidity 235 | function incrementNonceSalt() external returns (uint256) 236 | ``` 237 | 238 | Increments the nonce salt of `msg.sender`. 239 | 240 | For making all unused signatures with the current nonce salt invalid. 241 | 242 | Will NOT make invalidated nonces available for use. 243 | 244 | Emits a `NonceSaltIncremented(msg.sender, newNonceSalt)` event. 245 | 246 | Returns the new nonce salt. 247 | 248 | #### `incrementNonceSaltForSigner` 249 | ```solidity 250 | function incrementNonceSaltForSigner( 251 | address signer, 252 | bytes calldata signature 253 | ) external returns (uint256) 254 | ``` 255 | 256 | Increments the nonce salt of `signer`. 257 | 258 | For making all unused signatures with the current nonce salt invalid. 259 | 260 | Will NOT make invalidated nonces available for use. 261 | 262 | Emits a `NonceSaltIncremented(signer, newNonceSalt)` event. 263 | 264 | Returns the new nonce salt. 265 | 266 | #### `nonceSaltOf` 267 | ```solidity 268 | function nonceSaltOf(address signer) external view returns (uint256) 269 | ``` 270 | 271 | Returns the nonce salt of `signer`. 272 | 273 | #### `eip712Domain` 274 | ```solidity 275 | function eip712Domain() 276 | external 277 | view 278 | returns ( 279 | bytes1 fields, 280 | string memory name, 281 | string memory version, 282 | uint256 chainId, 283 | address verifyingContract, 284 | bytes32 salt, 285 | uint256[] memory extensions 286 | ) 287 | ``` 288 | 289 | Returns the EIP-712 domain information, as specified in [EIP-5267](https://eips.ethereum.org/EIPS/eip-5267). 290 | 291 | - `fields`: `hex"0f"` (`0b01111`). 292 | - `name`: `"MulticallerWithSigner"`. 293 | - `version`: `"1"`. 294 | - `chainId`: The chain ID which this contract is on. 295 | - `verifyingContract`: `address(this)`, the address of this contract. 296 | - `salt`: `bytes32(0)` (not used). 297 | - `extensions`: `[]` (not used). 298 | 299 | #### `receive` 300 | ```solidity 301 | receive() external payable 302 | ``` 303 | Returns the caller of `aggregateWithSigner` on the contract. 304 | 305 | The value is always the zero address outside a transaction. 306 | 307 | 308 | ## LibMulticaller 309 | 310 | Library to read the multicaller contracts. 311 | 312 | ### Constants 313 | 314 | #### `MULTICALLER` 315 | ```solidity 316 | address internal constant MULTICALLER = 317 | 0x0000000000002Bdbf1Bf3279983603Ec279CC6dF; 318 | ``` 319 | 320 | The address of the multicaller contract. 321 | 322 | #### `MULTICALLER_WITH_SENDER` 323 | ```solidity 324 | address internal constant MULTICALLER_WITH_SENDER = 325 | 0x00000000002Fd5Aeb385D324B580FCa7c83823A0; 326 | ``` 327 | 328 | The address of the multicaller with sender contract. 329 | 330 | #### `MULTICALLER_WITH_SIGNER` 331 | ```solidity 332 | address internal constant MULTICALLER_WITH_SIGNER = 333 | 0x000000000000D9ECebf3C23529de49815Dac1c4c; 334 | ``` 335 | 336 | The address of the multicaller with signer contract. 337 | 338 | ### Functions 339 | 340 | The functions in this library do NOT guard against reentrancy. 341 | 342 | A single transaction can recurse through different Multicallers 343 | (e.g. `MulticallerWithSender -> contract -> MulticallerWithSigner -> contract`). 344 | 345 | Think of these functions like `msg.sender`. 346 | 347 | If your contract `C` can handle reentrancy safely with plain old `msg.sender` for any `A -> C -> B -> C`, you should be fine substituting `msg.sender` with these functions. 348 | 349 | #### `multicallerSender` 350 | ```solidity 351 | function multicallerSender() internal view returns (address) 352 | ``` 353 | Returns the caller of `aggregateWithSender` on the multicaller with sender contract. 354 | 355 | 356 | #### `multicallerSigner` 357 | ```solidity 358 | function multicallerSigner() internal view returns (address) 359 | ``` 360 | Returns the signer of `aggregateWithSigner` on the multicaller with signer contract. 361 | 362 | 363 | #### `sender` 364 | ```solidity 365 | function sender() internal view returns (address result) 366 | ``` 367 | Returns the caller of `aggregateWithSender` on the multicaller with sender contract, if `msg.sender` is the multicaller with sender contract. 368 | 369 | Otherwise, returns `msg.sender`. 370 | 371 | 372 | #### `signer` 373 | ```solidity 374 | function signer() internal view returns (address result) 375 | ``` 376 | Returns the caller of `aggregateWithSigner` on the multicaller with signer contract, if `msg.sender` is the multicaller with signer contract. 377 | 378 | Otherwise, returns `msg.sender`. 379 | 380 | 381 | #### `senderOrSigner` 382 | ```solidity 383 | function senderOrSigner() internal view returns (address result) 384 | ``` 385 | Returns the caller of `aggregateWithSender` on the multicaller with sender contract, if `msg.sender` is the multicaller with sender contract. 386 | 387 | Returns the signer of `aggregateWithSigner` on the multicaller with signer contract, if `msg.sender` is the multicaller with signer contract. 388 | 389 | Otherwise, returns `msg.sender`. 390 | -------------------------------------------------------------------------------- /test/utils/TestPlus.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | contract TestPlus is Test { 7 | /// @dev Fills the memory with junk, for more robust testing of inline assembly 8 | /// which reads/write to the memory. 9 | modifier brutalizeMemory() { 10 | // To prevent a solidity 0.8.13 bug. 11 | // See: https://blog.soliditylang.org/2022/06/15/inline-assembly-memory-side-effects-bug 12 | // Basically, we need to access a solidity variable from the assembly to 13 | // tell the compiler that this assembly block is not in isolation. 14 | { 15 | uint256 zero; 16 | /// @solidity memory-safe-assembly 17 | assembly { 18 | let offset := mload(0x40) // Start the offset at the free memory pointer. 19 | calldatacopy(offset, zero, calldatasize()) 20 | 21 | // Fill the 64 bytes of scratch space with garbage. 22 | mstore(zero, caller()) 23 | mstore(0x20, keccak256(offset, calldatasize())) 24 | mstore(zero, keccak256(zero, 0x40)) 25 | 26 | let r0 := mload(zero) 27 | let r1 := mload(0x20) 28 | 29 | let cSize := add(codesize(), iszero(codesize())) 30 | if iszero(lt(cSize, 32)) { cSize := sub(cSize, and(mload(0x02), 31)) } 31 | let start := mod(mload(0x10), cSize) 32 | let size := mul(sub(cSize, start), gt(cSize, start)) 33 | let times := div(0x7ffff, cSize) 34 | if iszero(lt(times, 128)) { times := 128 } 35 | 36 | // Occasionally offset the offset by a pseudorandom large amount. 37 | // Can't be too large, or we will easily get out-of-gas errors. 38 | offset := add(offset, mul(iszero(and(r1, 0xf)), and(r0, 0xfffff))) 39 | 40 | // Fill the free memory with garbage. 41 | // prettier-ignore 42 | for { let w := not(0) } 1 {} { 43 | mstore(offset, r0) 44 | mstore(add(offset, 0x20), r1) 45 | offset := add(offset, 0x40) 46 | // We use codecopy instead of the identity precompile 47 | // to avoid polluting the `forge test -vvvv` output with tons of junk. 48 | codecopy(offset, start, size) 49 | codecopy(add(offset, size), 0, start) 50 | offset := add(offset, cSize) 51 | times := add(times, w) // `sub(times, 1)`. 52 | if iszero(times) { break } 53 | } 54 | } 55 | } 56 | 57 | _; 58 | 59 | _checkMemory(); 60 | } 61 | 62 | /// @dev Returns a pseudorandom random number from [0 .. 2**256 - 1] (inclusive). 63 | /// For usage in fuzz tests, please ensure that the function has an unnamed uint256 argument. 64 | /// e.g. `testSomething(uint256) public`. 65 | function _random() internal returns (uint256 r) { 66 | /// @solidity memory-safe-assembly 67 | assembly { 68 | // This is the keccak256 of a very long string I randomly mashed on my keyboard. 69 | let sSlot := 0xd715531fe383f818c5f158c342925dcf01b954d24678ada4d07c36af0f20e1ee 70 | let sValue := sload(sSlot) 71 | 72 | mstore(0x20, sValue) 73 | r := keccak256(0x20, 0x40) 74 | 75 | // If the storage is uninitialized, initialize it to the keccak256 of the calldata. 76 | if iszero(sValue) { 77 | sValue := sSlot 78 | let m := mload(0x40) 79 | calldatacopy(m, 0, calldatasize()) 80 | r := keccak256(m, calldatasize()) 81 | } 82 | sstore(sSlot, add(r, 1)) 83 | 84 | // Do some biased sampling for more robust tests. 85 | // prettier-ignore 86 | for {} 1 {} { 87 | let d := byte(0, r) 88 | // With a 1/256 chance, randomly set `r` to any of 0,1,2. 89 | if iszero(d) { 90 | r := and(r, 3) 91 | break 92 | } 93 | // With a 1/2 chance, set `r` to near a random power of 2. 94 | if iszero(and(2, d)) { 95 | // Set `t` either `not(0)` or `xor(sValue, r)`. 96 | let t := xor(not(0), mul(iszero(and(4, d)), not(xor(sValue, r)))) 97 | // Set `r` to `t` shifted left or right by a random multiple of 8. 98 | switch and(8, d) 99 | case 0 { 100 | if iszero(and(16, d)) { t := 1 } 101 | r := add(shl(shl(3, and(byte(3, r), 31)), t), sub(and(r, 7), 3)) 102 | } 103 | default { 104 | if iszero(and(16, d)) { t := shl(255, 1) } 105 | r := add(shr(shl(3, and(byte(3, r), 31)), t), sub(and(r, 7), 3)) 106 | } 107 | // With a 1/2 chance, negate `r`. 108 | if iszero(and(32, d)) { r := not(r) } 109 | break 110 | } 111 | // Otherwise, just set `r` to `xor(sValue, r)`. 112 | r := xor(sValue, r) 113 | break 114 | } 115 | } 116 | } 117 | 118 | /// @dev Returns a random signer and its private key. 119 | function _randomSigner() internal returns (address signer, uint256 privateKey) { 120 | uint256 privateKeyMax = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140; 121 | privateKey = _bound(_random(), 1, privateKeyMax); 122 | signer = vm.addr(privateKey); 123 | } 124 | 125 | /// @dev Rounds up the free memory pointer the next word boundary. 126 | /// Sometimes, some Solidity operations causes the free memory pointer to be misaligned. 127 | function _roundUpFreeMemoryPointer() internal pure { 128 | // To prevent a solidity 0.8.13 bug. 129 | // See: https://blog.soliditylang.org/2022/06/15/inline-assembly-memory-side-effects-bug 130 | // Basically, we need to access a solidity variable from the assembly to 131 | // tell the compiler that this assembly block is not in isolation. 132 | uint256 twoWords = 0x40; 133 | /// @solidity memory-safe-assembly 134 | assembly { 135 | mstore(twoWords, and(add(mload(twoWords), 31), not(31))) 136 | } 137 | } 138 | 139 | /// @dev Misaligns the free memory pointer. 140 | function _misalignFreeMemoryPointer() internal pure { 141 | uint256 twoWords = 0x40; 142 | /// @solidity memory-safe-assembly 143 | assembly { 144 | let m := mload(twoWords) 145 | m := add(m, mul(and(keccak256(0x00, twoWords), 31), iszero(and(m, 31)))) 146 | mstore(twoWords, add(m, iszero(and(m, 31)))) 147 | } 148 | } 149 | 150 | /// @dev Check if the free memory pointer and the zero slot are not contaminated. 151 | /// Useful for cases where these slots are used for temporary storage. 152 | function _checkMemory() internal pure { 153 | bool zeroSlotIsNotZero; 154 | bool freeMemoryPointerOverflowed; 155 | /// @solidity memory-safe-assembly 156 | assembly { 157 | // Test at a lower, but reasonable limit for more safety room. 158 | if gt(mload(0x40), 0xffffffff) { freeMemoryPointerOverflowed := 1 } 159 | // Check the value of the zero slot. 160 | zeroSlotIsNotZero := mload(0x60) 161 | } 162 | if (freeMemoryPointerOverflowed) revert("Free memory pointer overflowed!"); 163 | if (zeroSlotIsNotZero) revert("Zero slot is not zero!"); 164 | } 165 | 166 | /// @dev Check if `s`: 167 | /// - Has sufficient memory allocated. 168 | /// - Is aligned to a word boundary 169 | /// - Is zero right padded (cuz some frontends like Etherscan has issues 170 | /// with decoding non-zero-right-padded strings) 171 | function _checkMemory(bytes memory s) internal pure { 172 | bool notZeroRightPadded; 173 | bool fmpNotWordAligned; 174 | bool insufficientMalloc; 175 | /// @solidity memory-safe-assembly 176 | assembly { 177 | let length := mload(s) 178 | let lastWord := mload(add(add(s, 0x20), and(length, not(31)))) 179 | let remainder := and(length, 31) 180 | if remainder { if shl(mul(8, remainder), lastWord) { notZeroRightPadded := 1 } } 181 | // Check if the free memory pointer is a multiple of 32. 182 | fmpNotWordAligned := and(mload(0x40), 31) 183 | // Write some garbage to the free memory. 184 | mstore(mload(0x40), keccak256(0x00, 0x60)) 185 | // Check if the memory allocated is sufficient. 186 | if length { if gt(add(add(s, 0x20), length), mload(0x40)) { insufficientMalloc := 1 } } 187 | } 188 | if (notZeroRightPadded) revert("Not zero right padded!"); 189 | if (fmpNotWordAligned) revert("Free memory pointer `0x40` not 32-byte word aligned!"); 190 | if (insufficientMalloc) revert("Insufficient memory allocation!"); 191 | _checkMemory(); 192 | } 193 | 194 | /// @dev For checking the memory allocation for string `s`. 195 | function _checkMemory(string memory s) internal pure { 196 | _checkMemory(bytes(s)); 197 | } 198 | 199 | /// @dev Adapted from: 200 | /// https://github.com/foundry-rs/forge-std/blob/ff4bf7db008d096ea5a657f2c20516182252a3ed/src/StdUtils.sol#L10 201 | /// Differentially fuzzed tested against the original implementation. 202 | function _bound(uint256 x, uint256 min, uint256 max) 203 | internal 204 | pure 205 | virtual 206 | returns (uint256 result) 207 | { 208 | require(min <= max, "_bound(uint256,uint256,uint256): Max is less than min."); 209 | 210 | /// @solidity memory-safe-assembly 211 | assembly { 212 | // prettier-ignore 213 | for {} 1 {} { 214 | // If `x` is between `min` and `max`, return `x` directly. 215 | // This is to ensure that dictionary values 216 | // do not get shifted if the min is nonzero. 217 | // More info: https://github.com/foundry-rs/forge-std/issues/188 218 | if iszero(or(lt(x, min), gt(x, max))) { 219 | result := x 220 | break 221 | } 222 | 223 | let size := add(sub(max, min), 1) 224 | if and(iszero(gt(x, 3)), gt(size, x)) { 225 | result := add(min, x) 226 | break 227 | } 228 | 229 | let w := not(0) 230 | if and(iszero(lt(x, sub(0, 4))), gt(size, sub(w, x))) { 231 | result := sub(max, sub(w, x)) 232 | break 233 | } 234 | 235 | // Otherwise, wrap x into the range [min, max], 236 | // i.e. the range is inclusive. 237 | if iszero(lt(x, max)) { 238 | let d := sub(x, max) 239 | let r := mod(d, size) 240 | if iszero(r) { 241 | result := max 242 | break 243 | } 244 | result := add(add(min, r), w) 245 | break 246 | } 247 | let d := sub(min, x) 248 | let r := mod(d, size) 249 | if iszero(r) { 250 | result := min 251 | break 252 | } 253 | result := add(sub(max, r), 1) 254 | break 255 | } 256 | } 257 | } 258 | 259 | /// @dev Deploys a contract via 0age's immutable create 2 factory for testing. 260 | function _safeCreate2(uint256 payableAmount, bytes32 salt, bytes memory initializationCode) 261 | internal 262 | returns (address deploymentAddress) 263 | { 264 | // Canonical address of 0age's immutable create 2 factory. 265 | address c2f = 0x0000000000FFe8B47B3e2130213B802212439497; 266 | if (c2f.code.length == 0) { 267 | vm.etch( 268 | c2f, 269 | hex"60806040526004361061003f5760003560e01c806308508b8f1461004457806364e030871461009857806385cf97ab14610138578063a49a7c90146101bc575b600080fd5b34801561005057600080fd5b506100846004803603602081101561006757600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166101ec565b604080519115158252519081900360200190f35b61010f600480360360408110156100ae57600080fd5b813591908101906040810160208201356401000000008111156100d057600080fd5b8201836020820111156100e257600080fd5b8035906020019184600183028401116401000000008311171561010457600080fd5b509092509050610217565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b34801561014457600080fd5b5061010f6004803603604081101561015b57600080fd5b8135919081019060408101602082013564010000000081111561017d57600080fd5b82018360208201111561018f57600080fd5b803590602001918460018302840111640100000000831117156101b157600080fd5b509092509050610592565b3480156101c857600080fd5b5061010f600480360360408110156101df57600080fd5b508035906020013561069e565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205460ff1690565b600083606081901c33148061024c57507fffffffffffffffffffffffffffffffffffffffff0000000000000000000000008116155b6102a1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260458152602001806107746045913960600191505060405180910390fd5b606084848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920182905250604051855195965090943094508b93508692506020918201918291908401908083835b6020831061033557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016102f8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff018019909216911617905260408051929094018281037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00183528085528251928201929092207fff000000000000000000000000000000000000000000000000000000000000008383015260609890981b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016602183015260358201969096526055808201979097528251808203909701875260750182525084519484019490942073ffffffffffffffffffffffffffffffffffffffff81166000908152938490529390922054929350505060ff16156104a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603f815260200180610735603f913960400191505060405180910390fd5b81602001825188818334f5955050508073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161461053a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260468152602001806107b96046913960600191505060405180910390fd5b50505073ffffffffffffffffffffffffffffffffffffffff8116600090815260208190526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790559392505050565b6000308484846040516020018083838082843760408051919093018181037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001825280845281516020928301207fff000000000000000000000000000000000000000000000000000000000000008383015260609990991b7fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166021820152603581019790975260558088019890985282518088039098018852607590960182525085519585019590952073ffffffffffffffffffffffffffffffffffffffff81166000908152948590529490932054939450505060ff909116159050610697575060005b9392505050565b604080517fff000000000000000000000000000000000000000000000000000000000000006020808301919091523060601b6021830152603582018590526055808301859052835180840390910181526075909201835281519181019190912073ffffffffffffffffffffffffffffffffffffffff81166000908152918290529190205460ff161561072e575060005b9291505056fe496e76616c696420636f6e7472616374206372656174696f6e202d20636f6e74726163742068617320616c7265616479206265656e206465706c6f7965642e496e76616c69642073616c74202d206669727374203230206279746573206f66207468652073616c74206d757374206d617463682063616c6c696e6720616464726573732e4661696c656420746f206465706c6f7920636f6e7472616374207573696e672070726f76696465642073616c7420616e6420696e697469616c697a6174696f6e20636f64652ea265627a7a723058202bdc55310d97c4088f18acf04253db593f0914059f0c781a9df3624dcef0d1cf64736f6c634300050a0032" 270 | ); 271 | } 272 | /// @solidity memory-safe-assembly 273 | assembly { 274 | let m := mload(0x40) 275 | let n := mload(initializationCode) 276 | mstore(m, 0x64e03087) // `safeCreate2(bytes32,bytes)`. 277 | mstore(add(m, 0x20), salt) 278 | mstore(add(m, 0x40), 0x40) 279 | mstore(add(m, 0x60), n) 280 | // prettier-ignore 281 | for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { 282 | mstore(add(add(m, 0x80), i), mload(add(add(initializationCode, 0x20), i))) 283 | } 284 | if iszero(call(gas(), c2f, payableAmount, add(m, 0x1c), add(n, 0x64), m, 0x20)) { 285 | returndatacopy(m, m, returndatasize()) 286 | revert(m, returndatasize()) 287 | } 288 | deploymentAddress := mload(m) 289 | } 290 | } 291 | 292 | /// @dev Deploys a contract via 0age's immutable create 2 factory for testing. 293 | function _safeCreate2(bytes32 salt, bytes memory initializationCode) 294 | internal 295 | returns (address deploymentAddress) 296 | { 297 | deploymentAddress = _safeCreate2(0, salt, initializationCode); 298 | } 299 | 300 | /// @dev This function will make forge's gas output display the approximate codesize of 301 | /// the test contract as the amount of gas burnt. Useful for quick guess checking if 302 | /// certain optimizations actually compiles to similar bytecode. 303 | function test__codesize() external view { 304 | /// @solidity memory-safe-assembly 305 | assembly { 306 | // If the caller is the contract itself (i.e. recursive call), burn all the gas. 307 | if eq(caller(), address()) { invalid() } 308 | mstore(0x00, 0xf09ff470) // Store the function selector of `test__codesize()`. 309 | pop(staticcall(codesize(), address(), 0x1c, 0x04, 0x00, 0x00)) 310 | } 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /src/MulticallerEtcher.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import "./Multicaller.sol"; 5 | import "./MulticallerWithSender.sol"; 6 | import "./MulticallerWithSigner.sol"; 7 | import "./LibMulticaller.sol"; 8 | 9 | /** 10 | * @title LibMulticaller 11 | * @author vectorized.eth 12 | * @notice Library for etching the multicaller contracts for testing in Foundry forge. 13 | * It uses the VM cheatcodes to etch the multicaller contracts and initialize their storage. 14 | * Not to be used on an actual EVM chain. 15 | */ 16 | library MulticallerEtcher { 17 | // ============================================================= 18 | // CONSTANTS 19 | // ============================================================= 20 | 21 | /** 22 | * @dev The initcode for the multicaller. 23 | */ 24 | bytes internal constant MULTICALLER_INITCODE = 25 | hex"60808060405234610016576102b9908161001c8239f35b600080fdfe60806040526004361015610015575b366101fd57005b6000803560e01c63991f255f1461002c575061000e565b60803660031901126100aa5767ffffffffffffffff6004358181116100b5576100599036906004016100b9565b916024358181116100b1576100729036906004016100b9565b916044359081116100ad5761008b9036906004016100b9565b6064359690959194906001600160a01b03881688036100aa57506100ef565b80fd5b8580fd5b8480fd5b8280fd5b9181601f840112156100ea5782359167ffffffffffffffff83116100ea576020808501948460051b0101116100ea57565b600080fd5b959390949295606093871487871416156101f0578693604097610166575b505050505080610125575b5060206000526020526000f35b47156101185733811860018214021860003881804785620186a0f1610118576000526073600b5360ff6020536016600b47f0156101625738610118565b3838fd5b8794919395979160051b9384878737848601945b835188019087810182359081602080950182376000808093838a8c603f19918291010135908c8b0101355af1156101e7578287523d90523d908583013e603f601f19913d010116930196898689146101d45750969261017a565b975050505091505001923880808061010d565b503d81803e3d90fd5b633b800a463d526004601cfd5b3d356366e0daa08160e01c14610211573d3dfd5b193d5260043d815b36811061023a57600080808581305af43d82803e15610236573d90f35b3d90fd5b8035821a92600180920193801561025757815301905b9091610219565b503d19815283820193607f90353d1a81811115610278575b16010190610250565b83810138843961026f56fea26469706673582212200dfa3a85cbd068a99fd4d5051615c4bde5995f9e1dd4a095bb55fc5af681c44064736f6c63430008120033"; 26 | 27 | /** 28 | * @dev The salt for the multicaller to be deployed via 29 | * 0age's immutable create2 factory. 30 | */ 31 | bytes32 internal constant MULTICALLER_CREATE2_SALT = 32 | 0x0000000000000000000000000000000000000000ef4834b251a91000a916248a; 33 | 34 | /** 35 | * @dev The initcode for the multicaller with sender. 36 | */ 37 | bytes internal constant MULTICALLER_WITH_SENDER_INITCODE = 38 | hex"60806040819052600160a01b3d55610247908161001a8239f3fe60406080815260049081361015610023575b5050361561001e57600080fd5b6101f2565b600091823560e01c63d985f1e81461003b5750610011565b606090817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b85767ffffffffffffffff81358181116101b45761008690369084016101bc565b916024358181116101b05761009e90369086016101bc565b9590936044359283116101ac576100b98793369088016101bc565b9390938114911416156101a0577401000000000000000000000000000000000000000094853d5416156101955750602090813d52868252861561019157333d55929560051b93919287929185838537858901955b84518401988b80848d85019c8d81359283920190378c8a3585355af115610188577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe091838080603f9401990197828152019a3d90523d8d8683013e3d010116928689101561017f57929793949761010d565b878b55838a018bf35b8b3d81803e3d90fd5b873df35b63ab143c063d52601cfd5b84633b800a463d52601cfd5b8880fd5b8780fd5b8580fd5b8380fd5b9181601f840112156101ed5782359167ffffffffffffffff83116101ed576020808501948460051b0101116101ed57565b600080fd5b3d5473ffffffffffffffffffffffffffffffffffffffff163d5260203df3fea2646970667358221220802fc1f04a279628c77438e5942439f44c7eaf734a7dca754fef889a35be139764736f6c63430008120033"; 39 | 40 | /** 41 | * @dev The salt for the multicaller with sender to be deployed via 42 | * 0age's immutable create2 factory. 43 | */ 44 | bytes32 internal constant MULTICALLER_WITH_SENDER_CREATE2_SALT = 45 | 0x00000000000000000000000000000000000000006bfa48b413e5be01a8e9fe0c; 46 | 47 | /** 48 | * @dev The initcode for the multicaller with signer. 49 | */ 50 | bytes internal constant MULTICALLER_WITH_SIGNER_INITCODE = 51 | hex"60808060405260013d55610b6d90816100168239f3fe6040608081526004361015610020575b50361561001b57600080fd5b610839565b6000803560e01c91826317447cf1146100aa57505080632eb48a80146100a55780633aeb2206146100a057806356b1a87f1461009b57806384b0196e1461009657806387ec11ca14610091578063ad3aacb81461008c5763f0c60f1a14610087573861000f565b6107e3565b610753565b610507565b6104b2565b61028f565b61023e565b6101a3565b346101275780600319360112610127576100c261012b565b9060243567ffffffffffffffff8111610123576100e3903690600401610172565b909284528060051b92845b848103610102575050602084526020520190f35b80602091830135808352603f8820549060ff161c60011681860152016100ee565b8380fd5b5080fd5b600435906001600160a01b038216820361014157565b600080fd5b602435906001600160a01b038216820361014157565b608435906001600160a01b038216820361014157565b9181601f840112156101415782359167ffffffffffffffff8311610141576020808501948460051b01011161014157565b34610141576020806003193601126101415760043567ffffffffffffffff8111610141576101d5903690600401610172565b6000913383528160051b91835b83810361021f5750848495849552526040377fc45e3a0dd412bcad8d62398d74d66b1c8449f38beb10da275e4da0c6d3a811a4339160400183a280f35b8086918401358083526001603f88209160ff161b8154179055016101e2565b346101415760203660031901126101415761025761012b565b3001543d5260203df35b9181601f840112156101415782359167ffffffffffffffff8311610141576020838186019501011161014157565b3461014157604080600319360112610141576102a961012b565b602491823567ffffffffffffffff8111610141576102cc60049136908301610261565b85929192308601948554947ffa181078c7d1d4d369301511d3c5611e9367d0cebbf65eefdee9dfc75849c1d33d526020988991898352878452606095863d2085527f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f87528360807f301013e8a31863902646dc218ecd889c37491c2967a8104d5ff1cf42af0f9ea481527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660a0524660c0523060e05260a0892082526119013d526042601e209887841461045b575b506041831461041d575b5050630b135d3f60e11b94600097868952895283528060445280606492833701858a5afa91511416156104125750506001905b60001943014060e01c01018091556000527f997a42216df16c8b9e7caf2fc71c59dba956f1f2b12320f87a80a5879464217d826000a26000f35b638baa579f9052601cfd5b9091929350873d528284873780513d1a82526001906000825afa518a183d15171561044c5790849183386103a5565b505050505050506001906103d8565b3d8a90528685013560ff81901c601b018852853589526001600160ff1b0316905292935090919050836001826000825afa518b183d1517156104a25790838693923861039b565b50505050505050506001906103d8565b3461014157600036600319011261014157600f3d5360e060205275154d756c746963616c6c6572576974685369676e657260f55261012060405261013161012152466060523060805261016060c0526101803df35b346101415760608060031936011261014157600467ffffffffffffffff81358181116101415761053a9036908401610172565b929091610545610146565b9260449182359081116101415761055f9036908501610261565b9390968660051b947f12b047058eea3df4085cdc159a103d9c100c4e78cfb7029cc39d02cb8b9e48f53d52602098878a5289604096888789378888208852308a01548552816080803d208a527f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f87527f301013e8a31863902646dc218ecd889c37491c2967a8104d5ff1cf42af0f9ea481527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660a0524660c0523060e05260a0872082526119013d526042601e20968a8714610708575b50604186146106d6575b5050630b135d3f60e11b9260009584875287528460249586938b85525280606492833701858b5afa91511416156104125750505b600094848652855b8481036106b757509083918787987fc45e3a0dd412bcad8d62398d74d66b1c8449f38beb10da275e4da0c6d3a811a49798525282370183a280f35b8088918401358083526001603f8a209160ff161b81541790550161067c565b863d5285858b3780513d1a82526001906000825afa518a183d1517156106fd578138610640565b505050505050610674565b3d8890528585013560ff81901c601b01865286358c526001600160ff1b031690529050826001826000825afa518b183d15171561074757829038610636565b50505050505050610674565b60c03660031901126101415767ffffffffffffffff60043581811161014157610780903690600401610172565b90916024358181116101415761079a903690600401610172565b604494919435838111610141576107b5903690600401610172565b916107be61015c565b9460a435908111610141576107d7903690600401610261565b97909660643595610843565b3461014157600080600319360112610836576020903033016001815460001943014060e01c01018091558152337f997a42216df16c8b9e7caf2fc71c59dba956f1f2b12320f87a80a5879464217d8383a2f35b80fd5b3d54600c5260203df35b969893949195929790976060928114818a141615610b2a573d5460011615610b1d5760051b9384863d37843d20913d5b868103610b015750853d20868a3d37863d207ffb989fd34c8af81a76f18167f528fc7315f92cacc19a0e63215abd54633f8a283d528c602052604052845260809283528460a052308b015460c05260e03d206040527f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f84527f301013e8a31863902646dc218ecd889c37491c2967a8104d5ff1cf42af0f9ea483527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660a0524660c0523060e05260a084206020526119013d526042601e209260408314610ac2575b60418314610a91575b50630b135d3f60e11b602060009382855260049586528d856024958693604085528060445280606492833701915afa91511416156104125750505b8760205281604052603f918260202090600182549160ff161b90808216610a8357179055600097602089526001602052807fc45e3a0dd412bcad8d62398d74d66b1c8449f38beb10da275e4da0c6d3a811a4838ba28315610a7a5781969395961b885587604093868860403786604001965b8551890190838060206040840194803591829101863784603f198b8d0181013590888d0101355af115610a71576020918188523d90523d848683013e85601f19913d010116950197878914610a61579794610a0b565b826040878c602052600183550190f35b833d81803e3d90fd5b60408985602052f35b638baa579f6000526004601cfd5b6001602091853d52848460403780513d1a83526000825afa518b183d151715610aba573861095e565b505050610999565b3d84905260208281013560ff81901c601b01825283356040526001600160ff1b031686526001826000825afa518c183d15176109555750505050610999565b80602080928b01358b0180359182910183378120815201610873565b63ab143c063d526004601cfd5b633b800a463d526004601cfdfea2646970667358221220b683a260ba0dddc73675b5b1b1f3f56075ddbb13397d349dfdb5e7fc3e1e3bb164736f6c63430008120033"; 52 | 53 | /** 54 | * @dev The salt for the multicaller with signer to be deployed via 55 | * 0age's immutable create2 factory. 56 | */ 57 | bytes32 internal constant MULTICALLER_WITH_SIGNER_CREATE2_SALT = 58 | 0x0000000000000000000000000000000000000000d7eebd756f8ae3022dc33bdb; 59 | 60 | // ============================================================= 61 | // OPERATIONS 62 | // ============================================================= 63 | 64 | /** 65 | * @dev Returns the multicaller. 66 | */ 67 | function multicaller() internal returns (Multicaller deployment) { 68 | address expectedDeployment = LibMulticaller.MULTICALLER; 69 | if (_extcodesize(expectedDeployment) == 0) { 70 | bytes32 salt = MULTICALLER_CREATE2_SALT; 71 | address d = _safeCreate2(salt, MULTICALLER_INITCODE); 72 | require(d == expectedDeployment, "Unable to etch Multicaller."); 73 | deployment = Multicaller(payable(d)); 74 | } 75 | } 76 | 77 | /** 78 | * @dev Returns the multicaller with sender. 79 | */ 80 | function multicallerWithSender() internal returns (MulticallerWithSender deployment) { 81 | address expectedDeployment = LibMulticaller.MULTICALLER_WITH_SENDER; 82 | if (_extcodesize(expectedDeployment) == 0) { 83 | bytes32 salt = MULTICALLER_WITH_SENDER_CREATE2_SALT; 84 | address d = _safeCreate2(salt, MULTICALLER_WITH_SENDER_INITCODE); 85 | require(d == expectedDeployment, "Unable to etch MulticallerWithSender."); 86 | deployment = MulticallerWithSender(payable(d)); 87 | } 88 | } 89 | 90 | /** 91 | * @dev Returns the multicaller with signer. 92 | */ 93 | function multicallerWithSigner() internal returns (MulticallerWithSigner deployment) { 94 | address expectedDeployment = LibMulticaller.MULTICALLER_WITH_SIGNER; 95 | if (_extcodesize(expectedDeployment) == 0) { 96 | bytes32 salt = MULTICALLER_WITH_SIGNER_CREATE2_SALT; 97 | address d = _safeCreate2(salt, MULTICALLER_WITH_SIGNER_INITCODE); 98 | require(d == expectedDeployment, "Unable to etch MulticallerWithSigner."); 99 | deployment = MulticallerWithSigner(payable(d)); 100 | } 101 | } 102 | 103 | // ============================================================= 104 | // PRIVATE HELPERS 105 | // ============================================================= 106 | 107 | /** 108 | * @dev Deploys a contract via 0age's immutable create 2 factory for testing. 109 | */ 110 | function _safeCreate2(bytes32 salt, bytes memory initializationCode) 111 | private 112 | returns (address deployment) 113 | { 114 | // Canonical address of 0age's immutable create 2 factory. 115 | address c2f = 0x0000000000FFe8B47B3e2130213B802212439497; 116 | if (_extcodesize(c2f) == 0) { 117 | bytes memory ic2fBytecode = 118 | hex"60806040526004361061003f5760003560e01c806308508b8f1461004457806364e030871461009857806385cf97ab14610138578063a49a7c90146101bc575b600080fd5b34801561005057600080fd5b506100846004803603602081101561006757600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166101ec565b604080519115158252519081900360200190f35b61010f600480360360408110156100ae57600080fd5b813591908101906040810160208201356401000000008111156100d057600080fd5b8201836020820111156100e257600080fd5b8035906020019184600183028401116401000000008311171561010457600080fd5b509092509050610217565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b34801561014457600080fd5b5061010f6004803603604081101561015b57600080fd5b8135919081019060408101602082013564010000000081111561017d57600080fd5b82018360208201111561018f57600080fd5b803590602001918460018302840111640100000000831117156101b157600080fd5b509092509050610592565b3480156101c857600080fd5b5061010f600480360360408110156101df57600080fd5b508035906020013561069e565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205460ff1690565b600083606081901c33148061024c57507fffffffffffffffffffffffffffffffffffffffff0000000000000000000000008116155b6102a1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260458152602001806107746045913960600191505060405180910390fd5b606084848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920182905250604051855195965090943094508b93508692506020918201918291908401908083835b6020831061033557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016102f8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff018019909216911617905260408051929094018281037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00183528085528251928201929092207fff000000000000000000000000000000000000000000000000000000000000008383015260609890981b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016602183015260358201969096526055808201979097528251808203909701875260750182525084519484019490942073ffffffffffffffffffffffffffffffffffffffff81166000908152938490529390922054929350505060ff16156104a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603f815260200180610735603f913960400191505060405180910390fd5b81602001825188818334f5955050508073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161461053a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260468152602001806107b96046913960600191505060405180910390fd5b50505073ffffffffffffffffffffffffffffffffffffffff8116600090815260208190526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790559392505050565b6000308484846040516020018083838082843760408051919093018181037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001825280845281516020928301207fff000000000000000000000000000000000000000000000000000000000000008383015260609990991b7fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166021820152603581019790975260558088019890985282518088039098018852607590960182525085519585019590952073ffffffffffffffffffffffffffffffffffffffff81166000908152948590529490932054939450505060ff909116159050610697575060005b9392505050565b604080517fff000000000000000000000000000000000000000000000000000000000000006020808301919091523060601b6021830152603582018590526055808301859052835180840390910181526075909201835281519181019190912073ffffffffffffffffffffffffffffffffffffffff81166000908152918290529190205460ff161561072e575060005b9291505056fe496e76616c696420636f6e7472616374206372656174696f6e202d20636f6e74726163742068617320616c7265616479206265656e206465706c6f7965642e496e76616c69642073616c74202d206669727374203230206279746573206f66207468652073616c74206d757374206d617463682063616c6c696e6720616464726573732e4661696c656420746f206465706c6f7920636f6e7472616374207573696e672070726f76696465642073616c7420616e6420696e697469616c697a6174696f6e20636f64652ea265627a7a723058202bdc55310d97c4088f18acf04253db593f0914059f0c781a9df3624dcef0d1cf64736f6c634300050a0032"; 119 | /// @solidity memory-safe-assembly 120 | assembly { 121 | let m := mload(0x40) 122 | mstore(m, 0xb4d6c782) // `etch(address,bytes)`. 123 | mstore(add(m, 0x20), c2f) 124 | mstore(add(m, 0x40), 0x40) 125 | let n := mload(ic2fBytecode) 126 | mstore(add(m, 0x60), n) 127 | for { let i := 0 } lt(i, n) { i := add(0x20, i) } { 128 | mstore(add(add(m, 0x80), i), mload(add(add(ic2fBytecode, 0x20), i))) 129 | } 130 | let vmAddress := 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D 131 | if iszero(call(gas(), vmAddress, 0, add(m, 0x1c), add(n, 0x64), 0x00, 0x00)) { 132 | revert(0, 0) 133 | } 134 | } 135 | } 136 | /// @solidity memory-safe-assembly 137 | assembly { 138 | let m := mload(0x40) 139 | let n := mload(initializationCode) 140 | mstore(m, 0x64e03087) // `safeCreate2(bytes32,bytes)`. 141 | mstore(add(m, 0x20), salt) 142 | mstore(add(m, 0x40), 0x40) 143 | mstore(add(m, 0x60), n) 144 | // prettier-ignore 145 | for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { 146 | mstore(add(add(m, 0x80), i), mload(add(add(initializationCode, 0x20), i))) 147 | } 148 | if iszero(call(gas(), c2f, 0, add(m, 0x1c), add(n, 0x64), m, 0x20)) { 149 | returndatacopy(m, m, returndatasize()) 150 | revert(m, returndatasize()) 151 | } 152 | deployment := mload(m) 153 | } 154 | } 155 | 156 | /** 157 | * @dev Returns the extcodesize of `deployment`. 158 | */ 159 | function _extcodesize(address deployment) private view returns (uint256 result) { 160 | /// @solidity memory-safe-assembly 161 | assembly { 162 | result := extcodesize(deployment) 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /audits/0xphaze_review.md: -------------------------------------------------------------------------------- 1 | Original at: https://gist.github.com/0xPhaze/911518718a8c69d345954f8096d2c598 2 | 3 | # General 4 | 5 | No major vulnerabilities were identified. 6 | 7 | # Scope 8 | 9 | Review for [Vectorized/multicaller commit 0d5cb01](https://github.com/Vectorized/multicaller/commit/0d5cb01764404f94bf237459ef41cff1aa391fb2) 10 | 11 | ``` 12 | src 13 | ├── LibMulticaller.sol 14 | ├── Multicaller.sol 15 | ├── MulticallerSolidity.sol 16 | ├── MulticallerWithSender.sol 17 | └── MulticallerWithSigner.sol 18 | ``` 19 | 20 | # Developer Integration Notes 21 | 22 | Some developer integration notes and considerations could be further highlighted in the documentation. 23 | 24 | - Leftover Ether resting in the Multicaller contracts is claimable by anyone. 25 | - Multicalls are atomic: any revert in one of the calls reverts the entire transaction. 26 | - Multicall contracts do not enforce a minimum gas amount for calls. 27 | - Make sure the Multicall contracts are deployed for your chain. 28 | - Multicall contracts should not be trusted with token approvals. 29 | - Multicall allows receiving Ether without any functionality. 30 | - Multicall provides a `refundTo` address to which any leftover Ether is sent after a multicall is performed. MulticallerWithSender and MulticallerWithSigner do not have this option and must ensure correct accounting. 31 | - The call to `refundTo` is made with `100_000` gas, if this fails, a forced transfer via `selfdestruct`/`sendall` is made 32 | - The Multicall contracts do not respect Solidity's memory model, however, this is not an issue if they remain self-contained external functions 33 | 34 | # Notable Multicaller contract differences 35 | 36 | | Contract | Provides secured `sender`/`signer` | Calldata compression | Refund address | 37 | | :--------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------: | :------------------: | :------------: | 38 | | [Multicaller](https://github.com/Vectorized/multicaller/blob/0d5cb01764404f94bf237459ef41cff1aa391fb2/src/Multicaller.sol) | NO | YES | YES | 39 | | [MulticallerWithSender](https://github.com/Vectorized/multicaller/blob/0d5cb01764404f94bf237459ef41cff1aa391fb2/src/MulticallerWithSender.sol) | YES | NO | NO | 40 | | [MulticallerWithSigner](https://github.com/Vectorized/multicaller/blob/0d5cb01764404f94bf237459ef41cff1aa391fb2/src/MulticallerWithSigner.sol) | YES | NO | NO | 41 | 42 | - Multicaller's usability is restricted to general calls that do not require special access control, as it does not provide a guarded `sender` to the target contract which could be used for access control checks 43 | - If access control is required, this could be achieved via `tx.origin` checks, however these are generally discouraged due to phishing attacks 44 | - Calldata compression is only added to Multicaller, as the others have already been deployed in production 45 | - The `refundTo` address was not deemed necessary or added too late (further clarification required?) 46 | 47 | --- 48 | 49 | ### Cross-contract "re-entrancy" could break security assumptions when relying on `LibMulticaller.senderOrSigner` 50 | 51 | **Severity:** Undetermined 52 | **Likelihood:** Low 53 | **Files:** 54 | [LibMulticaller](src/LibMulticaller.sol), 55 | [MulticallerWithSender](https://github.com/Vectorized/multicaller/blob/0d5cb01764404f94bf237459ef41cff1aa391fb2/src/MulticallerWithSender.sol), 56 | [MulticallerWithSigner](https://github.com/Vectorized/multicaller/blob/0d5cb01764404f94bf237459ef41cff1aa391fb2/src/MulticallerWithSigner.sol) 57 | 58 | `MulticallerWithSender` and `MulticallerWithSigner` contain re-entrancy guards to protect the `sender`/`signer` variable. These disallow re-entering the Multicall contracts that were initially called, however the re-entrancy guards do not apply to both contracts at the same time potentially allowing calls to re-enter the contract using `LibMulticaller.senderOrSigner` through the other Multicall contract. The affected contract might operate under the assumption that the `sender`/`signer` variables cannot equal the currently executing contract. 59 | 60 | _Note:_ Any example scenarios of a contract relying on the assumption that the Multicaller contract cannot be re-entered for security purposes seem very unlikely. 61 | 62 | **Exploit Scenario:** 63 | A governance contract is responsible for maintaining and upgrading a bridge contract. Arbitrary calls can be executed immediately from a team controlled vault and admin account which is only allowed to call `MulticallerWithSigner` in order to batch process calls. Any critical operations directed towards the governance contract itself or the bridge contract are forbidden for the admin (`require(target != governance && target != bridge)`). These require an on-chain vote and a 7 day time delay to pass, allowing users to withdraw their funds before the proposals pass. After the 7 days, anyone can execute the call from the timelock contract which is routed through the governance contract allowing unconstrained calls. The governance contract checks access control via `LibMulticaller.senderOrSigner()`. 64 | 65 | A rogue team member sends a transaction from the admin vault to `MulticallerWithSigner` which, in turn, calls the governance contract (`LibMulticaller.senderOrSigner() == admin`). The call then calls `MulticallerWithSender`, which then calls back into the governance contract (`LibMulticaller.senderOrSigner() == governance`). This is possible because the re-entrancy lock does not apply to both contracts at once. The call then upgrades the bridge contract to allow the rogue team member to withdraw all the locked funds bypassing the 7 day time window. 66 | 67 | **Mitigation:** 68 | Consider combining the logic of `MulticallerWithSender` and `MulticallerWithSigner` into one contract. Alternatively (not recommended), when retrieving the `sender`/`signer` from the Multicall contracts, check the re-entrancy guard of the other. At a minimum, highlight in the documentation that additional re-entrancy guards should be considered for developers when checking access via `LibMulticaller.senderOrSigner` or recommend that only one or the other (`sender` xor `signer`) should be used for contracts with complex access control requirements. 69 | 70 | **Response:** 71 | 72 | > We will make a comment that `LibMulticaller.senderOrSigner` does not offer re-entrancy protection. 73 | > Users should add an re-entrancy guard on functions using `LibMulticaller.senderOrSigner`, if necessary. 74 | 75 | --- 76 | 77 | ### Decompress method differs from `LibZip.cdDecompress` 78 | 79 | **Severity:** Low 80 | **Likelihood:** Low 81 | **Files:** [Multicaller](https://github.com/Vectorized/multicaller/blob/0d5cb01764404f94bf237459ef41cff1aa391fb2/src/Multicaller.sol), 82 | 83 | `LibZip.cdDecompress` differs slightly from the way calldata is decompressed in Multicaller's fallback function. The Javascript implementation will decompress a run-length encoded value `0x00ff` as the byte `0xff` repeated `128` (`0x80 = 0x7f & 0xff + 0x01`) times. The Solidity implementation will decode `0x00ff` as `0xff` repeated `32` (`0x20`) times followed by `96` (`0x60`) zero bytes `0x00`. 84 | 85 | [solady.js](https://github.com/Vectorized/solady/blob/ecee8d989eb869420a4ddfc82155531c8cc2b809/js/solady.js#L184C1-L206C6) 86 | 87 | ```js 88 | /** 89 | * Decompresses hex encoded calldata. 90 | * @param {string} data A hex encoded string representing the compressed data. 91 | * @returns {string} The decompressed result as a hex encoded string. 92 | */ 93 | LibZip.cdDecompress = function (data) { 94 | data = hexString(data); 95 | var o = "0x", 96 | i = 0, 97 | c, 98 | s; 99 | 100 | while (i < data.length) { 101 | c = ((i < 4 * 2) * 0xff) ^ parseByte(data, i); 102 | i += 2; 103 | if (!c) { 104 | c = ((i < 4 * 2) * 0xff) ^ parseByte(data, i); 105 | s = (c & 0x7f) + 1; 106 | i += 2; 107 | while (s--) o += byteToString((c >> 7) * 0xff); 108 | continue; 109 | } 110 | o += byteToString(c); 111 | } 112 | return o; 113 | }; 114 | ``` 115 | 116 | This is not an issue for data encoded via `LibZip.cdCompress`, as the maximum length (`32`) of `0xff` bytes is handled correctly and only applies to compressed calldata which has been formed deliberately. 117 | 118 | [solady.js](https://github.com/Vectorized/solady/blob/ecee8d989eb869420a4ddfc82155531c8cc2b809/js/solady.js#L165C1-L174C14) 119 | 120 | ```js 121 | if (!c) { 122 | if (y) rle(1, y), y = 0; 123 | if (++z === 0x80) rle(0, 0x80), z = 0; 124 | continue; 125 | } 126 | if (c === 0xff) { 127 | if (z) rle(0, z), z = 0; 128 | if (++y === 0x20) rle(1, 0x20), y = 0; 129 | continue; 130 | } 131 | ``` 132 | 133 | However, this could mean that front-end applications might not display the decoded calldata correctly in such a scenario where it is crafted by hand. 134 | 135 | **Exploit Scenario:** 136 | In a future where calldata compression is extended to be included in `MulicallerWithSender`, it is conceivable that some multisig or governance contracts might integrate `MulticallerWithSender` and `solady.js`. A malicious actor could then propose a transaction with hand crafted values hiding its true intentions. For example, a front-end application using `solady.js` would 137 | decode the compressed calldata `0xff80` as `0xffffff... 0xffffff... 0xffffff...`, whereas, the actual calldata executed on-chain will end up being `0xffffff... 0x000000... 0x000000...`. 138 | 139 | **Response:** 140 | Acknowledged and fixed in [PR 559](https://github.com/Vectorized/solady/pull/559/files). 141 | 142 | ### `aggregateWithSigner` allows control over forwarded gas 143 | 144 | **Severity:** Low 145 | **Likelihood:** Low 146 | **Files:** [MulticallerWithSigner](https://github.com/Vectorized/multicaller/blob/0d5cb01764404f94bf237459ef41cff1aa391fb2/src/MulticallerWithSigner.sol) 147 | 148 | `MulticallerWithSigner`'s `aggregateWithSigner` function can be executed by anyone given a valid signature. The Multicall contracts do not enforce a minimum required amount of gas to be supplied with the calls. This opens up the ability for another user to control the forwarded gas amount. Some smart contracts define different behavior depending on the available gas. Examples are messaging protocols that include try-catch statements and store failed messages to be re-executed later. Combined with other protocols that require a certain minimum amount of gas to be forwarded (or with the 63/64 rule for transactions that use a lot of gas), this could allow another user to control the execution flow by front-running a transaction and modifying the `tx.gas` value. 149 | 150 | **Exploit Scenario:** 151 | A messaging protocol integrates `MulticallerWithSigner` Eve, a malicious actor, front-runs a transaction and is able to override the supplied gas values. This changes the transactions execution behavior. 152 | 153 | **Mitigation:** 154 | Consider including `uint256 gas` as a parameter (part of the signature) to ensure a necessary minimum amount of gas is available 155 | 156 | **Response:** 157 | 158 | > This is a limitation with the current approach we are willing to accept. 159 | 160 | 176 | 177 | --- 178 | 179 | ### Missing implementation for `LibMulticaller.signer()` 180 | 181 | **Severity:** Informational 182 | **Files:** [LibMulticaller](src/LibMulticaller.sol) 183 | 184 | `LibMulticaller` does not contain a method for retrieving the `signer` from `MulticallerWithSigner` without optionally allowing the `sender` from `MulticallerWithSender` to be retrieved instead. 185 | This functionality could be important when considering the issue related to cross-contract re-entrancy. 186 | 187 | **Mitigation:** 188 | 189 | Include 190 | 191 | ```solidity 192 | /** 193 | * @dev Returns the signer of `aggregateWithSigner` on `MULTICALLER_WITH_SIGNER`, 194 | * if the current context's `msg.sender` is `MULTICALLER_WITH_SIGNER`. 195 | * Otherwise, returns `msg.sender`. 196 | */ 197 | function signer() internal view returns (address result) { 198 | /// @solidity memory-safe-assembly 199 | assembly { 200 | mstore(0x00, caller()) 201 | let withSigner := MULTICALLER_WITH_SIGNER 202 | if eq(caller(), withSigner) { 203 | if iszero(staticcall(gas(), withSigner, 0x00, 0x00, 0x00, 0x20)) { 204 | revert(0x00, 0x00) // For better gas estimation. 205 | } 206 | } 207 | result := mload(0x00) 208 | } 209 | } 210 | ``` 211 | 212 | **Response:** 213 | 214 | > May be good to add. 215 | 216 | ### `multicallerSender()` could be misunderstood 217 | 218 | **Severity:** Informational 219 | **Files:** [LibMulticaller](src/LibMulticaller.sol) 220 | 221 | An app that integrates the `MulticallerWithSender` and `MulticallerWithSigner` contracts might end up using `LibMulticaller.multicallerSender()` even though they would like to integrate both contracts, not knowing that `multicallerSender` does not work for `MulticallerWithSigner`. 222 | 223 | They might not include sufficient tests after testing with `MulticallerWithSender` and expect `MulticallerWithSigner` to work as well. 224 | 225 | **Mitigation:** 226 | Highlight in the documentation that `multicallerSender()` and `multicallerSigner()` can only be used for their respective separate contracts. The functionality of both contracts could be merged into one. 227 | 228 | ### "Forwarding `msg.sender`" might be misleading 229 | 230 | **Severity:** Informational 231 | **Files:** [LibMulticaller](src/LibMulticaller.sol) 232 | 233 | The documentation mentions that `MulticallerWithSender` and `MulticallerWithSigner` "forward" `msg.sender`. This might be misleading, as the Multicaller's `msg.sender` is not forwarded as it is with [EIP-2771](https://eips.ethereum.org/EIPS/eip-2771), 234 | where the `msg.sender` information is appended and retrieved via calldata of the current context. `LibMulticaller` instead requires external calls to the Multicaller contracts to be made. This information could be highlighted to allow developers to optimize their code by caching `LibMulticaller.sender` instead of repeatedly calling the function. 235 | 236 | ## Consider removing `receive()` for `Multicaller` 237 | 238 | **Severity:** Informational 239 | **Files:** [Multicaller](https://github.com/Vectorized/multicaller/blob/0d5cb01764404f94bf237459ef41cff1aa391fb2/src/Multicaller.sol) 240 | 241 | The `Multicaller` contract is able to accept Ether, however there does not seem to be a clear use-case for allowing this. 242 | Calling `Multicaller` with value and no data should be unintended as anyone is able to claim Ether in the contract. Consider removing the `receive()` function for `Multicaller`. 243 | 244 | --- 245 | 246 | ## Further Considerations 247 | 248 | Consider adapting [EIP-4337's semi-abstracted nonces](https://eips.ethereum.org/EIPS/eip-4337#semi-abstracted-nonce-support). These allow for either sequential or unordered nonces, however, these come at the cost of increased complexity. 249 | 250 | # Code Quality Recommendations 251 | 252 | ## Use early return pattern to avoid nested if conditions 253 | 254 | ```solidity 255 | if data.length { 256 | // ... 257 | } 258 | ``` 259 | 260 | This increases readability. 261 | 262 | **Response:** 263 | 264 | > This is intentional for reducing bytecode size in the case of the Multicaller. 265 | 266 | ## Declare arbitrary bytes as named constants 267 | 268 | The selector of `error ArrayLengthsMismatch()` could be declared as a named constant `uint256 constant _SELECTOR_ARRAY_LENGTHS_MISMATCH = 0x3b800a46;`The same could be done for other constant byte selectors, e.g. `error InvalidSignature()`, or `name` in `eip712Domain`: `mstore(0xf5, 0x154d756c746963616c6c6572576974685369676e6572)`. 269 | 270 | # Testing recommendations 271 | 272 | ## Include differential tests testing Yul against Solidity implementations 273 | 274 | Consider writing the Multicaller contracts in Solidity. Differential tests can help discover discrepancies in functionality and can improve auditability by making these more accessible to a wider audience. 275 | 276 | ## Tests might be run against old version of Multicaller 277 | 278 | The `setUp` script creates the various `Multicaller` contracts given hardcoded `bytes` values. 279 | 280 | https://github.com/Vectorized/multicaller/blob/0d5cb01764404f94bf237459ef41cff1aa391fb2/test/Multicaller.t.sol#L153-L160 281 | 282 | ```solidity 283 | bytes public constant MULTICALLER_INITCODE = hex"6080806..."; 284 | 285 | function setUp() public virtual { 286 | { 287 | bytes32 salt = MULTICALLER_CREATE2_SALT; 288 | bytes memory initcode = MULTICALLER_INITCODE; 289 | address expectedDeployment = MULTICALLER_CREATE2_DEPLOYED_ADDRESS; 290 | multicaller = Multicaller(payable(_safeCreate2(salt, initcode))); 291 | assertEq(address(multicaller), expectedDeployment); 292 | } 293 | 294 | // ... 295 | ``` 296 | 297 | Modifications made to the source code will not be reflected in the tests and errors might be overlooked. To ensure that the tests are run against the latest version of the code consider retrieving the latest creation code from the source. 298 | 299 | ```solidity 300 | bytes public constant MULTICALLER_INITCODE = type(Multicaller).creationCode; 301 | ``` 302 | 303 | ## Include test for `error ArrayLengthsMismatch()` selector 304 | 305 | ```solidity 306 | function testArrayLengthsMismatch( 307 | address[] calldata targets, 308 | bytes[] calldata data, 309 | uint256[] calldata values 310 | ) public { 311 | vm.assume(targets.length != data.length || targets.length != values.length); 312 | 313 | vm.expectRevert(ArrayLengthsMismatch.selector); 314 | multicaller.aggregate(targets, data, values, address(fallbackTargetA)); 315 | } 316 | ``` 317 | 318 | ## Consider replacing `vm.expectRevert()` with `vm.expectRevert(bytes(""))` 319 | 320 | `vm.expectRevert()` catches any possible reverts. When specifically a revert with empty bytes is expected, `vm.expectRevert(bytes(""))` can be used. 321 | 322 | # Optimizations 323 | 324 | ## `codecopy` could be unnecessary 325 | 326 | **Files:** [Multicaller](https://github.com/Vectorized/multicaller/blob/0d5cb01764404f94bf237459ef41cff1aa391fb2/src/Multicaller.sol) 327 | 328 | Assuming that memory is initialized to zero for a new call context, using `codecopy` could be unnecessary. 329 | 330 | **Before:** 331 | 332 | ```solidity 333 | // Fill with either 0xff or 0x00. 334 | mstore(o, not(returndatasize())) 335 | if iszero(gt(d, 0x7f)) { codecopy(o, codesize(), add(d, 1)) } 336 | o := add(o, add(and(d, 0x7f), 1)) 337 | ``` 338 | 339 | **After:** 340 | 341 | ```solidity 342 | // Fill with either 0xff or 0x00. 343 | mstore(o, mul(not(returndatasize()), gt(d, 0x7f)) 344 | // if iszero(gt(d, 0x7f)) { codecopy(o, codesize(), add(d, 1)) } 345 | o := add(o, add(and(d, 0x7f), 1)) 346 | ``` 347 | 348 | ## Using `jumpi` could be cheaper 349 | 350 | **Files:** [Multicaller](https://github.com/Vectorized/multicaller/blob/0d5cb01764404f94bf237459ef41cff1aa391fb2/src/Multicaller.sol) 351 | 352 | **Before:** 353 | 354 | ```solidity 355 | // If `refundTo` is `address(1)`, replace it with the `msg.sender`. 356 | refundTo := xor(refundTo, mul(eq(refundTo, 1), xor(refundTo, caller()))) 357 | ``` 358 | 359 | **After:** 360 | 361 | ```solidity 362 | // If `refundTo` is `address(1)`, replace it with the `msg.sender`. 363 | // refundTo := xor(refundTo, mul(eq(refundTo, 1), xor(refundTo, caller()))) 364 | if eq(refundTo, 1) { refundTo := caller() } 365 | ``` 366 | 367 | ## `shr` instead of `shl` + `and` 368 | 369 | **Files:** [MulticallerWithSender](https://github.com/Vectorized/multicaller/blob/0d5cb01764404f94bf237459ef41cff1aa391fb2/src/MulticallerWithSender.sol) 370 | 371 | **Before:** 372 | 373 | ```solidity 374 | if iszero(and(sload(returndatasize()), shl(160, 1))) { 375 | // ... 376 | } 377 | ``` 378 | 379 | **After:** 380 | 381 | ```solidity 382 | // if iszero(and(sload(returndatasize()), shl(160, 1))) { 383 | if iszero(shr(160, sload(returndatasize()))) { 384 | // ... 385 | } 386 | ``` 387 | 388 | ## Use `eq` instead of `iszero(lt(...))` 389 | 390 | **Files:** [MulticallerWithSender](https://github.com/Vectorized/multicaller/blob/0d5cb01764404f94bf237459ef41cff1aa391fb2/src/MulticallerWithSender.sol) 391 | 392 | **Before:** 393 | 394 | ```solidity 395 | if iszero(lt(results, data.length)) { break } 396 | ``` 397 | 398 | **After:** 399 | 400 | ```solidity 401 | // if iszero(lt(results, data.length)) { break } 402 | if eq(results, data.length) { break } 403 | ``` 404 | -------------------------------------------------------------------------------- /src/MulticallerWithSigner.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | /** 5 | * @title MulticallerWithSigner 6 | * @author vectorized.eth 7 | * @notice Contract that allows for efficient aggregation of multiple calls 8 | * in a single transaction, while "forwarding" the `signer`. 9 | */ 10 | contract MulticallerWithSigner { 11 | // ============================================================= 12 | // EVENTS 13 | // ============================================================= 14 | 15 | /** 16 | * @dev Emitted when the `nonces` of `signer` are invalidated. 17 | * @param signer The signer of the signature. 18 | * @param nonces The array of nonces invalidated. 19 | */ 20 | event NoncesInvalidated(address indexed signer, uint256[] nonces); 21 | 22 | /** 23 | * @dev Emitted when the nonce salt of `signer` is incremented. 24 | * @param signer The signer of the signature. 25 | * @param newNonceSalt The new nonce salt. 26 | */ 27 | event NonceSaltIncremented(address indexed signer, uint256 newNonceSalt); 28 | 29 | /** 30 | * @dev `keccak256("NoncesInvalidated(address,uint256[])")`. 31 | */ 32 | uint256 private constant _NONCES_INVALIDATED_EVENT_SIGNATURE = 33 | 0xc45e3a0dd412bcad8d62398d74d66b1c8449f38beb10da275e4da0c6d3a811a4; 34 | 35 | /** 36 | * @dev `keccak256("NonceSaltIncremented(address,uint256)")`. 37 | */ 38 | uint256 private constant _NONCE_SALT_INCREMENTED_EVENT_SIGNATURE = 39 | 0x997a42216df16c8b9e7caf2fc71c59dba956f1f2b12320f87a80a5879464217d; 40 | 41 | // ============================================================= 42 | // CONSTANTS 43 | // ============================================================= 44 | 45 | // These EIP-712 constants are made private to save function dispatch gas. 46 | // If you need them in your code, please copy and paste them. 47 | 48 | /** 49 | * @dev For EIP-712 signature digest calculation for the 50 | * `aggregateWithSigner` function. 51 | * `keccak256("AggregateWithSigner(address signer,address[] targets,bytes[] data,uint256[] values,uint256 nonce,uint256 nonceSalt)")`. 52 | */ 53 | bytes32 private constant _AGGREGATE_WITH_SIGNER_TYPEHASH = 54 | 0xfb989fd34c8af81a76f18167f528fc7315f92cacc19a0e63215abd54633f8a28; 55 | 56 | /** 57 | * @dev For EIP-712 signature digest calculation for the 58 | * `invalidateNoncesForSigner` function. 59 | * `keccak256("InvalidateNoncesForSigner(address signer,uint256[] nonces,uint256 nonceSalt)")`. 60 | */ 61 | bytes32 private constant _INVALIDATE_NONCES_FOR_SIGNER_TYPEHASH = 62 | 0x12b047058eea3df4085cdc159a103d9c100c4e78cfb7029cc39d02cb8b9e48f5; 63 | 64 | /** 65 | * @dev For EIP-712 signature digest calculation for the 66 | * `incrementNonceSaltForSigner` function. 67 | * `keccak256("IncrementNonceSaltForSigner(address signer,uint256 nonceSalt)")`. 68 | */ 69 | bytes32 private constant _INCREMENT_NONCE_SALT_FOR_SIGNER_TYPEHASH = 70 | 0xfa181078c7d1d4d369301511d3c5611e9367d0cebbf65eefdee9dfc75849c1d3; 71 | 72 | /** 73 | * @dev For EIP-712 signature digest calculation. 74 | * `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`. 75 | */ 76 | bytes32 private constant _DOMAIN_TYPEHASH = 77 | 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; 78 | 79 | /** 80 | * @dev For EIP-712 signature digest calculation. 81 | * `keccak256("MulticallerWithSigner")`. 82 | */ 83 | bytes32 private constant _NAME_HASH = 84 | 0x301013e8a31863902646dc218ecd889c37491c2967a8104d5ff1cf42af0f9ea4; 85 | 86 | /** 87 | * @dev For EIP-712 signature digest calculation. 88 | * `keccak256("1")`. 89 | */ 90 | bytes32 private constant _VERSION_HASH = 91 | 0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6; 92 | 93 | // ============================================================= 94 | // ERRORS 95 | // ============================================================= 96 | 97 | /** 98 | * @dev The lengths of the input arrays are not the same. 99 | */ 100 | error ArrayLengthsMismatch(); 101 | 102 | /** 103 | * @dev This function does not support reentrancy. 104 | */ 105 | error Reentrancy(); 106 | 107 | /** 108 | * @dev The signature is invalid: it must be correctly signed by the signer, 109 | * with the correct data, an unused nonce, and the signer's current nonce salt. 110 | */ 111 | error InvalidSignature(); 112 | 113 | // ============================================================= 114 | // CONSTRUCTOR 115 | // ============================================================= 116 | 117 | constructor() payable { 118 | assembly { 119 | // Throughout this code, we will abuse returndatasize 120 | // in place of zero anywhere before a call to save a bit of gas. 121 | // We will use storage slot zero to store the signer at 122 | // bits [96..255] and reentrancy guard at bit 1. 123 | sstore(returndatasize(), 1) 124 | } 125 | } 126 | 127 | // ============================================================= 128 | // AGGREGATION OPERATIONS 129 | // ============================================================= 130 | 131 | /** 132 | * @dev Returns the signer passed into `aggregateWithSigner` on this contract. 133 | * The value is always the zero address outside a transaction. 134 | */ 135 | receive() external payable { 136 | assembly { 137 | mstore(0x0c, sload(returndatasize())) 138 | return(returndatasize(), 0x20) 139 | } 140 | } 141 | 142 | /** 143 | * @dev Aggregates multiple calls in a single transaction. 144 | * This method will store the `signer` temporarily 145 | * for the span of its execution. 146 | * This method does not support reentrancy. 147 | * Emits a `NoncesInvalidated(signer, [nonce])` event. 148 | * @param targets An array of addresses to call. 149 | * @param data An array of calldata to forward to the targets. 150 | * @param values How much ETH to forward to each target. 151 | * @param nonce The nonce for the signature. 152 | * @param signer The signer of the signature. 153 | * @param signature The signature by the signer. 154 | * @return An array of the returndata from each call. 155 | */ 156 | function aggregateWithSigner( 157 | address[] calldata targets, 158 | bytes[] calldata data, 159 | uint256[] calldata values, 160 | uint256 nonce, 161 | address signer, 162 | bytes calldata signature 163 | ) external payable returns (bytes[] memory) { 164 | assembly { 165 | if iszero(and(eq(targets.length, data.length), eq(data.length, values.length))) { 166 | mstore(returndatasize(), 0x3b800a46) // `ArrayLengthsMismatch()`. 167 | revert(0x1c, 0x04) 168 | } 169 | 170 | if iszero(and(1, sload(returndatasize()))) { 171 | mstore(returndatasize(), 0xab143c06) // `Reentrancy()`. 172 | revert(0x1c, 0x04) 173 | } 174 | 175 | // Multiply `data.length` by 0x20 to give the byte length of the `data` offsets array. 176 | // This is the also the byte length of the `targets` array and `values` array. 177 | data.length := shl(5, data.length) 178 | 179 | /* -------------------- CHECK SIGNATURE --------------------- */ 180 | 181 | // Compute `keccak256(abi.encodePacked(values))`. 182 | calldatacopy(returndatasize(), values.offset, data.length) 183 | let valuesHash := keccak256(returndatasize(), data.length) 184 | // Compute `keccak256(abi.encodePacked(keccak256(data[0]), ..))`. 185 | for { let i := returndatasize() } iszero(eq(i, data.length)) { i := add(i, 0x20) } { 186 | let o := add(data.offset, calldataload(add(data.offset, i))) 187 | calldatacopy(i, add(o, 0x20), calldataload(o)) 188 | mstore(i, keccak256(i, calldataload(o))) 189 | } 190 | let dataHash := keccak256(returndatasize(), data.length) 191 | // Compute `keccak256(abi.encodePacked(targets))`. 192 | calldatacopy(returndatasize(), targets.offset, data.length) 193 | let targetsHash := keccak256(returndatasize(), data.length) 194 | 195 | // Layout the fields of the struct hash. 196 | mstore(returndatasize(), _AGGREGATE_WITH_SIGNER_TYPEHASH) 197 | mstore(0x20, signer) 198 | mstore(0x40, targetsHash) 199 | mstore(0x60, dataHash) 200 | mstore(0x80, valuesHash) 201 | mstore(0xa0, nonce) 202 | mstore(0xc0, sload(add(signer, address()))) // Store the nonce salt. 203 | mstore(0x40, keccak256(returndatasize(), 0xe0)) // Compute and store the struct hash. 204 | // Layout the fields of the domain separator. 205 | mstore(0x60, _DOMAIN_TYPEHASH) 206 | mstore(0x80, _NAME_HASH) 207 | mstore(0xa0, _VERSION_HASH) 208 | mstore(0xc0, chainid()) 209 | mstore(0xe0, address()) 210 | mstore(0x20, keccak256(0x60, 0xa0)) // Compute and store the domain separator. 211 | // Layout the fields of `ecrecover`. 212 | mstore(returndatasize(), 0x1901) // Store "\x19\x01". 213 | let digest := keccak256(0x1e, 0x42) // Compute the digest. 214 | for {} 1 {} { 215 | if eq(signature.length, 64) { 216 | mstore(returndatasize(), digest) // Store the digest. 217 | let vs := calldataload(add(signature.offset, 0x20)) 218 | mstore(0x20, add(shr(255, vs), 27)) // `v`. 219 | mstore(0x40, calldataload(signature.offset)) // `r`. 220 | mstore(0x60, shr(1, shl(1, vs))) // `s`. 221 | let t := staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20) 222 | if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) { break } 223 | } 224 | if eq(signature.length, 65) { 225 | mstore(returndatasize(), digest) // Store the digest. 226 | calldatacopy(0x40, signature.offset, signature.length) // Copy `r`, `s`, `v`. 227 | mstore(0x20, byte(returndatasize(), mload(0x80))) // `v`. 228 | let t := staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20) 229 | if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) { break } 230 | } 231 | // ERC1271 fallback. 232 | let f := shl(224, 0x1626ba7e) // `isValidSignature(bytes32,bytes)`. 233 | mstore(0x00, f) 234 | mstore(0x04, digest) 235 | mstore(0x24, 0x40) 236 | mstore(0x44, signature.length) 237 | calldatacopy(0x64, signature.offset, signature.length) 238 | let t := staticcall(gas(), signer, 0x00, add(signature.length, 0x64), 0x24, 0x20) 239 | if iszero(and(eq(mload(0x24), f), t)) { 240 | mstore(0x00, 0x8baa579f) // `InvalidSignature()`. 241 | revert(0x1c, 0x04) 242 | } 243 | break 244 | } 245 | 246 | // Check the nonce. 247 | mstore(0x20, signer) 248 | mstore(0x40, nonce) 249 | let bucketSlot := keccak256(0x20, 0x3f) 250 | let bucketValue := sload(bucketSlot) 251 | let bit := shl(and(0xff, nonce), 1) 252 | if and(bit, bucketValue) { 253 | mstore(0x00, 0x8baa579f) // `InvalidSignature()`. 254 | revert(0x1c, 0x04) 255 | } 256 | sstore(bucketSlot, or(bucketValue, bit)) // Invalidate the nonce. 257 | 258 | // Emit `NoncesInvalidated(signer, [nonce])`. 259 | mstore(0x00, 0x20) 260 | mstore(0x20, 1) 261 | // The nonce is already at 0x40. 262 | log2(0x00, 0x60, _NONCES_INVALIDATED_EVENT_SIGNATURE, signer) 263 | 264 | /* ------------------- PERFORM AGGREGATE -------------------- */ 265 | 266 | // Early return if no data. 267 | if iszero(data.length) { 268 | // Slot 0x00's value is already 0x20. 269 | mstore(0x20, data.length) // Store `data.length` into `results`. 270 | return(0x00, 0x40) 271 | } 272 | 273 | // Set the signer slot temporarily for the span of this transaction. 274 | sstore(0, shl(96, signer)) 275 | 276 | let results := 0x40 277 | // Copy the offsets from calldata into memory. 278 | calldatacopy(results, data.offset, data.length) 279 | // Offset into `results`. 280 | let resultsOffset := data.length 281 | // Pointer to the end of `results`. 282 | let end := add(results, data.length) 283 | // For deriving the calldata offsets from the `results` pointer. 284 | let valuesOffsetDiff := sub(values.offset, results) 285 | let targetsOffsetDiff := sub(targets.offset, results) 286 | 287 | for {} 1 {} { 288 | // The offset of the current bytes in the calldata. 289 | let o := add(data.offset, mload(results)) 290 | let memPtr := add(resultsOffset, 0x40) 291 | // Copy the current bytes from calldata to the memory. 292 | calldatacopy( 293 | memPtr, 294 | add(o, 0x20), // The offset of the current bytes' bytes. 295 | calldataload(o) // The length of the current bytes. 296 | ) 297 | if iszero( 298 | call( 299 | gas(), // Remaining gas. 300 | calldataload(add(targetsOffsetDiff, results)), // Address to call. 301 | calldataload(add(valuesOffsetDiff, results)), // ETH to send. 302 | memPtr, // Start of input calldata in memory. 303 | calldataload(o), // Size of input calldata. 304 | 0x00, // We will use returndatacopy instead. 305 | 0x00 // We will use returndatacopy instead. 306 | ) 307 | ) { 308 | // Bubble up the revert if the call reverts. 309 | returndatacopy(0x00, 0x00, returndatasize()) 310 | revert(0x00, returndatasize()) 311 | } 312 | // Append the current `resultsOffset` into `results`. 313 | mstore(results, resultsOffset) 314 | // Append the returndatasize, and the returndata. 315 | mstore(memPtr, returndatasize()) 316 | returndatacopy(add(memPtr, 0x20), 0x00, returndatasize()) 317 | // Advance the `resultsOffset` by `returndatasize() + 0x20`, 318 | // rounded up to the next multiple of 0x20. 319 | resultsOffset := and(add(add(resultsOffset, returndatasize()), 0x3f), not(0x1f)) 320 | // Advance the `results` pointer. 321 | results := add(results, 0x20) 322 | if eq(results, end) { break } 323 | } 324 | // Slot 0x00's value is already 0x20. 325 | mstore(0x20, targets.length) // Store `targets.length` into `results`. 326 | 327 | // Restore the `signer` slot. 328 | sstore(0, 1) 329 | // Direct return. 330 | return(0x00, add(resultsOffset, 0x40)) 331 | } 332 | } 333 | 334 | // ============================================================= 335 | // SIGNATURE OPERATIONS 336 | // ============================================================= 337 | 338 | /** 339 | * @dev Invalidates the `nonces` of `msg.sender`. 340 | * Emits a `NoncesInvalidated(msg.sender, nonces)` event. 341 | * @param nonces An array of nonces to invalidate. 342 | */ 343 | function invalidateNonces(uint256[] calldata nonces) external { 344 | assembly { 345 | mstore(0x00, caller()) 346 | // Iterate through all the nonces and set their boolean values in the storage. 347 | let end := shl(5, nonces.length) 348 | for { let i := 0 } iszero(eq(i, end)) { i := add(i, 0x20) } { 349 | let nonce := calldataload(add(nonces.offset, i)) 350 | mstore(0x20, nonce) 351 | let bucketSlot := keccak256(0x00, 0x3f) 352 | sstore(bucketSlot, or(sload(bucketSlot), shl(and(0xff, nonce), 1))) 353 | } 354 | // Emit `NoncesInvalidated(msg.sender, nonces)`. 355 | mstore(0x00, 0x20) 356 | mstore(0x20, nonces.length) 357 | calldatacopy(0x40, nonces.offset, end) 358 | log2(0x00, add(0x40, end), _NONCES_INVALIDATED_EVENT_SIGNATURE, caller()) 359 | } 360 | } 361 | 362 | /** 363 | * @dev Invalidates the `nonces` of `signer`. 364 | * Emits a `NoncesInvalidated(signer, nonces)` event. 365 | * @param nonces An array of nonces to invalidate. 366 | * @param signer The signer of the signature. 367 | * @param signature The signature by the signer. 368 | */ 369 | function invalidateNoncesForSigner( 370 | uint256[] calldata nonces, 371 | address signer, 372 | bytes calldata signature 373 | ) external { 374 | assembly { 375 | let end := shl(5, nonces.length) 376 | // Layout the fields of the struct hash. 377 | mstore(returndatasize(), _INVALIDATE_NONCES_FOR_SIGNER_TYPEHASH) 378 | mstore(0x20, signer) 379 | // Compute and store `keccak256(abi.encodePacked(nonces))`. 380 | calldatacopy(0x40, nonces.offset, end) 381 | mstore(0x40, keccak256(0x40, end)) 382 | mstore(0x60, sload(add(signer, address()))) // Store the nonce salt. 383 | mstore(0x40, keccak256(returndatasize(), 0x80)) // Compute and store the struct hash. 384 | // Layout the fields of the domain separator. 385 | mstore(0x60, _DOMAIN_TYPEHASH) 386 | mstore(0x80, _NAME_HASH) 387 | mstore(0xa0, _VERSION_HASH) 388 | mstore(0xc0, chainid()) 389 | mstore(0xe0, address()) 390 | mstore(0x20, keccak256(0x60, 0xa0)) // Compute and store the domain separator. 391 | // Layout the fields of `ecrecover`. 392 | mstore(returndatasize(), 0x1901) // Store "\x19\x01". 393 | let digest := keccak256(0x1e, 0x42) // Compute the digest. 394 | for {} 1 {} { 395 | if eq(signature.length, 64) { 396 | mstore(returndatasize(), digest) // Store the digest. 397 | let vs := calldataload(add(signature.offset, 0x20)) 398 | mstore(0x20, add(shr(255, vs), 27)) // `v`. 399 | mstore(0x40, calldataload(signature.offset)) // `r`. 400 | mstore(0x60, shr(1, shl(1, vs))) // `s`. 401 | let t := staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20) 402 | if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) { break } 403 | } 404 | if eq(signature.length, 65) { 405 | mstore(returndatasize(), digest) // Store the digest. 406 | calldatacopy(0x40, signature.offset, signature.length) // Copy `r`, `s`, `v`. 407 | mstore(0x20, byte(returndatasize(), mload(0x80))) // `v`. 408 | let t := staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20) 409 | if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) { break } 410 | } 411 | // ERC1271 fallback. 412 | let f := shl(224, 0x1626ba7e) // `isValidSignature(bytes32,bytes)`. 413 | mstore(0x00, f) 414 | mstore(0x04, digest) 415 | mstore(0x24, 0x40) 416 | mstore(0x44, signature.length) 417 | calldatacopy(0x64, signature.offset, signature.length) 418 | let t := staticcall(gas(), signer, 0x00, add(signature.length, 0x64), 0x24, 0x20) 419 | if iszero(and(eq(mload(0x24), f), t)) { 420 | mstore(0x00, 0x8baa579f) // `InvalidSignature()`. 421 | revert(0x1c, 0x04) 422 | } 423 | break 424 | } 425 | 426 | mstore(0x00, signer) 427 | // Iterate through all the nonces and set their boolean values in the storage. 428 | for { let i := 0 } iszero(eq(i, end)) { i := add(i, 0x20) } { 429 | let nonce := calldataload(add(nonces.offset, i)) 430 | mstore(0x20, nonce) 431 | let bucketSlot := keccak256(0x00, 0x3f) 432 | sstore(bucketSlot, or(sload(bucketSlot), shl(and(0xff, nonce), 1))) 433 | } 434 | // Emit `NoncesInvalidated(signer, nonces)`. 435 | mstore(0x00, 0x20) 436 | mstore(0x20, nonces.length) 437 | calldatacopy(0x40, nonces.offset, end) 438 | log2(0x00, add(0x40, end), _NONCES_INVALIDATED_EVENT_SIGNATURE, signer) 439 | } 440 | } 441 | 442 | /** 443 | * @dev Returns whether each of the `nonces` of `signer` has been invalidated. 444 | * @param signer The signer of the signature. 445 | * @param nonces An array of nonces. 446 | * @return A bool array representing whether each nonce has been invalidated. 447 | */ 448 | function noncesInvalidated(address signer, uint256[] calldata nonces) 449 | external 450 | view 451 | returns (bool[] memory) 452 | { 453 | assembly { 454 | mstore(0x00, signer) 455 | // Iterate through all the nonces and append their boolean values. 456 | let end := shl(5, nonces.length) 457 | for { let i := 0 } iszero(eq(i, end)) { i := add(i, 0x20) } { 458 | let nonce := calldataload(add(nonces.offset, i)) 459 | mstore(0x20, nonce) 460 | let bit := and(1, shr(and(0xff, nonce), sload(keccak256(0x00, 0x3f)))) 461 | mstore(add(0x40, i), bit) 462 | } 463 | mstore(0x00, 0x20) // Store the memory offset of the `results`. 464 | mstore(0x20, nonces.length) // Store `data.length` into `results`. 465 | return(0x00, add(0x40, end)) 466 | } 467 | } 468 | 469 | /** 470 | * @dev Increments the nonce salt of `msg.sender`. 471 | * For making all unused signatures with the current nonce salt invalid. 472 | * Will NOT make invalidated nonces available for use. 473 | * Emits a `NonceSaltIncremented(msg.sender, newNonceSalt)` event. 474 | * @return The new nonce salt. 475 | */ 476 | function incrementNonceSalt() external returns (uint256) { 477 | assembly { 478 | let nonceSaltSlot := add(caller(), address()) 479 | // Increment by some pseudorandom amount from [1..4294967296]. 480 | let nonceSalt := sload(nonceSaltSlot) 481 | let newNonceSalt := add(add(1, shr(224, blockhash(sub(number(), 1)))), nonceSalt) 482 | sstore(nonceSaltSlot, newNonceSalt) 483 | // Emit `NonceSaltIncremented(msg.sender, newNonceSalt)`. 484 | mstore(0x00, newNonceSalt) 485 | log2(0x00, 0x20, _NONCE_SALT_INCREMENTED_EVENT_SIGNATURE, caller()) 486 | return(0x00, 0x20) 487 | } 488 | } 489 | 490 | /** 491 | * @dev Increments the nonce salt of `signer`. 492 | * For making all unused signatures with the current nonce salt invalid. 493 | * Will NOT make invalidated nonces available for use. 494 | * Emits a `NonceSaltIncremented(signer, newNonceSalt)` event. 495 | * @param signer The signer of the signature. 496 | * @param signature The signature by the signer. 497 | * @return The new nonce salt. 498 | */ 499 | function incrementNonceSaltForSigner(address signer, bytes calldata signature) 500 | external 501 | returns (uint256) 502 | { 503 | assembly { 504 | let nonceSaltSlot := add(signer, address()) 505 | let nonceSalt := sload(nonceSaltSlot) 506 | // Layout the fields of the struct hash. 507 | mstore(returndatasize(), _INCREMENT_NONCE_SALT_FOR_SIGNER_TYPEHASH) 508 | mstore(0x20, signer) 509 | mstore(0x40, nonceSalt) // Store the nonce salt. 510 | mstore(0x40, keccak256(returndatasize(), 0x60)) // Compute and store the struct hash. 511 | // Layout the fields of the domain separator. 512 | mstore(0x60, _DOMAIN_TYPEHASH) 513 | mstore(0x80, _NAME_HASH) 514 | mstore(0xa0, _VERSION_HASH) 515 | mstore(0xc0, chainid()) 516 | mstore(0xe0, address()) 517 | mstore(0x20, keccak256(0x60, 0xa0)) // Compute and store the domain separator. 518 | // Layout the fields of `ecrecover`. 519 | mstore(returndatasize(), 0x1901) // Store "\x19\x01". 520 | let digest := keccak256(0x1e, 0x42) // Compute the digest. 521 | for {} 1 {} { 522 | if eq(signature.length, 64) { 523 | mstore(returndatasize(), digest) // Store the digest. 524 | let vs := calldataload(add(signature.offset, 0x20)) 525 | mstore(0x20, add(shr(255, vs), 27)) // `v`. 526 | mstore(0x40, calldataload(signature.offset)) // `r`. 527 | mstore(0x60, shr(1, shl(1, vs))) // `s`. 528 | let t := staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20) 529 | if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) { break } 530 | } 531 | if eq(signature.length, 65) { 532 | mstore(returndatasize(), digest) // Store the digest. 533 | calldatacopy(0x40, signature.offset, signature.length) // Copy `r`, `s`, `v`. 534 | mstore(0x20, byte(returndatasize(), mload(0x80))) // `v`. 535 | let t := staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20) 536 | if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) { break } 537 | } 538 | // ERC1271 fallback. 539 | let f := shl(224, 0x1626ba7e) // `isValidSignature(bytes32,bytes)`. 540 | mstore(0x00, f) 541 | mstore(0x04, digest) 542 | mstore(0x24, 0x40) 543 | mstore(0x44, signature.length) 544 | calldatacopy(0x64, signature.offset, signature.length) 545 | let t := staticcall(gas(), signer, 0x00, add(signature.length, 0x64), 0x24, 0x20) 546 | if iszero(and(eq(mload(0x24), f), t)) { 547 | mstore(0x00, 0x8baa579f) // `InvalidSignature()`. 548 | revert(0x1c, 0x04) 549 | } 550 | break 551 | } 552 | 553 | // Increment by some pseudorandom amount from [1..4294967296]. 554 | let newNonceSalt := add(add(1, shr(224, blockhash(sub(number(), 1)))), nonceSalt) 555 | sstore(nonceSaltSlot, newNonceSalt) 556 | // Emit `NonceSaltIncremented(signer, newNonceSalt)`. 557 | mstore(0x00, newNonceSalt) 558 | log2(0x00, 0x20, _NONCE_SALT_INCREMENTED_EVENT_SIGNATURE, signer) 559 | return(0x00, 0x20) 560 | } 561 | } 562 | 563 | /** 564 | * @dev Returns the nonce salt of `signer`. 565 | * @param signer The signer of the signature. 566 | * @return The current nonce salt of `signer`. 567 | */ 568 | function nonceSaltOf(address signer) external view returns (uint256) { 569 | assembly { 570 | mstore(returndatasize(), sload(add(signer, address()))) 571 | return(returndatasize(), 0x20) 572 | } 573 | } 574 | 575 | /** 576 | * @dev Returns the EIP-712 domain information, as specified in 577 | * [EIP-5267](https://eips.ethereum.org/EIPS/eip-5267). 578 | * @return fields `hex"0f"` (`0b01111`). 579 | * @return name `"MulticallerWithSigner"`. 580 | * @return version `"1"`. 581 | * @return chainId The chain ID which this contract is on. 582 | * @return verifyingContract `address(this)`, the address of this contract. 583 | * @return salt `bytes32(0)` (not used). 584 | * @return extensions `[]` (not used). 585 | */ 586 | function eip712Domain() 587 | external 588 | view 589 | returns ( 590 | bytes1 fields, 591 | string memory name, 592 | string memory version, 593 | uint256 chainId, 594 | address verifyingContract, 595 | bytes32 salt, 596 | uint256[] memory extensions 597 | ) 598 | { 599 | assembly { 600 | pop(fields) 601 | mstore8(returndatasize(), 0x0f) 602 | pop(name) 603 | mstore(0x20, 0xe0) 604 | mstore(0xf5, 0x154d756c746963616c6c6572576974685369676e6572) 605 | pop(version) 606 | mstore(0x40, 0x120) 607 | mstore(0x121, 0x0131) 608 | pop(chainId) 609 | mstore(0x60, chainid()) 610 | pop(verifyingContract) 611 | mstore(0x80, address()) 612 | pop(salt) 613 | pop(extensions) 614 | mstore(0xc0, 0x160) 615 | return(returndatasize(), 0x180) 616 | } 617 | } 618 | } 619 | -------------------------------------------------------------------------------- /test/utils/forge-std/Test.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.0 <0.9.0; 3 | 4 | import "./Script.sol"; 5 | 6 | abstract contract Test is Script { 7 | bool private __failed; 8 | 9 | function failed() public view returns (bool) { 10 | if (__failed) { 11 | return __failed; 12 | } else { 13 | return vm.load(address(vm), bytes32("failed")) != bytes32(0); 14 | } 15 | } 16 | 17 | function fail() internal virtual { 18 | vm.store(address(vm), bytes32("failed"), bytes32(uint256(1))); 19 | __failed = true; 20 | } 21 | 22 | // We intentionally do NOT mark these functions as pure, 23 | // so that importing this file into codebases built with old forge-std 24 | // won't trigger the compiler warning 2018. 25 | // 26 | // For performance, wherever possible, we do Solidity comparisons and 27 | // only make vm call if we know know the assert fails, 28 | // as preparing calls definitely costs more compute than doing 29 | // just the Solidity comparisons. 30 | 31 | function assertTrue(bool data) internal virtual { 32 | if (!data) vm.assertTrue(data); 33 | } 34 | 35 | function assertTrue(bool data, string memory err) internal virtual { 36 | if (!data) vm.assertTrue(data, err); 37 | } 38 | 39 | function assertFalse(bool data) internal virtual { 40 | if (data) vm.assertFalse(data); 41 | } 42 | 43 | function assertFalse(bool data, string memory err) internal virtual { 44 | if (data) vm.assertFalse(data, err); 45 | } 46 | 47 | function assertEq(bool left, bool right) internal virtual { 48 | if (left != right) vm.assertEq(left, right); 49 | } 50 | 51 | function assertEq(bool left, bool right, string memory err) internal virtual { 52 | if (left != right) vm.assertEq(left, right, err); 53 | } 54 | 55 | function assertEq(uint256 left, uint256 right) internal virtual { 56 | if (left != right) vm.assertEq(left, right); 57 | } 58 | 59 | function assertEq(uint256 left, uint256 right, string memory err) internal virtual { 60 | if (left != right) vm.assertEq(left, right, err); 61 | } 62 | 63 | function assertEqDecimal(uint256 left, uint256 right, uint256 decimals) internal virtual { 64 | if (left != right) vm.assertEqDecimal(left, right, decimals); 65 | } 66 | 67 | function assertEqDecimal(uint256 left, uint256 right, uint256 decimals, string memory err) 68 | internal 69 | virtual 70 | { 71 | if (left != right) vm.assertEqDecimal(left, right, decimals, err); 72 | } 73 | 74 | function assertEq(int256 left, int256 right) internal virtual { 75 | if (left != right) vm.assertEq(left, right); 76 | } 77 | 78 | function assertEq(int256 left, int256 right, string memory err) internal virtual { 79 | if (left != right) vm.assertEq(left, right, err); 80 | } 81 | 82 | function assertEqDecimal(int256 left, int256 right, uint256 decimals) internal virtual { 83 | if (left != right) vm.assertEqDecimal(left, right, decimals); 84 | } 85 | 86 | function assertEqDecimal(int256 left, int256 right, uint256 decimals, string memory err) 87 | internal 88 | virtual 89 | { 90 | if (left != right) vm.assertEqDecimal(left, right, decimals, err); 91 | } 92 | 93 | function assertEq(address left, address right) internal virtual { 94 | if (left != right) vm.assertEq(left, right); 95 | } 96 | 97 | function assertEq(address left, address right, string memory err) internal virtual { 98 | if (left != right) vm.assertEq(left, right, err); 99 | } 100 | 101 | function assertEq(bytes32 left, bytes32 right) internal virtual { 102 | if (left != right) vm.assertEq(left, right); 103 | } 104 | 105 | function assertEq(bytes32 left, bytes32 right, string memory err) internal virtual { 106 | if (left != right) vm.assertEq(left, right, err); 107 | } 108 | 109 | function assertEq(string memory left, string memory right) internal virtual { 110 | if (!__eq(left, right)) vm.assertEq(left, right); 111 | } 112 | 113 | function assertEq(string memory left, string memory right, string memory err) 114 | internal 115 | virtual 116 | { 117 | if (!__eq(left, right)) vm.assertEq(left, right, err); 118 | } 119 | 120 | function assertEq(bytes memory left, bytes memory right) internal virtual { 121 | if (!__eq(left, right)) vm.assertEq(left, right); 122 | } 123 | 124 | function assertEq(bytes memory left, bytes memory right, string memory err) internal virtual { 125 | if (!__eq(left, right)) vm.assertEq(left, right, err); 126 | } 127 | 128 | function assertEq(bool[] memory left, bool[] memory right) internal virtual { 129 | if (!__eq(left, right)) vm.assertEq(left, right); 130 | } 131 | 132 | function assertEq(bool[] memory left, bool[] memory right, string memory err) 133 | internal 134 | virtual 135 | { 136 | if (!__eq(left, right)) vm.assertEq(left, right, err); 137 | } 138 | 139 | function assertEq(uint256[] memory left, uint256[] memory right) internal virtual { 140 | if (!__eq(left, right)) vm.assertEq(left, right); 141 | } 142 | 143 | function assertEq(uint256[] memory left, uint256[] memory right, string memory err) 144 | internal 145 | virtual 146 | { 147 | if (!__eq(left, right)) vm.assertEq(left, right, err); 148 | } 149 | 150 | function assertEq(int256[] memory left, int256[] memory right) internal virtual { 151 | if (!__eq(left, right)) vm.assertEq(left, right); 152 | } 153 | 154 | function assertEq(int256[] memory left, int256[] memory right, string memory err) 155 | internal 156 | virtual 157 | { 158 | if (!__eq(left, right)) vm.assertEq(left, right, err); 159 | } 160 | 161 | function assertEq(address[] memory left, address[] memory right) internal virtual { 162 | if (!__eq(left, right)) vm.assertEq(left, right); 163 | } 164 | 165 | function assertEq(address[] memory left, address[] memory right, string memory err) 166 | internal 167 | virtual 168 | { 169 | if (!__eq(left, right)) vm.assertEq(left, right, err); 170 | } 171 | 172 | function assertEq(bytes32[] memory left, bytes32[] memory right) internal virtual { 173 | if (!__eq(left, right)) vm.assertEq(left, right); 174 | } 175 | 176 | function assertEq(bytes32[] memory left, bytes32[] memory right, string memory err) 177 | internal 178 | virtual 179 | { 180 | if (!__eq(left, right)) vm.assertEq(left, right, err); 181 | } 182 | 183 | function assertEq(string[] memory left, string[] memory right) internal virtual { 184 | if (!__eq(left, right)) vm.assertEq(left, right); 185 | } 186 | 187 | function assertEq(string[] memory left, string[] memory right, string memory err) 188 | internal 189 | virtual 190 | { 191 | if (!__eq(left, right)) vm.assertEq(left, right, err); 192 | } 193 | 194 | function assertEq(bytes[] memory left, bytes[] memory right) internal virtual { 195 | if (!__eq(left, right)) vm.assertEq(left, right); 196 | } 197 | 198 | function assertEq(bytes[] memory left, bytes[] memory right, string memory err) 199 | internal 200 | virtual 201 | { 202 | if (!__eq(left, right)) vm.assertEq(left, right, err); 203 | } 204 | 205 | function assertNotEq(bool left, bool right) internal virtual { 206 | if (left == right) vm.assertNotEq(left, right); 207 | } 208 | 209 | function assertNotEq(bool left, bool right, string memory err) internal virtual { 210 | if (left == right) vm.assertNotEq(left, right, err); 211 | } 212 | 213 | function assertNotEq(uint256 left, uint256 right) internal virtual { 214 | if (left == right) vm.assertNotEq(left, right); 215 | } 216 | 217 | function assertNotEq(uint256 left, uint256 right, string memory err) internal virtual { 218 | if (left == right) vm.assertNotEq(left, right, err); 219 | } 220 | 221 | function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals) internal virtual { 222 | if (left == right) vm.assertNotEqDecimal(left, right, decimals); 223 | } 224 | 225 | function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals, string memory err) 226 | internal 227 | virtual 228 | { 229 | if (left == right) vm.assertNotEqDecimal(left, right, decimals, err); 230 | } 231 | 232 | function assertNotEq(int256 left, int256 right) internal virtual { 233 | if (left == right) vm.assertNotEq(left, right); 234 | } 235 | 236 | function assertNotEq(int256 left, int256 right, string memory err) internal virtual { 237 | if (left == right) vm.assertNotEq(left, right, err); 238 | } 239 | 240 | function assertNotEqDecimal(int256 left, int256 right, uint256 decimals) internal virtual { 241 | if (left == right) vm.assertNotEqDecimal(left, right, decimals); 242 | } 243 | 244 | function assertNotEqDecimal(int256 left, int256 right, uint256 decimals, string memory err) 245 | internal 246 | virtual 247 | { 248 | if (left == right) vm.assertNotEqDecimal(left, right, decimals, err); 249 | } 250 | 251 | function assertNotEq(address left, address right) internal virtual { 252 | if (left == right) vm.assertNotEq(left, right); 253 | } 254 | 255 | function assertNotEq(address left, address right, string memory err) internal virtual { 256 | if (left == right) vm.assertNotEq(left, right, err); 257 | } 258 | 259 | function assertNotEq(bytes32 left, bytes32 right) internal virtual { 260 | if (left == right) vm.assertNotEq(left, right); 261 | } 262 | 263 | function assertNotEq(bytes32 left, bytes32 right, string memory err) internal virtual { 264 | if (left == right) vm.assertNotEq(left, right, err); 265 | } 266 | 267 | function assertNotEq(string memory left, string memory right) internal virtual { 268 | if (__eq(left, right)) vm.assertNotEq(left, right); 269 | } 270 | 271 | function assertNotEq(string memory left, string memory right, string memory err) 272 | internal 273 | virtual 274 | { 275 | if (__eq(left, right)) vm.assertNotEq(left, right, err); 276 | } 277 | 278 | function assertNotEq(bytes memory left, bytes memory right) internal virtual { 279 | if (__eq(left, right)) vm.assertNotEq(left, right); 280 | } 281 | 282 | function assertNotEq(bytes memory left, bytes memory right, string memory err) 283 | internal 284 | virtual 285 | { 286 | if (__eq(left, right)) vm.assertNotEq(left, right, err); 287 | } 288 | 289 | function assertNotEq(bool[] memory left, bool[] memory right) internal virtual { 290 | if (__eq(left, right)) vm.assertNotEq(left, right); 291 | } 292 | 293 | function assertNotEq(bool[] memory left, bool[] memory right, string memory err) 294 | internal 295 | virtual 296 | { 297 | if (__eq(left, right)) vm.assertNotEq(left, right, err); 298 | } 299 | 300 | function assertNotEq(uint256[] memory left, uint256[] memory right) internal virtual { 301 | if (__eq(left, right)) vm.assertNotEq(left, right); 302 | } 303 | 304 | function assertNotEq(uint256[] memory left, uint256[] memory right, string memory err) 305 | internal 306 | virtual 307 | { 308 | if (__eq(left, right)) vm.assertNotEq(left, right, err); 309 | } 310 | 311 | function assertNotEq(int256[] memory left, int256[] memory right) internal virtual { 312 | if (__eq(left, right)) vm.assertNotEq(left, right); 313 | } 314 | 315 | function assertNotEq(int256[] memory left, int256[] memory right, string memory err) 316 | internal 317 | virtual 318 | { 319 | if (__eq(left, right)) vm.assertNotEq(left, right, err); 320 | } 321 | 322 | function assertNotEq(address[] memory left, address[] memory right) internal virtual { 323 | if (__eq(left, right)) vm.assertNotEq(left, right); 324 | } 325 | 326 | function assertNotEq(address[] memory left, address[] memory right, string memory err) 327 | internal 328 | virtual 329 | { 330 | if (__eq(left, right)) vm.assertNotEq(left, right, err); 331 | } 332 | 333 | function assertNotEq(bytes32[] memory left, bytes32[] memory right) internal virtual { 334 | if (__eq(left, right)) vm.assertNotEq(left, right); 335 | } 336 | 337 | function assertNotEq(bytes32[] memory left, bytes32[] memory right, string memory err) 338 | internal 339 | virtual 340 | { 341 | if (__eq(left, right)) vm.assertNotEq(left, right, err); 342 | } 343 | 344 | function assertNotEq(string[] memory left, string[] memory right) internal virtual { 345 | if (__eq(left, right)) vm.assertNotEq(left, right); 346 | } 347 | 348 | function assertNotEq(string[] memory left, string[] memory right, string memory err) 349 | internal 350 | virtual 351 | { 352 | if (__eq(left, right)) vm.assertNotEq(left, right, err); 353 | } 354 | 355 | function assertNotEq(bytes[] memory left, bytes[] memory right) internal virtual { 356 | if (__eq(left, right)) vm.assertNotEq(left, right); 357 | } 358 | 359 | function assertNotEq(bytes[] memory left, bytes[] memory right, string memory err) 360 | internal 361 | virtual 362 | { 363 | if (__eq(left, right)) vm.assertNotEq(left, right, err); 364 | } 365 | 366 | function assertLt(uint256 left, uint256 right) internal virtual { 367 | if (left >= right) vm.assertLt(left, right); 368 | } 369 | 370 | function assertLt(uint256 left, uint256 right, string memory err) internal virtual { 371 | if (left >= right) vm.assertLt(left, right, err); 372 | } 373 | 374 | function assertLtDecimal(uint256 left, uint256 right, uint256 decimals) internal virtual { 375 | if (left >= right) vm.assertLtDecimal(left, right, decimals); 376 | } 377 | 378 | function assertLtDecimal(uint256 left, uint256 right, uint256 decimals, string memory err) 379 | internal 380 | virtual 381 | { 382 | if (left >= right) vm.assertLtDecimal(left, right, decimals, err); 383 | } 384 | 385 | function assertLt(int256 left, int256 right) internal virtual { 386 | if (left >= right) vm.assertLt(left, right); 387 | } 388 | 389 | function assertLt(int256 left, int256 right, string memory err) internal virtual { 390 | if (left >= right) vm.assertLt(left, right, err); 391 | } 392 | 393 | function assertLtDecimal(int256 left, int256 right, uint256 decimals) internal virtual { 394 | if (left >= right) vm.assertLtDecimal(left, right, decimals); 395 | } 396 | 397 | function assertLtDecimal(int256 left, int256 right, uint256 decimals, string memory err) 398 | internal 399 | virtual 400 | { 401 | if (left >= right) vm.assertLtDecimal(left, right, decimals, err); 402 | } 403 | 404 | function assertGt(uint256 left, uint256 right) internal virtual { 405 | if (left <= right) vm.assertGt(left, right); 406 | } 407 | 408 | function assertGt(uint256 left, uint256 right, string memory err) internal virtual { 409 | if (left <= right) vm.assertGt(left, right, err); 410 | } 411 | 412 | function assertGtDecimal(uint256 left, uint256 right, uint256 decimals) internal virtual { 413 | if (left <= right) vm.assertGtDecimal(left, right, decimals); 414 | } 415 | 416 | function assertGtDecimal(uint256 left, uint256 right, uint256 decimals, string memory err) 417 | internal 418 | virtual 419 | { 420 | if (left <= right) vm.assertGtDecimal(left, right, decimals, err); 421 | } 422 | 423 | function assertGt(int256 left, int256 right) internal virtual { 424 | if (left <= right) vm.assertGt(left, right); 425 | } 426 | 427 | function assertGt(int256 left, int256 right, string memory err) internal virtual { 428 | if (left <= right) vm.assertGt(left, right, err); 429 | } 430 | 431 | function assertGtDecimal(int256 left, int256 right, uint256 decimals) internal virtual { 432 | if (left <= right) vm.assertGtDecimal(left, right, decimals); 433 | } 434 | 435 | function assertGtDecimal(int256 left, int256 right, uint256 decimals, string memory err) 436 | internal 437 | virtual 438 | { 439 | if (left <= right) vm.assertGtDecimal(left, right, decimals, err); 440 | } 441 | 442 | function assertLe(uint256 left, uint256 right) internal virtual { 443 | if (left > right) vm.assertLe(left, right); 444 | } 445 | 446 | function assertLe(uint256 left, uint256 right, string memory err) internal virtual { 447 | if (left > right) vm.assertLe(left, right, err); 448 | } 449 | 450 | function assertLeDecimal(uint256 left, uint256 right, uint256 decimals) internal virtual { 451 | if (left > right) vm.assertLeDecimal(left, right, decimals); 452 | } 453 | 454 | function assertLeDecimal(uint256 left, uint256 right, uint256 decimals, string memory err) 455 | internal 456 | virtual 457 | { 458 | if (left > right) vm.assertLeDecimal(left, right, decimals, err); 459 | } 460 | 461 | function assertLe(int256 left, int256 right) internal virtual { 462 | if (left > right) vm.assertLe(left, right); 463 | } 464 | 465 | function assertLe(int256 left, int256 right, string memory err) internal virtual { 466 | if (left > right) vm.assertLe(left, right, err); 467 | } 468 | 469 | function assertLeDecimal(int256 left, int256 right, uint256 decimals) internal virtual { 470 | if (left > right) vm.assertLeDecimal(left, right, decimals); 471 | } 472 | 473 | function assertLeDecimal(int256 left, int256 right, uint256 decimals, string memory err) 474 | internal 475 | virtual 476 | { 477 | if (left > right) vm.assertLeDecimal(left, right, decimals, err); 478 | } 479 | 480 | function assertGe(uint256 left, uint256 right) internal virtual { 481 | if (left < right) vm.assertGe(left, right); 482 | } 483 | 484 | function assertGe(uint256 left, uint256 right, string memory err) internal virtual { 485 | if (left < right) vm.assertGe(left, right, err); 486 | } 487 | 488 | function assertGeDecimal(uint256 left, uint256 right, uint256 decimals) internal virtual { 489 | if (left < right) vm.assertGeDecimal(left, right, decimals); 490 | } 491 | 492 | function assertGeDecimal(uint256 left, uint256 right, uint256 decimals, string memory err) 493 | internal 494 | virtual 495 | { 496 | if (left < right) vm.assertGeDecimal(left, right, decimals, err); 497 | } 498 | 499 | function assertGe(int256 left, int256 right) internal virtual { 500 | if (left < right) vm.assertGe(left, right); 501 | } 502 | 503 | function assertGe(int256 left, int256 right, string memory err) internal virtual { 504 | if (left < right) vm.assertGe(left, right, err); 505 | } 506 | 507 | function assertGeDecimal(int256 left, int256 right, uint256 decimals) internal virtual { 508 | if (left < right) vm.assertGeDecimal(left, right, decimals); 509 | } 510 | 511 | function assertGeDecimal(int256 left, int256 right, uint256 decimals, string memory err) 512 | internal 513 | virtual 514 | { 515 | if (left < right) vm.assertGeDecimal(left, right, decimals, err); 516 | } 517 | 518 | function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta) internal virtual { 519 | vm.assertApproxEqAbs(left, right, maxDelta); 520 | } 521 | 522 | function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta, string memory err) 523 | internal 524 | virtual 525 | { 526 | vm.assertApproxEqAbs(left, right, maxDelta, err); 527 | } 528 | 529 | function assertApproxEqAbsDecimal( 530 | uint256 left, 531 | uint256 right, 532 | uint256 maxDelta, 533 | uint256 decimals 534 | ) internal virtual { 535 | vm.assertApproxEqAbsDecimal(left, right, maxDelta, decimals); 536 | } 537 | 538 | function assertApproxEqAbsDecimal( 539 | uint256 left, 540 | uint256 right, 541 | uint256 maxDelta, 542 | uint256 decimals, 543 | string memory err 544 | ) internal virtual { 545 | vm.assertApproxEqAbsDecimal(left, right, maxDelta, decimals, err); 546 | } 547 | 548 | function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta) internal virtual { 549 | vm.assertApproxEqAbs(left, right, maxDelta); 550 | } 551 | 552 | function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta, string memory err) 553 | internal 554 | virtual 555 | { 556 | vm.assertApproxEqAbs(left, right, maxDelta, err); 557 | } 558 | 559 | function assertApproxEqAbsDecimal(int256 left, int256 right, uint256 maxDelta, uint256 decimals) 560 | internal 561 | virtual 562 | { 563 | vm.assertApproxEqAbsDecimal(left, right, maxDelta, decimals); 564 | } 565 | 566 | function assertApproxEqAbsDecimal( 567 | int256 left, 568 | int256 right, 569 | uint256 maxDelta, 570 | uint256 decimals, 571 | string memory err 572 | ) internal virtual { 573 | vm.assertApproxEqAbsDecimal(left, right, maxDelta, decimals, err); 574 | } 575 | 576 | function assertApproxEqRel( 577 | uint256 left, 578 | uint256 right, 579 | uint256 maxPercentDelta // An 18 decimal fixed point number, where 1e18 == 100% 580 | ) internal virtual { 581 | vm.assertApproxEqRel(left, right, maxPercentDelta); 582 | } 583 | 584 | function assertApproxEqRel( 585 | uint256 left, 586 | uint256 right, 587 | uint256 maxPercentDelta, // An 18 decimal fixed point number, where 1e18 == 100% 588 | string memory err 589 | ) internal virtual { 590 | vm.assertApproxEqRel(left, right, maxPercentDelta, err); 591 | } 592 | 593 | function assertApproxEqRelDecimal( 594 | uint256 left, 595 | uint256 right, 596 | uint256 maxPercentDelta, // An 18 decimal fixed point number, where 1e18 == 100% 597 | uint256 decimals 598 | ) internal virtual { 599 | vm.assertApproxEqRelDecimal(left, right, maxPercentDelta, decimals); 600 | } 601 | 602 | function assertApproxEqRelDecimal( 603 | uint256 left, 604 | uint256 right, 605 | uint256 maxPercentDelta, // An 18 decimal fixed point number, where 1e18 == 100% 606 | uint256 decimals, 607 | string memory err 608 | ) internal virtual { 609 | vm.assertApproxEqRelDecimal(left, right, maxPercentDelta, decimals, err); 610 | } 611 | 612 | function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta) 613 | internal 614 | virtual 615 | { 616 | vm.assertApproxEqRel(left, right, maxPercentDelta); 617 | } 618 | 619 | function assertApproxEqRel( 620 | int256 left, 621 | int256 right, 622 | uint256 maxPercentDelta, // An 18 decimal fixed point number, where 1e18 == 100% 623 | string memory err 624 | ) internal virtual { 625 | vm.assertApproxEqRel(left, right, maxPercentDelta, err); 626 | } 627 | 628 | function assertApproxEqRelDecimal( 629 | int256 left, 630 | int256 right, 631 | uint256 maxPercentDelta, // An 18 decimal fixed point number, where 1e18 == 100% 632 | uint256 decimals 633 | ) internal virtual { 634 | vm.assertApproxEqRelDecimal(left, right, maxPercentDelta, decimals); 635 | } 636 | 637 | function assertApproxEqRelDecimal( 638 | int256 left, 639 | int256 right, 640 | uint256 maxPercentDelta, // An 18 decimal fixed point number, where 1e18 == 100% 641 | uint256 decimals, 642 | string memory err 643 | ) internal virtual { 644 | vm.assertApproxEqRelDecimal(left, right, maxPercentDelta, decimals, err); 645 | } 646 | 647 | function __eq(bool[] memory left, bool[] memory right) internal pure returns (bool result) { 648 | /// @solidity memory-safe-assembly 649 | assembly { 650 | let n := mload(left) 651 | if eq(n, mload(right)) { 652 | returndatacopy(returndatasize(), returndatasize(), shr(128, n)) 653 | result := 1 654 | let d := sub(right, left) 655 | for { n := add(left, shl(5, n)) } iszero(eq(left, n)) {} { 656 | left := add(left, 0x20) 657 | result := and(result, eq(iszero(mload(left)), iszero(mload(add(left, d))))) 658 | } 659 | } 660 | } 661 | } 662 | 663 | function __eq(address[] memory left, address[] memory right) 664 | internal 665 | pure 666 | returns (bool result) 667 | { 668 | /// @solidity memory-safe-assembly 669 | assembly { 670 | let n := mload(left) 671 | if eq(n, mload(right)) { 672 | returndatacopy(returndatasize(), returndatasize(), shr(128, n)) 673 | result := 1 674 | let d := sub(right, left) 675 | for { n := add(left, shl(5, n)) } iszero(eq(left, n)) {} { 676 | left := add(left, 0x20) 677 | result := and(result, eq(shl(96, mload(left)), shl(96, mload(add(left, d))))) 678 | } 679 | } 680 | } 681 | } 682 | 683 | function __eq(bytes32[] memory left, bytes32[] memory right) 684 | internal 685 | pure 686 | returns (bool result) 687 | { 688 | /// @solidity memory-safe-assembly 689 | assembly { 690 | result := keccak256(left, shl(5, add(1, mload(left)))) 691 | result := eq(keccak256(right, shl(5, add(1, mload(right)))), result) 692 | } 693 | } 694 | 695 | function __eq(int256[] memory left, int256[] memory right) 696 | internal 697 | pure 698 | returns (bool result) 699 | { 700 | /// @solidity memory-safe-assembly 701 | assembly { 702 | result := keccak256(left, shl(5, add(1, mload(left)))) 703 | result := eq(keccak256(right, shl(5, add(1, mload(right)))), result) 704 | } 705 | } 706 | 707 | function __eq(uint256[] memory left, uint256[] memory right) 708 | internal 709 | pure 710 | returns (bool result) 711 | { 712 | /// @solidity memory-safe-assembly 713 | assembly { 714 | result := keccak256(left, shl(5, add(1, mload(left)))) 715 | result := eq(keccak256(right, shl(5, add(1, mload(right)))), result) 716 | } 717 | } 718 | 719 | function __eq(string[] memory left, string[] memory right) 720 | internal 721 | pure 722 | returns (bool result) 723 | { 724 | /// @solidity memory-safe-assembly 725 | assembly { 726 | let n := mload(left) 727 | if eq(n, mload(right)) { 728 | returndatacopy(returndatasize(), returndatasize(), shr(128, n)) 729 | result := 1 730 | let d := sub(right, left) 731 | for { n := add(left, shl(5, n)) } iszero(eq(left, n)) {} { 732 | left := add(left, 0x20) 733 | let l := mload(left) 734 | l := keccak256(l, add(0x20, mload(l))) 735 | let r := mload(add(left, d)) 736 | r := keccak256(r, add(0x20, mload(r))) 737 | result := and(result, eq(l, r)) 738 | } 739 | } 740 | } 741 | } 742 | 743 | function __eq(bytes[] memory left, bytes[] memory right) internal pure returns (bool result) { 744 | /// @solidity memory-safe-assembly 745 | assembly { 746 | let n := mload(left) 747 | if eq(n, mload(right)) { 748 | returndatacopy(returndatasize(), returndatasize(), shr(128, n)) 749 | result := 1 750 | let d := sub(right, left) 751 | for { n := add(left, shl(5, n)) } iszero(eq(left, n)) {} { 752 | left := add(left, 0x20) 753 | let l := mload(left) 754 | l := keccak256(l, add(0x20, mload(l))) 755 | let r := mload(add(left, d)) 756 | r := keccak256(r, add(0x20, mload(r))) 757 | result := and(result, eq(l, r)) 758 | } 759 | } 760 | } 761 | } 762 | 763 | function __eq(string memory left, string memory right) internal pure returns (bool result) { 764 | /// @solidity memory-safe-assembly 765 | assembly { 766 | result := keccak256(left, add(0x20, mload(left))) 767 | result := eq(keccak256(right, add(0x20, mload(right))), result) 768 | } 769 | } 770 | 771 | function __eq(bytes memory left, bytes memory right) internal pure returns (bool result) { 772 | /// @solidity memory-safe-assembly 773 | assembly { 774 | result := keccak256(left, add(0x20, mload(left))) 775 | result := eq(keccak256(right, add(0x20, mload(right))), result) 776 | } 777 | } 778 | } 779 | 780 | library stdError { 781 | bytes public constant assertionError = abi.encodeWithSignature("Panic(uint256)", 0x01); 782 | bytes public constant arithmeticError = abi.encodeWithSignature("Panic(uint256)", 0x11); 783 | bytes public constant divisionError = abi.encodeWithSignature("Panic(uint256)", 0x12); 784 | bytes public constant enumConversionError = abi.encodeWithSignature("Panic(uint256)", 0x21); 785 | bytes public constant encodeStorageError = abi.encodeWithSignature("Panic(uint256)", 0x22); 786 | bytes public constant popError = abi.encodeWithSignature("Panic(uint256)", 0x31); 787 | bytes public constant indexOOBError = abi.encodeWithSignature("Panic(uint256)", 0x32); 788 | bytes public constant memOverflowError = abi.encodeWithSignature("Panic(uint256)", 0x41); 789 | bytes public constant zeroVarError = abi.encodeWithSignature("Panic(uint256)", 0x51); 790 | // DEPRECATED: Use Vm's `expectRevert` without any arguments instead 791 | bytes public constant lowLevelError = bytes(""); // `0x` 792 | } 793 | -------------------------------------------------------------------------------- /test/Multicaller.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import "./utils/TestPlus.sol"; 5 | import {MulticallerEtcher} from "../src/MulticallerEtcher.sol"; 6 | import {Multicaller} from "../src/Multicaller.sol"; 7 | import {MulticallerWithSender} from "../src/MulticallerWithSender.sol"; 8 | import {MulticallerWithSigner} from "../src/MulticallerWithSigner.sol"; 9 | import {LibMulticaller} from "../src/LibMulticaller.sol"; 10 | import {MockERC1271Wallet} from "./utils/mocks/MockERC1271Wallet.sol"; 11 | import {MockERC1271Malicious} from "./utils/mocks/MockERC1271Malicious.sol"; 12 | 13 | /** 14 | * @dev Target contract for the multicaller for testing purposes. 15 | */ 16 | contract MulticallerTarget { 17 | error CustomError(); 18 | 19 | string private _name; 20 | 21 | constructor(string memory name_) { 22 | _name = name_; 23 | } 24 | 25 | struct Tuple { 26 | uint256 a; 27 | uint256 b; 28 | } 29 | 30 | function revertsWithString(string memory e) external pure { 31 | revert(e); 32 | } 33 | 34 | function revertsWithCustomError() external pure { 35 | revert CustomError(); 36 | } 37 | 38 | function revertsWithNothing() external pure { 39 | revert(); 40 | } 41 | 42 | function returnsTuple(uint256 a, uint256 b) external pure returns (Tuple memory tuple) { 43 | tuple = Tuple({a: a, b: b}); 44 | } 45 | 46 | function returnsString(string calldata s) external pure returns (string memory) { 47 | return s; 48 | } 49 | 50 | uint256 public paid; 51 | 52 | function pay() external payable { 53 | paid += msg.value; 54 | } 55 | 56 | function returnsSender() external view returns (address) { 57 | return LibMulticaller.sender(); 58 | } 59 | 60 | function returnsMulticallerSender() external view returns (address) { 61 | return LibMulticaller.multicallerSender(); 62 | } 63 | 64 | function returnsMulticallerSigner() external view returns (address) { 65 | return LibMulticaller.multicallerSigner(); 66 | } 67 | 68 | function returnsSenderOrSigner() external view returns (address) { 69 | return LibMulticaller.senderOrSigner(); 70 | } 71 | 72 | function name() external view returns (string memory) { 73 | return _name; 74 | } 75 | } 76 | 77 | contract FallbackTarget { 78 | uint256 public hashSum; 79 | 80 | fallback() external payable { 81 | _fallback(); 82 | } 83 | 84 | receive() external payable { 85 | _fallback(); 86 | } 87 | 88 | function _fallback() internal { 89 | assembly { 90 | calldatacopy(0x00, 0x00, calldatasize()) 91 | let h := keccak256(0x00, calldatasize()) 92 | sstore(hashSum.slot, add(sload(hashSum.slot), h)) 93 | return(0x00, calldatasize()) 94 | } 95 | } 96 | } 97 | 98 | contract MulticallerTest is TestPlus { 99 | Multicaller multicaller; 100 | MulticallerWithSender multicallerWithSender; 101 | MulticallerWithSigner multicallerWithSigner; 102 | 103 | MulticallerTarget targetA; 104 | MulticallerTarget targetB; 105 | 106 | FallbackTarget fallbackTargetA; 107 | FallbackTarget fallbackTargetB; 108 | 109 | address erc721Signer; 110 | uint256 erc721SignerPrivateKey; 111 | address erc1271Wallet; 112 | address erc1271Malicious; 113 | 114 | event NoncesInvalidated(address indexed signer, uint256[] nonces); 115 | 116 | event NonceSaltIncremented(address indexed signer, uint256 newNonceSalt); 117 | 118 | function setUp() public virtual { 119 | multicaller = MulticallerEtcher.multicaller(); 120 | multicallerWithSender = MulticallerEtcher.multicallerWithSender(); 121 | multicallerWithSigner = MulticallerEtcher.multicallerWithSigner(); 122 | 123 | // Uncomment as needed to test the source files directly: 124 | // _etchMulticaller(); 125 | // _etchMulticallerWithSender(); 126 | // _etchMulticallerWithSigner(); 127 | 128 | _deployTargets(); 129 | } 130 | 131 | function _etchMulticaller() internal { 132 | vm.etch(LibMulticaller.MULTICALLER, address(new Multicaller()).code); 133 | multicaller = Multicaller(payable(LibMulticaller.MULTICALLER)); 134 | } 135 | 136 | function _etchMulticallerWithSender() internal { 137 | vm.etch(LibMulticaller.MULTICALLER_WITH_SENDER, address(new MulticallerWithSender()).code); 138 | vm.store(LibMulticaller.MULTICALLER_WITH_SENDER, 0, bytes32(uint256(1 << 160))); 139 | multicallerWithSender = 140 | MulticallerWithSender(payable(LibMulticaller.MULTICALLER_WITH_SENDER)); 141 | } 142 | 143 | function _etchMulticallerWithSigner() internal { 144 | vm.etch(LibMulticaller.MULTICALLER_WITH_SIGNER, address(new MulticallerWithSigner()).code); 145 | vm.store(LibMulticaller.MULTICALLER_WITH_SIGNER, 0, bytes32(uint256(1))); 146 | multicallerWithSigner = 147 | MulticallerWithSigner(payable(LibMulticaller.MULTICALLER_WITH_SIGNER)); 148 | } 149 | 150 | function _deployTargets() internal { 151 | targetA = new MulticallerTarget("A"); 152 | targetB = new MulticallerTarget("B"); 153 | fallbackTargetA = new FallbackTarget(); 154 | fallbackTargetB = new FallbackTarget(); 155 | } 156 | 157 | function _deployERC1271Contracts() internal { 158 | (erc721Signer, erc721SignerPrivateKey) = _randomSigner(); 159 | erc1271Wallet = address(new MockERC1271Wallet(erc721Signer)); 160 | erc1271Malicious = address(new MockERC1271Malicious()); 161 | } 162 | 163 | function testMulticallerRefund(uint256) public { 164 | uint256 payment = _bound(_random(), 0, type(uint128).max); 165 | 166 | vm.deal(address(this), type(uint160).max); 167 | 168 | address[] memory targets = new address[](1); 169 | targets[0] = address(targetA); 170 | bytes[] memory data = new bytes[](1); 171 | data[0] = abi.encodeWithSelector(MulticallerTarget.pay.selector); 172 | uint256[] memory values = new uint256[](1); 173 | values[0] = payment; 174 | 175 | multicaller.aggregate{value: address(this).balance}(targets, data, values, address(1)); 176 | assertEq(address(this).balance, type(uint160).max - payment); 177 | 178 | uint256 excess = _bound(_random(), 0, type(uint128).max); 179 | uint256 value = payment + excess; 180 | multicaller.aggregate{value: value}(targets, data, values, address(fallbackTargetA)); 181 | assertEq(address(fallbackTargetA).balance, excess); 182 | } 183 | 184 | function testMulticallerRevertWithMessage(string memory revertMessage) public { 185 | address[] memory targets = new address[](1); 186 | targets[0] = address(targetA); 187 | bytes[] memory data = new bytes[](1); 188 | data[0] = 189 | abi.encodeWithSelector(MulticallerTarget.revertsWithString.selector, revertMessage); 190 | vm.expectRevert(bytes(revertMessage)); 191 | multicaller.aggregate(targets, data, new uint256[](1), address(0)); 192 | vm.expectRevert(bytes(revertMessage)); 193 | multicallerWithSender.aggregateWithSender(targets, data, new uint256[](1)); 194 | } 195 | 196 | function testMulticallerRevertWithMessage() public { 197 | testMulticallerRevertWithMessage("Milady"); 198 | } 199 | 200 | function testMulticallerRevertWithCustomError() public { 201 | address[] memory targets = new address[](1); 202 | targets[0] = address(targetA); 203 | bytes[] memory data = new bytes[](1); 204 | data[0] = abi.encodeWithSelector(MulticallerTarget.revertsWithCustomError.selector); 205 | vm.expectRevert(MulticallerTarget.CustomError.selector); 206 | multicaller.aggregate(targets, data, new uint256[](1), address(0)); 207 | vm.expectRevert(MulticallerTarget.CustomError.selector); 208 | multicallerWithSender.aggregateWithSender(targets, data, new uint256[](1)); 209 | } 210 | 211 | function testMulticallerRevertWithNothing() public { 212 | address[] memory targets = new address[](1); 213 | targets[0] = address(targetA); 214 | bytes[] memory data = new bytes[](1); 215 | data[0] = abi.encodeWithSelector(MulticallerTarget.revertsWithNothing.selector); 216 | vm.expectRevert(); 217 | multicaller.aggregate(targets, data, new uint256[](1), address(0)); 218 | vm.expectRevert(); 219 | multicallerWithSender.aggregateWithSender(targets, data, new uint256[](1)); 220 | } 221 | 222 | function testMulticallerReturnDataIsProperlyEncoded( 223 | uint256 a0, 224 | uint256 b0, 225 | uint256 a1, 226 | uint256 b1 227 | ) public { 228 | address[] memory targets = new address[](2); 229 | targets[0] = address(targetA); 230 | targets[1] = address(targetB); 231 | bytes[] memory data = new bytes[](2); 232 | data[0] = abi.encodeWithSelector(MulticallerTarget.returnsTuple.selector, a0, b0); 233 | data[1] = abi.encodeWithSelector(MulticallerTarget.returnsTuple.selector, a1, b1); 234 | bytes[] memory results = multicaller.aggregate(targets, data, new uint256[](2), address(0)); 235 | MulticallerTarget.Tuple memory t0 = abi.decode(results[0], (MulticallerTarget.Tuple)); 236 | MulticallerTarget.Tuple memory t1 = abi.decode(results[1], (MulticallerTarget.Tuple)); 237 | assertEq(t0.a, a0); 238 | assertEq(t0.b, b0); 239 | assertEq(t1.a, a1); 240 | assertEq(t1.b, b1); 241 | assertEq( 242 | abi.encode(multicallerWithSender.aggregateWithSender(targets, data, new uint256[](2))), 243 | abi.encode(results) 244 | ); 245 | } 246 | 247 | function testMulticallerReturnDataIsProperlyEncoded( 248 | string memory s0, 249 | string memory s1, 250 | uint256 n 251 | ) public { 252 | n = _bound(_random(), 0, 5); 253 | uint256[] memory choices = new uint256[](n); 254 | address[] memory targets = new address[](n); 255 | bytes[] memory data = new bytes[](n); 256 | for (uint256 i; i != n; ++i) { 257 | targets[i] = _random() % 2 == 0 ? address(targetA) : address(targetB); 258 | uint256 c = _random() % 2; 259 | choices[i] = c; 260 | string memory s = c == 0 ? s0 : s1; 261 | data[i] = abi.encodeWithSelector(MulticallerTarget.returnsString.selector, s); 262 | } 263 | bytes[] memory results = multicaller.aggregate(targets, data, new uint256[](n), address(0)); 264 | for (uint256 i; i != n; ++i) { 265 | string memory s = choices[i] == 0 ? s0 : s1; 266 | assertEq(abi.decode(results[i], (string)), s); 267 | } 268 | assertEq( 269 | abi.encode(multicallerWithSender.aggregateWithSender(targets, data, new uint256[](n))), 270 | abi.encode(results) 271 | ); 272 | 273 | (bool success, bytes memory encodedResults) = address(multicaller).call( 274 | _cdCompress( 275 | abi.encodeWithSelector( 276 | Multicaller.aggregate.selector, targets, data, new uint256[](n), address(0) 277 | ) 278 | ) 279 | ); 280 | assertTrue(success); 281 | assertEq(encodedResults, abi.encode(results)); 282 | } 283 | 284 | function testMulticallerCdFallback(string memory s) public { 285 | address[] memory targets = new address[](2); 286 | targets[0] = address(targetA); 287 | targets[1] = address(targetA); 288 | bytes[] memory data = new bytes[](2); 289 | data[0] = abi.encodeWithSelector(MulticallerTarget.returnsString.selector, s); 290 | data[1] = abi.encodeWithSelector(MulticallerTarget.returnsString.selector, s); 291 | uint256[] memory values = new uint256[](2); 292 | 293 | bytes[] memory results = multicaller.aggregate(targets, data, values, address(0)); 294 | 295 | (bool success, bytes memory encodedResults) = address(multicaller).call( 296 | _cdCompress( 297 | abi.encodeWithSelector( 298 | Multicaller.aggregate.selector, targets, data, values, address(0) 299 | ) 300 | ) 301 | ); 302 | assertTrue(success); 303 | assertEq(encodedResults, abi.encode(results)); 304 | 305 | uint256 value = _bound(_random(), 0, 1 ether); 306 | vm.deal(address(this), value); 307 | (success, encodedResults) = address(multicaller).call{value: value}(""); 308 | assertTrue(success); 309 | assertEq(encodedResults.length, 0); 310 | assertEq(address(multicaller).balance, value); 311 | } 312 | 313 | function testMulticallerReturnDataIsProperlyEncoded() public { 314 | testMulticallerReturnDataIsProperlyEncoded(0, 1, 2, 3); 315 | } 316 | 317 | function testMulticallerWithNoData() public { 318 | address[] memory targets = new address[](0); 319 | bytes[] memory data = new bytes[](0); 320 | assertEq(multicaller.aggregate(targets, data, new uint256[](0), address(0)).length, 0); 321 | assertEq( 322 | multicallerWithSender.aggregateWithSender(targets, data, new uint256[](0)).length, 0 323 | ); 324 | } 325 | 326 | function testMulticallerForwardsMessageValue() public { 327 | address[] memory targets = new address[](4); 328 | targets[0] = address(targetA); 329 | targets[1] = address(targetA); 330 | targets[2] = address(targetB); 331 | targets[3] = address(targetB); 332 | bytes[] memory data = new bytes[](4); 333 | data[0] = abi.encodeWithSelector(MulticallerTarget.pay.selector); 334 | data[1] = abi.encodeWithSelector(MulticallerTarget.pay.selector); 335 | data[2] = abi.encodeWithSelector(MulticallerTarget.pay.selector); 336 | data[3] = abi.encodeWithSelector(MulticallerTarget.pay.selector); 337 | uint256[] memory values = new uint256[](4); 338 | values[0] = 1; 339 | values[1] = 0; 340 | values[2] = 0; 341 | values[3] = 3; 342 | multicaller.aggregate{value: 4}(targets, data, values, address(0)); 343 | multicallerWithSender.aggregateWithSender{value: 4}(targets, data, values); 344 | assertEq(targetA.paid(), 2); 345 | assertEq(targetB.paid(), 6); 346 | 347 | targets[0] = address(targetB); 348 | targets[1] = address(targetB); 349 | targets[2] = address(targetA); 350 | targets[3] = address(targetA); 351 | values[0] = 0; 352 | values[3] = 5; 353 | multicaller.aggregate{value: 5}(targets, data, values, address(0)); 354 | multicallerWithSender.aggregateWithSender{value: 5}(targets, data, values); 355 | assertEq(targetA.paid(), 12); 356 | assertEq(targetB.paid(), 6); 357 | 358 | targets = new address[](1); 359 | targets[0] = address(targetA); 360 | data = new bytes[](1); 361 | data[0] = abi.encodeWithSelector(MulticallerTarget.pay.selector); 362 | values = new uint256[](1); 363 | values[0] = 3; 364 | multicaller.aggregate{value: 3}(targets, data, values, address(0)); 365 | multicallerWithSender.aggregateWithSender{value: 3}(targets, data, values); 366 | assertEq(targetA.paid(), 18); 367 | } 368 | 369 | function testMulticallerGetNames() public { 370 | address[] memory targets = new address[](2); 371 | targets[0] = address(targetA); 372 | targets[1] = address(targetB); 373 | bytes[] memory data = new bytes[](2); 374 | data[0] = abi.encodeWithSelector(MulticallerTarget.name.selector); 375 | data[1] = abi.encodeWithSelector(MulticallerTarget.name.selector); 376 | bytes[] memory results = multicaller.aggregate(targets, data, new uint256[](2), address(0)); 377 | assertEq(abi.decode(results[0], (string)), "A"); 378 | assertEq(abi.decode(results[1], (string)), "B"); 379 | assertEq( 380 | abi.encode(multicallerWithSender.aggregateWithSender(targets, data, new uint256[](2))), 381 | abi.encode(results) 382 | ); 383 | } 384 | 385 | function testMulticallerReentrancyGuard() public { 386 | address[] memory targets = new address[](1); 387 | targets[0] = address(multicallerWithSender); 388 | bytes[] memory data = new bytes[](1); 389 | data[0] = abi.encodeWithSelector( 390 | MulticallerWithSender.aggregateWithSender.selector, 391 | new address[](0), 392 | new bytes[](0), 393 | new uint256[](0) 394 | ); 395 | vm.expectRevert(MulticallerWithSender.Reentrancy.selector); 396 | multicallerWithSender.aggregateWithSender(targets, data, new uint256[](1)); 397 | } 398 | 399 | function testMulticallerTargetGetMulticallerSender() public { 400 | address[] memory targets = new address[](1); 401 | targets[0] = address(targetA); 402 | bytes[] memory data = new bytes[](1); 403 | data[0] = abi.encodeWithSelector(MulticallerTarget.returnsMulticallerSender.selector); 404 | 405 | bytes[] memory results = 406 | multicallerWithSender.aggregateWithSender(targets, data, new uint256[](1)); 407 | assertEq(abi.decode(results[0], (address)), address(this)); 408 | data[0] = abi.encodeWithSelector(MulticallerTarget.returnsSender.selector); 409 | results = multicallerWithSender.aggregateWithSender(targets, data, new uint256[](1)); 410 | assertEq(abi.decode(results[0], (address)), address(this)); 411 | 412 | data[0] = abi.encodeWithSelector(MulticallerTarget.returnsSenderOrSigner.selector); 413 | results = multicallerWithSender.aggregateWithSender(targets, data, new uint256[](1)); 414 | assertEq(abi.decode(results[0], (address)), address(this)); 415 | } 416 | 417 | function testMulticallerSenderDoesNotRevertWithoutMulticallerDeployed() public { 418 | vm.etch(LibMulticaller.MULTICALLER_WITH_SENDER, ""); 419 | assertEq(LibMulticaller.multicallerSender(), address(0)); 420 | } 421 | 422 | struct _TestTemps { 423 | address[] targets; 424 | bytes[] data; 425 | uint256[] values; 426 | uint256 nonce; 427 | uint256 nonceSalt; 428 | bytes signature; 429 | address signer; 430 | uint256 privateKey; 431 | } 432 | 433 | function _randomBytes() internal returns (bytes memory result) { 434 | uint256 r0 = _random(); 435 | uint256 r1 = _random(); 436 | uint256 r2 = _random(); 437 | uint256 r3 = _random(); 438 | uint256 n = _random() % 128; 439 | assembly { 440 | result := mload(0x40) 441 | mstore(result, n) 442 | mstore(add(result, 0x20), r0) 443 | mstore(add(result, 0x40), r1) 444 | mstore(add(result, 0x60), r2) 445 | mstore(add(result, 0x80), r3) 446 | mstore(0x40, add(result, 0xa0)) 447 | } 448 | } 449 | 450 | function _generateSignature(_TestTemps memory t) internal view { 451 | unchecked { 452 | bytes32[] memory dataHashes = new bytes32[](t.data.length); 453 | for (uint256 i; i < t.data.length; ++i) { 454 | dataHashes[i] = keccak256(t.data[i]); 455 | } 456 | bytes32 digest = keccak256( 457 | abi.encodePacked( 458 | "\x19\x01", 459 | _multicallerWithSignerDomainSeparator(), 460 | keccak256( 461 | abi.encode( 462 | keccak256( 463 | "AggregateWithSigner(address signer,address[] targets,bytes[] data,uint256[] values,uint256 nonce,uint256 nonceSalt)" 464 | ), 465 | t.signer, 466 | keccak256(abi.encodePacked(t.targets)), 467 | keccak256(abi.encodePacked(dataHashes)), 468 | keccak256(abi.encodePacked(t.values)), 469 | t.nonce, 470 | t.nonceSalt 471 | ) 472 | ) 473 | ) 474 | ); 475 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(t.privateKey, digest); 476 | t.signature = abi.encodePacked(r, s, v); 477 | } 478 | } 479 | 480 | function _nextNonceSalt(uint256 nonceSalt) internal view returns (uint256 result) { 481 | assembly { 482 | result := add(add(1, shr(224, blockhash(sub(number(), 1)))), nonceSalt) 483 | } 484 | } 485 | 486 | function _testTemps() internal returns (_TestTemps memory t) { 487 | (t.signer, t.privateKey) = _randomSigner(); 488 | uint256 n = _random() % 3; // 0, 1, 2 489 | t.targets = new address[](n); 490 | t.data = new bytes[](n); 491 | t.values = new uint256[](n); 492 | for (uint256 i; i < n; ++i) { 493 | t.targets[i] = _random() % 2 == 0 ? address(fallbackTargetA) : address(fallbackTargetB); 494 | t.data[i] = _randomBytes(); 495 | t.values[i] = _random() % 32; 496 | } 497 | t.nonce = _random(); 498 | 499 | { 500 | uint256 newNonceSalt = _nextNonceSalt(multicallerWithSigner.nonceSaltOf(t.signer)); 501 | vm.expectEmit(true, true, true, true); 502 | emit NonceSaltIncremented(t.signer, newNonceSalt); 503 | vm.prank(t.signer); 504 | assertEq(multicallerWithSigner.incrementNonceSalt(), newNonceSalt); 505 | t.nonceSalt = multicallerWithSigner.nonceSaltOf(t.signer); 506 | } 507 | _generateSignature(t); 508 | } 509 | 510 | function testMulticallerWithSigner(uint256) public { 511 | _TestTemps memory t = _testTemps(); 512 | 513 | vm.deal(address(this), type(uint160).max); 514 | 515 | if (_random() % 2 == 0) { 516 | uint256 r = _random() % 3; 517 | if (r == 0) { 518 | uint256 newNonceSalt = _nextNonceSalt(multicallerWithSigner.nonceSaltOf(t.signer)); 519 | vm.expectEmit(true, true, true, true); 520 | emit NonceSaltIncremented(t.signer, newNonceSalt); 521 | vm.prank(t.signer); 522 | multicallerWithSigner.incrementNonceSalt(); 523 | _callAndCheckMulticallerWithSigner( 524 | t, MulticallerWithSigner.InvalidSignature.selector 525 | ); 526 | return; 527 | } 528 | if (r == 1) { 529 | uint256[] memory noncesToInvalidate = new uint256[](1); 530 | noncesToInvalidate[0] = t.nonce; 531 | vm.prank(t.signer); 532 | multicallerWithSigner.invalidateNonces(noncesToInvalidate); 533 | _callAndCheckMulticallerWithSigner( 534 | t, MulticallerWithSigner.InvalidSignature.selector 535 | ); 536 | return; 537 | } 538 | if (r == 2) { 539 | t.signature[0] = bytes1(uint8(t.signature[0]) ^ 1); 540 | _callAndCheckMulticallerWithSigner( 541 | t, MulticallerWithSigner.InvalidSignature.selector 542 | ); 543 | return; 544 | } 545 | } 546 | 547 | bytes[] memory results = _callAndCheckMulticallerWithSigner(t, bytes4(0)); 548 | 549 | unchecked { 550 | uint256 expectedHashSum; 551 | for (uint256 i; i < t.data.length; ++i) { 552 | expectedHashSum += uint256(keccak256(t.data[i])); 553 | } 554 | uint256 actualHashSum = fallbackTargetA.hashSum() + fallbackTargetB.hashSum(); 555 | assertEq(actualHashSum, expectedHashSum); 556 | for (uint256 i; i < results.length; ++i) { 557 | assertEq(keccak256(results[i]), keccak256(t.data[i])); 558 | } 559 | } 560 | 561 | _checkBalance(t, address(fallbackTargetA)); 562 | _checkBalance(t, address(fallbackTargetB)); 563 | 564 | if (_random() % 2 == 0) { 565 | _callAndCheckMulticallerWithSigner(t, MulticallerWithSigner.InvalidSignature.selector); 566 | } 567 | } 568 | 569 | function testMulticallerWithSignerWithERC1271(uint256) public { 570 | _deployERC1271Contracts(); 571 | _TestTemps memory t = _testTemps(); 572 | t.signer = erc1271Wallet; 573 | t.privateKey = erc721SignerPrivateKey; 574 | t.nonceSalt = multicallerWithSigner.nonceSaltOf(t.signer); 575 | _generateSignature(t); 576 | 577 | vm.deal(address(this), type(uint160).max); 578 | 579 | if (_random() % 2 == 0) { 580 | t.signer = erc1271Malicious; 581 | _callAndCheckMulticallerWithSigner(t, MulticallerWithSigner.InvalidSignature.selector); 582 | } else { 583 | bytes[] memory results = _callAndCheckMulticallerWithSigner(t, bytes4(0)); 584 | 585 | unchecked { 586 | uint256 expectedHashSum; 587 | for (uint256 i; i < t.data.length; ++i) { 588 | expectedHashSum += uint256(keccak256(t.data[i])); 589 | } 590 | uint256 actualHashSum = fallbackTargetA.hashSum() + fallbackTargetB.hashSum(); 591 | assertEq(actualHashSum, expectedHashSum); 592 | for (uint256 i; i < results.length; ++i) { 593 | assertEq(keccak256(results[i]), keccak256(t.data[i])); 594 | } 595 | } 596 | } 597 | } 598 | 599 | function _checkBalance(_TestTemps memory t, address target) internal { 600 | unchecked { 601 | uint256 expected; 602 | for (uint256 i; i < t.data.length; ++i) { 603 | if (t.targets[i] == target) { 604 | expected += t.values[i]; 605 | } 606 | } 607 | assertEq(target.balance, expected); 608 | } 609 | } 610 | 611 | function testMulticallerWithSignerReentrancyGuard() public { 612 | _TestTemps memory t = _testTemps(); 613 | t.targets = new address[](1); 614 | t.targets[0] = address(multicallerWithSigner); 615 | 616 | t.data = new bytes[](1); 617 | t.data[0] = abi.encodeWithSelector( 618 | MulticallerWithSigner.aggregateWithSigner.selector, 619 | "", 620 | new address[](0), 621 | new bytes[](0), 622 | new uint256[](0), 623 | 0, 624 | 0, 625 | address(0), 626 | "" 627 | ); 628 | 629 | t.values = new uint256[](1); 630 | 631 | _generateSignature(t); 632 | 633 | _callAndCheckMulticallerWithSigner(t, MulticallerWithSigner.Reentrancy.selector); 634 | } 635 | 636 | function testMulticallerWithSignerRevert() public { 637 | string memory revertMessage = "Hehehehe"; 638 | 639 | _TestTemps memory t = _testTemps(); 640 | t.targets = new address[](1); 641 | t.targets[0] = address(targetA); 642 | 643 | t.values = new uint256[](1); 644 | 645 | t.data = new bytes[](1); 646 | t.data[0] = 647 | abi.encodeWithSelector(MulticallerTarget.revertsWithString.selector, revertMessage); 648 | 649 | _generateSignature(t); 650 | 651 | vm.expectRevert(bytes(revertMessage)); 652 | _callMulticallerWithSigner(t, 0); 653 | 654 | t.data[0] = abi.encodeWithSelector(MulticallerTarget.revertsWithCustomError.selector); 655 | 656 | _generateSignature(t); 657 | 658 | vm.expectRevert(MulticallerTarget.CustomError.selector); 659 | _callMulticallerWithSigner(t, 0); 660 | 661 | t.data[0] = abi.encodeWithSelector(MulticallerTarget.revertsWithNothing.selector); 662 | 663 | _generateSignature(t); 664 | 665 | vm.expectRevert(); 666 | _callMulticallerWithSigner(t, 0); 667 | } 668 | 669 | function testMulticallerWithSignerGetMulticallerSigner() public { 670 | _TestTemps memory t = _testTemps(); 671 | t.targets = new address[](4); 672 | t.targets[0] = address(targetA); 673 | t.targets[1] = address(targetA); 674 | t.targets[2] = address(targetA); 675 | t.targets[3] = address(targetA); 676 | 677 | t.data = new bytes[](4); 678 | t.data[0] = abi.encodeWithSelector(MulticallerTarget.returnsMulticallerSigner.selector); 679 | t.data[1] = abi.encodeWithSelector(MulticallerTarget.returnsSenderOrSigner.selector); 680 | t.data[2] = abi.encodeWithSelector(MulticallerTarget.returnsSender.selector); 681 | t.data[3] = abi.encodeWithSelector(MulticallerTarget.returnsMulticallerSender.selector); 682 | 683 | t.values = new uint256[](4); 684 | 685 | _generateSignature(t); 686 | 687 | bytes[] memory results = _callAndCheckMulticallerWithSigner(t, bytes4(0)); 688 | assertEq(abi.decode(results[0], (address)), t.signer); 689 | assertEq(abi.decode(results[1], (address)), t.signer); 690 | assertEq(abi.decode(results[2], (address)), address(multicallerWithSigner)); 691 | assertEq(abi.decode(results[3], (address)), address(0)); 692 | } 693 | 694 | function testMulticallerWithSignerWithNoData() public { 695 | _TestTemps memory t = _testTemps(); 696 | t.targets = new address[](0); 697 | 698 | t.data = new bytes[](0); 699 | 700 | t.values = new uint256[](0); 701 | 702 | _generateSignature(t); 703 | 704 | bytes[] memory results = _callAndCheckMulticallerWithSigner(t, bytes4(0)); 705 | assertEq(results.length, 0); 706 | 707 | _callAndCheckMulticallerWithSigner(t, MulticallerWithSigner.InvalidSignature.selector); 708 | } 709 | 710 | function testMulticallerWithSignerEIP712Domain() public { 711 | vm.chainId(12345); 712 | 713 | ( 714 | bytes1 fields, 715 | string memory name, 716 | string memory version, 717 | uint256 chainId, 718 | address verifyingContract, 719 | bytes32 salt, 720 | uint256[] memory extensions 721 | ) = multicallerWithSigner.eip712Domain(); 722 | 723 | assertEq(fields, bytes1(hex"0f")); 724 | assertEq(name, "MulticallerWithSigner"); 725 | assertEq(version, "1"); 726 | assertEq(chainId, block.chainid); 727 | assertEq(verifyingContract, address(multicallerWithSigner)); 728 | assertEq(salt, bytes32(0)); 729 | assertEq(extensions.length, 0); 730 | } 731 | 732 | function testMulticallerWithSignerNonPayableFunctions() public { 733 | bool success; 734 | bytes memory data; 735 | vm.deal(address(this), 1 ether); 736 | 737 | data = abi.encodeWithSelector(MulticallerWithSigner.nonceSaltOf.selector, address(this)); 738 | (success,) = address(multicallerWithSigner).call{value: 1}(data); 739 | assertFalse(success); 740 | data = abi.encodeWithSelector(MulticallerWithSigner.nonceSaltOf.selector, address(this)); 741 | (success,) = address(multicallerWithSigner).call{value: 0}(data); 742 | assertTrue(success); 743 | 744 | data = abi.encodeWithSelector(MulticallerWithSigner.incrementNonceSalt.selector); 745 | (success,) = address(multicallerWithSigner).call{value: 1}(data); 746 | assertFalse(success); 747 | data = abi.encodeWithSelector(MulticallerWithSigner.incrementNonceSalt.selector); 748 | (success,) = address(multicallerWithSigner).call{value: 0}(data); 749 | assertTrue(success); 750 | } 751 | 752 | function _callMulticallerWithSigner(_TestTemps memory t, uint256 value) 753 | internal 754 | returns (bytes[] memory results) 755 | { 756 | results = multicallerWithSigner.aggregateWithSigner{value: value}( 757 | t.targets, t.data, t.values, t.nonce, t.signer, _maybeMake2098(t.signature) 758 | ); 759 | } 760 | 761 | function _callAndCheckMulticallerWithSigner(_TestTemps memory t, bytes4 errorSelector) 762 | internal 763 | returns (bytes[] memory results) 764 | { 765 | uint256 valuesSum; 766 | unchecked { 767 | for (uint256 i; i < t.values.length; ++i) { 768 | valuesSum += t.values[i]; 769 | } 770 | } 771 | 772 | uint256[] memory nonces = new uint256[](1); 773 | nonces[0] = t.nonce; 774 | 775 | if (errorSelector == bytes4(0)) { 776 | vm.expectEmit(true, true, true, true); 777 | emit NoncesInvalidated(t.signer, nonces); 778 | } else { 779 | vm.expectRevert(errorSelector); 780 | } 781 | 782 | results = _callMulticallerWithSigner(t, valuesSum); 783 | 784 | if (errorSelector == bytes4(0)) { 785 | bool[] memory invalidated = multicallerWithSigner.noncesInvalidated(t.signer, nonces); 786 | assertEq(invalidated[0], true); 787 | } 788 | } 789 | 790 | function testMulticallerWithSignerInvalidateNonces(uint256) public { 791 | unchecked { 792 | uint256[] memory nonces = new uint256[](_random() % 4); 793 | if (_random() % 2 == 0) { 794 | for (uint256 i; i < nonces.length; ++i) { 795 | nonces[i] = _random(); 796 | } 797 | } else { 798 | for (uint256 i; i < nonces.length; ++i) { 799 | nonces[i] = _random() % 8; 800 | } 801 | } 802 | 803 | (address signer, uint256 privateKey) = _randomSigner(); 804 | 805 | bool[] memory invalidated = multicallerWithSigner.noncesInvalidated(signer, nonces); 806 | for (uint256 i; i < nonces.length; ++i) { 807 | assertEq(invalidated[i], false); 808 | } 809 | 810 | if (_random() % 2 == 0) { 811 | vm.prank(signer); 812 | vm.expectEmit(true, true, true, true); 813 | emit NoncesInvalidated(signer, nonces); 814 | multicallerWithSigner.invalidateNonces(nonces); 815 | } else { 816 | bytes memory signature = 817 | _generateInvalidateNoncesSignature(nonces, signer, privateKey); 818 | vm.expectEmit(true, true, true, true); 819 | emit NoncesInvalidated(signer, nonces); 820 | multicallerWithSigner.invalidateNoncesForSigner( 821 | nonces, signer, _maybeMake2098(signature) 822 | ); 823 | } 824 | 825 | invalidated = multicallerWithSigner.noncesInvalidated(signer, nonces); 826 | for (uint256 i; i < nonces.length; ++i) { 827 | assertEq(invalidated[i], true); 828 | } 829 | 830 | { 831 | (address anotherSigner,) = _randomSigner(); 832 | invalidated = multicallerWithSigner.noncesInvalidated(anotherSigner, nonces); 833 | for (uint256 i; i < nonces.length; ++i) { 834 | assertEq(invalidated[i], anotherSigner == signer); 835 | } 836 | } 837 | 838 | uint256[] memory otherNonces = new uint256[](1); 839 | otherNonces[0] = _random(); 840 | bool expectedUsed; 841 | for (uint256 i; i < nonces.length; ++i) { 842 | if (nonces[i] == otherNonces[0]) expectedUsed = true; 843 | } 844 | invalidated = multicallerWithSigner.noncesInvalidated(signer, otherNonces); 845 | assertEq(invalidated[0], expectedUsed); 846 | } 847 | } 848 | 849 | function testMulticallerWithSignerInvalidateNoncesWithERC1271(uint256) public { 850 | _deployERC1271Contracts(); 851 | unchecked { 852 | uint256[] memory nonces = new uint256[](_random() % 4); 853 | if (_random() % 2 == 0) { 854 | for (uint256 i; i < nonces.length; ++i) { 855 | nonces[i] = _random(); 856 | } 857 | } else { 858 | for (uint256 i; i < nonces.length; ++i) { 859 | nonces[i] = _random() % 8; 860 | } 861 | } 862 | 863 | bytes memory signature = 864 | _generateInvalidateNoncesSignature(nonces, erc1271Wallet, erc721SignerPrivateKey); 865 | 866 | vm.expectRevert(MulticallerWithSigner.InvalidSignature.selector); 867 | multicallerWithSigner.invalidateNoncesForSigner( 868 | nonces, erc1271Malicious, _maybeMake2098(signature) 869 | ); 870 | 871 | vm.expectEmit(true, true, true, true); 872 | emit NoncesInvalidated(erc1271Wallet, nonces); 873 | multicallerWithSigner.invalidateNoncesForSigner( 874 | nonces, erc1271Wallet, _maybeMake2098(signature) 875 | ); 876 | 877 | vm.expectRevert(MulticallerWithSigner.InvalidSignature.selector); 878 | multicallerWithSigner.invalidateNoncesForSigner( 879 | nonces, erc1271Malicious, _maybeMake2098(signature) 880 | ); 881 | } 882 | } 883 | 884 | function testMultiCallerWithSignerIncrementNonceSalt(uint256) public { 885 | (address signer, uint256 privateKey) = _randomSigner(); 886 | 887 | for (uint256 q; q < 2; ++q) { 888 | uint256 nonceSaltBefore = multicallerWithSigner.nonceSaltOf(signer); 889 | uint256 nextNonceSalt = _nextNonceSalt(nonceSaltBefore); 890 | if (_random() % 2 == 0) { 891 | vm.prank(signer); 892 | vm.expectEmit(true, true, true, true); 893 | emit NonceSaltIncremented(signer, nextNonceSalt); 894 | multicallerWithSigner.incrementNonceSalt(); 895 | } else { 896 | bytes memory signature = _generateIncrementNonceSaltSignature(signer, privateKey); 897 | vm.expectEmit(true, true, true, true); 898 | emit NonceSaltIncremented(signer, nextNonceSalt); 899 | multicallerWithSigner.incrementNonceSaltForSigner(signer, _maybeMake2098(signature)); 900 | 901 | vm.expectRevert(MulticallerWithSigner.InvalidSignature.selector); 902 | multicallerWithSigner.incrementNonceSaltForSigner(signer, _maybeMake2098(signature)); 903 | } 904 | uint256 nonceSaltAfter = multicallerWithSigner.nonceSaltOf(signer); 905 | assertEq(nextNonceSalt, nonceSaltAfter); 906 | } 907 | } 908 | 909 | function testMultiCallerWithSignerIncrementNonceSaltWithERC1271(uint256) public { 910 | _deployERC1271Contracts(); 911 | uint256 nonceSaltBefore = multicallerWithSigner.nonceSaltOf(erc1271Wallet); 912 | uint256 nextNonceSalt = _nextNonceSalt(nonceSaltBefore); 913 | bytes memory signature = 914 | _generateIncrementNonceSaltSignature(erc1271Wallet, erc721SignerPrivateKey); 915 | 916 | vm.expectRevert(MulticallerWithSigner.InvalidSignature.selector); 917 | multicallerWithSigner.incrementNonceSaltForSigner( 918 | erc1271Malicious, _maybeMake2098(signature) 919 | ); 920 | 921 | emit NonceSaltIncremented(erc1271Wallet, nextNonceSalt); 922 | multicallerWithSigner.incrementNonceSaltForSigner(erc1271Wallet, _maybeMake2098(signature)); 923 | 924 | vm.expectRevert(MulticallerWithSigner.InvalidSignature.selector); 925 | multicallerWithSigner.incrementNonceSaltForSigner(erc1271Wallet, _maybeMake2098(signature)); 926 | } 927 | 928 | function _generateInvalidateNoncesSignature( 929 | uint256[] memory nonces, 930 | address signer, 931 | uint256 privateKey 932 | ) internal view returns (bytes memory signature) { 933 | bytes32 digest = keccak256( 934 | abi.encodePacked( 935 | "\x19\x01", 936 | _multicallerWithSignerDomainSeparator(), 937 | keccak256( 938 | abi.encode( 939 | keccak256( 940 | "InvalidateNoncesForSigner(address signer,uint256[] nonces,uint256 nonceSalt)" 941 | ), 942 | signer, 943 | keccak256(abi.encodePacked(nonces)), 944 | multicallerWithSigner.nonceSaltOf(signer) 945 | ) 946 | ) 947 | ) 948 | ); 949 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); 950 | signature = abi.encodePacked(r, s, v); 951 | } 952 | 953 | function _generateIncrementNonceSaltSignature(address signer, uint256 privateKey) 954 | internal 955 | view 956 | returns (bytes memory signature) 957 | { 958 | bytes32 digest = keccak256( 959 | abi.encodePacked( 960 | "\x19\x01", 961 | _multicallerWithSignerDomainSeparator(), 962 | keccak256( 963 | abi.encode( 964 | keccak256("IncrementNonceSaltForSigner(address signer,uint256 nonceSalt)"), 965 | signer, 966 | multicallerWithSigner.nonceSaltOf(signer) 967 | ) 968 | ) 969 | ) 970 | ); 971 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); 972 | signature = abi.encodePacked(r, s, v); 973 | } 974 | 975 | function _multicallerWithSignerDomainSeparator() internal view returns (bytes32) { 976 | return keccak256( 977 | abi.encode( 978 | keccak256( 979 | "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" 980 | ), 981 | keccak256("MulticallerWithSigner"), 982 | keccak256("1"), 983 | block.chainid, 984 | address(multicallerWithSigner) 985 | ) 986 | ); 987 | } 988 | 989 | function _maybeMake2098(bytes memory signature) 990 | internal 991 | returns (bytes memory shortSignature) 992 | { 993 | if (_random() % 2 == 0 || signature.length != 65) { 994 | shortSignature = signature; 995 | } else { 996 | /// @solidity memory-safe-assembly 997 | assembly { 998 | let r := mload(add(signature, 0x20)) 999 | let s := mload(add(signature, 0x40)) 1000 | let v := byte(0, mload(add(signature, 0x60))) 1001 | shortSignature := mload(0x40) 1002 | mstore(shortSignature, 0x40) 1003 | mstore(add(shortSignature, 0x20), r) 1004 | mstore(add(shortSignature, 0x40), or(shl(255, sub(v, 27)), s)) 1005 | mstore(0x40, add(shortSignature, 0x60)) 1006 | } 1007 | } 1008 | } 1009 | 1010 | function testOffsetTrick(uint256 a, uint256 b, uint256 c) public { 1011 | unchecked { 1012 | uint256 aDiff = a - c; 1013 | uint256 bDiff = b - c; 1014 | uint256 cPlus = c + 0x20; 1015 | assertEq(cPlus + aDiff, a + 0x20); 1016 | assertEq(cPlus + bDiff, b + 0x20); 1017 | } 1018 | } 1019 | 1020 | function revertsOnNastyCalldata(address[] calldata a) public pure returns (uint256) { 1021 | return a.length; 1022 | } 1023 | 1024 | function testNastyCalldataRevert() public { 1025 | /// @solidity memory-safe-assembly 1026 | assembly { 1027 | let m := mload(0x40) 1028 | 1029 | mstore(m, 0xcf74109c) 1030 | mstore(add(m, 0x20), 0x20) 1031 | mstore(add(m, 0x40), 1) 1032 | mstore(add(m, 0x60), 0x112233) 1033 | 1034 | // Check does not revert with valid calldata. 1035 | if iszero(call(gas(), address(), 0, add(m, 0x1c), 0x80, 0x00, 0x00)) { 1036 | revert(0x00, 0x00) 1037 | } 1038 | 1039 | // Check does not revert if address in array has dirty upper bits. 1040 | // But don't worry, for Multicaller and MulticallerWithSender, 1041 | // the `call` opcode can handle dirty upper bits. 1042 | // For MulticallerWithSigner, it will revert, as the dirty bits will 1043 | // corrupt the digest. 1044 | mstore(add(m, 0x60), not(0)) 1045 | if iszero(call(gas(), address(), 0, add(m, 0x1c), 0x80, 0x00, 0x00)) { 1046 | revert(0x00, 0x00) 1047 | } 1048 | mstore(add(m, 0x60), 0x112233) 1049 | 1050 | // Check reverts if length of array is excessive. 1051 | mstore(add(m, 0x40), 2) 1052 | if call(gas(), address(), 0, add(m, 0x1c), 0x80, 0x00, 0x00) { revert(0x00, 0x00) } 1053 | 1054 | // Check reverts if length of array is excessive. 1055 | mstore(add(m, 0x40), shl(255, 1)) 1056 | if call(gas(), address(), 0, add(m, 0x1c), 0x80, 0x00, 0x00) { revert(0x00, 0x00) } 1057 | 1058 | // Check does not revert if array length is not excessive. 1059 | let n := 20 // You can increase this and see that it will increase the gas spent. 1060 | mstore(add(m, 0x40), n) 1061 | if iszero(call(gas(), address(), 0, add(m, 0x1c), add(0x60, mul(n, 0x20)), 0x00, 0x00)) 1062 | { 1063 | revert(0x00, 0x00) 1064 | } 1065 | } 1066 | } 1067 | 1068 | function _cdCompress(bytes memory data) internal pure returns (bytes memory result) { 1069 | /// @solidity memory-safe-assembly 1070 | assembly { 1071 | function rle(v_, o_, d_) -> _o, _d { 1072 | mstore(o_, shl(240, or(and(0xff, add(d_, 0xff)), and(0x80, v_)))) 1073 | _o := add(o_, 2) 1074 | } 1075 | result := mload(0x40) 1076 | let o := add(result, 0x20) 1077 | let z := 0 // Number of consecutive 0x00. 1078 | let y := 0 // Number of consecutive 0xff. 1079 | for { let end := add(data, mload(data)) } iszero(eq(data, end)) {} { 1080 | data := add(data, 1) 1081 | let c := byte(31, mload(data)) 1082 | if iszero(c) { 1083 | if y { o, y := rle(0xff, o, y) } 1084 | z := add(z, 1) 1085 | if eq(z, 0x80) { o, z := rle(0x00, o, 0x80) } 1086 | continue 1087 | } 1088 | if eq(c, 0xff) { 1089 | if z { o, z := rle(0x00, o, z) } 1090 | y := add(y, 1) 1091 | if eq(y, 0x20) { o, y := rle(0xff, o, 0x20) } 1092 | continue 1093 | } 1094 | if y { o, y := rle(0xff, o, y) } 1095 | if z { o, z := rle(0x00, o, z) } 1096 | mstore8(o, c) 1097 | o := add(o, 1) 1098 | } 1099 | if y { o, y := rle(0xff, o, y) } 1100 | if z { o, z := rle(0x00, o, z) } 1101 | // Bitwise negate the first 4 bytes. 1102 | mstore(add(result, 4), not(mload(add(result, 4)))) 1103 | mstore(result, sub(o, add(result, 0x20))) // Store the length. 1104 | mstore(o, 0) // Zeroize the slot after the string. 1105 | mstore(0x40, add(o, 0x20)) // Allocate the memory. 1106 | } 1107 | } 1108 | } 1109 | --------------------------------------------------------------------------------