#![cfg_attr(not(feature = "std"), no_std)]
use evm::{ExitError, ExitReason};
use fp_evm::{Context, Log, PrecompileFailure, PrecompileHandle, Transfer};
use frame_support::traits::ConstU32;
use precompile_utils::{evm::costs::call_cost, prelude::*};
use sp_core::{H160, U256};
use sp_std::{iter::repeat, marker::PhantomData, vec, vec::Vec};
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Mode {
BatchSome, BatchSomeUntilFailure, BatchAll, }
pub const LOG_SUBCALL_SUCCEEDED: [u8; 32] = keccak256!("SubcallSucceeded(uint256)");
pub const LOG_SUBCALL_FAILED: [u8; 32] = keccak256!("SubcallFailed(uint256)");
pub const CALL_DATA_LIMIT: u32 = 2u32.pow(16);
pub const ARRAY_LIMIT: u32 = 2u32.pow(9);
type GetCallDataLimit = ConstU32<CALL_DATA_LIMIT>;
type GetArrayLimit = ConstU32<ARRAY_LIMIT>;
pub fn log_subcall_succeeded(address: impl Into<H160>, index: usize) -> Log {
log1(
address,
LOG_SUBCALL_SUCCEEDED,
solidity::encode_event_data(U256::from(index)),
)
}
pub fn log_subcall_failed(address: impl Into<H160>, index: usize) -> Log {
log1(
address,
LOG_SUBCALL_FAILED,
solidity::encode_event_data(U256::from(index)),
)
}
#[derive(Debug, Clone)]
pub struct BatchPrecompile<Runtime>(PhantomData<Runtime>);
#[precompile_utils::precompile]
impl<Runtime> BatchPrecompile<Runtime>
where
Runtime: pallet_evm::Config,
{
#[precompile::public("batchSome(address[],uint256[],bytes[],uint64[])")]
fn batch_some(
handle: &mut impl PrecompileHandle,
to: BoundedVec<Address, GetArrayLimit>,
value: BoundedVec<U256, GetArrayLimit>,
call_data: BoundedVec<BoundedBytes<GetCallDataLimit>, GetArrayLimit>,
gas_limit: BoundedVec<u64, GetArrayLimit>,
) -> EvmResult {
Self::inner_batch(Mode::BatchSome, handle, to, value, call_data, gas_limit)
}
#[precompile::public("batchSomeUntilFailure(address[],uint256[],bytes[],uint64[])")]
fn batch_some_until_failure(
handle: &mut impl PrecompileHandle,
to: BoundedVec<Address, GetArrayLimit>,
value: BoundedVec<U256, GetArrayLimit>,
call_data: BoundedVec<BoundedBytes<GetCallDataLimit>, GetArrayLimit>,
gas_limit: BoundedVec<u64, GetArrayLimit>,
) -> EvmResult {
Self::inner_batch(
Mode::BatchSomeUntilFailure,
handle,
to,
value,
call_data,
gas_limit,
)
}
#[precompile::public("batchAll(address[],uint256[],bytes[],uint64[])")]
fn batch_all(
handle: &mut impl PrecompileHandle,
to: BoundedVec<Address, GetArrayLimit>,
value: BoundedVec<U256, GetArrayLimit>,
call_data: BoundedVec<BoundedBytes<GetCallDataLimit>, GetArrayLimit>,
gas_limit: BoundedVec<u64, GetArrayLimit>,
) -> EvmResult {
Self::inner_batch(Mode::BatchAll, handle, to, value, call_data, gas_limit)
}
fn inner_batch(
mode: Mode,
handle: &mut impl PrecompileHandle,
to: BoundedVec<Address, GetArrayLimit>,
value: BoundedVec<U256, GetArrayLimit>,
call_data: BoundedVec<BoundedBytes<GetCallDataLimit>, GetArrayLimit>,
gas_limit: BoundedVec<u64, GetArrayLimit>,
) -> EvmResult {
let addresses = Vec::from(to).into_iter().enumerate();
let values = Vec::from(value)
.into_iter()
.map(|x| Some(x))
.chain(repeat(None));
let calls_data = Vec::from(call_data)
.into_iter()
.map(|x| Some(x.into()))
.chain(repeat(None));
let gas_limits = Vec::from(gas_limit).into_iter().map(|x|
if x == 0 {
None
} else {
Some(x)
}
).chain(repeat(None));
let log_cost = log_subcall_failed(handle.code_address(), 0)
.compute_cost()
.map_err(|_| revert("Failed to compute log cost"))?;
for ((i, address), (value, (call_data, gas_limit))) in
addresses.zip(values.zip(calls_data.zip(gas_limits)))
{
let address = address.0;
let value = value.unwrap_or(U256::zero());
let call_data = call_data.unwrap_or(vec![]);
let sub_context = Context {
caller: handle.context().caller,
address: address.clone(),
apparent_value: value,
};
let transfer = if value.is_zero() {
None
} else {
Some(Transfer {
source: handle.context().caller,
target: address.clone(),
value,
})
};
let remaining_gas = handle.remaining_gas();
let forwarded_gas = match (remaining_gas.checked_sub(log_cost), mode) {
(Some(remaining), _) => remaining,
(None, Mode::BatchAll) => {
return Err(PrecompileFailure::Error {
exit_status: ExitError::OutOfGas,
})
}
(None, _) => {
return Ok(());
}
};
let call_cost = call_cost(value, <Runtime as pallet_evm::Config>::config());
let forwarded_gas = match forwarded_gas.checked_sub(call_cost) {
Some(remaining) => remaining,
None => {
let log = log_subcall_failed(handle.code_address(), i);
handle.record_log_costs(&[&log])?;
log.record(handle)?;
match mode {
Mode::BatchAll => {
return Err(PrecompileFailure::Error {
exit_status: ExitError::OutOfGas,
})
}
Mode::BatchSomeUntilFailure => return Ok(()),
Mode::BatchSome => continue,
}
}
};
let forwarded_gas = match gas_limit {
None => forwarded_gas, Some(limit) => {
if limit > forwarded_gas {
let log = log_subcall_failed(handle.code_address(), i);
handle.record_log_costs(&[&log])?;
log.record(handle)?;
match mode {
Mode::BatchAll => {
return Err(PrecompileFailure::Error {
exit_status: ExitError::OutOfGas,
})
}
Mode::BatchSomeUntilFailure => return Ok(()),
Mode::BatchSome => continue,
}
}
limit
}
};
let (reason, output) = handle.call(
address,
transfer,
call_data,
Some(forwarded_gas),
false,
&sub_context,
);
match reason {
ExitReason::Revert(_) | ExitReason::Error(_) => {
let log = log_subcall_failed(handle.code_address(), i);
handle.record_log_costs(&[&log])?;
log.record(handle)?
}
ExitReason::Succeed(_) => {
let log = log_subcall_succeeded(handle.code_address(), i);
handle.record_log_costs(&[&log])?;
log.record(handle)?
}
_ => (),
}
match (mode, reason) {
(_, ExitReason::Fatal(exit_status)) => {
return Err(PrecompileFailure::Fatal { exit_status })
}
(Mode::BatchAll, ExitReason::Revert(exit_status)) => {
return Err(PrecompileFailure::Revert {
exit_status,
output,
})
}
(Mode::BatchAll, ExitReason::Error(exit_status)) => {
return Err(PrecompileFailure::Error { exit_status })
}
(Mode::BatchSomeUntilFailure, ExitReason::Revert(_) | ExitReason::Error(_)) => {
return Ok(())
}
(_, _) => (),
}
}
Ok(())
}
}
impl<Runtime> BatchPrecompileCall<Runtime>
where
Runtime: pallet_evm::Config,
{
pub fn batch_from_mode(
mode: Mode,
to: Vec<Address>,
value: Vec<U256>,
call_data: Vec<Vec<u8>>,
gas_limit: Vec<u64>,
) -> Self {
let to = to.into();
let value = value.into();
let call_data: Vec<_> = call_data.into_iter().map(|inner| inner.into()).collect();
let call_data = call_data.into();
let gas_limit = gas_limit.into();
match mode {
Mode::BatchSome => Self::batch_some {
to,
value,
call_data,
gas_limit,
},
Mode::BatchSomeUntilFailure => Self::batch_some_until_failure {
to,
value,
call_data,
gas_limit,
},
Mode::BatchAll => Self::batch_all {
to,
value,
call_data,
gas_limit,
},
}
}
}