├── assets └── makerdao.png ├── .gitignore ├── .gitmodules ├── src ├── test │ ├── utils │ │ └── DSTestPlus.sol │ ├── mocks │ │ ├── EtherSink.sol │ │ └── MockCallee.sol │ ├── Multicall.t.sol │ ├── Multicall2.t.sol │ └── Multicall3.t.sol ├── Multicall.sol ├── Multicall2.sol └── Multicall3.sol ├── foundry.toml ├── .github └── workflows │ └── tests.yml ├── LICENSE ├── .gas-snapshot └── README.md /assets/makerdao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shafu0x/multicall/master/assets/makerdao.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | cache 3 | 4 | .vscode 5 | 6 | # Ignore environment variables incase someone accidentally sets and commits 7 | .env 8 | .env.prod 9 | 10 | old.gas-snapshot -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/ds-test"] 2 | path = lib/ds-test 3 | url = https://github.com/dapphub/ds-test 4 | [submodule "lib/forge-std"] 5 | path = lib/forge-std 6 | url = https://github.com/brockelmore/forge-std 7 | -------------------------------------------------------------------------------- /src/test/utils/DSTestPlus.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.12; 3 | 4 | import {DSTest} from "@ds/test.sol"; 5 | import {stdCheats} from "@std/stdlib.sol"; 6 | import {Vm} from "@std/Vm.sol"; 7 | 8 | contract DSTestPlus is DSTest, stdCheats { 9 | /// @dev Use forge-std Vm logic 10 | Vm public constant vm = Vm(HEVM_ADDRESS); 11 | } -------------------------------------------------------------------------------- /src/test/mocks/EtherSink.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.12; 3 | 4 | /// @title EtherSink 5 | /// @notice Receives Ether, that's about it \( o_o )/ 6 | /// @author andreas@nascent.xyz 7 | contract EtherSink { 8 | 9 | /// >>>>>>>>>>>>>>>>>>>>>> ACCEPT CALLS <<<<<<<<<<<<<<<<<<<<<<< /// 10 | 11 | /// @notice Allows the test to receive eth via low level calls 12 | receive() external payable {} 13 | } -------------------------------------------------------------------------------- /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 | [default] 7 | # Sets the concrete solc version to use 8 | # This overrides the `auto_detect_solc` value 9 | solc_version = '0.8.12' 10 | auto_detect_solc = false 11 | # Increase optimizer_runs since we expect multicalls to be highly queried 12 | optimizer = true 13 | optimizer_runs = 10_000_000 14 | # Fuzz more than the default 256 15 | fuzz_runs = 1_000 16 | # Configure remappings 17 | remappings = [ 18 | "@ds=lib/ds-test/src/", 19 | "@std=lib/forge-std/src/", 20 | ] 21 | 22 | # Extreme Fuzzing CI Profile :P 23 | [ci] 24 | fuzz_runs = 100_000 25 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - 'v*.*.*' 9 | pull_request: 10 | 11 | jobs: 12 | tests: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: onbjerg/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Tests 26 | run: FOUNDRY_PROFILE=ci forge test 27 | 28 | - name: Publish ABIs 29 | if: startsWith(github.ref, 'refs/tags/v') 30 | uses: softprops/action-gh-release@v1 31 | with: 32 | name: ABIs 33 | files: | 34 | ./out/Multicall.sol/Multicall.json 35 | ./out/Multicall2.sol/Multicall2.json 36 | ./out/Multicall3.sol/Multicall3.json 37 | -------------------------------------------------------------------------------- /src/test/mocks/MockCallee.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.12; 3 | 4 | /// @title MockCallee 5 | /// @notice Receives calls from the Multicaller 6 | /// @author andreas@nascent.xyz 7 | contract MockCallee { 8 | 9 | /// @notice Failure 10 | error Unsuccessful(); 11 | 12 | /// @notice Returns the block hash for the given block number 13 | /// @param blockNumber The block number 14 | /// @return blockHash The 32 byte block hash 15 | function getBlockHash(uint256 blockNumber) public view returns (bytes32 blockHash) { 16 | blockHash = blockhash(blockNumber); 17 | } 18 | 19 | /// @notice Reverts o______O 20 | function thisMethodReverts() public pure { 21 | revert Unsuccessful(); 22 | } 23 | 24 | /// @notice Accepts a value 25 | function sendBackValue(address target) public payable { 26 | (bool ok, ) = target.call{value: msg.value}(""); 27 | if (!ok) revert Unsuccessful(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Maker Foundation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Multicall.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.12; 3 | 4 | /// @title Multicall - Aggregate results from multiple read-only function calls 5 | /// @author Michael Elliot 6 | /// @author Joshua Levine 7 | /// @author Nick Johnson 8 | 9 | contract Multicall { 10 | struct Call { 11 | address target; 12 | bytes callData; 13 | } 14 | 15 | function aggregate(Call[] calldata calls) public returns (uint256 blockNumber, bytes[] memory returnData) { 16 | blockNumber = block.number; 17 | returnData = new bytes[](calls.length); 18 | for (uint256 i = 0; i < calls.length; i++) { 19 | (bool success, bytes memory ret) = calls[i].target.call(calls[i].callData); 20 | require(success); 21 | returnData[i] = ret; 22 | } 23 | } 24 | 25 | // Helper functions 26 | function getEthBalance(address addr) public view returns (uint256 balance) { 27 | balance = addr.balance; 28 | } 29 | 30 | function getBlockHash(uint256 blockNumber) public view returns (bytes32 blockHash) { 31 | blockHash = blockhash(blockNumber); 32 | } 33 | 34 | function getLastBlockHash() public view returns (bytes32 blockHash) { 35 | blockHash = blockhash(block.number - 1); 36 | } 37 | 38 | function getCurrentBlockTimestamp() public view returns (uint256 timestamp) { 39 | timestamp = block.timestamp; 40 | } 41 | 42 | function getCurrentBlockDifficulty() public view returns (uint256 difficulty) { 43 | difficulty = block.difficulty; 44 | } 45 | 46 | function getCurrentBlockGasLimit() public view returns (uint256 gaslimit) { 47 | gaslimit = block.gaslimit; 48 | } 49 | 50 | function getCurrentBlockCoinbase() public view returns (address coinbase) { 51 | coinbase = block.coinbase; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /.gas-snapshot: -------------------------------------------------------------------------------- 1 | Multicall2Test:testAggregation() (gas: 6043) 2 | Multicall2Test:testBlockAndAggregateUnsuccessful() (gas: 10331) 3 | Multicall2Test:testGetBlockHash(uint256) (runs: 1000, μ: 1151, ~: 1151) 4 | Multicall2Test:testGetBlockNumber() (gas: 942) 5 | Multicall2Test:testGetCurrentBlockCoinbase() (gas: 1079) 6 | Multicall2Test:testGetCurrentBlockDifficulty() (gas: 985) 7 | Multicall2Test:testGetCurrentBlockGasLimit() (gas: 941) 8 | Multicall2Test:testGetCurrentBlockTimestamp() (gas: 976) 9 | Multicall2Test:testGetEthBalance(address) (runs: 1000, μ: 3944, ~: 3944) 10 | Multicall2Test:testGetLastBlockHash() (gas: 270) 11 | Multicall2Test:testTryAggregate() (gas: 10324) 12 | Multicall2Test:testTryAggregateUnsuccessful() (gas: 10234) 13 | Multicall2Test:testTryBlockAndAggregate() (gas: 10693) 14 | Multicall2Test:testTryBlockAndAggregateUnsuccessful() (gas: 10382) 15 | Multicall2Test:testUnsuccessulAggregation() (gas: 9874) 16 | Multicall3Test:testAggregate3() (gas: 9979) 17 | Multicall3Test:testAggregate3Unsuccessful() (gas: 9822) 18 | Multicall3Test:testAggregate3Value() (gas: 34245) 19 | Multicall3Test:testAggregate3ValueUnsuccessful() (gas: 61032) 20 | Multicall3Test:testAggregation() (gas: 5883) 21 | Multicall3Test:testBlockAndAggregateUnsuccessful() (gas: 9873) 22 | Multicall3Test:testGetBasefee() (gas: 985) 23 | Multicall3Test:testGetBlockHash(uint256) (runs: 1000, μ: 1240, ~: 1240) 24 | Multicall3Test:testGetBlockNumber() (gas: 1009) 25 | Multicall3Test:testGetChainId() (gas: 941) 26 | Multicall3Test:testGetCurrentBlockCoinbase() (gas: 1034) 27 | Multicall3Test:testGetCurrentBlockDifficulty() (gas: 985) 28 | Multicall3Test:testGetCurrentBlockGasLimit() (gas: 1008) 29 | Multicall3Test:testGetCurrentBlockTimestamp() (gas: 954) 30 | Multicall3Test:testGetEthBalance(address) (runs: 1000, μ: 3900, ~: 3900) 31 | Multicall3Test:testGetLastBlockHash() (gas: 248) 32 | Multicall3Test:testTryAggregate() (gas: 9788) 33 | Multicall3Test:testTryAggregateUnsuccessful() (gas: 9777) 34 | Multicall3Test:testTryBlockAndAggregate() (gas: 10158) 35 | Multicall3Test:testTryBlockAndAggregateUnsuccessful() (gas: 9924) 36 | Multicall3Test:testUnsuccessulAggregation() (gas: 9543) 37 | MulticallTest:testAggregation() (gas: 5921) 38 | MulticallTest:testGetBlockHash(uint256) (runs: 1000, μ: 1173, ~: 1173) 39 | MulticallTest:testGetCurrentBlockCoinbase() (gas: 1034) 40 | MulticallTest:testGetCurrentBlockDifficulty() (gas: 962) 41 | MulticallTest:testGetCurrentBlockGasLimit() (gas: 963) 42 | MulticallTest:testGetCurrentBlockTimestamp() (gas: 886) 43 | MulticallTest:testGetEthBalance(address) (runs: 1000, μ: 3922, ~: 3922) 44 | MulticallTest:testGetLastBlockHash() (gas: 248) 45 | MulticallTest:testUnsuccessulAggregation() (gas: 9586) 46 | -------------------------------------------------------------------------------- /src/test/Multicall.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.12; 3 | 4 | import {Multicall} from "../Multicall.sol"; 5 | import {DSTestPlus} from "./utils/DSTestPlus.sol"; 6 | import {MockCallee} from "./mocks/MockCallee.sol"; 7 | 8 | contract MulticallTest is DSTestPlus { 9 | Multicall multicall; 10 | MockCallee callee; 11 | 12 | /// @notice Setups up the testing suite 13 | function setUp() public { 14 | multicall = new Multicall(); 15 | callee = new MockCallee(); 16 | } 17 | 18 | /// >>>>>>>>>>>>>>>>>>>> AGGREGATION TESTS <<<<<<<<<<<<<<<<<<<< /// 19 | 20 | function testAggregation() public { 21 | // Test successful call 22 | Multicall.Call[] memory calls = new Multicall.Call[](1); 23 | calls[0] = Multicall.Call(address(callee), abi.encodeWithSignature("getBlockHash(uint256)", block.number)); 24 | (uint256 blockNumber, bytes[] memory returnData) = multicall.aggregate(calls); 25 | assertEq(blockNumber, block.number); 26 | assertEq(keccak256(returnData[0]), keccak256(abi.encodePacked(blockhash(block.number)))); 27 | } 28 | 29 | function testUnsuccessulAggregation() public { 30 | // Test unexpected revert 31 | Multicall.Call[] memory calls = new Multicall.Call[](2); 32 | calls[0] = Multicall.Call( 33 | address(callee), 34 | abi.encodeWithSignature("getBlockHash(uint256)", block.number) 35 | ); 36 | calls[1] = Multicall.Call(address(callee), abi.encodeWithSignature("thisMethodReverts()") 37 | ); 38 | vm.expectRevert(bytes("")); 39 | multicall.aggregate(calls); 40 | } 41 | 42 | /// >>>>>>>>>>>>>>>>>>>>>> HELPER TESTS <<<<<<<<<<<<<<<<<<<<<<< /// 43 | 44 | function testGetEthBalance(address addr) public { 45 | assertEq(addr.balance, multicall.getEthBalance(addr)); 46 | } 47 | 48 | function testGetBlockHash(uint256 blockNumber) public { 49 | assertEq(blockhash(blockNumber), multicall.getBlockHash(blockNumber)); 50 | } 51 | 52 | function testGetLastBlockHash() public { 53 | // Prevent arithmetic underflow on the genesis block 54 | if (block.number == 0) return; 55 | assertEq(blockhash(block.number - 1), multicall.getLastBlockHash()); 56 | } 57 | 58 | function testGetCurrentBlockTimestamp() public { 59 | assertEq(block.timestamp, multicall.getCurrentBlockTimestamp()); 60 | } 61 | 62 | function testGetCurrentBlockDifficulty() public { 63 | assertEq(block.difficulty, multicall.getCurrentBlockDifficulty()); 64 | } 65 | 66 | function testGetCurrentBlockGasLimit() public { 67 | assertEq(block.gaslimit, multicall.getCurrentBlockGasLimit()); 68 | } 69 | 70 | function testGetCurrentBlockCoinbase() public { 71 | assertEq(block.coinbase, multicall.getCurrentBlockCoinbase()); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Multicall2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.12; 3 | 4 | /// @title Multicall2 - Aggregate results from multiple read-only function calls 5 | /// @author Michael Elliot 6 | /// @author Joshua Levine 7 | /// @author Nick Johnson 8 | contract Multicall2 { 9 | struct Call { 10 | address target; 11 | bytes callData; 12 | } 13 | 14 | struct Result { 15 | bool success; 16 | bytes returnData; 17 | } 18 | 19 | function aggregate(Call[] calldata calls) public returns (uint256 blockNumber, bytes[] memory returnData) { 20 | blockNumber = block.number; 21 | returnData = new bytes[](calls.length); 22 | for (uint256 i = 0; i < calls.length; i++) { 23 | (bool success, bytes memory ret) = calls[i].target.call(calls[i].callData); 24 | require(success, "Multicall aggregate: call failed"); 25 | returnData[i] = ret; 26 | } 27 | } 28 | 29 | function tryAggregate(bool requireSuccess, Call[] calldata calls) public returns (Result[] memory returnData) { 30 | returnData = new Result[](calls.length); 31 | for (uint256 i = 0; i < calls.length; i++) { 32 | (bool success, bytes memory ret) = calls[i].target.call(calls[i].callData); 33 | 34 | if (requireSuccess) { 35 | require(success, "Multicall2 aggregate: call failed"); 36 | } 37 | 38 | returnData[i] = Result(success, ret); 39 | } 40 | } 41 | 42 | function tryBlockAndAggregate(bool requireSuccess, Call[] calldata calls) public returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) { 43 | blockNumber = block.number; 44 | blockHash = blockhash(block.number); 45 | returnData = tryAggregate(requireSuccess, calls); 46 | } 47 | 48 | function blockAndAggregate(Call[] calldata calls) public returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) { 49 | (blockNumber, blockHash, returnData) = tryBlockAndAggregate(true, calls); 50 | } 51 | 52 | function getBlockHash(uint256 blockNumber) public view returns (bytes32 blockHash) { 53 | blockHash = blockhash(blockNumber); 54 | } 55 | 56 | function getBlockNumber() public view returns (uint256 blockNumber) { 57 | blockNumber = block.number; 58 | } 59 | 60 | function getCurrentBlockCoinbase() public view returns (address coinbase) { 61 | coinbase = block.coinbase; 62 | } 63 | 64 | function getCurrentBlockDifficulty() public view returns (uint256 difficulty) { 65 | difficulty = block.difficulty; 66 | } 67 | 68 | function getCurrentBlockGasLimit() public view returns (uint256 gaslimit) { 69 | gaslimit = block.gaslimit; 70 | } 71 | 72 | function getCurrentBlockTimestamp() public view returns (uint256 timestamp) { 73 | timestamp = block.timestamp; 74 | } 75 | 76 | function getEthBalance(address addr) public view returns (uint256 balance) { 77 | balance = addr.balance; 78 | } 79 | 80 | function getLastBlockHash() public view returns (bytes32 blockHash) { 81 | blockHash = blockhash(block.number - 1); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/test/Multicall2.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.12; 3 | 4 | import {Multicall2} from "../Multicall2.sol"; 5 | import {DSTestPlus} from "./utils/DSTestPlus.sol"; 6 | import {MockCallee} from "./mocks/MockCallee.sol"; 7 | 8 | contract Multicall2Test is DSTestPlus { 9 | Multicall2 multicall; 10 | MockCallee callee; 11 | 12 | /// @notice Setups up the testing suite 13 | function setUp() public { 14 | multicall = new Multicall2(); 15 | callee = new MockCallee(); 16 | } 17 | 18 | /// >>>>>>>>>>>>>>>>>>>>> AGGREGATE TESTS <<<<<<<<<<<<<<<<<<<<< /// 19 | 20 | function testAggregation() public { 21 | // Test successful call 22 | Multicall2.Call[] memory calls = new Multicall2.Call[](1); 23 | calls[0] = Multicall2.Call(address(callee), abi.encodeWithSignature("getBlockHash(uint256)", block.number)); 24 | (uint256 blockNumber, bytes[] memory returnData) = multicall.aggregate(calls); 25 | assertEq(blockNumber, block.number); 26 | assertEq(keccak256(returnData[0]), keccak256(abi.encodePacked(blockhash(block.number)))); 27 | } 28 | 29 | function testUnsuccessulAggregation() public { 30 | // Test unexpected revert 31 | Multicall2.Call[] memory calls = new Multicall2.Call[](2); 32 | calls[0] = Multicall2.Call(address(callee), abi.encodeWithSignature("getBlockHash(uint256)", block.number)); 33 | calls[1] = Multicall2.Call(address(callee), abi.encodeWithSignature("thisMethodReverts()")); 34 | vm.expectRevert(bytes("Multicall aggregate: call failed")); 35 | multicall.aggregate(calls); 36 | } 37 | 38 | /// >>>>>>>>>>>>>>>>>>> TRY AGGREGATE TESTS <<<<<<<<<<<<<<<<<<< /// 39 | 40 | function testTryAggregate() public { 41 | Multicall2.Call[] memory calls = new Multicall2.Call[](2); 42 | calls[0] = Multicall2.Call(address(callee), abi.encodeWithSignature("getBlockHash(uint256)", block.number)); 43 | calls[1] = Multicall2.Call(address(callee), abi.encodeWithSignature("thisMethodReverts()")); 44 | (Multicall2.Result[] memory returnData) = multicall.tryAggregate(false, calls); 45 | assertTrue(returnData[0].success); 46 | assertEq(keccak256(returnData[0].returnData), keccak256(abi.encodePacked(blockhash(block.number)))); 47 | assertTrue(!returnData[1].success); 48 | } 49 | 50 | function testTryAggregateUnsuccessful() public { 51 | Multicall2.Call[] memory calls = new Multicall2.Call[](2); 52 | calls[0] = Multicall2.Call(address(callee), abi.encodeWithSignature("getBlockHash(uint256)", block.number)); 53 | calls[1] = Multicall2.Call(address(callee), abi.encodeWithSignature("thisMethodReverts()")); 54 | vm.expectRevert(bytes("Multicall2 aggregate: call failed")); 55 | multicall.tryAggregate(true, calls); 56 | } 57 | 58 | /// >>>>>>>>>>>>>> TRY BLOCK AND AGGREGATE TESTS <<<<<<<<<<<<<< /// 59 | 60 | function testTryBlockAndAggregate() public { 61 | Multicall2.Call[] memory calls = new Multicall2.Call[](2); 62 | calls[0] = Multicall2.Call(address(callee), abi.encodeWithSignature("getBlockHash(uint256)", block.number)); 63 | calls[1] = Multicall2.Call(address(callee), abi.encodeWithSignature("thisMethodReverts()")); 64 | (uint256 blockNumber, bytes32 blockHash, Multicall2.Result[] memory returnData) = multicall.tryBlockAndAggregate(false, calls); 65 | assertEq(blockNumber, block.number); 66 | assertEq(blockHash, blockhash(block.number)); 67 | assertTrue(returnData[0].success); 68 | assertEq(keccak256(returnData[0].returnData), keccak256(abi.encodePacked(blockhash(block.number)))); 69 | assertTrue(!returnData[1].success); 70 | } 71 | 72 | function testTryBlockAndAggregateUnsuccessful() public { 73 | Multicall2.Call[] memory calls = new Multicall2.Call[](2); 74 | calls[0] = Multicall2.Call(address(callee), abi.encodeWithSignature("getBlockHash(uint256)", block.number)); 75 | calls[1] = Multicall2.Call(address(callee), abi.encodeWithSignature("thisMethodReverts()")); 76 | vm.expectRevert(bytes("Multicall2 aggregate: call failed")); 77 | multicall.tryBlockAndAggregate(true, calls); 78 | } 79 | 80 | function testBlockAndAggregateUnsuccessful() public { 81 | Multicall2.Call[] memory calls = new Multicall2.Call[](2); 82 | calls[0] = Multicall2.Call(address(callee), abi.encodeWithSignature("getBlockHash(uint256)", block.number)); 83 | calls[1] = Multicall2.Call(address(callee), abi.encodeWithSignature("thisMethodReverts()")); 84 | vm.expectRevert(bytes("Multicall2 aggregate: call failed")); 85 | multicall.blockAndAggregate(calls); 86 | } 87 | 88 | /// >>>>>>>>>>>>>>>>>>>>>> HELPER TESTS <<<<<<<<<<<<<<<<<<<<<<< /// 89 | 90 | function testGetBlockHash(uint256 blockNumber) public { 91 | assertEq(blockhash(blockNumber), multicall.getBlockHash(blockNumber)); 92 | } 93 | 94 | function testGetBlockNumber() public { 95 | assertEq(block.number, multicall.getBlockNumber()); 96 | } 97 | 98 | function testGetCurrentBlockCoinbase() public { 99 | assertEq(block.coinbase, multicall.getCurrentBlockCoinbase()); 100 | } 101 | 102 | function testGetCurrentBlockDifficulty() public { 103 | assertEq(block.difficulty, multicall.getCurrentBlockDifficulty()); 104 | } 105 | 106 | function testGetCurrentBlockGasLimit() public { 107 | assertEq(block.gaslimit, multicall.getCurrentBlockGasLimit()); 108 | } 109 | 110 | function testGetCurrentBlockTimestamp() public { 111 | assertEq(block.timestamp, multicall.getCurrentBlockTimestamp()); 112 | } 113 | 114 | function testGetEthBalance(address addr) public { 115 | assertEq(addr.balance, multicall.getEthBalance(addr)); 116 | } 117 | 118 | function testGetLastBlockHash() public { 119 | // Prevent arithmetic underflow on the genesis block 120 | if (block.number == 0) return; 121 | assertEq(blockhash(block.number - 1), multicall.getLastBlockHash()); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/test/Multicall3.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.12; 3 | 4 | import {Multicall3} from "../Multicall3.sol"; 5 | import {DSTestPlus} from "./utils/DSTestPlus.sol"; 6 | import {MockCallee} from "./mocks/MockCallee.sol"; 7 | import {EtherSink} from "./mocks/EtherSink.sol"; 8 | 9 | contract Multicall3Test is DSTestPlus { 10 | Multicall3 multicall; 11 | MockCallee callee; 12 | EtherSink etherSink; 13 | 14 | /// @notice Setups up the testing suite 15 | function setUp() public { 16 | multicall = new Multicall3(); 17 | callee = new MockCallee(); 18 | etherSink = new EtherSink(); 19 | } 20 | 21 | /// >>>>>>>>>>>>>>>>>>>>> AGGREGATE TESTS <<<<<<<<<<<<<<<<<<<<< /// 22 | 23 | function testAggregation() public { 24 | // Test successful call 25 | Multicall3.Call[] memory calls = new Multicall3.Call[](1); 26 | calls[0] = Multicall3.Call(address(callee), abi.encodeWithSignature("getBlockHash(uint256)", block.number)); 27 | (uint256 blockNumber, bytes[] memory returnData) = multicall.aggregate(calls); 28 | assertEq(blockNumber, block.number); 29 | assertEq(keccak256(returnData[0]), keccak256(abi.encodePacked(blockhash(block.number)))); 30 | } 31 | 32 | function testUnsuccessulAggregation() public { 33 | // Test unexpected revert 34 | Multicall3.Call[] memory calls = new Multicall3.Call[](2); 35 | calls[0] = Multicall3.Call(address(callee), abi.encodeWithSignature("getBlockHash(uint256)", block.number)); 36 | calls[1] = Multicall3.Call(address(callee), abi.encodeWithSignature("thisMethodReverts()")); 37 | vm.expectRevert(bytes("Multicall3: call failed")); 38 | multicall.aggregate(calls); 39 | } 40 | 41 | /// >>>>>>>>>>>>>>>>>>> TRY AGGREGATE TESTS <<<<<<<<<<<<<<<<<<< /// 42 | 43 | function testTryAggregate() public { 44 | Multicall3.Call[] memory calls = new Multicall3.Call[](2); 45 | calls[0] = Multicall3.Call(address(callee), abi.encodeWithSignature("getBlockHash(uint256)", block.number)); 46 | calls[1] = Multicall3.Call(address(callee), abi.encodeWithSignature("thisMethodReverts()")); 47 | (Multicall3.Result[] memory returnData) = multicall.tryAggregate(false, calls); 48 | assertTrue(returnData[0].success); 49 | assertEq(keccak256(returnData[0].returnData), keccak256(abi.encodePacked(blockhash(block.number)))); 50 | assertTrue(!returnData[1].success); 51 | } 52 | 53 | function testTryAggregateUnsuccessful() public { 54 | Multicall3.Call[] memory calls = new Multicall3.Call[](2); 55 | calls[0] = Multicall3.Call(address(callee), abi.encodeWithSignature("getBlockHash(uint256)", block.number)); 56 | calls[1] = Multicall3.Call(address(callee), abi.encodeWithSignature("thisMethodReverts()")); 57 | vm.expectRevert(bytes("Multicall3: call failed")); 58 | multicall.tryAggregate(true, calls); 59 | } 60 | 61 | /// >>>>>>>>>>>>>> TRY BLOCK AND AGGREGATE TESTS <<<<<<<<<<<<<< /// 62 | 63 | function testTryBlockAndAggregate() public { 64 | Multicall3.Call[] memory calls = new Multicall3.Call[](2); 65 | calls[0] = Multicall3.Call(address(callee), abi.encodeWithSignature("getBlockHash(uint256)", block.number)); 66 | calls[1] = Multicall3.Call(address(callee), abi.encodeWithSignature("thisMethodReverts()")); 67 | (uint256 blockNumber, bytes32 blockHash, Multicall3.Result[] memory returnData) = multicall.tryBlockAndAggregate(false, calls); 68 | assertEq(blockNumber, block.number); 69 | assertEq(blockHash, blockhash(block.number)); 70 | assertTrue(returnData[0].success); 71 | assertEq(keccak256(returnData[0].returnData), keccak256(abi.encodePacked(blockhash(block.number)))); 72 | assertTrue(!returnData[1].success); 73 | } 74 | 75 | function testTryBlockAndAggregateUnsuccessful() public { 76 | Multicall3.Call[] memory calls = new Multicall3.Call[](2); 77 | calls[0] = Multicall3.Call(address(callee), abi.encodeWithSignature("getBlockHash(uint256)", block.number)); 78 | calls[1] = Multicall3.Call(address(callee), abi.encodeWithSignature("thisMethodReverts()")); 79 | vm.expectRevert(bytes("Multicall3: call failed")); 80 | multicall.tryBlockAndAggregate(true, calls); 81 | } 82 | 83 | function testBlockAndAggregateUnsuccessful() public { 84 | Multicall3.Call[] memory calls = new Multicall3.Call[](2); 85 | calls[0] = Multicall3.Call(address(callee), abi.encodeWithSignature("getBlockHash(uint256)", block.number)); 86 | calls[1] = Multicall3.Call(address(callee), abi.encodeWithSignature("thisMethodReverts()")); 87 | vm.expectRevert(bytes("Multicall3: call failed")); 88 | multicall.blockAndAggregate(calls); 89 | } 90 | 91 | /// >>>>>>>>>>>>>>>>>>> AGGREGATE3 TESTS <<<<<<<<<<<<<<<<<<<<<< /// 92 | 93 | function testAggregate3() public { 94 | Multicall3.Call3[] memory calls = new Multicall3.Call3[](2); 95 | calls[0] = Multicall3.Call3(address(callee), false, abi.encodeWithSignature("getBlockHash(uint256)", block.number)); 96 | calls[1] = Multicall3.Call3(address(callee), true, abi.encodeWithSignature("thisMethodReverts()")); 97 | (Multicall3.Result[] memory returnData) = multicall.aggregate3(calls); 98 | assertTrue(returnData[0].success); 99 | assertEq(keccak256(returnData[0].returnData), keccak256(abi.encodePacked(blockhash(block.number)))); 100 | assertTrue(!returnData[1].success); 101 | } 102 | 103 | function testAggregate3Unsuccessful() public { 104 | Multicall3.Call3[] memory calls = new Multicall3.Call3[](2); 105 | calls[0] = Multicall3.Call3(address(callee), false, abi.encodeWithSignature("getBlockHash(uint256)", block.number)); 106 | calls[1] = Multicall3.Call3(address(callee), false, abi.encodeWithSignature("thisMethodReverts()")); 107 | vm.expectRevert(bytes("Multicall3: call failed")); 108 | multicall.aggregate3(calls); 109 | } 110 | 111 | /// >>>>>>>>>>>>>>>>> AGGREGATE3VALUE TESTS <<<<<<<<<<<<<<<<<<< /// 112 | 113 | function testAggregate3Value() public { 114 | Multicall3.Call3Value[] memory calls = new Multicall3.Call3Value[](3); 115 | calls[0] = Multicall3.Call3Value(address(callee), false, 0, abi.encodeWithSignature("getBlockHash(uint256)", block.number)); 116 | calls[1] = Multicall3.Call3Value(address(callee), true, 0, abi.encodeWithSignature("thisMethodReverts()")); 117 | calls[2] = Multicall3.Call3Value(address(callee), true, 1, abi.encodeWithSignature("sendBackValue(address)", address(etherSink))); 118 | (Multicall3.Result[] memory returnData) = multicall.aggregate3Value{value: 1}(calls); 119 | assertTrue(returnData[0].success); 120 | assertEq(keccak256(returnData[0].returnData), keccak256(abi.encodePacked(blockhash(block.number)))); 121 | assertTrue(!returnData[1].success); 122 | assertTrue(returnData[2].success); 123 | } 124 | 125 | function testAggregate3ValueUnsuccessful() public { 126 | Multicall3.Call3Value[] memory calls = new Multicall3.Call3Value[](3); 127 | calls[0] = Multicall3.Call3Value(address(callee), false, 0, abi.encodeWithSignature("getBlockHash(uint256)", block.number)); 128 | calls[1] = Multicall3.Call3Value(address(callee), false, 0, abi.encodeWithSignature("thisMethodReverts()")); 129 | calls[2] = Multicall3.Call3Value(address(callee), false, 1, abi.encodeWithSignature("sendBackValue(address)", address(etherSink))); 130 | vm.expectRevert(bytes("Multicall3: call failed")); 131 | multicall.aggregate3Value(calls); 132 | 133 | // Should fail if we don't provide enough value 134 | Multicall3.Call3Value[] memory calls2 = new Multicall3.Call3Value[](3); 135 | calls2[0] = Multicall3.Call3Value(address(callee), true, 0, abi.encodeWithSignature("getBlockHash(uint256)", block.number)); 136 | calls2[1] = Multicall3.Call3Value(address(callee), true, 0, abi.encodeWithSignature("thisMethodReverts()")); 137 | calls2[2] = Multicall3.Call3Value(address(callee), true, 1, abi.encodeWithSignature("sendBackValue(address)", address(etherSink))); 138 | vm.expectRevert(bytes("Multicall3: value mismatch")); 139 | multicall.aggregate3Value(calls2); 140 | 141 | // Works if we provide enough value 142 | Multicall3.Call3Value[] memory calls3 = new Multicall3.Call3Value[](3); 143 | calls3[0] = Multicall3.Call3Value(address(callee), false, 0, abi.encodeWithSignature("getBlockHash(uint256)", block.number)); 144 | calls3[1] = Multicall3.Call3Value(address(callee), true, 0, abi.encodeWithSignature("thisMethodReverts()")); 145 | calls3[2] = Multicall3.Call3Value(address(callee), false, 1, abi.encodeWithSignature("sendBackValue(address)", address(etherSink))); 146 | multicall.aggregate3Value{value: 1}(calls3); 147 | } 148 | 149 | /// >>>>>>>>>>>>>>>>>>>>>> HELPER TESTS <<<<<<<<<<<<<<<<<<<<<<< /// 150 | 151 | function testGetBlockHash(uint256 blockNumber) public { 152 | assertEq(blockhash(blockNumber), multicall.getBlockHash(blockNumber)); 153 | } 154 | 155 | function testGetBlockNumber() public { 156 | assertEq(block.number, multicall.getBlockNumber()); 157 | } 158 | 159 | function testGetCurrentBlockCoinbase() public { 160 | assertEq(block.coinbase, multicall.getCurrentBlockCoinbase()); 161 | } 162 | 163 | function testGetCurrentBlockDifficulty() public { 164 | assertEq(block.difficulty, multicall.getCurrentBlockDifficulty()); 165 | } 166 | 167 | function testGetCurrentBlockGasLimit() public { 168 | assertEq(block.gaslimit, multicall.getCurrentBlockGasLimit()); 169 | } 170 | 171 | function testGetCurrentBlockTimestamp() public { 172 | assertEq(block.timestamp, multicall.getCurrentBlockTimestamp()); 173 | } 174 | 175 | function testGetEthBalance(address addr) public { 176 | assertEq(addr.balance, multicall.getEthBalance(addr)); 177 | } 178 | 179 | function testGetLastBlockHash() public { 180 | // Prevent arithmetic underflow on the genesis block 181 | if (block.number == 0) return; 182 | assertEq(blockhash(block.number - 1), multicall.getLastBlockHash()); 183 | } 184 | 185 | function testGetBasefee() public { 186 | assertEq(block.basefee, multicall.getBasefee()); 187 | } 188 | 189 | function testGetChainId() public { 190 | assertEq(block.chainid, multicall.getChainId()); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/Multicall3.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.12; 3 | 4 | /// @title Multicall3 5 | /// @notice Aggregate results from multiple function calls 6 | /// @dev Multicall & Multicall2 backwards-compatible 7 | /// @dev Aggregate methods are marked `payable` to save 24 gas per call 8 | /// @author Michael Elliot 9 | /// @author Joshua Levine 10 | /// @author Nick Johnson 11 | /// @author Andreas Bigger 12 | /// @author Matt Solomon 13 | contract Multicall3 { 14 | struct Call { 15 | address target; 16 | bytes callData; 17 | } 18 | 19 | struct Call3 { 20 | address target; 21 | bool allowFailure; 22 | bytes callData; 23 | } 24 | 25 | struct Call3Value { 26 | address target; 27 | bool allowFailure; 28 | uint256 value; 29 | bytes callData; 30 | } 31 | 32 | struct Result { 33 | bool success; 34 | bytes returnData; 35 | } 36 | 37 | /// @notice Backwards-compatible call aggregation with Multicall 38 | /// @param calls An array of Call structs 39 | /// @return blockNumber The block number where the calls were executed 40 | /// @return returnData An array of bytes containing the responses 41 | function aggregate(Call[] calldata calls) public payable returns (uint256 blockNumber, bytes[] memory returnData) { 42 | blockNumber = block.number; 43 | uint256 length = calls.length; 44 | returnData = new bytes[](length); 45 | Call calldata call; 46 | for (uint256 i = 0; i < length;) { 47 | bool success; 48 | call = calls[i]; 49 | (success, returnData[i]) = call.target.call(call.callData); 50 | require(success, "Multicall3: call failed"); 51 | unchecked { ++i; } 52 | } 53 | } 54 | 55 | /// @notice Backwards-compatible with Multicall2 56 | /// @notice Aggregate calls without requiring success 57 | /// @param requireSuccess If true, require all calls to succeed 58 | /// @param calls An array of Call structs 59 | /// @return returnData An array of Result structs 60 | function tryAggregate(bool requireSuccess, Call[] calldata calls) public payable returns (Result[] memory returnData) { 61 | uint256 length = calls.length; 62 | returnData = new Result[](length); 63 | Call calldata call; 64 | for (uint256 i = 0; i < length;) { 65 | Result memory result = returnData[i]; 66 | call = calls[i]; 67 | (result.success, result.returnData) = call.target.call(call.callData); 68 | if (requireSuccess) require(result.success, "Multicall3: call failed"); 69 | unchecked { ++i; } 70 | } 71 | } 72 | 73 | /// @notice Backwards-compatible with Multicall2 74 | /// @notice Aggregate calls and allow failures using tryAggregate 75 | /// @param calls An array of Call structs 76 | /// @return blockNumber The block number where the calls were executed 77 | /// @return blockHash The hash of the block where the calls were executed 78 | /// @return returnData An array of Result structs 79 | function tryBlockAndAggregate(bool requireSuccess, Call[] calldata calls) public payable returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) { 80 | blockNumber = block.number; 81 | blockHash = blockhash(block.number); 82 | returnData = tryAggregate(requireSuccess, calls); 83 | } 84 | 85 | /// @notice Backwards-compatible with Multicall2 86 | /// @notice Aggregate calls and allow failures using tryAggregate 87 | /// @param calls An array of Call structs 88 | /// @return blockNumber The block number where the calls were executed 89 | /// @return blockHash The hash of the block where the calls were executed 90 | /// @return returnData An array of Result structs 91 | function blockAndAggregate(Call[] calldata calls) public payable returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) { 92 | (blockNumber, blockHash, returnData) = tryBlockAndAggregate(true, calls); 93 | } 94 | 95 | /// @notice Aggregate calls, ensuring each returns success if required 96 | /// @param calls An array of Call3 structs 97 | /// @return returnData An array of Result structs 98 | function aggregate3(Call3[] calldata calls) public payable returns (Result[] memory returnData) { 99 | uint256 length = calls.length; 100 | returnData = new Result[](length); 101 | Call3 calldata calli; 102 | for (uint256 i = 0; i < length;) { 103 | Result memory result = returnData[i]; 104 | calli = calls[i]; 105 | (result.success, result.returnData) = calli.target.call(calli.callData); 106 | assembly { 107 | // Revert if the call fails and failure is not allowed 108 | // `allowFailure := calldataload(add(calli, 0x20))` and `success := mload(result)` 109 | if iszero(or(calldataload(add(calli, 0x20)), mload(result))) { 110 | // set "Error(string)" signature: bytes32(bytes4(keccak256("Error(string)"))) 111 | mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) 112 | // set data offset 113 | mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020) 114 | // set length of revert string 115 | mstore(0x24, 0x0000000000000000000000000000000000000000000000000000000000000017) 116 | // set revert string: bytes32(abi.encodePacked("Multicall3: call failed")) 117 | mstore(0x44, 0x4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000) 118 | revert(0x00, 0x64) 119 | } 120 | } 121 | unchecked { ++i; } 122 | } 123 | } 124 | 125 | /// @notice Aggregate calls with a msg value 126 | /// @notice Reverts if msg.value is less than the sum of the call values 127 | /// @param calls An array of Call3Value structs 128 | /// @return returnData An array of Result structs 129 | function aggregate3Value(Call3Value[] calldata calls) public payable returns (Result[] memory returnData) { 130 | uint256 valAccumulator; 131 | uint256 length = calls.length; 132 | returnData = new Result[](length); 133 | Call3Value calldata calli; 134 | for (uint256 i = 0; i < length;) { 135 | Result memory result = returnData[i]; 136 | calli = calls[i]; 137 | uint256 val = calli.value; 138 | // Humanity will be a Type V Kardashev Civilization before this overflows - andreas 139 | // ~ 10^25 Wei in existence << ~ 10^76 size uint fits in a uint256 140 | unchecked { valAccumulator += val; } 141 | (result.success, result.returnData) = calli.target.call{value: val}(calli.callData); 142 | assembly { 143 | // Revert if the call fails and failure is not allowed 144 | // `allowFailure := calldataload(add(calli, 0x20))` and `success := mload(result)` 145 | if iszero(or(calldataload(add(calli, 0x20)), mload(result))) { 146 | // set "Error(string)" signature: bytes32(bytes4(keccak256("Error(string)"))) 147 | mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) 148 | // set data offset 149 | mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020) 150 | // set length of revert string 151 | mstore(0x24, 0x0000000000000000000000000000000000000000000000000000000000000017) 152 | // set revert string: bytes32(abi.encodePacked("Multicall3: call failed")) 153 | mstore(0x44, 0x4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000) 154 | revert(0x00, 0x84) 155 | } 156 | } 157 | unchecked { ++i; } 158 | } 159 | // Finally, make sure the msg.value = SUM(call[0...i].value) 160 | require(msg.value == valAccumulator, "Multicall3: value mismatch"); 161 | } 162 | 163 | /// @notice Returns the block hash for the given block number 164 | /// @param blockNumber The block number 165 | function getBlockHash(uint256 blockNumber) public view returns (bytes32 blockHash) { 166 | blockHash = blockhash(blockNumber); 167 | } 168 | 169 | /// @notice Returns the block number 170 | function getBlockNumber() public view returns (uint256 blockNumber) { 171 | blockNumber = block.number; 172 | } 173 | 174 | /// @notice Returns the block coinbase 175 | function getCurrentBlockCoinbase() public view returns (address coinbase) { 176 | coinbase = block.coinbase; 177 | } 178 | 179 | /// @notice Returns the block difficulty 180 | function getCurrentBlockDifficulty() public view returns (uint256 difficulty) { 181 | difficulty = block.difficulty; 182 | } 183 | 184 | /// @notice Returns the block gas limit 185 | function getCurrentBlockGasLimit() public view returns (uint256 gaslimit) { 186 | gaslimit = block.gaslimit; 187 | } 188 | 189 | /// @notice Returns the block timestamp 190 | function getCurrentBlockTimestamp() public view returns (uint256 timestamp) { 191 | timestamp = block.timestamp; 192 | } 193 | 194 | /// @notice Returns the (ETH) balance of a given address 195 | function getEthBalance(address addr) public view returns (uint256 balance) { 196 | balance = addr.balance; 197 | } 198 | 199 | /// @notice Returns the block hash of the last block 200 | function getLastBlockHash() public view returns (bytes32 blockHash) { 201 | unchecked { 202 | blockHash = blockhash(block.number - 1); 203 | } 204 | } 205 | 206 | /// @notice Gets the base fee of the given block 207 | /// @notice Can revert if the BASEFEE opcode is not implemented by the given chain 208 | function getBasefee() public view returns (uint256 basefee) { 209 | basefee = block.basefee; 210 | } 211 | 212 | /// @notice Returns the chain id 213 | function getChainId() public view returns (uint256 chainid) { 214 | chainid = block.chainid; 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Multicall • [![tests](https://github.com/mds1/multicall/actions/workflows/tests.yml/badge.svg)](https://github.com/mds1/multicall/actions/workflows/tests.yml) ![GitHub](https://img.shields.io/github/license/mds1/multicall) 4 | 5 | Multicall aggregates results from multiple contract constant function calls. 6 | 7 | This reduces the number of separate JSON RPC requests that need to be sent 8 | (especially useful if using remote nodes like Infura), while also providing the 9 | guarantee that all values returned are from the same block (like an atomic read) 10 | and returning the block number the values are from (giving them important 11 | context so that results from old blocks can be ignored if they're from an 12 | out-of-date node). 13 | 14 | There are three contracts in this repository: 15 | 16 | - [`Multicall`](./src/Multicall.sol): The original contract containing an `aggregate` method to batch calls 17 | - [`Multicall2`](./src/Multicall2.sol): The same as Multicall, but provides additional functions that allow calls within the batch to fail. Useful for situations where a call may fail depending on the state of the contract. 18 | - [`Multicall3`](./src/Multicall3.sol): **This is the recommended version**. It's ABI is backwards compatible with Multicall and Multicall2, but it's cheaper to use (so you can fit more calls into a single request), and it adds an `aggregate3` method so you can specify whether calls are allowed to fail on a per-call basis. Additionally, it's deployed on every network at the same address. 19 | 20 | These contracts can also be used to batch on-chain transactions. 21 | If using them for this purpose, be aware these contracts are unaudited so use them at your own risk. 22 | Additionally, **make sure you understand how `msg.sender` works when calling vs. delegatecalling to the Multicall contract, as well as the risks of using `msg.value` in a multicall**. 23 | To learn more about the latter, see [here](https://github.com/runtimeverification/verified-smart-contracts/wiki/List-of-Security-Vulnerabilities#payable-multicall) and [here](https://samczsun.com/two-rights-might-make-a-wrong/). 24 | 25 | You can obtain the ABI for the Multicall contracts in the following ways: 26 | 27 | - Download the ABI from the [releases](https://github.com/mds1/multicall/releases) page 28 | - Copy the ABI from [Etherscan](https://etherscan.io/address/0xcA11bde05977b3631167028862bE2a173976CA11#code) 29 | - Install [Foundry](https://github.com/gakonst/foundry/) and run `cast interface 0xcA11bde05977b3631167028862bE2a173976CA11` 30 | - Copy the human-readable ABI [below](#human-readable-abi) for use with [ethers.js](https://github.com/ethers-io/ethers.js/). 31 | 32 | ## Deployments 33 | 34 | ### Multicall3 Contract Addresses 35 | 36 | Multicall3 contains the following improvements over prior multicall contracts: 37 | 38 | - Cheaper to use: fit more calls into a single request before hitting the RPC's `eth_call` gas limit. 39 | - Backwards compatible: it can be dropped in to existing code by simply changing the address. 40 | - Uses the same, memorable deployment address on all 50 networks it's deployed to. 41 | 42 | | Chain | [Chain ID](https://chainlist.org/) | Address | 43 | | ----------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | 44 | | Mainnet | 1 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://etherscan.io/address/0xca11bde05977b3631167028862be2a173976ca11#code) | 45 | | Kovan | 42 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://kovan.etherscan.io/address/0xca11bde05977b3631167028862be2a173976ca11#code) | 46 | | Rinkeby | 4 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://rinkeby.etherscan.io/address/0xca11bde05977b3631167028862be2a173976ca11#code) | 47 | | Görli | 5 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://goerli.etherscan.io/address/0xca11bde05977b3631167028862be2a173976ca11#code) | 48 | | Ropsten | 3 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://ropsten.etherscan.io/address/0xca11bde05977b3631167028862be2a173976ca11#code) | 49 | | Sepolia | 11155111 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://sepolia.etherscan.io/address/0xcA11bde05977b3631167028862bE2a173976CA11#code) | 50 | | Optimism | 10 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://optimistic.etherscan.io/address/0xca11bde05977b3631167028862be2a173976ca11#code) | 51 | | Optimism Kovan | 69 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://kovan-optimistic.etherscan.io/address/0xca11bde05977b3631167028862be2a173976ca11#code) | 52 | | Optimism Görli | 420 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://blockscout.com/optimism/goerli/address/0xcA11bde05977b3631167028862bE2a173976CA11/contracts) | 53 | | Arbitrum | 42161 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://arbiscan.io/address/0xca11bde05977b3631167028862be2a173976ca11#code) | 54 | | Arbitrum Görli | 421613 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://goerli-rollup-explorer.arbitrum.io/address/0xcA11bde05977b3631167028862bE2a173976CA11/contracts) | 55 | | Arbitrum Rinkeby | 421611 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://testnet.arbiscan.io/address/0xca11bde05977b3631167028862be2a173976ca11#code) | 56 | | Polygon | 137 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://polygonscan.com/address/0xca11bde05977b3631167028862be2a173976ca11#code) | 57 | | Mumbai | 80001 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://mumbai.polygonscan.com/address/0xca11bde05977b3631167028862be2a173976ca11#code) | 58 | | Gnosis Chain (xDai) | 100 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://blockscout.com/xdai/mainnet/address/0xcA11bde05977b3631167028862bE2a173976CA11/contracts) | 59 | | Avalanche | 43114 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://snowtrace.io/address/0xcA11bde05977b3631167028862bE2a173976CA11#code) | 60 | | Avalanche Fuji | 43113 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://testnet.snowtrace.io/address/0xcA11bde05977b3631167028862bE2a173976CA11#code) | 61 | | Fantom Testnet | 4002 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://testnet.ftmscan.com/address/0xcA11bde05977b3631167028862bE2a173976CA11#code) | 62 | | Fantom Opera | 250 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://ftmscan.com/address/0xcA11bde05977b3631167028862bE2a173976CA11#code) | 63 | | BNB Smart Chain | 56 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://bscscan.com/address/0xcA11bde05977b3631167028862bE2a173976CA11#code) | 64 | | BNB Smart Chain Testnet | 97 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://testnet.bscscan.com/address/0xcA11bde05977b3631167028862bE2a173976CA11#code) | 65 | | Moonbeam | 1284 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://moonscan.io/address/0xca11bde05977b3631167028862be2a173976ca11#code) | 66 | | Moonriver | 1285 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://moonriver.moonscan.io/address/0xca11bde05977b3631167028862be2a173976ca11#code) | 67 | | Moonbase Alpha Testnet | 1287 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://moonbase.moonscan.io/address/0xca11bde05977b3631167028862be2a173976ca11#code) | 68 | | Harmony | 1666600000 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://explorer.harmony.one/address/0xca11bde05977b3631167028862be2a173976ca11?activeTab=7) | 69 | | Cronos | 25 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://cronoscan.com/address/0xca11bde05977b3631167028862be2a173976ca11#code) | 70 | | Fuse | 122 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://explorer.fuse.io/address/0xcA11bde05977b3631167028862bE2a173976CA11/contracts) | 71 | | Songbird Canary Network | 19 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://songbird-explorer.flare.network/address/0xcA11bde05977b3631167028862bE2a173976CA11/contracts) | 72 | | Coston Testnet | 16 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://coston-explorer.flare.network/address/0xcA11bde05977b3631167028862bE2a173976CA11/contracts) | 73 | | Coston2 Testnet | 114 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://coston2-explorer.flare.network/address/0xcA11bde05977b3631167028862bE2a173976CA11/contracts) | 74 | | Boba | 288 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://blockexplorer.boba.network/address/0xcA11bde05977b3631167028862bE2a173976CA11/contracts) | 75 | | Aurora | 1313161554 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://explorer.mainnet.aurora.dev/address/0xcA11bde05977b3631167028862bE2a173976CA11) | 76 | | Astar | 592 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://blockscout.com/astar/address/0xcA11bde05977b3631167028862bE2a173976CA11/contracts) | 77 | | OKC | 66 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://www.oklink.com/en/okc/address/0xca11bde05977b3631167028862be2a173976ca11) | 78 | | Heco Chain | 128 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://hecoinfo.com/address/0xca11bde05977b3631167028862be2a173976ca11#code) | 79 | | Metis | 1088 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://andromeda-explorer.metis.io/address/0xcA11bde05977b3631167028862bE2a173976CA11/contracts) | 80 | | RSK | 30 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://explorer.rsk.co/address/0xca11bde05977b3631167028862be2a173976ca11) | 81 | | RSK Testnet | 31 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://explorer.testnet.rsk.co/address/0xca11bde05977b3631167028862be2a173976ca11) | 82 | | Evmos | 9001 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://evm.evmos.org/address/0xcA11bde05977b3631167028862bE2a173976CA11/contracts) | 83 | | Evmos Testnet | 9000 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://evm.evmos.dev/address/0xcA11bde05977b3631167028862bE2a173976CA11/contracts) | 84 | | Thundercore | 108 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://viewblock.io/thundercore/address/0xca11bde05977b3631167028862be2a173976ca11?tab=code) | 85 | | Thundercore Testnet | 18 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://explorer-testnet.thundercore.com/address/0xcA11bde05977b3631167028862bE2a173976CA11/contracts) | 86 | | Oasis | 42262 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://explorer.emerald.oasis.dev/address/0xcA11bde05977b3631167028862bE2a173976CA11/contracts) | 87 | | Celo | 42220 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://explorer.celo.org/address/0xcA11bde05977b3631167028862bE2a173976CA11/contracts) | 88 | | Godwoken | 71402 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://v1.gwscan.com/account/0xca11bde05977b3631167028862be2a173976ca11) | 89 | | Godwoken Testnet | 71401 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://gw-explorer.nervosdao.community/address/0xcA11bde05977b3631167028862bE2a173976CA11/contracts) | 90 | | Klaytn | 8217 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://scope.klaytn.com/account/0xca11bde05977b3631167028862be2a173976ca11) | 91 | | Milkomeda | 2001 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://explorer-mainnet-cardano-evm.c1.milkomeda.com/address/0xcA11bde05977b3631167028862bE2a173976CA11/contracts) | 92 | | KCC | 321 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://explorer.kcc.io/en/address/0xca11bde05977b3631167028862be2a173976ca11) | 93 | | Velas | 106 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://evmexplorer.velas.com/address/0xcA11bde05977b3631167028862bE2a173976CA11/contracts) | 94 | 95 | If there is a network Multicall3 is not yet deployed on, please open an issue 96 | with a link to the block explorer. You can speed up the new deploy by sending 97 | funds to cover the deploy cost to the deployer account: 0x05f32B3cC3888453ff71B01135B34FF8e41263F2 98 | 99 | ## Historical Deployments 100 | 101 | Multicall3 is the recommended version for most use cases, but deployment addresses for 102 | Multicall and Multicall2 are retained below for posterity. The Multicall smart contract 103 | was originally intended to be used with 104 | [Multicall.js](https://github.com/makerdao/multicall.js) 105 | in front-end dapps. However, that library has not been updated to work with Multicall2 106 | and Multicall3, so it will likely only work for the original Multicall contract. 107 | 108 | ### Multicall Contract Addresses 109 | 110 | The deployed [Multicall](https://github.com/mds1/multicall/blob/master/src/Multicall.sol) contract can be found in commit [`bb309a9`](https://github.com/mds1/multicall/commit/bb309a985379c40bdbbc9a8613501732ed98bb9c) or earlier. After that commit, the contract was updated to a more recent Solidity version (with minimal improvements), primarily for compatibility with the test suite. 111 | 112 | | Chain | Address | 113 | | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | 114 | | Mainnet | [0xeefba1e63905ef1d7acba5a8513c70307c1ce441](https://etherscan.io/address/0xeefba1e63905ef1d7acba5a8513c70307c1ce441#contracts) | 115 | | Kovan | [0x2cc8688c5f75e365aaeeb4ea8d6a480405a48d2a](https://kovan.etherscan.io/address/0x2cc8688c5f75e365aaeeb4ea8d6a480405a48d2a#contracts) | 116 | | Rinkeby | [0x42ad527de7d4e9d9d011ac45b31d8551f8fe9821](https://rinkeby.etherscan.io/address/0x42ad527de7d4e9d9d011ac45b31d8551f8fe9821#contracts) | 117 | | Görli | [0x77dca2c955b15e9de4dbbcf1246b4b85b651e50e](https://goerli.etherscan.io/address/0x77dca2c955b15e9de4dbbcf1246b4b85b651e50e#contracts) | 118 | | Ropsten | [0x53c43764255c17bd724f74c4ef150724ac50a3ed](https://ropsten.etherscan.io/address/0x53c43764255c17bd724f74c4ef150724ac50a3ed#code) | 119 | | xDai | [0xb5b692a88bdfc81ca69dcb1d924f59f0413a602a](https://blockscout.com/poa/dai/address/0xb5b692a88bdfc81ca69dcb1d924f59f0413a602a) | 120 | | Polygon | [0x11ce4B23bD875D7F5C6a31084f55fDe1e9A87507](https://explorer-mainnet.maticvigil.com/address/0x11ce4B23bD875D7F5C6a31084f55fDe1e9A87507/contracts) | 121 | | Mumbai | [0x08411ADd0b5AA8ee47563b146743C13b3556c9Cc](https://explorer-mumbai.maticvigil.com/address/0x08411ADd0b5AA8ee47563b146743C13b3556c9Cc/transactions) | 122 | | Optimism | [0x187C0F98FEF80E87880Db50241D40551eDd027Bf](https://optimistic.etherscan.io/address/0x187C0F98FEF80E87880Db50241D40551eDd027Bf#code) | 123 | | Arbitrum | [0xB064Fe785d8131653eE12f3581F9A55F6D6E1ca3](https://arbiscan.io/address/0xB064Fe785d8131653eE12f3581F9A55F6D6E1ca3#code) | 124 | 125 | ### Multicall2 Contract Addresses 126 | 127 | The deployed [Multicall2](https://github.com/mds1/multicall/blob/master/src/Multicall2.sol) contract can be found in commit [`bb309a9`](https://github.com/mds1/multicall/commit/bb309a985379c40bdbbc9a8613501732ed98bb9c) or earlier. After that commit, the contract was updated to a more recent Solidity version (with minimal improvements), primarily for compatibility with the test suite. 128 | 129 | Multicall2 is the same as Multicall, but provides additional functions that allow calls within the batch to fail. Useful for situations where a call may fail depending on the state of the contract. 130 | 131 | | Chain | Address | 132 | | ------- | --------------------------------------------------------------------------------------------------------------------------------------- | 133 | | Mainnet | [0x5ba1e12693dc8f9c48aad8770482f4739beed696](https://etherscan.io/address/0x5ba1e12693dc8f9c48aad8770482f4739beed696#contracts) | 134 | | Kovan | [0x5ba1e12693dc8f9c48aad8770482f4739beed696](https://kovan.etherscan.io/address/0x5ba1e12693dc8f9c48aad8770482f4739beed696#contracts) | 135 | | Rinkeby | [0x5ba1e12693dc8f9c48aad8770482f4739beed696](https://rinkeby.etherscan.io/address/0x5ba1e12693dc8f9c48aad8770482f4739beed696#contracts) | 136 | | Görli | [0x5ba1e12693dc8f9c48aad8770482f4739beed696](https://goerli.etherscan.io/address/0x5ba1e12693dc8f9c48aad8770482f4739beed696#contracts) | 137 | | Ropsten | [0x5ba1e12693dc8f9c48aad8770482f4739beed696](https://ropsten.etherscan.io/address/0x5ba1e12693dc8f9c48aad8770482f4739beed696#code) | 138 | 139 | ### Third-Party Deployments 140 | 141 | The following addresses have been submitted by external contributors and have not been vetted by Multicall maintainers. 142 | 143 | | Chain | Address | 144 | | ----------- | -------------------------------------------------------------------------------------------------------------------------------- | 145 | | RSK Mainnet | [0x6c62bf5440de2cb157205b15c424bceb5c3368f5](https://explorer.rsk.co/address/0x6c62bf5440de2cb157205b15c424bceb5c3368f5) | 146 | | RSK Testnet | [0x9e469e1fc7fb4c5d17897b68eaf1afc9df39f103](https://explorer.testnet.rsk.co/address/0x9e469e1fc7fb4c5d17897b68eaf1afc9df39f103) | 147 | | BSC Mainnet | [0x41263cba59eb80dc200f3e2544eda4ed6a90e76c](https://bscscan.com/address/0x41263cba59eb80dc200f3e2544eda4ed6a90e76c) | 148 | | BSC Testnet | [0xae11C5B5f29A6a25e955F0CB8ddCc416f522AF5C](https://testnet.bscscan.com/address/0xae11c5b5f29a6a25e955f0cb8ddcc416f522af5c) | 149 | 150 | ## Development 151 | 152 | This repo uses [Foundry](https://github.com/gakonst/foundry) for development and testing 153 | and git submodules for dependency management. 154 | 155 | Clone the repo and run `forge install` to install dependencies and `forge test` to run tests. 156 | 157 | ### Foundry Setup 158 | 159 | If you don't have Foundry installed, run the command below to get `foundryup`, the Foundry toolchain installer: 160 | 161 | ```sh 162 | curl -L https://foundry.paradigm.xyz | bash 163 | ``` 164 | 165 | Then, in a new terminal session or after reloading your `PATH`, run `foundryup` to get the latest `forge` and `cast` binaries. 166 | 167 | To learn more about Foundry: 168 | 169 | - Visit the [repo](https://github.com/gakonst/foundry) 170 | - Check out the Foundry [book](https://onbjerg.github.io/foundry-book/) 171 | - Learn advanced ways to use `foundryup` in the [foundryup package](https://github.com/gakonst/foundry/tree/master/foundryup) 172 | - Check out the [awesome-foundry](https://github.com/crisgarner/awesome-foundry) repo 173 | 174 | ### Gas Golfing Tricks and Optimizations 175 | 176 | Below is a list of some of the optimizations used by Multicall3's `aggregate3` and `aggregate3Value` methods: 177 | 178 | - In for loops, array length is cached to avoid reading the length on each loop iteration 179 | - In for loops, the counter is incremented within an `unchecked` block 180 | - In for loops, the counter is incremented with the prefix increment (`++i`) instead of a postfix increment (`i++`) 181 | - All revert strings fit within a single 32 byte slot 182 | - Function parameters use `calldata` instead of `memory` 183 | - Instead of requiring `call.allowFailure || result.success`, we use assembly's `or()` instruction to [avoid](https://twitter.com/transmissions11/status/1501645922266091524) a `JUMPI` and `iszero()` since it's cheaper to evaluate both conditions 184 | - Methods are given a `payable` modifier which removes a check that `msg.value == 0` when calling a method 185 | - Calldata and memory pointers are used to cache values so they are not read multiple times within a loop 186 | - No block data (e.g. block number, hash, or timestamp) is returned by default, and is instead left up to the caller 187 | - The value accumulator in `aggregate3Value` is within an `unchecked` block 188 | 189 | Read more about Solidity gas optimization tips: 190 | 191 | - [Generic writeup about common gas optimizations, etc.](https://gist.github.com/hrkrshnn/ee8fabd532058307229d65dcd5836ddc) by [Harikrishnan Mulackal](https://twitter.com/_hrkrshnn) 192 | - [Yul (and Some Solidity) Optimizations and Tricks](https://hackmd.io/@gn56kcRBQc6mOi7LCgbv1g/rJez8O8st) by [ControlCplusControlV](https://twitter.com/controlcthenv) 193 | 194 | ## Human-Readable ABI 195 | 196 | Below is the human-readable ABI. 197 | This can be directly passed into an ethers.js `Contract` or `Interface` constructor. 198 | 199 | ```typescript 200 | const MULTICALL_ABI = [ 201 | // https://github.com/mds1/multicall 202 | 'function aggregate(tuple(address target, bytes callData)[] calls) payable returns (uint256 blockNumber, bytes[] returnData)', 203 | 'function aggregate3(tuple(address target, bool allowFailure, bytes callData)[] calls) payable returns (tuple(bool success, bytes returnData)[] returnData)', 204 | 'function aggregate3Value(tuple(address target, bool allowFailure, uint256 value, bytes callData)[] calls) payable returns (tuple(bool success, bytes returnData)[] returnData)', 205 | 'function blockAndAggregate(tuple(address target, bytes callData)[] calls) payable returns (uint256 blockNumber, bytes32 blockHash, tuple(bool success, bytes returnData)[] returnData)', 206 | 'function getBasefee() view returns (uint256 basefee)', 207 | 'function getBlockHash(uint256 blockNumber) view returns (bytes32 blockHash)', 208 | 'function getBlockNumber() view returns (uint256 blockNumber)', 209 | 'function getChainId() view returns (uint256 chainid)', 210 | 'function getCurrentBlockCoinbase() view returns (address coinbase)', 211 | 'function getCurrentBlockDifficulty() view returns (uint256 difficulty)', 212 | 'function getCurrentBlockGasLimit() view returns (uint256 gaslimit)', 213 | 'function getCurrentBlockTimestamp() view returns (uint256 timestamp)', 214 | 'function getEthBalance(address addr) view returns (uint256 balance)', 215 | 'function getLastBlockHash() view returns (bytes32 blockHash)', 216 | 'function tryAggregate(bool requireSuccess, tuple(address target, bytes callData)[] calls) payable returns (tuple(bool success, bytes returnData)[] returnData)', 217 | 'function tryBlockAndAggregate(bool requireSuccess, tuple(address target, bytes callData)[] calls) payable returns (uint256 blockNumber, bytes32 blockHash, tuple(bool success, bytes returnData)[] returnData)', 218 | ]; 219 | ``` 220 | --------------------------------------------------------------------------------