├── LICENSE ├── README.md └── contracts ├── DeploylessMulticall.sol ├── DeploylessMulticall2.sol └── DeploylessMulticall3.sol /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Timur Badretdinov 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deployless Multicall 2 | 3 | An implementation of Multicall contract that doesn't require it to be deployed. Can be used in any EVM chain at any block. 4 | 5 | ## Usage 6 | 7 | [ethcall](https://github.com/Destiner/ethcall) provides a convenient wrapper around low-level interation. Alternatively, you can compile a contract yourself, and make an "read-only deploy" eth_call: 8 | 9 | ```js 10 | const args = encode(deploylessMulticallAbi, [calls]); 11 | const data = ethers.utils.hexConcat([deploylessMulticallBytecode, args]); 12 | const callData = await provider.call({ 13 | data, 14 | }); 15 | const result = decode(multicallAbi, 'aggregate', callData); 16 | return result; 17 | ``` 18 | where `encode` and `decode` is the ABI coding functions. 19 | 20 | ## How 21 | 22 | Normally, a consturcor in Solidity contract returns the bytecode of the newly created contract. Here, we use inline assembly to overwrite return data with the call result. 23 | 24 | The code makes some assuptions about how the variables stored in memory, so it's better to stick to Solidity version specified in the contract. 25 | -------------------------------------------------------------------------------- /contracts/DeploylessMulticall.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity 0.8.9; 4 | 5 | contract DeploylessMulticall { 6 | struct Call { 7 | address target; 8 | bytes callData; 9 | } 10 | 11 | constructor(Call[] memory calls) { 12 | uint256 blockNumber = block.number; 13 | bytes[] memory returnData = new bytes[](calls.length); 14 | for (uint256 i = 0; i < calls.length; i++) { 15 | (bool success, bytes memory ret) = calls[i].target.call(calls[i].callData); 16 | require(success); 17 | returnData[i] = ret; 18 | } 19 | 20 | assembly { 21 | // 'Multicall.aggregate' returns (uint256, bytes[]) 22 | // Overwrite memory to comply with the expected format 23 | mstore(sub(returnData, 0x40), blockNumber) 24 | mstore(sub(returnData, 0x20), 0x40) 25 | 26 | // Fix returnData index pointers 27 | let indexOffset := add(returnData, 0x20) 28 | for 29 | { let pointerIndex := add(returnData, 0x20) } 30 | lt(pointerIndex, add(returnData, mul(add(mload(returnData), 1), 0x20))) 31 | { pointerIndex := add(pointerIndex, 0x20) } 32 | { 33 | mstore(pointerIndex, sub(mload(pointerIndex), indexOffset)) 34 | } 35 | 36 | return( 37 | sub(returnData, 0x40), 38 | // We assume that 'returnData' is placed at the end of memory 39 | // Therefore, 'sub(mload(0x40), returnData)' provides the array length 40 | add(sub(mload(0x40), returnData), 0x40) 41 | ) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /contracts/DeploylessMulticall2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity 0.8.9; 4 | 5 | contract DeploylessMulticall2 { 6 | struct Call { 7 | address target; 8 | bytes callData; 9 | } 10 | 11 | struct Result { 12 | bool success; 13 | bytes returnData; 14 | } 15 | 16 | constructor(bool requireSuccess, Call[] memory calls) { 17 | Result[] memory returnData = new Result[](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 | 21 | if (requireSuccess) { 22 | require(success, "Multicall2 aggregate: call failed"); 23 | } 24 | 25 | returnData[i] = Result(success, ret); 26 | } 27 | 28 | assembly { 29 | // returnData.size = memory.size - returnData.pointer - 2 * 32 * returnData.length + 32 30 | let size := add(sub(sub(mload(0x40), returnData), mul(0x40, mload(returnData))), 0x20) 31 | let start := sub(sub(mload(0x40), size), 0x40) 32 | 33 | // Fix pointers 34 | if gt(mload(returnData), 1) 35 | { 36 | for 37 | { let index := 0 } 38 | lt(index, mload(returnData)) 39 | { index := add(index, 1) } 40 | { 41 | let oldPointer := add(add(returnData, 0x20), mul(0x20, index)) 42 | let pointer := add(start, add(0x40, mul(0x20, index))) 43 | if iszero(index) 44 | { 45 | mstore(pointer, mul(0x20, mload(returnData))) 46 | } 47 | if gt(index, 0) 48 | { 49 | let length := sub( 50 | mload(add(mload(oldPointer), 0x20)), 51 | mload(add(mload(sub(oldPointer, 0x20)), 0x20)) 52 | ) 53 | let value := add(mload(sub(pointer, 0x20)), length) 54 | mstore(pointer, value) 55 | } 56 | } 57 | } 58 | 59 | // Fix order: (returnData, success) -> (success, returnData) 60 | for 61 | { let index := 0 } 62 | lt(index, mload(returnData)) 63 | { index := add(index, 1) } 64 | { 65 | let oldPointer := add(add(returnData, 0x20), mul(0x20, index)) 66 | mstore(sub(mload(add(mload(oldPointer), 0x20)), 0x40), mload(mload(oldPointer))) 67 | } 68 | 69 | // Fix lengths 70 | for 71 | { let index := 0 } 72 | lt(index, mload(returnData)) 73 | { index := add(index, 1) } 74 | { 75 | let oldPointer := add(add(returnData, 0x20), mul(0x20, index)) 76 | mstore(sub(mload(add(mload(oldPointer), 0x20)), 0x20), 0x40) 77 | } 78 | 79 | // Fix pointers (single element) 80 | if eq(mload(returnData), 1) 81 | { 82 | let oldPointer := add(returnData, 0x20) 83 | let pointer := add(start, 0x40) 84 | mstore(pointer, mul(0x20, mload(returnData))) 85 | } 86 | 87 | // Write header 88 | mstore(start, 0x20) 89 | mstore(add(start, 0x20), mload(returnData)) 90 | 91 | return(start, size) 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /contracts/DeploylessMulticall3.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity 0.8.9; 4 | 5 | contract DeploylessMulticall3 { 6 | struct Call { 7 | address target; 8 | bool allowFailure; 9 | bytes callData; 10 | } 11 | 12 | struct Result { 13 | bool success; 14 | bytes returnData; 15 | } 16 | 17 | constructor(Call[] memory calls) { 18 | Result[] memory returnData = new Result[](calls.length); 19 | for(uint256 i = 0; i < calls.length; i++) { 20 | (bool success, bytes memory ret) = calls[i].target.call(calls[i].callData); 21 | 22 | if (!calls[i].allowFailure) { 23 | require(success, "Multicall3 aggregate: call failed"); 24 | } 25 | 26 | returnData[i] = Result(success, ret); 27 | } 28 | 29 | assembly { 30 | // returnData.size = memory.size - returnData.pointer - 2 * 32 * returnData.length + 32 31 | let size := add(sub(sub(mload(0x40), returnData), mul(0x40, mload(returnData))), 0x20) 32 | let start := sub(sub(mload(0x40), size), 0x40) 33 | 34 | // Fix pointers 35 | if gt(mload(returnData), 1) 36 | { 37 | for 38 | { let index := 0 } 39 | lt(index, mload(returnData)) 40 | { index := add(index, 1) } 41 | { 42 | let oldPointer := add(add(returnData, 0x20), mul(0x20, index)) 43 | let pointer := add(start, add(0x40, mul(0x20, index))) 44 | if iszero(index) 45 | { 46 | mstore(pointer, mul(0x20, mload(returnData))) 47 | } 48 | if gt(index, 0) 49 | { 50 | let length := sub( 51 | mload(add(mload(oldPointer), 0x20)), 52 | mload(add(mload(sub(oldPointer, 0x20)), 0x20)) 53 | ) 54 | let value := add(mload(sub(pointer, 0x20)), length) 55 | mstore(pointer, value) 56 | } 57 | } 58 | } 59 | 60 | // Fix order: (returnData, success) -> (success, returnData) 61 | for 62 | { let index := 0 } 63 | lt(index, mload(returnData)) 64 | { index := add(index, 1) } 65 | { 66 | let oldPointer := add(add(returnData, 0x20), mul(0x20, index)) 67 | mstore(sub(mload(add(mload(oldPointer), 0x20)), 0x40), mload(mload(oldPointer))) 68 | } 69 | 70 | // Fix lengths 71 | for 72 | { let index := 0 } 73 | lt(index, mload(returnData)) 74 | { index := add(index, 1) } 75 | { 76 | let oldPointer := add(add(returnData, 0x20), mul(0x20, index)) 77 | mstore(sub(mload(add(mload(oldPointer), 0x20)), 0x20), 0x40) 78 | } 79 | 80 | // Fix pointers (single element) 81 | if eq(mload(returnData), 1) 82 | { 83 | let oldPointer := add(returnData, 0x20) 84 | let pointer := add(start, 0x40) 85 | mstore(pointer, mul(0x20, mload(returnData))) 86 | } 87 | 88 | // Write header 89 | mstore(start, 0x20) 90 | mstore(add(start, 0x20), mload(returnData)) 91 | 92 | return(start, size) 93 | } 94 | } 95 | } 96 | --------------------------------------------------------------------------------