// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; /// @title Multicall3 /// @notice Aggregate results from multiple function calls. /// @dev Multicall and Multicall2 backwards-compatible. /// @dev Aggregate methods are marked `payable` to save gas per call. contract Multicall3 is Initializable, OwnableUpgradeable, UUPSUpgradeable { address public constant CREATE2_FACTORY_DEV = 0x2370B48f20FFd75A46B14A9888B3E79677eFC28c; address public constant CREATE2_FACTORY_PROD = 0x6497eC1A66A2dF192179A760e8D35f8d30ddaA06; mapping(address => bool) public callers; mapping(address => mapping(address => bool)) public factoryCallers; error OnlyCaller(); error InvalidFactory(address factory); error FactoryCallerNotAllowed(address factory, address caller); event CallerUpdated(address indexed caller, bool allowed); event FactoryCallerUpdated( address indexed factory, address indexed caller, bool allowed ); modifier onlyCaller() { if (!callers[_msgSender()]) revert OnlyCaller(); _; } /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } function initialize( address initialOwner, address[] memory caller ) external initializer { __Ownable_init(initialOwner); __UUPSUpgradeable_init(); callers[initialOwner] = true; factoryCallers[CREATE2_FACTORY_DEV][initialOwner] = true; factoryCallers[CREATE2_FACTORY_PROD][initialOwner] = true; for (uint256 i; i < caller.length; i++) { callers[caller[i]] = true; factoryCallers[CREATE2_FACTORY_DEV][caller[i]] = true; emit CallerUpdated(caller[i], true); } emit CallerUpdated(initialOwner, true); } function setCaller(address caller, bool allowed) external onlyOwner { callers[caller] = allowed; emit CallerUpdated(caller, allowed); } function setFactoryCaller( address factory, address caller, bool allowed ) external onlyOwner { if (!_isCreate2Factory(factory)) revert InvalidFactory(factory); factoryCallers[factory][caller] = allowed; emit FactoryCallerUpdated(factory, caller, allowed); } function _authorizeUpgrade( address newImplementation ) internal view override onlyOwner { newImplementation; } function _isCreate2Factory(address target) internal pure returns (bool) { return target == CREATE2_FACTORY_DEV || target == CREATE2_FACTORY_PROD; } function _guardCall(address target) internal view { if (!_isCreate2Factory(target)) return; if (!factoryCallers[target][_msgSender()]) { revert FactoryCallerNotAllowed(target, _msgSender()); } } 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. function aggregate( Call[] calldata calls ) public payable onlyCaller 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]; _guardCall(call.target); (success, returnData[i]) = call.target.call(call.callData); require(success, "Multicall3: call failed"); unchecked { ++i; } } } /// @notice Backwards-compatible with Multicall2. function tryAggregate( bool requireSuccess, Call[] calldata calls ) public payable onlyCaller 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]; _guardCall(call.target); (result.success, result.returnData) = call.target.call( call.callData ); if (requireSuccess) { require(result.success, "Multicall3: call failed"); } unchecked { ++i; } } } /// @notice Aggregate calls and include block metadata. function tryBlockAndAggregate( bool requireSuccess, Call[] calldata calls ) public payable onlyCaller returns ( uint256 blockNumber, bytes32 blockHash, Result[] memory returnData ) { blockNumber = block.number; blockHash = blockhash(block.number); returnData = tryAggregate(requireSuccess, calls); } /// @notice Aggregate calls and require all calls to succeed. function blockAndAggregate( Call[] calldata calls ) public payable onlyCaller returns ( uint256 blockNumber, bytes32 blockHash, Result[] memory returnData ) { (blockNumber, blockHash, returnData) = tryBlockAndAggregate( true, calls ); } /// @notice Aggregate calls, allowing each call to choose failure behavior. function aggregate3( Call3[] calldata calls ) public payable onlyCaller 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]; _guardCall(calli.target); (result.success, result.returnData) = calli.target.call( calli.callData ); assembly { // Revert if the call fails and failure is not allowed. if iszero(or(calldataload(add(calli, 0x20)), mload(result))) { mstore( 0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000 ) mstore( 0x04, 0x0000000000000000000000000000000000000000000000000000000000000020 ) mstore( 0x24, 0x0000000000000000000000000000000000000000000000000000000000000017 ) mstore( 0x44, 0x4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000 ) revert(0x00, 0x64) } } unchecked { ++i; } } } /// @notice Aggregate calls with ETH value. function aggregate3Value( Call3Value[] calldata calls ) public payable onlyCaller 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; unchecked { valAccumulator += val; } _guardCall(calli.target); (result.success, result.returnData) = calli.target.call{ value: val }(calli.callData); assembly { // Revert if the call fails and failure is not allowed. if iszero(or(calldataload(add(calli, 0x20)), mload(result))) { mstore( 0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000 ) mstore( 0x04, 0x0000000000000000000000000000000000000000000000000000000000000020 ) mstore( 0x24, 0x0000000000000000000000000000000000000000000000000000000000000017 ) mstore( 0x44, 0x4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000 ) revert(0x00, 0x84) } } unchecked { ++i; } } require(msg.value == valAccumulator, "Multicall3: value mismatch"); } /// @notice Returns the block hash for the given 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. 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; } }