pallet_evm_precompile_batch/
lib.rs1#![cfg_attr(not(feature = "std"), no_std)]
20
21use evm::{ExitError, ExitReason};
22use fp_evm::{Context, Log, PrecompileFailure, PrecompileHandle, Transfer};
23use frame_support::traits::ConstU32;
24use precompile_utils::{evm::costs::call_cost, prelude::*};
25use sp_core::{H160, U256};
26use sp_std::{iter::repeat, marker::PhantomData, vec, vec::Vec};
27
28#[cfg(test)]
29mod mock;
30#[cfg(test)]
31mod tests;
32
33#[derive(Copy, Clone, Debug, PartialEq)]
34pub enum Mode {
35 BatchSome, BatchSomeUntilFailure, BatchAll, }
39
40pub const LOG_SUBCALL_SUCCEEDED: [u8; 32] = keccak256!("SubcallSucceeded(uint256)");
41pub const LOG_SUBCALL_FAILED: [u8; 32] = keccak256!("SubcallFailed(uint256)");
42pub const CALL_DATA_LIMIT: u32 = 2u32.pow(16);
43pub const ARRAY_LIMIT: u32 = 2u32.pow(9);
44
45type GetCallDataLimit = ConstU32<CALL_DATA_LIMIT>;
46type GetArrayLimit = ConstU32<ARRAY_LIMIT>;
47
48pub fn log_subcall_succeeded(address: impl Into<H160>, index: usize) -> Log {
49 log1(
50 address,
51 LOG_SUBCALL_SUCCEEDED,
52 solidity::encode_event_data(U256::from(index)),
53 )
54}
55
56pub fn log_subcall_failed(address: impl Into<H160>, index: usize) -> Log {
57 log1(
58 address,
59 LOG_SUBCALL_FAILED,
60 solidity::encode_event_data(U256::from(index)),
61 )
62}
63
64#[derive(Debug, Clone)]
66pub struct BatchPrecompile<Runtime>(PhantomData<Runtime>);
67
68#[precompile_utils::precompile]
71impl<Runtime> BatchPrecompile<Runtime>
72where
73 Runtime: pallet_evm::Config,
74{
75 #[precompile::public("batchSome(address[],uint256[],bytes[],uint64[])")]
76 fn batch_some(
77 handle: &mut impl PrecompileHandle,
78 to: BoundedVec<Address, GetArrayLimit>,
79 value: BoundedVec<U256, GetArrayLimit>,
80 call_data: BoundedVec<BoundedBytes<GetCallDataLimit>, GetArrayLimit>,
81 gas_limit: BoundedVec<u64, GetArrayLimit>,
82 ) -> EvmResult {
83 Self::inner_batch(Mode::BatchSome, handle, to, value, call_data, gas_limit)
84 }
85
86 #[precompile::public("batchSomeUntilFailure(address[],uint256[],bytes[],uint64[])")]
87 fn batch_some_until_failure(
88 handle: &mut impl PrecompileHandle,
89 to: BoundedVec<Address, GetArrayLimit>,
90 value: BoundedVec<U256, GetArrayLimit>,
91 call_data: BoundedVec<BoundedBytes<GetCallDataLimit>, GetArrayLimit>,
92 gas_limit: BoundedVec<u64, GetArrayLimit>,
93 ) -> EvmResult {
94 Self::inner_batch(
95 Mode::BatchSomeUntilFailure,
96 handle,
97 to,
98 value,
99 call_data,
100 gas_limit,
101 )
102 }
103
104 #[precompile::public("batchAll(address[],uint256[],bytes[],uint64[])")]
105 fn batch_all(
106 handle: &mut impl PrecompileHandle,
107 to: BoundedVec<Address, GetArrayLimit>,
108 value: BoundedVec<U256, GetArrayLimit>,
109 call_data: BoundedVec<BoundedBytes<GetCallDataLimit>, GetArrayLimit>,
110 gas_limit: BoundedVec<u64, GetArrayLimit>,
111 ) -> EvmResult {
112 Self::inner_batch(Mode::BatchAll, handle, to, value, call_data, gas_limit)
113 }
114
115 fn inner_batch(
116 mode: Mode,
117 handle: &mut impl PrecompileHandle,
118 to: BoundedVec<Address, GetArrayLimit>,
119 value: BoundedVec<U256, GetArrayLimit>,
120 call_data: BoundedVec<BoundedBytes<GetCallDataLimit>, GetArrayLimit>,
121 gas_limit: BoundedVec<u64, GetArrayLimit>,
122 ) -> EvmResult {
123 let addresses = Vec::from(to).into_iter().enumerate();
124 let values = Vec::from(value)
125 .into_iter()
126 .map(|x| Some(x))
127 .chain(repeat(None));
128 let calls_data = Vec::from(call_data)
129 .into_iter()
130 .map(|x| Some(x.into()))
131 .chain(repeat(None));
132 let gas_limits = Vec::from(gas_limit).into_iter().map(|x|
133 if x == 0 {
135 None
136 } else {
137 Some(x)
138 }
139 ).chain(repeat(None));
140
141 let log_cost = log_subcall_failed(handle.code_address(), 0)
143 .compute_cost()
144 .map_err(|_| revert("Failed to compute log cost"))?;
145
146 for ((i, address), (value, (call_data, gas_limit))) in
147 addresses.zip(values.zip(calls_data.zip(gas_limits)))
148 {
149 let address = address.0;
150 let value = value.unwrap_or(U256::zero());
151 let call_data = call_data.unwrap_or(vec![]);
152
153 let sub_context = Context {
154 caller: handle.context().caller,
155 address: address.clone(),
156 apparent_value: value,
157 };
158
159 let transfer = if value.is_zero() {
160 None
161 } else {
162 Some(Transfer {
163 source: handle.context().caller,
164 target: address.clone(),
165 value,
166 })
167 };
168
169 let remaining_gas = handle.remaining_gas();
172
173 let forwarded_gas = match (remaining_gas.checked_sub(log_cost), mode) {
174 (Some(remaining), _) => remaining,
175 (None, Mode::BatchAll) => {
176 return Err(PrecompileFailure::Error {
177 exit_status: ExitError::OutOfGas,
178 })
179 }
180 (None, _) => {
181 return Ok(());
182 }
183 };
184
185 let call_cost = call_cost(value, <Runtime as pallet_evm::Config>::config());
187
188 let forwarded_gas = match forwarded_gas.checked_sub(call_cost) {
189 Some(remaining) => remaining,
190 None => {
191 let log = log_subcall_failed(handle.code_address(), i);
192 handle.record_log_costs(&[&log])?;
193 log.record(handle)?;
194
195 match mode {
196 Mode::BatchAll => {
197 return Err(PrecompileFailure::Error {
198 exit_status: ExitError::OutOfGas,
199 })
200 }
201 Mode::BatchSomeUntilFailure => return Ok(()),
202 Mode::BatchSome => continue,
203 }
204 }
205 };
206
207 let forwarded_gas = match gas_limit {
209 None => forwarded_gas, Some(limit) => {
211 if limit > forwarded_gas {
212 let log = log_subcall_failed(handle.code_address(), i);
213 handle.record_log_costs(&[&log])?;
214 log.record(handle)?;
215
216 match mode {
217 Mode::BatchAll => {
218 return Err(PrecompileFailure::Error {
219 exit_status: ExitError::OutOfGas,
220 })
221 }
222 Mode::BatchSomeUntilFailure => return Ok(()),
223 Mode::BatchSome => continue,
224 }
225 }
226 limit
227 }
228 };
229
230 let (reason, output) = handle.call(
231 address,
232 transfer,
233 call_data,
234 Some(forwarded_gas),
235 false,
236 &sub_context,
237 );
238
239 match reason {
242 ExitReason::Revert(_) | ExitReason::Error(_) => {
243 let log = log_subcall_failed(handle.code_address(), i);
244 handle.record_log_costs(&[&log])?;
245 log.record(handle)?
246 }
247 ExitReason::Succeed(_) => {
248 let log = log_subcall_succeeded(handle.code_address(), i);
249 handle.record_log_costs(&[&log])?;
250 log.record(handle)?
251 }
252 _ => (),
253 }
254
255 match (mode, reason) {
257 (_, ExitReason::Fatal(exit_status)) => {
259 return Err(PrecompileFailure::Fatal { exit_status })
260 }
261
262 (Mode::BatchAll, ExitReason::Revert(exit_status)) => {
264 return Err(PrecompileFailure::Revert {
265 exit_status,
266 output,
267 })
268 }
269 (Mode::BatchAll, ExitReason::Error(exit_status)) => {
270 return Err(PrecompileFailure::Error { exit_status })
271 }
272
273 (Mode::BatchSomeUntilFailure, ExitReason::Revert(_) | ExitReason::Error(_)) => {
276 return Ok(())
277 }
278
279 (_, _) => (),
281 }
282 }
283
284 Ok(())
285 }
286}
287
288impl<Runtime> BatchPrecompileCall<Runtime>
291where
292 Runtime: pallet_evm::Config,
293{
294 pub fn batch_from_mode(
295 mode: Mode,
296 to: Vec<Address>,
297 value: Vec<U256>,
298 call_data: Vec<Vec<u8>>,
299 gas_limit: Vec<u64>,
300 ) -> Self {
301 let to = to.into();
305 let value = value.into();
306 let call_data: Vec<_> = call_data.into_iter().map(|inner| inner.into()).collect();
307 let call_data = call_data.into();
308 let gas_limit = gas_limit.into();
309
310 match mode {
311 Mode::BatchSome => Self::batch_some {
312 to,
313 value,
314 call_data,
315 gas_limit,
316 },
317 Mode::BatchSomeUntilFailure => Self::batch_some_until_failure {
318 to,
319 value,
320 call_data,
321 gas_limit,
322 },
323 Mode::BatchAll => Self::batch_all {
324 to,
325 value,
326 call_data,
327 gas_limit,
328 },
329 }
330 }
331}