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