260 lines
9.8 KiB
Solidity
260 lines
9.8 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity >=0.6.0;
|
|
pragma experimental ABIEncoderV2;
|
|
|
|
/// @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 <mike@makerdao.com>
|
|
/// @author Joshua Levine <joshua@makerdao.com>
|
|
/// @author Nick Johnson <arachnid@notdot.net>
|
|
/// @author Andreas Bigger <andreas@nascent.xyz>
|
|
/// @author Matt Solomon <matt@mattsolomon.dev>
|
|
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;
|
|
}
|
|
}
|