Skip to content

Commit bce4b2d

Browse files
authored
feat: multicall3 on L2 (#805)
1 parent 9ebf9be commit bce4b2d

File tree

2 files changed

+261
-0
lines changed

2 files changed

+261
-0
lines changed

l1-contracts/deploy-scripts/DeployL2Contracts.sol

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ contract DeployL2Script is Script {
2828
address l2SharedBridgeProxy;
2929
address consensusRegistryImplementation;
3030
address consensusRegistryProxy;
31+
address multicall3;
3132
address forceDeployUpgraderAddress;
3233
}
3334

@@ -39,6 +40,7 @@ contract DeployL2Script is Script {
3940
bytes l2SharedBridgeProxyBytecode;
4041
bytes consensusRegistryBytecode;
4142
bytes consensusRegistryProxyBytecode;
43+
bytes multicall3Bytecode;
4244
bytes forceDeployUpgrader;
4345
}
4446

@@ -61,6 +63,7 @@ contract DeployL2Script is Script {
6163
deployForceDeployer();
6264
deployConsensusRegistry();
6365
deployConsensusRegistryProxy();
66+
deployMulticall3();
6467

6568
saveOutput();
6669
}
@@ -137,6 +140,10 @@ contract DeployL2Script is Script {
137140
"/../l2-contracts/artifacts-zk/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json"
138141
);
139142

143+
contracts.multicall3Bytecode = Utils.readHardhatBytecode(
144+
"/../l2-contracts/artifacts-zk/contracts/dev-contracts/Multicall3.sol/Multicall3.json"
145+
);
146+
140147
contracts.forceDeployUpgrader = Utils.readHardhatBytecode(
141148
"/../l2-contracts/artifacts-zk/contracts/ForceDeployUpgrader.sol/ForceDeployUpgrader.json"
142149
);
@@ -160,6 +167,7 @@ contract DeployL2Script is Script {
160167
vm.serializeAddress("root", "l2_shared_bridge_proxy", config.l2SharedBridgeProxy);
161168
vm.serializeAddress("root", "consensus_registry_implementation", config.consensusRegistryImplementation);
162169
vm.serializeAddress("root", "consensus_registry_proxy", config.consensusRegistryProxy);
170+
vm.serializeAddress("root", "multicall3", config.multicall3);
163171
string memory toml = vm.serializeAddress("root", "l2_default_upgrader", config.forceDeployUpgraderAddress);
164172
string memory root = vm.projectRoot();
165173
string memory path = string.concat(root, "/script-out/output-deploy-l2-contracts.toml");
@@ -261,6 +269,22 @@ contract DeployL2Script is Script {
261269
});
262270
}
263271

