diff --git a/test/chains/chain-tests.js b/test/chains/chain-tests.js index 945683174..59d8a143b 100644 --- a/test/chains/chain-tests.js +++ b/test/chains/chain-tests.js @@ -920,6 +920,15 @@ describe("Test Supported Chains", function () { "6119/UptnNFTsV1.metadata.json" ); + // BEAM Chain Testnet + verifyContract( + "0x9BF49b704EE2A095b95c1f2D4EB9010510c41C9E", + "13337", + "BEAM Chain", + ["multicall3/Multicall3.sol"], + "multicall3/multicall3.metadata.json" + ); + // KAVA EVM verifyContract( "0xAdFa11e737ec8fA6e91091468aEF33a66Ae0044c", diff --git a/test/chains/sources/multicall3/Multicall3.sol b/test/chains/sources/multicall3/Multicall3.sol new file mode 100644 index 000000000..7582ea960 --- /dev/null +++ b/test/chains/sources/multicall3/Multicall3.sol @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.12; + +/// @title Multicall3 +/// @notice Aggregate results from multiple function calls +/// @dev Multicall & Multicall2 backwards-compatible +/// @dev Aggregate methods are marked `payable` to save 24 gas per call +/// @author Michael Elliot +/// @author Joshua Levine +/// @author Nick Johnson +/// @author Andreas Bigger +/// @author Matt Solomon +contract Multicall3 { + struct Call { + address target; + bytes callData; + } + + struct Call3 { + address target; + bool allowFailure; + bytes callData; + } + + struct Call3Value { + address target; + bool allowFailure; + uint256 value; + bytes callData; + } + + struct Result { + bool success; + bytes returnData; + } + + /// @notice Backwards-compatible call aggregation with Multicall + /// @param calls An array of Call structs + /// @return blockNumber The block number where the calls were executed + /// @return returnData An array of bytes containing the responses + function aggregate(Call[] calldata calls) public payable returns (uint256 blockNumber, bytes[] memory returnData) { + blockNumber = block.number; + uint256 length = calls.length; + returnData = new bytes[](length); + Call calldata call; + for (uint256 i = 0; i < length;) { + bool success; + call = calls[i]; + (success, returnData[i]) = call.target.call(call.callData); + require(success, "Multicall3: call failed"); + unchecked { ++i; } + } + } + + /// @notice Backwards-compatible with Multicall2 + /// @notice Aggregate calls without requiring success + /// @param requireSuccess If true, require all calls to succeed + /// @param calls An array of Call structs + /// @return returnData An array of Result structs + function tryAggregate(bool requireSuccess, Call[] calldata calls) public payable returns (Result[] memory returnData) { + uint256 length = calls.length; + returnData = new Result[](length); + Call calldata call; + for (uint256 i = 0; i < length;) { + Result memory result = returnData[i]; + call = calls[i]; + (result.success, result.returnData) = call.target.call(call.callData); + if (requireSuccess) require(result.success, "Multicall3: call failed"); + unchecked { ++i; } + } + } + + /// @notice Backwards-compatible with Multicall2 + /// @notice Aggregate calls and allow failures using tryAggregate + /// @param calls An array of Call structs + /// @return blockNumber The block number where the calls were executed + /// @return blockHash The hash of the block where the calls were executed + /// @return returnData An array of Result structs + function tryBlockAndAggregate(bool requireSuccess, Call[] calldata calls) public payable returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) { + blockNumber = block.number; + blockHash = blockhash(block.number); + returnData = tryAggregate(requireSuccess, calls); + } + + /// @notice Backwards-compatible with Multicall2 + /// @notice Aggregate calls and allow failures using tryAggregate + /// @param calls An array of Call structs + /// @return blockNumber The block number where the calls were executed + /// @return blockHash The hash of the block where the calls were executed + /// @return returnData An array of Result structs + function blockAndAggregate(Call[] calldata calls) public payable returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) { + (blockNumber, blockHash, returnData) = tryBlockAndAggregate(true, calls); + } + + /// @notice Aggregate calls, ensuring each returns success if required + /// @param calls An array of Call3 structs + /// @return returnData An array of Result structs + function aggregate3(Call3[] calldata calls) public payable returns (Result[] memory returnData) { + uint256 length = calls.length; + returnData = new Result[](length); + Call3 calldata calli; + for (uint256 i = 0; i < length;) { + Result memory result = returnData[i]; + calli = calls[i]; + (result.success, result.returnData) = calli.target.call(calli.callData); + assembly { + // Revert if the call fails and failure is not allowed + // `allowFailure := calldataload(add(calli, 0x20))` and `success := mload(result)` + if iszero(or(calldataload(add(calli, 0x20)), mload(result))) { + // set "Error(string)" signature: bytes32(bytes4(keccak256("Error(string)"))) + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + // set data offset + mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020) + // set length of revert string + mstore(0x24, 0x0000000000000000000000000000000000000000000000000000000000000017) + // set revert string: bytes32(abi.encodePacked("Multicall3: call failed")) + mstore(0x44, 0x4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000) + revert(0x00, 0x64) + } + } + unchecked { ++i; } + } + } + + /// @notice Aggregate calls with a msg value + /// @notice Reverts if msg.value is less than the sum of the call values + /// @param calls An array of Call3Value structs + /// @return returnData An array of Result structs + function aggregate3Value(Call3Value[] calldata calls) public payable returns (Result[] memory returnData) { + uint256 valAccumulator; + uint256 length = calls.length; + returnData = new Result[](length); + Call3Value calldata calli; + for (uint256 i = 0; i < length;) { + Result memory result = returnData[i]; + calli = calls[i]; + uint256 val = calli.value; + // Humanity will be a Type V Kardashev Civilization before this overflows - andreas + // ~ 10^25 Wei in existence << ~ 10^76 size uint fits in a uint256 + unchecked { valAccumulator += val; } + (result.success, result.returnData) = calli.target.call{value: val}(calli.callData); + assembly { + // Revert if the call fails and failure is not allowed + // `allowFailure := calldataload(add(calli, 0x20))` and `success := mload(result)` + if iszero(or(calldataload(add(calli, 0x20)), mload(result))) { + // set "Error(string)" signature: bytes32(bytes4(keccak256("Error(string)"))) + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + // set data offset + mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020) + // set length of revert string + mstore(0x24, 0x0000000000000000000000000000000000000000000000000000000000000017) + // set revert string: bytes32(abi.encodePacked("Multicall3: call failed")) + mstore(0x44, 0x4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000) + revert(0x00, 0x84) + } + } + unchecked { ++i; } + } + // Finally, make sure the msg.value = SUM(call[0...i].value) + require(msg.value == valAccumulator, "Multicall3: value mismatch"); + } + + /// @notice Returns the block hash for the given block number + /// @param blockNumber The block number + function getBlockHash(uint256 blockNumber) public view returns (bytes32 blockHash) { + blockHash = blockhash(blockNumber); + } + + /// @notice Returns the block number + function getBlockNumber() public view returns (uint256 blockNumber) { + blockNumber = block.number; + } + + /// @notice Returns the block coinbase + function getCurrentBlockCoinbase() public view returns (address coinbase) { + coinbase = block.coinbase; + } + + /// @notice Returns the block difficulty + function getCurrentBlockDifficulty() public view returns (uint256 difficulty) { + difficulty = block.difficulty; + } + + /// @notice Returns the block gas limit + function getCurrentBlockGasLimit() public view returns (uint256 gaslimit) { + gaslimit = block.gaslimit; + } + + /// @notice Returns the block timestamp + function getCurrentBlockTimestamp() public view returns (uint256 timestamp) { + timestamp = block.timestamp; + } + + /// @notice Returns the (ETH) balance of a given address + function getEthBalance(address addr) public view returns (uint256 balance) { + balance = addr.balance; + } + + /// @notice Returns the block hash of the last block + function getLastBlockHash() public view returns (bytes32 blockHash) { + unchecked { + blockHash = blockhash(block.number - 1); + } + } + + /// @notice Gets the base fee of the given block + /// @notice Can revert if the BASEFEE opcode is not implemented by the given chain + function getBasefee() public view returns (uint256 basefee) { + basefee = block.basefee; + } + + /// @notice Returns the chain id + function getChainId() public view returns (uint256 chainid) { + chainid = block.chainid; + } +} diff --git a/test/chains/sources/multicall3/multicall3.metadata.json b/test/chains/sources/multicall3/multicall3.metadata.json new file mode 100644 index 000000000..41f5f0bac --- /dev/null +++ b/test/chains/sources/multicall3/multicall3.metadata.json @@ -0,0 +1,436 @@ +{ + "compiler": { "version": "0.8.12+commit.f00d7308" }, + "language": "Solidity", + "output": { + "abi": [ + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { "internalType": "bytes", "name": "callData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "aggregate", + "outputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + }, + { "internalType": "bytes[]", "name": "returnData", "type": "bytes[]" } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowFailure", + "type": "bool" + }, + { "internalType": "bytes", "name": "callData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Call3[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "aggregate3", + "outputs": [ + { + "components": [ + { "internalType": "bool", "name": "success", "type": "bool" }, + { "internalType": "bytes", "name": "returnData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowFailure", + "type": "bool" + }, + { "internalType": "uint256", "name": "value", "type": "uint256" }, + { "internalType": "bytes", "name": "callData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Call3Value[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "aggregate3Value", + "outputs": [ + { + "components": [ + { "internalType": "bool", "name": "success", "type": "bool" }, + { "internalType": "bytes", "name": "returnData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { "internalType": "bytes", "name": "callData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "blockAndAggregate", + "outputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + }, + { "internalType": "bytes32", "name": "blockHash", "type": "bytes32" }, + { + "components": [ + { "internalType": "bool", "name": "success", "type": "bool" }, + { "internalType": "bytes", "name": "returnData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "getBasefee", + "outputs": [ + { "internalType": "uint256", "name": "basefee", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "name": "getBlockHash", + "outputs": [ + { "internalType": "bytes32", "name": "blockHash", "type": "bytes32" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getBlockNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getChainId", + "outputs": [ + { "internalType": "uint256", "name": "chainid", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockCoinbase", + "outputs": [ + { "internalType": "address", "name": "coinbase", "type": "address" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockDifficulty", + "outputs": [ + { "internalType": "uint256", "name": "difficulty", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockGasLimit", + "outputs": [ + { "internalType": "uint256", "name": "gaslimit", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockTimestamp", + "outputs": [ + { "internalType": "uint256", "name": "timestamp", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "addr", "type": "address" } + ], + "name": "getEthBalance", + "outputs": [ + { "internalType": "uint256", "name": "balance", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getLastBlockHash", + "outputs": [ + { "internalType": "bytes32", "name": "blockHash", "type": "bytes32" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bool", "name": "requireSuccess", "type": "bool" }, + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { "internalType": "bytes", "name": "callData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "tryAggregate", + "outputs": [ + { + "components": [ + { "internalType": "bool", "name": "success", "type": "bool" }, + { "internalType": "bytes", "name": "returnData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bool", "name": "requireSuccess", "type": "bool" }, + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { "internalType": "bytes", "name": "callData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "tryBlockAndAggregate", + "outputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + }, + { "internalType": "bytes32", "name": "blockHash", "type": "bytes32" }, + { + "components": [ + { "internalType": "bool", "name": "success", "type": "bool" }, + { "internalType": "bytes", "name": "returnData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + } + ], + "devdoc": { + "author": "Michael Elliot Joshua Levine Nick Johnson Andreas Bigger Matt Solomon ", + "details": "Multicall & Multicall2 backwards-compatibleAggregate methods are marked `payable` to save 24 gas per call", + "kind": "dev", + "methods": { + "aggregate((address,bytes)[])": { + "params": { "calls": "An array of Call structs" }, + "returns": { + "blockNumber": "The block number where the calls were executed", + "returnData": "An array of bytes containing the responses" + } + }, + "aggregate3((address,bool,bytes)[])": { + "params": { "calls": "An array of Call3 structs" }, + "returns": { "returnData": "An array of Result structs" } + }, + "aggregate3Value((address,bool,uint256,bytes)[])": { + "params": { "calls": "An array of Call3Value structs" }, + "returns": { "returnData": "An array of Result structs" } + }, + "blockAndAggregate((address,bytes)[])": { + "params": { "calls": "An array of Call structs" }, + "returns": { + "blockHash": "The hash of the block where the calls were executed", + "blockNumber": "The block number where the calls were executed", + "returnData": "An array of Result structs" + } + }, + "getBlockHash(uint256)": { + "params": { "blockNumber": "The block number" } + }, + "tryAggregate(bool,(address,bytes)[])": { + "params": { + "calls": "An array of Call structs", + "requireSuccess": "If true, require all calls to succeed" + }, + "returns": { "returnData": "An array of Result structs" } + }, + "tryBlockAndAggregate(bool,(address,bytes)[])": { + "params": { "calls": "An array of Call structs" }, + "returns": { + "blockHash": "The hash of the block where the calls were executed", + "blockNumber": "The block number where the calls were executed", + "returnData": "An array of Result structs" + } + } + }, + "title": "Multicall3", + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": { + "aggregate((address,bytes)[])": { + "notice": "Backwards-compatible call aggregation with Multicall" + }, + "aggregate3((address,bool,bytes)[])": { + "notice": "Aggregate calls, ensuring each returns success if required" + }, + "aggregate3Value((address,bool,uint256,bytes)[])": { + "notice": "Aggregate calls with a msg valueReverts if msg.value is less than the sum of the call values" + }, + "blockAndAggregate((address,bytes)[])": { + "notice": "Backwards-compatible with Multicall2Aggregate calls and allow failures using tryAggregate" + }, + "getBasefee()": { + "notice": "Gets the base fee of the given blockCan revert if the BASEFEE opcode is not implemented by the given chain" + }, + "getBlockHash(uint256)": { + "notice": "Returns the block hash for the given block number" + }, + "getBlockNumber()": { "notice": "Returns the block number" }, + "getChainId()": { "notice": "Returns the chain id" }, + "getCurrentBlockCoinbase()": { "notice": "Returns the block coinbase" }, + "getCurrentBlockDifficulty()": { + "notice": "Returns the block difficulty" + }, + "getCurrentBlockGasLimit()": { + "notice": "Returns the block gas limit" + }, + "getCurrentBlockTimestamp()": { + "notice": "Returns the block timestamp" + }, + "getEthBalance(address)": { + "notice": "Returns the (ETH) balance of a given address" + }, + "getLastBlockHash()": { + "notice": "Returns the block hash of the last block" + }, + "tryAggregate(bool,(address,bytes)[])": { + "notice": "Backwards-compatible with Multicall2Aggregate calls without requiring success" + }, + "tryBlockAndAggregate(bool,(address,bytes)[])": { + "notice": "Backwards-compatible with Multicall2Aggregate calls and allow failures using tryAggregate" + } + }, + "notice": "Aggregate results from multiple function calls", + "version": 1 + } + }, + "settings": { + "compilationTarget": { "Multicall3.sol": "Multicall3" }, + "evmVersion": "london", + "libraries": {}, + "metadata": { "bytecodeHash": "ipfs" }, + "optimizer": { "enabled": false, "runs": 200 }, + "remappings": [] + }, + "sources": { + "Multicall3.sol": { + "keccak256": "0x95dfd0a2dd6626c7119ff7c3f214d56b289145f81a0521cd93a6252a326966f6", + "license": "MIT", + "urls": [ + "bzz-raw://ead513cb13fe5373523cb3e1f3f1a1052791503269f4cc22e5e6177ed57d03ae", + "dweb:/ipfs/Qmd5sRoq3rxHpBkcHr4baM2zP6Sud6eAL7AFoBbizFnWji" + ] + } + }, + "version": 1 +}