alloy_contract/
multicall.rs1use super::SolCallBuilder;
7use alloy_network::{Network, TransactionBuilder};
8use alloy_primitives::{Address, Bytes, U256};
9use alloy_provider::{MulticallItem, Provider};
10use alloy_sol_types::SolCall;
11
12impl<P: Provider<N>, C: SolCall, N: Network> MulticallItem for SolCallBuilder<P, C, N> {
13 type Decoder = C;
14
15 fn value(&self) -> U256 {
16 self.request.value().unwrap_or_default()
17 }
18
19 fn target(&self) -> Address {
20 self.request.to().expect("`to` not set for `SolCallBuilder`")
21 }
22
23 fn input(&self) -> Bytes {
24 self.calldata().clone()
25 }
26}
27
28#[cfg(test)]
29mod tests {
30 use super::*;
31 use alloy_primitives::{address, b256, U256};
32 use alloy_provider::{CallItem, Failure, MulticallBuilder, Provider, ProviderBuilder};
33 use alloy_sol_types::sol;
34 use DummyThatFails::DummyThatFailsInstance;
35
36 sol! {
37 #[derive(Debug, PartialEq)]
38 #[sol(rpc)]
39 interface ERC20 {
40 function totalSupply() external view returns (uint256 totalSupply);
41 function balanceOf(address owner) external view returns (uint256 balance);
42 function transfer(address to, uint256 value) external returns (bool);
43 }
44 }
45
46 sol! {
47 #[sol(rpc, bytecode = "6080604052348015600e575f80fd5b5060a780601a5f395ff3fe6080604052348015600e575f80fd5b50600436106030575f3560e01c80630b93381b146034578063a9cc4718146036575b5f80fd5b005b603460405162461bcd60e51b815260040160689060208082526004908201526319985a5b60e21b604082015260600190565b60405180910390fdfea2646970667358221220c90ee107375422bb3516f4f13cdd754387c374edb5d9815fb6aa5ca111a77cb264736f6c63430008190033")]
49 #[derive(Debug)]
50 contract DummyThatFails {
51 function fail() external {
52 revert("fail");
53 }
54
55 function success() external {}
56 }
57 }
58
59 async fn deploy_dummy(
60 provider: impl alloy_provider::Provider,
61 ) -> DummyThatFailsInstance<impl alloy_provider::Provider> {
62 DummyThatFails::deploy(provider).await.unwrap()
63 }
64
65 const FORK_URL: &str = "https://reth-ethereum.ithaca.xyz/rpc";
66
67 #[tokio::test]
68 async fn test_single() {
69 let weth = address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2");
70 let provider = ProviderBuilder::new().connect_anvil_with_config(|a| a.fork(FORK_URL));
71
72 let erc20 = ERC20::new(weth, &provider);
73 let multicall = provider.multicall().add(erc20.totalSupply());
74
75 let (_total_supply,) = multicall.aggregate().await.unwrap();
76 }
77
78 #[tokio::test]
79 async fn test_aggregate() {
80 let weth = address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2");
81 let provider = ProviderBuilder::new().connect_anvil_with_config(|a| a.fork(FORK_URL));
82
83 let erc20 = ERC20::new(weth, &provider);
84
85 let multicall = provider
86 .multicall()
87 .add(erc20.totalSupply())
88 .add(erc20.balanceOf(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045")))
89 .add(erc20.totalSupply())
90 .add(erc20.balanceOf(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045")));
91
92 let (t1, b1, t2, b2) = multicall.aggregate().await.unwrap();
93
94 assert_eq!(t1, t2);
95 assert_eq!(b1, b2);
96 }
97
98 #[tokio::test]
99 async fn test_try_aggregate_pass() {
100 let weth = address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2");
101 let provider = ProviderBuilder::new().connect_anvil_with_config(|a| a.fork(FORK_URL));
102 let erc20 = ERC20::new(weth, &provider);
103
104 let multicall = provider
105 .multicall()
106 .add(erc20.totalSupply())
107 .add(erc20.balanceOf(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045")))
108 .add(erc20.totalSupply())
109 .add(erc20.balanceOf(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045")));
110
111 let (_t1, _b1, _t2, _b2) = multicall.try_aggregate(true).await.unwrap();
112 }
113
114 #[tokio::test]
115 async fn aggregate3() {
116 let weth = address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2");
117
118 let provider = ProviderBuilder::new()
119 .connect_anvil_with_wallet_and_config(|a| a.fork(FORK_URL))
120 .unwrap();
121
122 let dummy = deploy_dummy(provider.clone()).await;
123 let erc20 = ERC20::new(weth, &provider);
124 let multicall = provider
125 .multicall()
126 .add(erc20.totalSupply())
127 .add(erc20.balanceOf(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045")))
128 .add(dummy.fail()); let err = multicall.aggregate3().await.unwrap_err();
131
132 assert!(err.to_string().contains("Multicall3: call failed"), "{err}");
133
134 let failing_call = dummy.fail().into_call(true);
135 let multicall = provider
136 .multicall()
137 .add(erc20.totalSupply())
138 .add(erc20.balanceOf(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045")))
139 .add_call(failing_call);
140 let (t1, b1, failure) = multicall.aggregate3().await.unwrap();
141
142 assert!(t1.is_ok());
143 assert!(b1.is_ok());
144 let err = failure.unwrap_err();
145 assert!(matches!(err, Failure { idx: 2, return_data: _ }));
146 }
147
148 #[tokio::test]
149 async fn test_try_aggregate_fail() {
150 let weth = address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2");
151 let provider = ProviderBuilder::new()
152 .connect_anvil_with_wallet_and_config(|a| a.fork(FORK_URL))
153 .unwrap();
154
155 let dummy_addr = deploy_dummy(provider.clone()).await;
156 let erc20 = ERC20::new(weth, &provider);
157 let multicall = provider
158 .multicall()
159 .add(erc20.totalSupply())
160 .add(erc20.balanceOf(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045")))
161 .add(erc20.totalSupply())
162 .add(erc20.balanceOf(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045")))
163 .add(dummy_addr.fail());
164
165 let err = multicall.try_aggregate(true).await.unwrap_err();
166
167 assert!(err.to_string().contains("execution reverted: Multicall3: call failed"));
168
169 let (t1, b1, t2, b2, failure) = multicall.try_aggregate(false).await.unwrap();
170
171 assert!(t1.is_ok());
172 assert!(b1.is_ok());
173 assert!(t2.is_ok());
174 assert!(b2.is_ok());
175 let err = failure.unwrap_err();
176 assert!(matches!(err, Failure { idx: 4, return_data: _ }));
177 }
178
179 #[tokio::test]
180 async fn test_add_call_fallible() {
181 let provider = ProviderBuilder::new()
182 .connect_anvil_with_wallet_and_config(|a| a.fork(FORK_URL))
183 .unwrap();
184
185 let dummy_addr = deploy_dummy(provider.clone()).await;
186
187 let multicall = provider.multicall().add_call(dummy_addr.fail().into_call(true));
189 let (failure,) = multicall.aggregate3().await.unwrap();
190
191 assert!(matches!(failure.unwrap_err(), Failure { idx: 0, return_data: _ }));
192 }
193
194 #[tokio::test]
195 async fn test_util() {
196 let weth = address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2");
197 let provider = ProviderBuilder::new()
198 .connect_anvil_with_config(|a| a.fork(FORK_URL).fork_block_number(21787144));
199 let erc20 = ERC20::new(weth, &provider);
200 let multicall = provider
201 .multicall()
202 .add(erc20.totalSupply())
203 .add(erc20.balanceOf(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045")))
204 .add(erc20.totalSupply())
205 .add(erc20.balanceOf(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045")))
206 .get_block_hash(21787144);
207
208 let (t1, b1, t2, b2, block_hash) = multicall.aggregate().await.unwrap();
209
210 assert_eq!(t1, t2);
211 assert_eq!(b1, b2);
212 assert_eq!(
213 block_hash,
214 b256!("31be03d4fb9a280d1699f1004f340573cd6d717dae79095d382e876415cb26ba")
215 );
216 }
217
218 sol! {
219 #[sol(rpc, bytecode = "6080604052348015600e575f80fd5b5061012c8061001c5f395ff3fe6080604052600436106025575f3560e01c806361bc221a146029578063d09de08a14604d575b5f80fd5b3480156033575f80fd5b50603b5f5481565b60405190815260200160405180910390f35b60536055565b005b5f341160bc5760405162461bcd60e51b815260206004820152602c60248201527f50617961626c65436f756e7465723a2076616c7565206d75737420626520677260448201526b06561746572207468616e20360a41b606482015260840160405180910390fd5b60015f8082825460cb919060d2565b9091555050565b8082018082111560f057634e487b7160e01b5f52601160045260245ffd5b9291505056fea264697066735822122064d656316647d3dc48d7ef0466bd10bc87694802a673183058725926a5190a5564736f6c63430008190033")]
221 #[derive(Debug)]
222 contract PayableCounter {
223 uint256 public counter;
224
225 function increment() public payable {
226 require(msg.value > 0, "PayableCounter: value must be greater than 0");
227 counter += 1;
228 }
229 }
230 }
231
232 #[tokio::test]
233 async fn aggregate3_value() {
234 let provider = ProviderBuilder::new()
235 .connect_anvil_with_wallet_and_config(|a| a.fork(FORK_URL))
236 .unwrap();
237
238 let payable_counter = PayableCounter::deploy(provider.clone()).await.unwrap();
239
240 let increment_call = CallItem::<PayableCounter::incrementCall>::new(
241 payable_counter.increment().target(),
242 payable_counter.increment().input(),
243 )
244 .value(U256::from(100));
245
246 let multicall = provider
247 .multicall()
248 .add(payable_counter.counter())
249 .add_call(increment_call)
250 .add(payable_counter.counter());
251
252 let (c1, inc, c2) = multicall.aggregate3_value().await.unwrap();
253
254 assert_eq!(c1.unwrap(), U256::ZERO);
255 assert!(inc.is_ok());
256 assert_eq!(c2.unwrap(), U256::from(1));
257
258 let increment_call = CallItem::<PayableCounter::incrementCall>::new(
260 payable_counter.increment().target(),
261 payable_counter.increment().input(),
262 )
263 .allow_failure(true);
264
265 let multicall = provider
266 .multicall()
267 .add(payable_counter.counter())
268 .add_call(increment_call)
269 .add(payable_counter.counter());
270
271 let (c1, inc, c2) = multicall.aggregate3_value().await.unwrap();
272
273 assert_eq!(c1.unwrap(), U256::ZERO);
274 assert!(inc.is_err_and(|failure| matches!(failure, Failure { idx: 1, return_data: _ })));
275 assert_eq!(c2.unwrap(), U256::ZERO);
276 }
277
278 #[tokio::test]
279 async fn test_clear() {
280 let weth = address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2");
281 let provider = ProviderBuilder::new().connect_anvil();
282
283 let erc20 = ERC20::new(weth, &provider);
284 let multicall = provider
285 .multicall()
286 .add(erc20.totalSupply())
287 .add(erc20.balanceOf(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045")));
288 assert_eq!(multicall.len(), 2);
289 let multicall = multicall.clear();
290 assert_eq!(multicall.len(), 0);
291 }
292
293 #[tokio::test]
294 async fn add_dynamic() {
295 let weth = address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2");
296 let provider = ProviderBuilder::new().connect_anvil_with_config(|a| a.fork(FORK_URL));
297
298 let erc20 = ERC20::new(weth, &provider);
299
300 let multicall = MulticallBuilder::new_dynamic(provider.clone())
301 .add_dynamic(erc20.totalSupply())
302 .add_dynamic(erc20.totalSupply())
307 .extend(vec![erc20.totalSupply(), erc20.totalSupply()]);
308
309 let res = multicall.aggregate().await.unwrap();
310
311 assert_eq!(res.len(), 4);
312 assert_eq!(res[0], res[1]);
313 }
314
315 #[tokio::test]
316 async fn test_add_call_dynamic_fallible() {
317 let provider = ProviderBuilder::new()
318 .connect_anvil_with_wallet_and_config(|a| a.fork(FORK_URL))
319 .unwrap();
320
321 let dummy = deploy_dummy(provider.clone()).await;
322
323 let multicall = MulticallBuilder::new_dynamic(provider.clone())
325 .add_call_dynamic(dummy.fail().into_call(true));
326 let res = multicall.aggregate3().await.unwrap();
327
328 assert_eq!(res.len(), 1);
329 assert!(matches!(res[0].clone().unwrap_err(), Failure { idx: 0, return_data: _ }));
330 }
331
332 #[tokio::test]
333 async fn test_extend_dynamic() {
334 let weth = address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2");
335 let provider = ProviderBuilder::new().connect_anvil_with_config(|a| a.fork(FORK_URL));
336 let erc20 = ERC20::new(weth, &provider);
337 let ts_calls = vec![erc20.totalSupply(); 18];
338 let multicall = MulticallBuilder::new_dynamic(provider.clone()).extend(ts_calls);
339
340 assert_eq!(multicall.len(), 18);
341 let res = multicall.aggregate().await.unwrap();
342 assert_eq!(res.len(), 18);
343 }
344}