272+
function deployMulticall3() internal {
273+
// Multicall3 doesn't have a constructor.
274+
bytes memory constructorData = "";
275+
276+
config.multicall3 = Utils.deployThroughL1({
277+
bytecode: contracts.multicall3Bytecode,
278+
constructorargs: constructorData,
279+
create2salt: "",
280+
l2GasLimit: Utils.MAX_PRIORITY_TX_GAS,
281+
factoryDeps: new bytes[](0),
282+
chainId: config.chainId,
283+
bridgehubAddress: config.bridgehubAddress,
284+
l1SharedBridgeProxy: config.l1SharedBridgeProxy
285+
});
286+
}
287+
264288
// Deploy a transparent upgradable proxy for the already deployed consensus registry
265289
// implementation and save its address into the config.
266290
function deployConsensusRegistryProxy() internal {
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.20;
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 <[email protected]>
9+
/// @author Joshua Levine <[email protected]>
10+
/// @author Nick Johnson <[email protected]>
11+
/// @author Andreas Bigger <[email protected]>
12+
/// @author Matt Solomon <[email protected]>
13+
contract Multicall3 {
14+
// add this to be excluded from coverage report
15+
function test() internal virtual {}
16+
17+
struct Call {
18+
address target;
19+
bytes callData;
20+
}
21+
22+
struct Call3 {
23+
address target;
24+
bool allowFailure;
25+
bytes callData;
26+
}
27+
28+
struct Call3Value {
29+
address target;
30+
bool allowFailure;
31+
uint256 value;
32+
bytes callData;
33+
}
34+
35+
struct Result {
36+
bool success;
37+
bytes returnData;
38+
}
39+
40+
/// @notice Backwards-compatible call aggregation with Multicall
41+
/// @param calls An array of Call structs
42+
/// @return blockNumber The block number where the calls were executed
43+
/// @return returnData An array of bytes containing the responses
44+
function aggregate(Call[] calldata calls) public payable returns (uint256 blockNumber, bytes[] memory returnData) {
45+
blockNumber = block.number;
46+
uint256 length = calls.length;
47+
returnData = new bytes[](length);
48+
Call calldata call;
49+
for (uint256 i = 0; i < length; ) {
50+
bool success;
51+
call = calls[i];
52+
(success, returnData[i]) = call.target.call(call.callData);
53+
require(success, "Multicall3: call failed");
54+
unchecked {
55+
++i;
56+
}
57+
}
58+
}
59+
60+
/// @notice Backwards-compatible with Multicall2
61+
/// @notice Aggregate calls without requiring success
62+
/// @param requireSuccess If true, require all calls to succeed
63+
/// @param calls An array of Call structs
64+
/// @return returnData An array of Result structs
65+
function tryAggregate(
66+
bool requireSuccess,
67+
Call[] calldata calls
68+
) public payable returns (Result[] memory returnData) {
69+
uint256 length = calls.length;
70+
returnData = new Result[](length);
71+
Call calldata call;
72+
for (uint256 i = 0; i < length; ) {
73+
Result memory result = returnData[i];
74+
call = calls[i];
75+
(result.success, result.returnData) = call.target.call(call.callData);
76+
if (requireSuccess) require(result.success, "Multicall3: call failed");
77+
unchecked {
78+
++i;
79+
}
80+
}
81+
}
82+
83+
/// @notice Backwards-compatible with Multicall2
84+
/// @notice Aggregate calls and allow failures using tryAggregate
85+
/// @param calls An array of Call structs
86+
/// @return blockNumber The block number where the calls were executed
87+
/// @return blockHash The hash of the block where the calls were executed
88+
/// @return returnData An array of Result structs
89+
function tryBlockAndAggregate(
90+
bool requireSuccess,
91+
Call[] calldata calls
92+
) public payable returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) {
93+
blockNumber = block.number;
94+
blockHash = blockhash(block.number);
95+
returnData = tryAggregate(requireSuccess, calls);
96+
}
97+
98+
/// @notice Backwards-compatible with Multicall2
99+
/// @notice Aggregate calls and allow failures using tryAggregate
100+
/// @param calls An array of Call structs
101+
/// @return blockNumber The block number where the calls were executed
102+
/// @return blockHash The hash of the block where the calls were executed
103+
/// @return returnData An array of Result structs
104+
function blockAndAggregate(
105+
Call[] calldata calls
106+
) public payable returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) {
107+
(blockNumber, blockHash, returnData) = tryBlockAndAggregate(true, calls);
108+
}
109+
110+
/// @notice Aggregate calls, ensuring each returns success if required
111+
/// @param calls An array of Call3 structs
112+
/// @return returnData An array of Result structs
113+
function aggregate3(Call3[] calldata calls) public payable returns (Result[] memory returnData) {
114+
uint256 length = calls.length;
115+
returnData = new Result[](length);
116+
Call3 calldata calli;
117+
for (uint256 i = 0; i < length; ) {
118+
Result memory result = returnData[i];
119+
calli = calls[i];
120+
(result.success, result.returnData) = calli.target.call(calli.callData);
121+
assembly {
122+
// Revert if the call fails and failure is not allowed
123+
// `allowFailure := calldataload(add(calli, 0x20))` and `success := mload(result)`
124+
if iszero(or(calldataload(add(calli, 0x20)), mload(result))) {
125+
// set "Error(string)" signature: bytes32(bytes4(keccak256("Error(string)")))
126+
mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000)
127+
// set data offset
128+
mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020)
129+
// set length of revert string
130+
mstore(0x24, 0x0000000000000000000000000000000000000000000000000000000000000017)
131+
// set revert string: bytes32(abi.encodePacked("Multicall3: call failed"))
132+
mstore(0x44, 0x4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000)
133+
revert(0x00, 0x64)
134+
}
135+
}
136+
unchecked {
137+
++i;
138+
}
139+
}
140+
}
141+
142+
/// @notice Aggregate calls with a msg value
143+
/// @notice Reverts if msg.value is less than the sum of the call values
144+
/// @param calls An array of Call3Value structs
145+
/// @return returnData An array of Result structs
146+
function aggregate3Value(Call3Value[] calldata calls) public payable returns (Result[] memory returnData) {
147+
uint256 valAccumulator;
148+
uint256 length = calls.length;
149+
returnData = new Result[](length);
150+
Call3Value calldata calli;
151+
for (uint256 i = 0; i < length; ) {
152+
Result memory result = returnData[i];
153+
calli = calls[i];
154+
uint256 val = calli.value;
155+
// Humanity will be a Type V Kardashev Civilization before this overflows - andreas
156+
// ~ 10^25 Wei in existence << ~ 10^76 size uint fits in a uint256
157+
unchecked {
158+
valAccumulator += val;
159+
}
160+
(result.success, result.returnData) = calli.target.call{value: val}(calli.callData);
161+
assembly {
162+
// Revert if the call fails and failure is not allowed
163+
// `allowFailure := calldataload(add(calli, 0x20))` and `success := mload(result)`
164+
if iszero(or(calldataload(add(calli, 0x20)), mload(result))) {
165+
// set "Error(string)" signature: bytes32(bytes4(keccak256("Error(string)")))
166+
mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000)
167+
// set data offset
168+
mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020)
169+
// set length of revert string
170+
mstore(0x24, 0x0000000000000000000000000000000000000000000000000000000000000017)
171+
// set revert string: bytes32(abi.encodePacked("Multicall3: call failed"))
172+
mstore(0x44, 0x4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000)
173+
revert(0x00, 0x84)
174+
}
175+
}
176+
unchecked {
177+
++i;
178+
}
179+
}
180+
// Finally, make sure the msg.value = SUM(call[0...i].value)
181+
require(msg.value == valAccumulator, "Multicall3: value mismatch");
182+
}
183+
184+
/// @notice Returns the block hash for the given block number
185+
/// @param blockNumber The block number
186+
function getBlockHash(uint256 blockNumber) public view returns (bytes32 blockHash) {
187+
blockHash = blockhash(blockNumber);
188+
}
189+
190+
/// @notice Returns the block number
191+
function getBlockNumber() public view returns (uint256 blockNumber) {
192+
blockNumber = block.number;
193+
}
194+
195+
/// @notice Returns the block coinbase
196+
function getCurrentBlockCoinbase() public view returns (address coinbase) {
197+
coinbase = block.coinbase;
198+
}
199+
200+
/// @notice Returns the block difficulty
201+
function getCurrentBlockDifficulty() public view returns (uint256 difficulty) {
202+
difficulty = block.prevrandao;
203+
}
204+
205+
/// @notice Returns the block gas limit
206+
function getCurrentBlockGasLimit() public view returns (uint256 gaslimit) {
207+
gaslimit = block.gaslimit;
208+
}
209+
210+
/// @notice Returns the block timestamp
211+
function getCurrentBlockTimestamp() public view returns (uint256 timestamp) {
212+
timestamp = block.timestamp;
213+
}
214+
215+
/// @notice Returns the (ETH) balance of a given address
216+
function getEthBalance(address addr) public view returns (uint256 balance) {
217+
balance = addr.balance;
218+
}
219+
220+
/// @notice Returns the block hash of the last block
221+
function getLastBlockHash() public view returns (bytes32 blockHash) {
222+
unchecked {
223+
blockHash = blockhash(block.number - 1);
224+
}
225+
}
226+
227+
/// @notice Gets the base fee of the given block
228+
/// @notice Can revert if the BASEFEE opcode is not implemented by the given chain
229+
function getBasefee() public view returns (uint256 basefee) {
230+
basefee = block.basefee;
231+
}
232+
233+
/// @notice Returns the chain id
234+
function getChainId() public view returns (uint256 chainid) {
235+
chainid = block.chainid;
236+
}
237+
}

0 commit comments

Comments
 (0)