From 746c254e349d9e34b0198bff5f45418c47b9e296 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Mon, 31 Jul 2023 11:02:07 +0530 Subject: [PATCH 01/21] feat: add batch precompile --- Cargo.lock | 32 + Cargo.toml | 1 + precompiles/batch/Batch.sol | 82 ++ precompiles/batch/Cargo.toml | 58 + precompiles/batch/src/lib.rs | 365 ++++++ precompiles/batch/src/mock.rs | 201 +++ precompiles/batch/src/tests.rs | 1064 ++++++++++++++++ precompiles/utils/Cargo.toml | 10 +- precompiles/utils/src/bytes.rs | 223 ++++ precompiles/utils/src/data.rs | 138 ++- precompiles/utils/src/evm/costs.rs | 117 ++ precompiles/utils/src/evm/handle.rs | 184 +++ precompiles/utils/src/evm/logs.rs | 108 ++ precompiles/utils/src/evm/mod.rs | 19 + precompiles/utils/src/lib.rs | 12 +- .../utils/src/{testing.rs => old_testing.rs} | 1 + precompiles/utils/src/precompile_set.rs | 1079 +++++++++++++++++ precompiles/utils/src/substrate.rs | 119 ++ precompiles/utils/src/testing/account.rs | 181 +++ precompiles/utils/src/testing/execution.rs | 238 ++++ precompiles/utils/src/testing/handle.rs | 205 ++++ precompiles/utils/src/testing/mod.rs | 96 ++ 22 files changed, 4524 insertions(+), 9 deletions(-) create mode 100644 precompiles/batch/Batch.sol create mode 100644 precompiles/batch/Cargo.toml create mode 100644 precompiles/batch/src/lib.rs create mode 100644 precompiles/batch/src/mock.rs create mode 100644 precompiles/batch/src/tests.rs create mode 100644 precompiles/utils/src/bytes.rs create mode 100644 precompiles/utils/src/evm/costs.rs create mode 100644 precompiles/utils/src/evm/handle.rs create mode 100644 precompiles/utils/src/evm/logs.rs create mode 100644 precompiles/utils/src/evm/mod.rs rename precompiles/utils/src/{testing.rs => old_testing.rs} (99%) create mode 100644 precompiles/utils/src/precompile_set.rs create mode 100644 precompiles/utils/src/substrate.rs create mode 100644 precompiles/utils/src/testing/account.rs create mode 100644 precompiles/utils/src/testing/execution.rs create mode 100644 precompiles/utils/src/testing/handle.rs create mode 100644 precompiles/utils/src/testing/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 5f4837adb5..b6488c4dfa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7121,6 +7121,34 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-evm-precompile-batch" +version = "0.1.0" +dependencies = [ + "derive_more", + "evm", + "fp-evm", + "frame-support", + "frame-system", + "hex-literal 0.4.1", + "log", + "num_enum", + "pallet-balances", + "pallet-evm", + "pallet-timestamp", + "parity-scale-codec", + "paste", + "precompile-utils", + "scale-info", + "serde", + "sha3", + "slices", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-evm-precompile-blake2" version = "2.0.0-dev" @@ -9564,6 +9592,8 @@ name = "precompile-utils" version = "0.4.3" dependencies = [ "assert_matches", + "derive_more", + "environmental", "evm", "fp-evm", "frame-support", @@ -9575,6 +9605,8 @@ dependencies = [ "pallet-evm", "parity-scale-codec", "precompile-utils-macro", + "scale-info", + "serde", "sha3", "similar-asserts", "sp-core", diff --git a/Cargo.toml b/Cargo.toml index 653e471631..e65c4b406c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,7 @@ num-traits = { version = "0.2", default-features = false } rand = { version = "0.8.5", default-features = false } bounded-collections = { version = "0.1.5", default-features = false } hex = { version = "0.4.3", default-features = false } +paste = "1.0.6" # (native) array-bytes = "6.0.0" diff --git a/precompiles/batch/Batch.sol b/precompiles/batch/Batch.sol new file mode 100644 index 0000000000..d4069d1b27 --- /dev/null +++ b/precompiles/batch/Batch.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity >=0.8.3; + +/// @dev The Batch contract's address. +address constant BATCH_ADDRESS = 0x0000000000000000000000000000000000000808; + +/// @dev The Batch contract's instance. +Batch constant BATCH_CONTRACT = Batch(BATCH_ADDRESS); + +/// @author The Moonbeam Team +/// @title Batch precompile +/// @dev Allows to perform multiple calls throught one call to the precompile. +/// Can be used by EOA to do multiple calls in a single transaction. +/// @custom:address 0x0000000000000000000000000000000000000808 +interface Batch { + /// @dev Batch multiple calls into a single transaction. + /// All calls are performed from the address calling this precompile. + /// + /// In case of one subcall reverting following subcalls will still be attempted. + /// + /// @param to List of addresses to call. + /// @param value List of values for each subcall. If array is shorter than "to" then additional + /// calls will be performed with a value of 0. + /// @param callData Call data for each `to` address. If array is shorter than "to" then + /// additional calls will be performed with an empty call data. + /// @param gasLimit Gas limit for each `to` address. Use 0 to forward all the remaining gas. + /// If array is shorter than "to" then the remaining gas available will be used. + /// @custom:selector 79df4b9c + function batchSome( + address[] memory to, + uint256[] memory value, + bytes[] memory callData, + uint64[] memory gasLimit + ) external; + + /// @dev Batch multiple calls into a single transaction. + /// All calls are performed from the address calling this precompile. + /// + /// In case of one subcall reverting, no more subcalls will be executed but + /// the batch transaction will succeed. Use batchAll to revert on any subcall revert. + /// + /// @param to List of addresses to call. + /// @param value List of values for each subcall. If array is shorter than "to" then additional + /// calls will be performed with a value of 0. + /// @param callData Call data for each `to` address. If array is shorter than "to" then + /// additional calls will be performed with an empty call data. + /// @param gasLimit Gas limit for each `to` address. Use 0 to forward all the remaining gas. + /// If array is shorter than "to" then the remaining gas available will be used. + /// @custom:selector cf0491c7 + function batchSomeUntilFailure( + address[] memory to, + uint256[] memory value, + bytes[] memory callData, + uint64[] memory gasLimit + ) external; + + /// @dev Batch multiple calls into a single transaction. + /// All calls are performed from the address calling this precompile. + /// + /// In case of one subcall reverting, the entire batch will revert. + /// + /// @param to List of addresses to call. + /// @param value List of values for each subcall. If array is shorter than "to" then additional + /// calls will be performed with a value of 0. + /// @param callData Call data for each `to` address. If array is shorter than "to" then + /// additional calls will be performed with an empty call data. + /// @param gasLimit Gas limit for each `to` address. Use 0 to forward all the remaining gas. + /// If array is shorter than "to" then the remaining gas available will be used. + /// @custom:selector 96e292b8 + function batchAll( + address[] memory to, + uint256[] memory value, + bytes[] memory callData, + uint64[] memory gasLimit + ) external; + + /// Emitted when a subcall succeeds. + event SubcallSucceeded(uint256 index); + + /// Emitted when a subcall fails. + event SubcallFailed(uint256 index); +} diff --git a/precompiles/batch/Cargo.toml b/precompiles/batch/Cargo.toml new file mode 100644 index 0000000000..857a7aca84 --- /dev/null +++ b/precompiles/batch/Cargo.toml @@ -0,0 +1,58 @@ +[package] +name = "pallet-evm-precompile-batch" +description = "A Precompile to batch multiple calls." +version = "0.1.0" +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +log = { workspace = true } +num_enum = { workspace = true } +paste = { workspace = true } +slices = { workspace = true } + +# Moonbeam +precompile-utils = { workspace = true } + +# Substrate +frame-support = { workspace = true } +frame-system = { workspace = true } +parity-scale-codec = { workspace = true, features = [ "max-encoded-len" ] } +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-std = { workspace = true } + +# Frontier +evm = { workspace = true, features = [ "with-codec" ] } +fp-evm = { workspace = true } +pallet-evm = { workspace = true, features = [ "forbid-evm-reentrancy" ] } + +[dev-dependencies] +derive_more = { workspace = true } +hex-literal = { workspace = true } +serde = { workspace = true } +sha3 = { workspace = true } + +pallet-balances = { workspace = true, features = [ "std" ] } +pallet-timestamp = { workspace = true, features = [ "std" ] } +parity-scale-codec = { workspace = true, features = [ "max-encoded-len", "std" ] } +precompile-utils = { workspace = true, features = [ "std", "testing" ] } +scale-info = { workspace = true, features = [ "derive", "std" ] } +sp-runtime = { workspace = true, features = [ "std" ] } + +[features] +default = [ "std" ] +std = [ + "fp-evm/std", + "frame-support/std", + "frame-system/std", + "pallet-evm/std", + "parity-scale-codec/std", + "precompile-utils/std", + "sp-core/std", + "sp-io/std", + "sp-std/std", +] + diff --git a/precompiles/batch/src/lib.rs b/precompiles/batch/src/lib.rs new file mode 100644 index 0000000000..b8a26beaa9 --- /dev/null +++ b/precompiles/batch/src/lib.rs @@ -0,0 +1,365 @@ +// Copyright 2019-2022 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonbeam is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonbeam. If not, see . + +//! Precompile to interact with pallet_balances instances using the ERC20 interface standard. + +#![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 pallet_evm::PrecompileOutput; +use precompile_utils::{ + bytes::BoundedBytes, + data::BoundedVec, + evm::{costs::call_cost, logs::log1}, + *, precompile_set::PrecompileSetFragment, +}; +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, // = "batchSome(address[],uint256[],bytes[],uint64[])", + BatchSomeUntilFailure, // = "batchSomeUntilFailure(address[],uint256[],bytes[],uint64[])", + BatchAll, // = "batchAll(address[],uint256[],bytes[],uint64[])", +} + +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; +type GetArrayLimit = ConstU32; + +pub fn log_subcall_succeeded(address: impl Into, index: usize) -> Log { + log1( + address, + LOG_SUBCALL_SUCCEEDED, + data::encode_event_data(U256::from(index)), + ) +} + +pub fn log_subcall_failed(address: impl Into, index: usize) -> Log { + log1( + address, + LOG_SUBCALL_FAILED, + data::encode_event_data(U256::from(index)), + ) +} + +#[precompile_utils::generate_function_selector] +#[derive(Debug, PartialEq)] +pub enum Action { + BatchSome = "batchSome(address[],uint256[],bytes[],uint64[])", + BatchSomeUntilFailure = "batchSomeUntilFailure(address[],uint256[],bytes[],uint64[])", + BatchAll = "batchAll(address[],uint256[],bytes[],uint64[])", +} + +/// Batch precompile. +#[derive(Debug, Clone)] +pub struct BatchPrecompile(PhantomData); + +impl BatchPrecompile +where + Runtime: pallet_evm::Config + PrecompileSetFragment, + +{ + fn execute(handle: &mut impl PrecompileHandle) -> EvmResult { + let selector = handle.read_selector()?; + + handle.check_function_modifier(FunctionModifier::NonPayable)?; + match selector { + Action::BatchSome => Self::batch_some(handle), + Action::BatchAll => Self::batch_all(handle), + Action::BatchSomeUntilFailure => Self::batch_some_until_failure(handle), + } + } +} +// No funds are transfered to the precompile address. +// Transfers will directly be made on the behalf of the user by the precompile. +// #[precompile_utils::precompile] +impl BatchPrecompile +where + Runtime: pallet_evm::Config, +{ + fn batch_some(handle: &mut impl PrecompileHandle) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(6)?; + let to = input.read::>()?; + let value = input.read::>()?; + let call_data = + input.read::, GetArrayLimit>>()?; + let gas_limit = input.read::>()?; + Self::inner_batch(Mode::BatchSome, handle, to, value, call_data, gas_limit) + } + + fn batch_some_until_failure(handle: &mut impl PrecompileHandle) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(6)?; + let to = input.read::>()?; + let value = input.read::>()?; + let call_data = + input.read::, GetArrayLimit>>()?; + let gas_limit = input.read::>()?; + Self::inner_batch( + Mode::BatchSomeUntilFailure, + handle, + to, + value, + call_data, + gas_limit, + ) + } + + fn batch_all(handle: &mut impl PrecompileHandle) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(6)?; + let to = input.read::>()?; + let value = input.read::>()?; + let call_data = + input.read::, GetArrayLimit>>()?; + let gas_limit = input.read::>()?; + Self::inner_batch(Mode::BatchAll, handle, to, value, call_data, gas_limit) + } + + fn inner_batch( + mode: Mode, + handle: &mut impl PrecompileHandle, + to: BoundedVec, + value: BoundedVec, + call_data: BoundedVec, GetArrayLimit>, + gas_limit: BoundedVec, + ) -> 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| + // x = 0 => forward all remaining gas + if x == 0 { + None + } else { + Some(x) + } + ).chain(repeat(None)); + + // Cost of batch log. (doesn't change when index changes) + 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, + }) + }; + + // We reserve enough gas to emit a final log and perform the subcall itself. + // If not enough gas we stop there according to Mode strategy. + 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(succeed(EvmDataWriter::new().write(true).build())); + } + }; + + // Cost of the call itself that the batch precompile must pay. + let call_cost = call_cost(value, ::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(succeed(EvmDataWriter::new().write(true).build())) + } + Mode::BatchSome => continue, + } + } + }; + + // If there is a provided gas limit we ensure there is enough gas remaining. + let forwarded_gas = match gas_limit { + None => forwarded_gas, // provide all gas if no gas limit, + 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(succeed(EvmDataWriter::new().write(true).build())) + } + Mode::BatchSome => continue, + } + } + limit + } + }; + + let (reason, output) = handle.call( + address, + transfer, + call_data, + Some(forwarded_gas), + false, + &sub_context, + ); + + // Logs + // We reserved enough gas so this should not OOG. + 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)? + } + _ => (), + } + + // How to proceed + match (mode, reason) { + // _: Fatal is always fatal + (_, ExitReason::Fatal(exit_status)) => { + return Err(PrecompileFailure::Fatal { exit_status }) + } + + // BatchAll : Reverts and errors are immediatly forwarded. + (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 }) + } + + // BatchSomeUntilFailure : Reverts and errors prevent subsequent subcalls to + // be executed but the precompile still succeed. + (Mode::BatchSomeUntilFailure, ExitReason::Revert(_) | ExitReason::Error(_)) => { + return Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + // Success or ignored revert/error. + (_, _) => (), + } + } + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } +} + +// // The enum is generated by the macro above. +// // We add this method to simplify writing tests generic over the mode. +// impl BatchPrecompileCall +// where +// Runtime: pallet_evm::Config, +// { +// pub fn batch_from_mode( +// mode: Mode, +// to: Vec
, +// value: Vec, +// call_data: Vec>, +// gas_limit: Vec, +// ) -> Self { +// // Convert Vecs into their bounded versions. +// // This is mainly a convenient function to write tests. +// // Bounds are only checked when parsing from call data. +// 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, +// }, +// } +// } +// } diff --git a/precompiles/batch/src/mock.rs b/precompiles/batch/src/mock.rs new file mode 100644 index 0000000000..d5bf246a29 --- /dev/null +++ b/precompiles/batch/src/mock.rs @@ -0,0 +1,201 @@ +// Copyright 2019-2022 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonbeam is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonbeam. If not, see . + +//! Test utilities +use super::*; + +use frame_support::traits::Everything; +use frame_support::{construct_runtime, parameter_types, weights::Weight}; +use pallet_evm::{EnsureAddressNever, EnsureAddressRoot}; +use precompile_utils::{mock_account, precompile_set::*, testing::MockAccount}; +use sp_core::H256; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + Perbill, +}; + +pub type AccountId = MockAccount; +pub type Balance = u128; +pub type BlockNumber = u32; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Evm: pallet_evm::{Pallet, Call, Storage, Event}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + } +); + +parameter_types! { + pub const BlockHashCount: u32 = 250; + pub const MaximumBlockWeight: Weight = Weight::from_parts(1024, 1); + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); + pub const SS58Prefix: u8 = 42; +} + +impl frame_system::Config for Runtime { + type BaseCallFilter = Everything; + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = BlockNumber; + type RuntimeCall = RuntimeCall; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = sp_runtime::generic::Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type BlockWeights = (); + type BlockLength = (); + type SS58Prefix = SS58Prefix; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} +parameter_types! { + pub const ExistentialDeposit: u128 = 1; +} +impl pallet_balances::Config for Runtime { + type MaxReserves = (); + type ReserveIdentifier = [u8; 4]; + type MaxLocks = (); + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); +} + +pub type Precompiles = PrecompileSetBuilder< + R, + ( + PrecompileAt< + AddressU64<1>, + BatchPrecompile, + ( + SubcallWithMaxNesting<1>, + // Batch is the only precompile allowed to call Batch. + CallableByPrecompile>>, + ), + >, + RevertPrecompile>, + ), +>; + +pub type PCall = BatchPrecompile; + +mock_account!(Batch, |_| MockAccount::from_u64(1)); +mock_account!(Revert, |_| MockAccount::from_u64(2)); + +parameter_types! { + pub BlockGasLimit: U256 = U256::max_value(); + pub PrecompilesValue: Precompiles = Precompiles::new(); + pub const WeightPerGas: Weight = Weight::from_parts(1, 0); +} + +impl pallet_evm::Config for Runtime { + type FeeCalculator = (); + type GasWeightMapping = pallet_evm::FixedGasWeightMapping; + type WeightPerGas = WeightPerGas; + type CallOrigin = EnsureAddressRoot; + type WithdrawOrigin = EnsureAddressNever; + type AddressMapping = AccountId; + type Currency = Balances; + type RuntimeEvent = RuntimeEvent; + type Runner = pallet_evm::runner::stack::Runner; + type PrecompilesType = Precopiles; + type PrecompilesValue = PrecompilesValue; + type Timestamp = Timestamp; + type ChainId = (); + type OnChargeTransaction = (); + type BlockGasLimit = BlockGasLimit; + type BlockHashMapping = pallet_evm::SubstrateBlockHashMapping; + type FindAuthor = (); + type OnCreate = (); + type WeightInfo = (); + +} + +parameter_types! { + pub const MinimumPeriod: u64 = 5; +} +impl pallet_timestamp::Config for Runtime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +pub(crate) struct ExtBuilder { + // endowed accounts with balances + balances: Vec<(AccountId, Balance)>, +} + +impl Default for ExtBuilder { + fn default() -> ExtBuilder { + ExtBuilder { balances: vec![] } + } +} + +impl ExtBuilder { + pub(crate) fn with_balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self { + self.balances = balances; + self + } + + pub(crate) fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .expect("Frame system builds valid default genesis config"); + + pallet_balances::GenesisConfig:: { + balances: self.balances, + } + .assimilate_storage(&mut t) + .expect("Pallet balances storage can be assimilated"); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + pallet_evm::Pallet::::create_account( + Revert.into(), + hex_literal::hex!("1460006000fd").to_vec(), + ); + }); + ext + } +} + +pub fn balance(account: impl Into) -> Balance { + pallet_balances::Pallet::::usable_balance(account.into()) +} diff --git a/precompiles/batch/src/tests.rs b/precompiles/batch/src/tests.rs new file mode 100644 index 0000000000..91bb564e73 --- /dev/null +++ b/precompiles/batch/src/tests.rs @@ -0,0 +1,1064 @@ +// Copyright 2019-2022 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonbeam is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonbeam. If not, see . + +use crate::mock::{ + balance, Batch, ExtBuilder, PCall, Precompiles, PrecompilesValue, Revert, Runtime, RuntimeCall, + RuntimeOrigin, +}; +use crate::{ + log_subcall_failed, log_subcall_succeeded, Mode, LOG_SUBCALL_FAILED, LOG_SUBCALL_SUCCEEDED, +}; +use fp_evm::ExitError; +use frame_support::{ + assert_ok, + dispatch::{DispatchError, Dispatchable}, +}; +use pallet_evm::Call as EvmCall; +use precompile_utils::{evm::costs::call_cost, *, testing::*}; +use sp_core::{H160, H256, U256}; +use sp_runtime::{DispatchErrorWithPostInfo, ModuleError}; + +fn precompiles() -> Precompiles { + PrecompilesValue::get() +} + +fn evm_call(from: impl Into, input: Vec) -> EvmCall { + EvmCall::call { + source: from.into(), + target: Batch.into(), + input, + value: U256::zero(), // No value sent in EVM + gas_limit: u64::max_value(), + max_fee_per_gas: 0.into(), + max_priority_fee_per_gas: Some(U256::zero()), + nonce: None, // Use the next nonce + access_list: Vec::new(), + } +} + +fn costs() -> (u64, u64) { + let return_log_cost = log_subcall_failed(Batch, 0).compute_cost().unwrap(); + let call_cost = + return_log_cost + call_cost(U256::one(), ::config()); + (return_log_cost, call_cost) +} + + +#[test] +fn batch_some_empty() { + ExtBuilder::default().build().execute_with(|| { + precompiles() + .prepare_test( + Alice, + Batch, + PCall::batch_some { + to: vec![].into(), + value: vec![].into(), + call_data: vec![].into(), + gas_limit: vec![].into(), + }, + ) + .with_subcall_handle(|Subcall { .. }| panic!("there should be no subcall")) + .execute_returns(()) + }) +} + +#[test] +fn batch_some_until_failure_empty() { + ExtBuilder::default().build().execute_with(|| { + precompiles() + .prepare_test( + Alice, + Batch, + PCall::batch_some_until_failure { + to: vec![].into(), + value: vec![].into(), + call_data: vec![].into(), + gas_limit: vec![].into(), + }, + ) + .with_subcall_handle(|Subcall { .. }| panic!("there should be no subcall")) + .execute_returns(()) + }) +} + +#[test] +fn batch_all_empty() { + ExtBuilder::default().build().execute_with(|| { + precompiles() + .prepare_test( + Alice, + Batch, + PCall::batch_all { + to: vec![].into(), + value: vec![].into(), + call_data: vec![].into(), + gas_limit: vec![].into(), + }, + ) + .with_subcall_handle(|Subcall { .. }| panic!("there should be no subcall")) + .execute_returns(()) + }) +} + +fn batch_returns( + precompiles: &Precompiles, + mode: Mode, +) -> PrecompilesTester> { + let mut counter = 0; + + let (_, total_call_cost) = costs(); + + precompiles + .prepare_test( + Alice, + Batch, + PCall::batch_from_mode( + mode, + vec![Address(Bob.into()), Address(Charlie.into())], + vec![U256::from(1u8), U256::from(2u8)], + vec![b"one".to_vec(), b"two".to_vec()], + vec![], + ), + ) + .with_target_gas(Some(100_000)) + .with_subcall_handle(move |subcall| { + let Subcall { + address, + transfer, + input, + target_gas, + is_static, + context, + } = subcall; + + // Called from the precompile caller. + assert_eq!(context.caller, Alice.into()); + assert_eq!(is_static, false); + + match address { + a if a == Bob.into() => { + assert_eq!(counter, 0, "this is the first call"); + counter += 1; + + assert_eq!( + target_gas, + Some(100_000 - total_call_cost), + "batch forward all gas" + ); + let transfer = transfer.expect("there is a transfer"); + assert_eq!(transfer.source, Alice.into()); + assert_eq!(transfer.target, Bob.into()); + assert_eq!(transfer.value, 1u8.into()); + + assert_eq!(context.address, Bob.into()); + assert_eq!(context.apparent_value, 1u8.into()); + + assert_eq!(&input, b"one"); + + SubcallOutput { + cost: 13, + logs: vec![log1(Bob, H256::repeat_byte(0x11), vec![])], + ..SubcallOutput::succeed() + } + } + a if a == Charlie.into() => { + assert_eq!(counter, 1, "this is the second call"); + counter += 1; + + assert_eq!( + target_gas, + Some(100_000 - 13 - total_call_cost * 2), + "batch forward all gas" + ); + let transfer = transfer.expect("there is a transfer"); + assert_eq!(transfer.source, Alice.into()); + assert_eq!(transfer.target, Charlie.into()); + assert_eq!(transfer.value, 2u8.into()); + + assert_eq!(context.address, Charlie.into()); + assert_eq!(context.apparent_value, 2u8.into()); + + assert_eq!(&input, b"two"); + + SubcallOutput { + cost: 17, + logs: vec![log1(Charlie, H256::repeat_byte(0x22), vec![])], + ..SubcallOutput::succeed() + } + } + _ => panic!("unexpected subcall"), + } + }) + .expect_cost(13 + 17 + total_call_cost * 2) +} + +#[test] +fn batch_some_returns() { + ExtBuilder::default().build().execute_with(|| { + batch_returns(&precompiles(), Mode::BatchSome) + .expect_log(log1(Bob, H256::repeat_byte(0x11), vec![])) + .expect_log(log_subcall_succeeded(Batch, 0)) + .expect_log(log1(Charlie, H256::repeat_byte(0x22), vec![])) + .expect_log(log_subcall_succeeded(Batch, 1)) + .execute_returns(()) + }) +} + +#[test] +fn batch_some_until_failure_returns() { + ExtBuilder::default().build().execute_with(|| { + batch_returns(&precompiles(), Mode::BatchSomeUntilFailure) + .expect_log(log1(Bob, H256::repeat_byte(0x11), vec![])) + .expect_log(log_subcall_succeeded(Batch, 0)) + .expect_log(log1(Charlie, H256::repeat_byte(0x22), vec![])) + .expect_log(log_subcall_succeeded(Batch, 1)) + .execute_returns(()) + }) +} + +#[test] +fn batch_all_returns() { + ExtBuilder::default().build().execute_with(|| { + batch_returns(&precompiles(), Mode::BatchAll) + .expect_log(log1(Bob, H256::repeat_byte(0x11), vec![])) + .expect_log(log_subcall_succeeded(Batch, 0)) + .expect_log(log1(Charlie, H256::repeat_byte(0x22), vec![])) + .expect_log(log_subcall_succeeded(Batch, 1)) + .execute_returns(()) + }) +} + +fn batch_out_of_gas( + precompiles: &Precompiles, + mode: Mode, +) -> PrecompilesTester> { + let (_, total_call_cost) = costs(); + + precompiles + .prepare_test( + Alice, + Batch, + PCall::batch_from_mode( + mode, + vec![Address(Bob.into())], + vec![U256::from(1u8)], + vec![b"one".to_vec()], + vec![], + ), + ) + .with_target_gas(Some(50_000)) + .with_subcall_handle(move |subcall| { + let Subcall { + address, + transfer, + input, + target_gas, + is_static, + context, + } = subcall; + + // Called from the precompile caller. + assert_eq!(context.caller, Alice.into()); + assert_eq!(is_static, false); + + match address { + a if a == Bob.into() => { + assert_eq!( + target_gas, + Some(50_000 - total_call_cost), + "batch forward all gas" + ); + let transfer = transfer.expect("there is a transfer"); + assert_eq!(transfer.source, Alice.into()); + assert_eq!(transfer.target, Bob.into()); + assert_eq!(transfer.value, 1u8.into()); + + assert_eq!(context.address, Bob.into()); + assert_eq!(context.apparent_value, 1u8.into()); + + assert_eq!(&input, b"one"); + + SubcallOutput { + cost: 11_000, + ..SubcallOutput::out_of_gas() + } + } + _ => panic!("unexpected subcall"), + } + }) +} + +#[test] +fn batch_some_out_of_gas() { + ExtBuilder::default().build().execute_with(|| { + batch_out_of_gas(&precompiles(), Mode::BatchSome) + .expect_log(log_subcall_failed(Batch, 0)) + .execute_returns(()) + }) +} + +#[test] +fn batch_some_until_failure_out_of_gas() { + ExtBuilder::default().build().execute_with(|| { + batch_out_of_gas(&precompiles(), Mode::BatchSomeUntilFailure) + .expect_log(log_subcall_failed(Batch, 0)) + .execute_returns(()) + }) +} + +#[test] +fn batch_all_out_of_gas() { + ExtBuilder::default().build().execute_with(|| { + batch_out_of_gas(&precompiles(), Mode::BatchAll).execute_error(ExitError::OutOfGas) + }) +} + +fn batch_incomplete( + precompiles: &Precompiles, + mode: Mode, +) -> PrecompilesTester> { + let mut counter = 0; + + let (_, total_call_cost) = costs(); + + precompiles + .prepare_test( + Alice, + Batch, + PCall::batch_from_mode( + mode, + vec![ + Address(Bob.into()), + Address(Charlie.into()), + Address(Alice.into()), + ], + vec![U256::from(1u8), U256::from(2u8), U256::from(3u8)], + vec![b"one".to_vec()], + vec![], + ), + ) + .with_target_gas(Some(300_000)) + .with_subcall_handle(move |subcall| { + let Subcall { + address, + transfer, + input, + target_gas, + is_static, + context, + } = subcall; + + // Called from the precompile caller. + assert_eq!(context.caller, Alice.into()); + assert_eq!(is_static, false); + + match address { + a if a == Bob.into() => { + assert_eq!(counter, 0, "this is the first call"); + counter += 1; + + assert_eq!( + target_gas, + Some(300_000 - total_call_cost), + "batch forward all gas" + ); + let transfer = transfer.expect("there is a transfer"); + assert_eq!(transfer.source, Alice.into()); + assert_eq!(transfer.target, Bob.into()); + assert_eq!(transfer.value, 1u8.into()); + + assert_eq!(context.address, Bob.into()); + assert_eq!(context.apparent_value, 1u8.into()); + + assert_eq!(&input, b"one"); + + SubcallOutput { + cost: 13, + logs: vec![log1(Bob, H256::repeat_byte(0x11), vec![])], + ..SubcallOutput::succeed() + } + } + a if a == Charlie.into() => { + assert_eq!(counter, 1, "this is the second call"); + counter += 1; + + assert_eq!( + target_gas, + Some(300_000 - 13 - total_call_cost * 2), + "batch forward all gas" + ); + let transfer = transfer.expect("there is a transfer"); + assert_eq!(transfer.source, Alice.into()); + assert_eq!(transfer.target, Charlie.into()); + assert_eq!(transfer.value, 2u8.into()); + + assert_eq!(context.address, Charlie.into()); + assert_eq!(context.apparent_value, 2u8.into()); + + assert_eq!(&input, b""); + + SubcallOutput { + output: String::from("Revert message").as_bytes().to_vec(), + cost: 17, + ..SubcallOutput::revert() + } + } + a if a == Alice.into() => { + assert_eq!(counter, 2, "this is the third call"); + counter += 1; + + assert_eq!( + target_gas, + Some(300_000 - 13 - 17 - total_call_cost * 3), + "batch forward all gas" + ); + let transfer = transfer.expect("there is a transfer"); + assert_eq!(transfer.source, Alice.into()); + assert_eq!(transfer.target, Alice.into()); + assert_eq!(transfer.value, 3u8.into()); + + assert_eq!(context.address, Alice.into()); + assert_eq!(context.apparent_value, 3u8.into()); + + assert_eq!(&input, b""); + + SubcallOutput { + cost: 19, + logs: vec![log1(Alice, H256::repeat_byte(0x33), vec![])], + ..SubcallOutput::succeed() + } + } + _ => panic!("unexpected subcall"), + } + }) +} + +#[test] +fn batch_some_incomplete() { + ExtBuilder::default().build().execute_with(|| { + let (_, total_call_cost) = costs(); + + batch_incomplete(&precompiles(), Mode::BatchSome) + .expect_log(log1(Bob, H256::repeat_byte(0x11), vec![])) + .expect_log(log_subcall_succeeded(Batch, 0)) + .expect_log(log_subcall_failed(Batch, 1)) + .expect_log(log1(Alice, H256::repeat_byte(0x33), vec![])) + .expect_log(log_subcall_succeeded(Batch, 2)) + .expect_cost(13 + 17 + 19 + total_call_cost * 3) + .execute_returns(()) + }) +} + +#[test] +fn batch_some_until_failure_incomplete() { + ExtBuilder::default().build().execute_with(|| { + let (_, total_call_cost) = costs(); + + batch_incomplete(&precompiles(), Mode::BatchSomeUntilFailure) + .expect_log(log1(Bob, H256::repeat_byte(0x11), vec![])) + .expect_log(log_subcall_succeeded(Batch, 0)) + .expect_log(log_subcall_failed(Batch, 1)) + .expect_cost(13 + 17 + total_call_cost * 2) + .execute_returns(()) + }) +} + +#[test] +fn batch_all_incomplete() { + ExtBuilder::default().build().execute_with(|| { + batch_incomplete(&precompiles(), Mode::BatchAll) + .execute_reverts(|output| output == b"Revert message") + }) +} + +fn batch_log_out_of_gas( + precompiles: &Precompiles, + mode: Mode, +) -> PrecompilesTester> { + let (log_cost, _) = costs(); + + precompiles + .prepare_test( + Alice, + Batch, + PCall::batch_from_mode( + mode, + vec![Address(Bob.into())], + vec![U256::from(1u8)], + vec![b"one".to_vec()], + vec![], + ), + ) + .with_target_gas(Some(log_cost - 1)) + .with_subcall_handle(move |_subcall| panic!("there shouldn't be any subcalls")) +} + +#[test] +fn batch_all_log_out_of_gas() { + ExtBuilder::default().build().execute_with(|| { + batch_log_out_of_gas(&precompiles(), Mode::BatchAll).execute_error(ExitError::OutOfGas); + }) +} + +#[test] +fn batch_some_log_out_of_gas() { + ExtBuilder::default().build().execute_with(|| { + batch_log_out_of_gas(&precompiles(), Mode::BatchSome) + .expect_no_logs() + .execute_returns(()); + }) +} + +#[test] +fn batch_some_until_failure_log_out_of_gas() { + ExtBuilder::default().build().execute_with(|| { + batch_log_out_of_gas(&precompiles(), Mode::BatchSomeUntilFailure) + .expect_no_logs() + .execute_returns(()); + }) +} + +fn batch_call_out_of_gas( + precompiles: &Precompiles, + mode: Mode, +) -> PrecompilesTester> { + let (_, total_call_cost) = costs(); + + precompiles + .prepare_test( + Alice, + Batch, + PCall::batch_from_mode( + mode, + vec![Address(Bob.into())], + vec![U256::from(1u8)], + vec![b"one".to_vec()], + vec![], + ), + ) + .with_target_gas(Some(total_call_cost - 1)) + .with_subcall_handle(move |_subcall| panic!("there shouldn't be any subcalls")) +} + +#[test] +fn batch_all_call_out_of_gas() { + ExtBuilder::default().build().execute_with(|| { + batch_call_out_of_gas(&precompiles(), Mode::BatchAll).execute_error(ExitError::OutOfGas); + }) +} + +#[test] +fn batch_some_call_out_of_gas() { + ExtBuilder::default().build().execute_with(|| { + batch_call_out_of_gas(&precompiles(), Mode::BatchSome) + .expect_log(log_subcall_failed(Batch, 0)) + .execute_returns(()); + }) +} + +#[test] +fn batch_some_until_failure_call_out_of_gas() { + ExtBuilder::default().build().execute_with(|| { + batch_call_out_of_gas(&precompiles(), Mode::BatchSomeUntilFailure) + .expect_log(log_subcall_failed(Batch, 0)) + .execute_returns(()); + }) +} + +fn batch_gas_limit( + precompiles: &Precompiles, + mode: Mode, +) -> PrecompilesTester> { + let (_, total_call_cost) = costs(); + + precompiles + .prepare_test( + Alice, + Batch, + PCall::batch_from_mode( + mode, + vec![Address(Bob.into())], + vec![U256::from(1u8)], + vec![b"one".to_vec()], + vec![50_000 - total_call_cost + 1], + ), + ) + .with_target_gas(Some(50_000)) + .with_subcall_handle(move |_subcall| panic!("there shouldn't be any subcalls")) +} + +#[test] +fn batch_all_gas_limit() { + ExtBuilder::default().build().execute_with(|| { + batch_gas_limit(&precompiles(), Mode::BatchAll).execute_error(ExitError::OutOfGas); + }) +} + +#[test] +fn batch_some_gas_limit() { + ExtBuilder::default().build().execute_with(|| { + let (return_log_cost, _) = costs(); + + batch_gas_limit(&precompiles(), Mode::BatchSome) + .expect_log(log_subcall_failed(Batch, 0)) + .expect_cost(return_log_cost) + .execute_returns(()); + }) +} + +#[test] +fn batch_some_until_failure_gas_limit() { + ExtBuilder::default().build().execute_with(|| { + batch_gas_limit(&precompiles(), Mode::BatchSomeUntilFailure) + .expect_log(log_subcall_failed(Batch, 0)) + .execute_returns(()); + }) +} + +#[test] +fn evm_batch_some_transfers_enough() { + ExtBuilder::default() + .with_balances(vec![(Alice.into(), 10_000)]) + .build() + .execute_with(|| { + assert_ok!(RuntimeCall::Evm(evm_call( + Alice, + PCall::batch_some { + to: vec![Address(Bob.into()), Address(Charlie.into())].into(), + value: vec![U256::from(1_000u16), U256::from(2_000u16)].into(), + call_data: vec![].into(), + gas_limit: vec![].into(), + } + .into() + )) + .dispatch(RuntimeOrigin::root())); + }) +} + +#[test] +fn evm_batch_some_until_failure_transfers_enough() { + ExtBuilder::default() + .with_balances(vec![(Alice.into(), 10_000)]) + .build() + .execute_with(|| { + assert_ok!(RuntimeCall::Evm(evm_call( + Alice, + PCall::batch_some_until_failure { + to: vec![Address(Bob.into()), Address(Charlie.into())].into(), + value: vec![U256::from(1_000u16), U256::from(2_000u16)].into(), + call_data: vec![].into(), + gas_limit: vec![].into(), + } + .into() + )) + .dispatch(RuntimeOrigin::root())); + }) +} + +#[test] +fn evm_batch_all_transfers_enough() { + ExtBuilder::default() + .with_balances(vec![(Alice.into(), 10_000)]) + .build() + .execute_with(|| { + assert_ok!(RuntimeCall::Evm(evm_call( + Alice, + PCall::batch_all { + to: vec![Address(Bob.into()), Address(Charlie.into())].into(), + value: vec![U256::from(1_000u16), U256::from(2_000u16)].into(), + call_data: vec![].into(), + gas_limit: vec![].into(), + } + .into() + )) + .dispatch(RuntimeOrigin::root())); + + assert_eq!(balance(Bob), 1_000); + assert_eq!(balance(Charlie), 2_000); + }) +} + +#[test] +fn evm_batch_some_transfers_too_much() { + ExtBuilder::default() + .with_balances(vec![(Alice.into(), 10_000)]) + .build() + .execute_with(|| { + assert_ok!(RuntimeCall::Evm(evm_call( + Alice, + PCall::batch_some { + to: vec![ + Address(Bob.into()), + Address(Charlie.into()), + Address(David.into()), + ] + .into(), + value: vec![ + U256::from(9_000u16), + U256::from(2_000u16), + U256::from(500u16) + ] + .into(), + call_data: vec![].into(), + gas_limit: vec![].into() + } + .into() + )) + .dispatch(RuntimeOrigin::root())); + + assert_eq!(balance(Alice), 500); // gasprice = 0 + assert_eq!(balance(Bob), 9_000); + assert_eq!(balance(Charlie), 0); + assert_eq!(balance(David), 500); + }) +} + +#[test] +fn evm_batch_some_until_failure_transfers_too_much() { + ExtBuilder::default() + .with_balances(vec![(Alice.into(), 10_000)]) + .build() + .execute_with(|| { + assert_ok!(RuntimeCall::Evm(evm_call( + Alice, + PCall::batch_some_until_failure { + to: vec![ + Address(Bob.into()), + Address(Charlie.into()), + Address(David.into()), + ] + .into(), + value: vec![ + U256::from(9_000u16), + U256::from(2_000u16), + U256::from(500u16) + ] + .into(), + call_data: vec![].into(), + gas_limit: vec![].into() + } + .into() + )) + .dispatch(RuntimeOrigin::root())); + + assert_eq!(balance(Alice), 1_000); // gasprice = 0 + assert_eq!(balance(Bob), 9_000); + assert_eq!(balance(Charlie), 0); + assert_eq!(balance(David), 0); + }) +} + +#[test] +fn evm_batch_all_transfers_too_much() { + ExtBuilder::default() + .with_balances(vec![(Alice.into(), 10_000)]) + .build() + .execute_with(|| { + assert_ok!(RuntimeCall::Evm(evm_call( + Alice, + PCall::batch_all { + to: vec![ + Address(Bob.into()), + Address(Charlie.into()), + Address(David.into()), + ] + .into(), + value: vec![ + U256::from(9_000u16), + U256::from(2_000u16), + U256::from(500u16) + ] + .into(), + call_data: vec![].into(), + gas_limit: vec![].into() + } + .into() + )) + .dispatch(RuntimeOrigin::root())); + + assert_eq!(balance(Alice), 10_000); // gasprice = 0 + assert_eq!(balance(Bob), 0); + assert_eq!(balance(Charlie), 0); + assert_eq!(balance(David), 0); + }) +} + +#[test] +fn evm_batch_some_contract_revert() { + ExtBuilder::default() + .with_balances(vec![(Alice.into(), 10_000)]) + .build() + .execute_with(|| { + assert_ok!(RuntimeCall::Evm(evm_call( + Alice, + PCall::batch_some { + to: vec![ + Address(Bob.into()), + Address(Revert.into()), + Address(David.into()), + ] + .into(), + value: vec![ + U256::from(1_000u16), + U256::from(2_000), + U256::from(3_000u16) + ] + .into(), + call_data: vec![].into(), + gas_limit: vec![].into() + } + .into() + )) + .dispatch(RuntimeOrigin::root())); + + assert_eq!(balance(Alice), 6_000); // gasprice = 0 + assert_eq!(balance(Bob), 1_000); + assert_eq!(balance(Revert), 0); + assert_eq!(balance(David), 3_000); + }) +} + +#[test] +fn evm_batch_some_until_failure_contract_revert() { + ExtBuilder::default() + .with_balances(vec![(Alice.into(), 10_000)]) + .build() + .execute_with(|| { + assert_ok!(RuntimeCall::Evm(evm_call( + Alice, + PCall::batch_some_until_failure { + to: vec![ + Address(Bob.into()), + Address(Revert.into()), + Address(David.into()), + ] + .into(), + value: vec![ + U256::from(1_000u16), + U256::from(2_000), + U256::from(3_000u16) + ] + .into(), + call_data: vec![].into(), + gas_limit: vec![].into() + } + .into() + )) + .dispatch(RuntimeOrigin::root())); + + assert_eq!(balance(Alice), 9_000); // gasprice = 0 + assert_eq!(balance(Bob), 1_000); + assert_eq!(balance(Revert), 0); + assert_eq!(balance(David), 0); + }) +} + +#[test] +fn evm_batch_all_contract_revert() { + ExtBuilder::default() + .with_balances(vec![(Alice.into(), 10_000)]) + .build() + .execute_with(|| { + assert_ok!(RuntimeCall::Evm(evm_call( + Alice, + PCall::batch_all { + to: vec![ + Address(Bob.into()), + Address(Revert.into()), + Address(David.into()), + ] + .into(), + value: vec![ + U256::from(1_000u16), + U256::from(2_000), + U256::from(3_000u16) + ] + .into(), + call_data: vec![].into(), + gas_limit: vec![].into() + } + .into() + )) + .dispatch(RuntimeOrigin::root())); + + assert_eq!(balance(Alice), 10_000); // gasprice = 0 + assert_eq!(balance(Bob), 0); + assert_eq!(balance(Revert), 0); + assert_eq!(balance(David), 0); + }) +} + +#[test] +fn evm_batch_recursion_under_limit() { + ExtBuilder::default() + .with_balances(vec![(Alice.into(), 10_000)]) + .build() + .execute_with(|| { + // Mock sets the recursion limit to 2, and we 2 nested batch. + // Thus it succeeds. + + let input = PCall::batch_all { + to: vec![Address(Batch.into())].into(), + value: vec![].into(), + gas_limit: vec![].into(), + call_data: vec![PCall::batch_all { + to: vec![Address(Bob.into())].into(), + value: vec![1000_u32.into()].into(), + gas_limit: vec![].into(), + call_data: vec![].into(), + } + .encode() + .into()] + .into(), + } + .into(); + + assert_ok!(RuntimeCall::Evm(evm_call(Alice, input)).dispatch(RuntimeOrigin::root())); + + assert_eq!(balance(Alice), 9_000); // gasprice = 0 + assert_eq!(balance(Bob), 1_000); + }) +} + +#[test] +fn evm_batch_recursion_over_limit() { + ExtBuilder::default() + .with_balances(vec![(Alice.into(), 10_000)]) + .build() + .execute_with(|| { + // Mock sets the recursion limit to 2, and we 3 nested batch. + // Thus it reverts. + + let input = PCall::batch_from_mode( + Mode::BatchAll, + vec![Address(Batch.into())], + vec![], + vec![PCall::batch_from_mode( + Mode::BatchAll, + vec![Address(Batch.into())], + vec![], + vec![PCall::batch_from_mode( + Mode::BatchAll, + vec![Address(Bob.into())], + vec![1000_u32.into()], + vec![], + vec![].into(), + ) + .into()], + vec![].into(), + ) + .into()], + vec![], + ) + .into(); + + assert_ok!(RuntimeCall::Evm(evm_call(Alice, input)).dispatch(RuntimeOrigin::root())); + + assert_eq!(balance(Alice), 10_000); // gasprice = 0 + assert_eq!(balance(Bob), 0); + }) +} + +#[test] +fn batch_not_callable_by_smart_contract() { + ExtBuilder::default() + .with_balances(vec![(Alice.into(), 10_000)]) + .build() + .execute_with(|| { + // "deploy" SC to alice address + let alice_h160: H160 = Alice.into(); + pallet_evm::AccountCodes::::insert(alice_h160, vec![10u8]); + + // succeeds if not called by SC, see `evm_batch_recursion_under_limit` + let input = PCall::batch_all { + to: vec![Address(Batch.into())].into(), + value: vec![].into(), + gas_limit: vec![].into(), + call_data: vec![PCall::batch_all { + to: vec![Address(Bob.into())].into(), + value: vec![1000_u32.into()].into(), + gas_limit: vec![].into(), + call_data: vec![].into(), + } + .encode() + .into()] + .into(), + } + .into(); + + match RuntimeCall::Evm(evm_call(Alice, input)).dispatch(RuntimeOrigin::root()) { + Err(DispatchErrorWithPostInfo { + error: + DispatchError::Module(ModuleError { + message: Some(err_msg), + .. + }), + .. + }) => assert_eq!("TransactionMustComeFromEOA", err_msg), + _ => panic!("expected error 'TransactionMustComeFromEOA'"), + } + }) +} + +#[test] +fn batch_is_not_callable_by_dummy_code() { + ExtBuilder::default() + .with_balances(vec![(Alice.into(), 10_000)]) + .build() + .execute_with(|| { + // "deploy" dummy code to alice address + let alice_h160: H160 = Alice.into(); + pallet_evm::AccountCodes::::insert( + alice_h160, + [0x60, 0x00, 0x60, 0x00, 0xfd].to_vec(), + ); + + // succeeds if called by dummy code, see `evm_batch_recursion_under_limit` + let input = PCall::batch_all { + to: vec![Address(Batch.into())].into(), + value: vec![].into(), + gas_limit: vec![].into(), + call_data: vec![PCall::batch_all { + to: vec![Address(Bob.into())].into(), + value: vec![1000_u32.into()].into(), + gas_limit: vec![].into(), + call_data: vec![].into(), + } + .encode() + .into()] + .into(), + } + .into(); + + match RuntimeCall::Evm(evm_call(Alice, input)).dispatch(RuntimeOrigin::root()) { + Err(DispatchErrorWithPostInfo { + error: + DispatchError::Module(ModuleError { + message: Some(err_msg), + .. + }), + .. + }) => assert_eq!("TransactionMustComeFromEOA", err_msg), + _ => panic!("expected error 'TransactionMustComeFromEOA'"), + } + }) +} + +#[test] +fn test_solidity_interface_has_all_function_selectors_documented_and_implemented() { + check_precompile_implements_solidity_interfaces(&["Batch.sol"], PCall::supports_selector) +} diff --git a/precompiles/utils/Cargo.toml b/precompiles/utils/Cargo.toml index 6be1724ce6..1d9adf6699 100644 --- a/precompiles/utils/Cargo.toml +++ b/precompiles/utils/Cargo.toml @@ -9,12 +9,17 @@ repository.workspace = true [dependencies] # There's a problem with --all-features when this is moved under dev-deps -evm = { workspace = true, features = ["std"], optional = true } +evm = { workspace = true, features = [ "with-codec" ] } impl-trait-for-tuples = { workspace = true } log = { workspace = true } num_enum = { workspace = true } sha3 = { workspace = true } similar-asserts = { workspace = true, optional = true } +scale-info = { workspace = true, optional = true, features = [ "derive" ] } +serde = { workspace = true, optional = true } +derive_more = { workspace = true, optional = true } +hex-literal = { workspace = true, optional = true } +environmental = { workspace = true } precompile-utils-macro = { path = "macro" } @@ -53,5 +58,6 @@ std = [ "sp-std/std", "sp-runtime/std", "xcm/std", + "environmental/std", ] -testing = ["similar-asserts", "std"] +testing = ["similar-asserts", "std", "scale-info", "serde", "derive_more", "hex-literal" ] diff --git a/precompiles/utils/src/bytes.rs b/precompiles/utils/src/bytes.rs new file mode 100644 index 0000000000..cfeb020a99 --- /dev/null +++ b/precompiles/utils/src/bytes.rs @@ -0,0 +1,223 @@ +// This file is part of Astar. + +// Copyright 2019-2022 PureStake Inc. +// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Utils package, originally developed by Purestake Inc. +// Utils package used in Astar Network in terms of GPLv3. +// +// Utils is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Utils is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Utils. If not, see . + +use super::*; +use alloc::borrow::ToOwned; +pub use alloc::string::String; +use sp_core::{ConstU32, Get}; + +type ConstU32Max = ConstU32<{ u32::MAX }>; + +pub type UnboundedBytes = BoundedBytesString; +pub type BoundedBytes = BoundedBytesString; + +pub type UnboundedString = BoundedBytesString; +pub type BoundedString = BoundedBytesString; + +trait Kind { + fn signature() -> String; +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct BytesKind; + +impl Kind for BytesKind { + fn signature() -> String { + String::from("bytes") + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct StringKind; + +impl Kind for StringKind { + fn signature() -> String { + String::from("string") + } +} + +/// The `bytes/string` type of Solidity. +/// It is different from `Vec` which will be serialized with padding for each `u8` element +/// of the array, while `Bytes` is tightly packed. +#[derive(Debug)] +pub struct BoundedBytesString { + data: Vec, + _phantom: PhantomData<(K, S)>, +} + +impl> Clone for BoundedBytesString { + fn clone(&self) -> Self { + Self { + data: self.data.clone(), + _phantom: PhantomData, + } + } +} + +impl PartialEq> for BoundedBytesString { + fn eq(&self, other: &BoundedBytesString) -> bool { + self.data.eq(&other.data) + } +} + +impl Eq for BoundedBytesString {} + +impl> BoundedBytesString { + pub fn as_bytes(&self) -> &[u8] { + &self.data + } + + pub fn as_str(&self) -> Result<&str, sp_std::str::Utf8Error> { + sp_std::str::from_utf8(&self.data) + } +} + +impl> EvmData for BoundedBytesString { + fn read(reader: &mut EvmDataReader) -> EvmResult { + let mut inner_reader = reader.read_pointer()?; + + // Read bytes/string size. + let array_size: usize = inner_reader + .read::() + .map_err(|_| revert("length, out of bounds"))? + .try_into() + .map_err(|_| revert("length, value too large"))?; + + if array_size > S::get() as usize { + return Err(revert("length, value too large").into()); + } + + // Get valid range over the bytes data. + let range = inner_reader.move_cursor(array_size)?; + + let data = inner_reader + .get_input_from_range(range) + .ok_or_else(|| revert(K::signature()))?; + + let bytes = Self { + data: data.to_owned(), + _phantom: PhantomData, + }; + + Ok(bytes) + } + + fn write(writer: &mut EvmDataWriter, value: Self) { + let value: Vec<_> = value.into(); + let length = value.len(); + + // Pad the data. + // Leave it as is if a multiple of 32, otherwise pad to next + // multiple or 32. + let chunks = length / 32; + let padded_size = match length % 32 { + 0 => chunks * 32, + _ => (chunks + 1) * 32, + }; + + let mut value = value.to_vec(); + value.resize(padded_size, 0); + + writer.write_pointer( + EvmDataWriter::new() + .write(U256::from(length)) + .write_raw_bytes(&value) + .build(), + ); + } + + fn has_static_size() -> bool { + false + } +} + +// BytesString <=> Vec/&[u8] + +impl From> for Vec { + fn from(value: BoundedBytesString) -> Self { + value.data + } +} + +impl From> for BoundedBytesString { + fn from(value: Vec) -> Self { + Self { + data: value, + _phantom: PhantomData, + } + } +} + +impl From<&[u8]> for BoundedBytesString { + fn from(value: &[u8]) -> Self { + Self { + data: value.to_vec(), + _phantom: PhantomData, + } + } +} + +impl From<[u8; N]> for BoundedBytesString { + fn from(value: [u8; N]) -> Self { + Self { + data: value.to_vec(), + _phantom: PhantomData, + } + } +} + +impl From<&[u8; N]> for BoundedBytesString { + fn from(value: &[u8; N]) -> Self { + Self { + data: value.to_vec(), + _phantom: PhantomData, + } + } +} + +// BytesString <=> String/str + +impl TryFrom> for String { + type Error = alloc::string::FromUtf8Error; + + fn try_from(value: BoundedBytesString) -> Result { + alloc::string::String::from_utf8(value.data) + } +} + +impl From<&str> for BoundedBytesString { + fn from(value: &str) -> Self { + Self { + data: value.as_bytes().into(), + _phantom: PhantomData, + } + } +} + +impl From for BoundedBytesString { + fn from(value: String) -> Self { + Self { + data: value.as_bytes().into(), + _phantom: PhantomData, + } + } +} diff --git a/precompiles/utils/src/data.rs b/precompiles/utils/src/data.rs index 2359bcefd4..e6bb0363c5 100644 --- a/precompiles/utils/src/data.rs +++ b/precompiles/utils/src/data.rs @@ -23,9 +23,9 @@ use crate::{revert, EvmResult}; use alloc::borrow::ToOwned; -use core::{any::type_name, ops::Range}; +use core::{any::type_name, marker::PhantomData, ops::Range}; use impl_trait_for_tuples::impl_for_tuples; -use sp_core::{H160, H256, U256}; +use sp_core::{Get, H160, H256, U256}; use sp_std::{convert::TryInto, vec, vec::Vec}; /// The `address` type of Solidity. @@ -187,10 +187,15 @@ impl<'a> EvmDataReader<'a> { Ok(data) } + /// Return Option<&[u8]> from a given range for EvmDataReader + pub fn get_input_from_range(&self, range: Range) -> Option<&[u8]> { + self.input.get(range) + } + /// Move the reading cursor with provided length, and return a range from the previous cursor /// location to the new one. /// Checks cursor overflows. - fn move_cursor(&mut self, len: usize) -> EvmResult> { + pub fn move_cursor(&mut self, len: usize) -> EvmResult> { let start = self.cursor; let end = self .cursor @@ -285,7 +290,7 @@ impl EvmDataWriter { /// Write arbitrary bytes. /// Doesn't handle any alignement checks, prefer using `write` instead if possible. - fn write_raw_bytes(mut self, value: &[u8]) -> Self { + pub fn write_raw_bytes(mut self, value: &[u8]) -> Self { self.data.extend_from_slice(value); self } @@ -325,8 +330,31 @@ pub trait EvmData: Sized { fn read(reader: &mut EvmDataReader) -> EvmResult; fn write(writer: &mut EvmDataWriter, value: Self); fn has_static_size() -> bool; + fn is_explicit_tuple() -> bool { + false + } +} +/// Encode the value into its Solidity ABI format. +/// If `T` is a tuple it is encoded as a Solidity tuple with dynamic-size offset. +fn encode(value: T) -> Vec { + EvmDataWriter::new().write(value).build() } +/// Encode the value into its Solidity ABI format. +/// If `T` is a tuple every element is encoded without a prefixed offset. +/// It matches the encoding of Solidity function arguments and return value, or event data. +pub fn encode_arguments(value: T) -> Vec { + let output = encode(value); + if T::is_explicit_tuple() && !T::has_static_size() { + output[32..].to_vec() + } else { + output + } +} + +pub use self::encode_arguments as encode_return_value; +pub use self::encode_arguments as encode_event_data; + #[impl_for_tuples(1, 18)] impl EvmData for Tuple { fn has_static_size() -> bool { @@ -604,3 +632,105 @@ impl EvmData for Bytes { false } } + +/// Wrapper around a Vec that provides a max length bound on read. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct BoundedVec { + inner: Vec, + _phantom: PhantomData, +} + +impl> EvmData for BoundedVec { + fn read(reader: &mut EvmDataReader) -> EvmResult { + let mut inner_reader = reader.read_pointer()?; + + let array_size: usize = inner_reader + .read::() + .map_err(|_| revert("out of bounds: length of array"))? + .try_into() + .map_err(|_| revert("value too large : Array has more than max items allowed"))?; + + if array_size > S::get() as usize { + return Err(revert("value too large : Array has more than max items allowed").into()); + } + + let mut array = vec![]; + + let mut item_reader = EvmDataReader { + input: inner_reader + .input + .get(32..) + .ok_or_else(|| revert("read out of bounds: array content"))?, + cursor: 0, + }; + + for _ in 0..array_size { + array.push(item_reader.read()?); + } + + Ok(BoundedVec { + inner: array, + _phantom: PhantomData, + }) + } + + fn write(writer: &mut EvmDataWriter, value: Self) { + let value: Vec<_> = value.into(); + let mut inner_writer = EvmDataWriter::new().write(U256::from(value.len())); + + for inner in value { + // Any offset in items are relative to the start of the item instead of the + // start of the array. However if there is offseted data it must but appended after + // all items (offsets) are written. We thus need to rely on `compute_offsets` to do + // that, and must store a "shift" to correct the offsets. + let shift = inner_writer.data.len(); + let item_writer = EvmDataWriter::new().write(inner); + + inner_writer = inner_writer.write_raw_bytes(&item_writer.data); + for mut offset_datum in item_writer.offset_data { + offset_datum.offset_shift += 32; + offset_datum.offset_position += shift; + inner_writer.offset_data.push(offset_datum); + } + } + + writer.write_pointer(inner_writer.build()); + } + + fn has_static_size() -> bool { + false + } +} + +impl From> for BoundedVec { + fn from(value: Vec) -> Self { + BoundedVec { + inner: value, + _phantom: PhantomData, + } + } +} + +impl From<&[T]> for BoundedVec { + fn from(value: &[T]) -> Self { + BoundedVec { + inner: value.to_vec(), + _phantom: PhantomData, + } + } +} + +impl From<[T; N]> for BoundedVec { + fn from(value: [T; N]) -> Self { + BoundedVec { + inner: value.to_vec(), + _phantom: PhantomData, + } + } +} + +impl From> for Vec { + fn from(value: BoundedVec) -> Self { + value.inner + } +} diff --git a/precompiles/utils/src/evm/costs.rs b/precompiles/utils/src/evm/costs.rs new file mode 100644 index 0000000000..cbdbffa0af --- /dev/null +++ b/precompiles/utils/src/evm/costs.rs @@ -0,0 +1,117 @@ +// Copyright 2019-2022 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonbeam is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonbeam. If not, see . + +//! Cost calculations. +//! TODO: PR EVM to make those cost calculations public. + +use { + crate::EvmResult, + fp_evm::{ExitError, PrecompileFailure}, + sp_core::U256, +}; + +pub fn log_costs(topics: usize, data_len: usize) -> EvmResult { + // Cost calculation is copied from EVM code that is not publicly exposed by the crates. + // https://github.com/rust-blockchain/evm/blob/master/gasometer/src/costs.rs#L148 + + const G_LOG: u64 = 375; + const G_LOGDATA: u64 = 8; + const G_LOGTOPIC: u64 = 375; + + let topic_cost = G_LOGTOPIC + .checked_mul(topics as u64) + .ok_or(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + })?; + + let data_cost = G_LOGDATA + .checked_mul(data_len as u64) + .ok_or(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + })?; + + G_LOG + .checked_add(topic_cost) + .ok_or(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + })? + .checked_add(data_cost) + .ok_or(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }) +} + +// Compute the cost of doing a subcall. +// Some parameters cannot be known in advance, so we estimate the worst possible cost. +pub fn call_cost(value: U256, config: &evm::Config) -> u64 { + // Copied from EVM code since not public. + pub const G_CALLVALUE: u64 = 9000; + pub const G_NEWACCOUNT: u64 = 25000; + + fn address_access_cost(is_cold: bool, regular_value: u64, config: &evm::Config) -> u64 { + if config.increase_state_access_gas { + if is_cold { + config.gas_account_access_cold + } else { + config.gas_storage_read_warm + } + } else { + regular_value + } + } + + fn xfer_cost(is_call_or_callcode: bool, transfers_value: bool) -> u64 { + if is_call_or_callcode && transfers_value { + G_CALLVALUE + } else { + 0 + } + } + + fn new_cost( + is_call_or_staticcall: bool, + new_account: bool, + transfers_value: bool, + config: &evm::Config, + ) -> u64 { + let eip161 = !config.empty_considered_exists; + if is_call_or_staticcall { + if eip161 { + if transfers_value && new_account { + G_NEWACCOUNT + } else { + 0 + } + } else if new_account { + G_NEWACCOUNT + } else { + 0 + } + } else { + 0 + } + } + + let transfers_value = value != U256::default(); + let is_cold = true; + let is_call_or_callcode = true; + let is_call_or_staticcall = true; + let new_account = true; + + address_access_cost(is_cold, config.gas_call, config) + + xfer_cost(is_call_or_callcode, transfers_value) + + new_cost(is_call_or_staticcall, new_account, transfers_value, config) +} diff --git a/precompiles/utils/src/evm/handle.rs b/precompiles/utils/src/evm/handle.rs new file mode 100644 index 0000000000..2d0d1c6973 --- /dev/null +++ b/precompiles/utils/src/evm/handle.rs @@ -0,0 +1,184 @@ +// Copyright 2019-2022 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonbeam is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonbeam. If not, see . + +use { + crate::{ + data::{EvmDataReader}, FunctionModifier, + EvmResult, check_function_modifier + }, + fp_evm::{Log, PrecompileHandle}, +}; + +pub trait PrecompileHandleExt: PrecompileHandle { + /// Record cost of a log manually. + /// This can be useful to record log costs early when their content have static size. + #[must_use] + fn record_log_costs_manual(&mut self, topics: usize, data_len: usize) -> EvmResult; + + /// Record cost of logs. + #[must_use] + fn record_log_costs(&mut self, logs: &[&Log]) -> EvmResult; + + #[must_use] + /// Check that a function call is compatible with the context it is + /// called into. + fn check_function_modifier(&self, modifier: FunctionModifier) -> EvmResult; + + #[must_use] + /// Returns a reader of the input, skipping the selector. + fn read_after_selector(&self) -> EvmResult; +} + +impl PrecompileHandleExt for T { + /// Record cost of a log manualy. + /// This can be useful to record log costs early when their content have static size. + #[must_use] + fn record_log_costs_manual(&mut self, topics: usize, data_len: usize) -> EvmResult { + self.record_cost(crate::evm::costs::log_costs(topics, data_len)?)?; + + Ok(()) + } + + /// Record cost of logs. + #[must_use] + fn record_log_costs(&mut self, logs: &[&Log]) -> EvmResult { + for log in logs { + self.record_log_costs_manual(log.topics.len(), log.data.len())?; + } + + Ok(()) + } + + #[must_use] + /// Check that a function call is compatible with the context it is + /// called into. + fn check_function_modifier(&self, modifier: FunctionModifier) -> EvmResult { + check_function_modifier( + self.context(), + self.is_static(), + modifier, + ) + } + + #[must_use] + /// Returns a reader of the input, skipping the selector. + fn read_after_selector(&self) -> EvmResult { + EvmDataReader::new_skip_selector(self.input()) + } +} + +environmental::environmental!(EVM_CONTEXT: trait PrecompileHandle); + +pub fn using_precompile_handle<'a, R, F: FnOnce() -> R>( + precompile_handle: &'a mut dyn PrecompileHandle, + mutator: F, +) -> R { + // # Safety + // + // unsafe rust does not mean unsafe, but "the compiler cannot guarantee the safety of the + // memory". + // + // The only risk here is that the lifetime 'a comes to its end while the global variable + // `EVM_CONTEXT` still contains the reference to the precompile handle. + // The `using` method guarantee that it can't happen because the global variable is freed right + // after the execution of the `mutator` closure (whatever the result of the execution). + unsafe { + EVM_CONTEXT::using( + core::mem::transmute::<&'a mut dyn PrecompileHandle, &'static mut dyn PrecompileHandle>( + precompile_handle, + ), + mutator, + ) + } +} + +pub fn with_precompile_handle R>(f: F) -> Option { + EVM_CONTEXT::with(|precompile_handle| f(precompile_handle)) +} + +#[cfg(test)] +mod tests { + use super::*; + + struct MockPrecompileHandle; + impl PrecompileHandle for MockPrecompileHandle { + fn call( + &mut self, + _: sp_core::H160, + _: Option, + _: Vec, + _: Option, + _: bool, + _: &evm::Context, + ) -> (evm::ExitReason, Vec) { + unimplemented!() + } + + fn record_cost(&mut self, _: u64) -> Result<(), evm::ExitError> { + unimplemented!() + } + + fn remaining_gas(&self) -> u64 { + unimplemented!() + } + + fn log( + &mut self, + _: sp_core::H160, + _: Vec, + _: Vec, + ) -> Result<(), evm::ExitError> { + unimplemented!() + } + + fn code_address(&self) -> sp_core::H160 { + unimplemented!() + } + + fn input(&self) -> &[u8] { + unimplemented!() + } + + fn context(&self) -> &evm::Context { + unimplemented!() + } + + fn is_static(&self) -> bool { + true + } + + fn gas_limit(&self) -> Option { + unimplemented!() + } + } + + #[test] + fn with_precompile_handle_without_context() { + assert_eq!(with_precompile_handle(|_| {}), None); + } + + #[test] + fn with_precompile_handle_with_context() { + let mut precompile_handle = MockPrecompileHandle; + + assert_eq!( + using_precompile_handle(&mut precompile_handle, || with_precompile_handle( + |handle| handle.is_static() + )), + Some(true) + ); + } +} diff --git a/precompiles/utils/src/evm/logs.rs b/precompiles/utils/src/evm/logs.rs new file mode 100644 index 0000000000..fdbd567f7d --- /dev/null +++ b/precompiles/utils/src/evm/logs.rs @@ -0,0 +1,108 @@ +// Copyright 2019-2022 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonbeam is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonbeam. If not, see . + +use { + crate::EvmResult, + pallet_evm::{Log, PrecompileHandle}, + sp_core::{H160, H256}, + sp_std::{vec, vec::Vec}, +}; + +/// Create a 0-topic log. +#[must_use] +pub fn log0(address: impl Into, data: impl Into>) -> Log { + Log { + address: address.into(), + topics: vec![], + data: data.into(), + } +} + +/// Create a 1-topic log. +#[must_use] +pub fn log1(address: impl Into, topic0: impl Into, data: impl Into>) -> Log { + Log { + address: address.into(), + topics: vec![topic0.into()], + data: data.into(), + } +} + +/// Create a 2-topics log. +#[must_use] +pub fn log2( + address: impl Into, + topic0: impl Into, + topic1: impl Into, + data: impl Into>, +) -> Log { + Log { + address: address.into(), + topics: vec![topic0.into(), topic1.into()], + data: data.into(), + } +} + +/// Create a 3-topics log. +#[must_use] +pub fn log3( + address: impl Into, + topic0: impl Into, + topic1: impl Into, + topic2: impl Into, + data: impl Into>, +) -> Log { + Log { + address: address.into(), + topics: vec![topic0.into(), topic1.into(), topic2.into()], + data: data.into(), + } +} + +/// Create a 4-topics log. +#[must_use] +pub fn log4( + address: impl Into, + topic0: impl Into, + topic1: impl Into, + topic2: impl Into, + topic3: impl Into, + data: impl Into>, +) -> Log { + Log { + address: address.into(), + topics: vec![topic0.into(), topic1.into(), topic2.into(), topic3.into()], + data: data.into(), + } +} + +/// Extension trait allowing to record logs into a PrecompileHandle. +pub trait LogExt { + fn record(self, handle: &mut impl PrecompileHandle) -> EvmResult; + + fn compute_cost(&self) -> EvmResult; +} + +impl LogExt for Log { + fn record(self, handle: &mut impl PrecompileHandle) -> EvmResult { + handle.log(self.address, self.topics, self.data)?; + Ok(()) + } + + fn compute_cost(&self) -> EvmResult { + crate::evm::costs::log_costs(self.topics.len(), self.data.len()) + } +} diff --git a/precompiles/utils/src/evm/mod.rs b/precompiles/utils/src/evm/mod.rs new file mode 100644 index 0000000000..e54f90eb39 --- /dev/null +++ b/precompiles/utils/src/evm/mod.rs @@ -0,0 +1,19 @@ +// Copyright 2019-2023 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonbeam is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonbeam. If not, see . + +pub mod costs; +pub mod logs; +pub mod handle; diff --git a/precompiles/utils/src/lib.rs b/precompiles/utils/src/lib.rs index f36412a21b..15256ab373 100644 --- a/precompiles/utils/src/lib.rs +++ b/precompiles/utils/src/lib.rs @@ -36,14 +36,20 @@ use frame_support::{ use pallet_evm::{GasWeightMapping, Log}; use sp_core::{H160, H256, U256}; use sp_std::{marker::PhantomData, vec, vec::Vec}; +pub use alloc::string::String; -mod data; +pub mod bytes; +pub mod data; +pub mod evm; +pub mod precompile_set; +pub mod substrate; +pub mod testing; pub use data::{Address, Bytes, EvmData, EvmDataReader, EvmDataWriter}; pub use precompile_utils_macro::{generate_function_selector, keccak256}; #[cfg(feature = "testing")] -pub mod testing; +pub mod old_testing; #[cfg(test)] mod tests; @@ -363,7 +369,7 @@ pub fn succeed(output: impl AsRef<[u8]>) -> PrecompileOutput { #[must_use] /// Check that a function call is compatible with the context it is /// called into. -fn check_function_modifier( +pub fn check_function_modifier( context: &Context, is_static: bool, modifier: FunctionModifier, diff --git a/precompiles/utils/src/testing.rs b/precompiles/utils/src/old_testing.rs similarity index 99% rename from precompiles/utils/src/testing.rs rename to precompiles/utils/src/old_testing.rs index 0f5f55ec35..3c45678657 100644 --- a/precompiles/utils/src/testing.rs +++ b/precompiles/utils/src/old_testing.rs @@ -25,6 +25,7 @@ use fp_evm::{ ExitReason, ExitSucceed, PrecompileOutput, PrecompileResult, PrecompileSet, Transfer, }; use sp_std::boxed::Box; +extern crate evm; pub struct Subcall { pub address: H160, diff --git a/precompiles/utils/src/precompile_set.rs b/precompiles/utils/src/precompile_set.rs new file mode 100644 index 0000000000..e3eb53905c --- /dev/null +++ b/precompiles/utils/src/precompile_set.rs @@ -0,0 +1,1079 @@ +// Copyright 2019-2022 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonbeam is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonbeam. If not, see . + +//! Provide utils to assemble precompiles and precompilesets into a +//! final precompile set with security checks. All security checks are enabled by +//! default and must be disabled explicely throught type annotations. + +use crate::{ + revert,String, + substrate::RuntimeHelper, + EvmResult, +}; +use fp_evm::{ + ExitError, IsPrecompileResult, Precompile, PrecompileFailure, PrecompileHandle, + PrecompileResult, PrecompileSet, +}; +use frame_support::pallet_prelude::Get; +use impl_trait_for_tuples::impl_for_tuples; +use pallet_evm::AddressMapping; +use sp_core::{H160, H256}; +use sp_std::{ + cell::RefCell, collections::btree_map::BTreeMap, marker::PhantomData, ops::RangeInclusive, vec, + vec::Vec, +}; + +/// Trait representing checks that can be made on a precompile call. +/// Types implementing this trait are made to be chained in a tuple. +/// +/// For that reason every method returns an Option, None meaning that +/// the implementor have no constraint and the decision is left to +/// latter elements in the chain. If None is returned by all elements of +/// the chain then sensible defaults are used. +/// +/// Both `PrecompileAt` and `PrecompileSetStartingWith` have a type parameter that must +/// implement this trait to configure the checks of the precompile(set) it represents. +pub trait PrecompileChecks { + #[inline(always)] + /// Is there a limit to the amount of recursions this precompile + /// can make using subcalls? 0 means this specific precompile will not + /// be callable as a subcall of itself, 1 will allow one level of recursion, + /// etc... + /// + /// If all checks return None, defaults to `Some(0)` (no recursion allowed). + fn recursion_limit() -> Option> { + None + } + + #[inline(always)] + /// Does this precompile supports being called with DELEGATECALL or CALLCODE? + /// + /// If all checks return None, defaults to `false`. + fn accept_delegate_call() -> Option { + None + } + + #[inline(always)] + /// Is this precompile callable by a smart contract? + /// + /// If all checks return None, defaults to `false`. + fn callable_by_smart_contract(_caller: H160, _called_selector: Option) -> Option { + None + } + + #[inline(always)] + /// Is this precompile callable by a precompile? + /// + /// If all checks return None, defaults to `false`. + fn callable_by_precompile(_caller: H160, _called_selector: Option) -> Option { + None + } + + #[inline(always)] + /// Is this precompile able to do subcalls? + /// + /// If all checks return None, defaults to `false`. + fn allow_subcalls() -> Option { + None + } + + /// Summarize the checks when being called by a smart contract. + fn callable_by_smart_contract_summary() -> Option { + None + } + + /// Summarize the checks when being called by a precompile. + fn callable_by_precompile_summary() -> Option { + None + } +} + +#[derive(Debug, Clone)] +pub enum DiscriminantResult { + Some(T, u64), + None(u64), + OutOfGas, +} + +impl Into for DiscriminantResult { + fn into(self) -> IsPrecompileResult { + match self { + Self::Some(_, extra_cost) => IsPrecompileResult::Answer { + is_precompile: true, + extra_cost, + }, + Self::None(extra_cost) => IsPrecompileResult::Answer { + is_precompile: false, + extra_cost, + }, + Self::OutOfGas => IsPrecompileResult::OutOfGas, + } + } +} + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "testing", derive(serde::Serialize, serde::Deserialize))] +pub enum PrecompileKind { + Single(H160), + Prefixed(Vec), +} + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "testing", derive(serde::Serialize, serde::Deserialize))] +pub struct PrecompileCheckSummary { + pub name: Option, + pub precompile_kind: PrecompileKind, + pub recursion_limit: Option, + pub accept_delegate_call: bool, + pub callable_by_smart_contract: String, + pub callable_by_precompile: String, +} + +#[impl_for_tuples(0, 20)] +impl PrecompileChecks for Tuple { + #[inline(always)] + fn recursion_limit() -> Option> { + for_tuples!(#( + if let Some(check) = Tuple::recursion_limit() { + return Some(check); + } + )*); + + None + } + + #[inline(always)] + fn accept_delegate_call() -> Option { + for_tuples!(#( + if let Some(check) = Tuple::accept_delegate_call() { + return Some(check); + } + )*); + + None + } + + #[inline(always)] + fn callable_by_smart_contract(caller: H160, called_selector: Option) -> Option { + for_tuples!(#( + if let Some(check) = Tuple::callable_by_smart_contract(caller, called_selector) { + return Some(check); + } + )*); + + None + } + + #[inline(always)] + fn callable_by_precompile(caller: H160, called_selector: Option) -> Option { + for_tuples!(#( + if let Some(check) = Tuple::callable_by_precompile(caller, called_selector) { + return Some(check); + } + )*); + + None + } + + #[inline(always)] + fn allow_subcalls() -> Option { + for_tuples!(#( + if let Some(check) = Tuple::allow_subcalls() { + return Some(check); + } + )*); + + None + } + + fn callable_by_smart_contract_summary() -> Option { + for_tuples!(#( + if let Some(check) = Tuple::callable_by_smart_contract_summary() { + return Some(check); + } + )*); + + None + } + + fn callable_by_precompile_summary() -> Option { + for_tuples!(#( + if let Some(check) = Tuple::callable_by_precompile_summary() { + return Some(check); + } + )*); + + None + } +} + +/// Precompile can be called using DELEGATECALL/CALLCODE. +pub struct AcceptDelegateCall; + +impl PrecompileChecks for AcceptDelegateCall { + #[inline(always)] + fn accept_delegate_call() -> Option { + Some(true) + } +} + +/// Precompile is able to do subcalls with provided nesting limit. +pub struct SubcallWithMaxNesting; + +impl PrecompileChecks for SubcallWithMaxNesting { + #[inline(always)] + fn recursion_limit() -> Option> { + Some(Some(R)) + } + + #[inline(always)] + fn allow_subcalls() -> Option { + Some(true) + } +} + +pub trait SelectorFilter { + fn is_allowed(_caller: H160, _selector: Option) -> bool; + + fn description() -> String; +} +pub struct ForAllSelectors; +impl SelectorFilter for ForAllSelectors { + fn is_allowed(_caller: H160, _selector: Option) -> bool { + true + } + + fn description() -> String { + "Allowed for all selectors and callers".into() + } +} + +pub struct OnlyFrom(PhantomData); +impl> SelectorFilter for OnlyFrom { + fn is_allowed(caller: H160, _selector: Option) -> bool { + caller == T::get() + } + + fn description() -> String { + alloc::format!("Allowed for all selectors only if called from {}", T::get()) + } +} + +pub struct CallableByContract(PhantomData); + +impl PrecompileChecks for CallableByContract { + #[inline(always)] + fn callable_by_smart_contract(caller: H160, called_selector: Option) -> Option { + Some(T::is_allowed(caller, called_selector)) + } + + fn callable_by_smart_contract_summary() -> Option { + Some(T::description()) + } +} + +/// Precompiles are allowed to call this precompile. +pub struct CallableByPrecompile(PhantomData); + +impl PrecompileChecks for CallableByPrecompile { + #[inline(always)] + fn callable_by_precompile(caller: H160, called_selector: Option) -> Option { + Some(T::is_allowed(caller, called_selector)) + } + + fn callable_by_precompile_summary() -> Option { + Some(T::description()) + } +} + +/// The type of EVM address. +#[derive(PartialEq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub enum AddressType { + /// The code stored at the address is less than 5 bytes, but not well known. + Unknown, + /// No code is stored at the address, therefore is EOA. + EOA, + /// The 5-byte magic constant for a precompile is stored at the address. + Precompile, + /// The code is greater than 5-bytes, potentially a Smart Contract. + Contract, +} + +/// Retrieves the type of address demarcated by `AddressType`. +pub fn get_address_type(address: H160) -> AddressType { + let code_len = pallet_evm::AccountCodes::::decode_len(address).unwrap_or(0); + + // 0 => either EOA or precompile without dummy code + if code_len == 0 { + return AddressType::EOA; + } + + // dummy code is 5 bytes long, so any other len means it is a contract. + if code_len != 5 { + return AddressType::Contract; + } + + // check code matches dummy code + let code = pallet_evm::AccountCodes::::get(address); + if &code == &[0x60, 0x00, 0x60, 0x00, 0xfd] { + return AddressType::Precompile; + } + + AddressType::Unknown +} + +fn is_address_eoa_or_precompile(address: H160) -> bool { + match get_address_type::(address) { + AddressType::EOA | AddressType::Precompile => true, + _ => false, + } +} + +/// Common checks for precompile and precompile sets. +/// Don't contain recursion check as precompile sets have recursion check for each member. +fn common_checks( + handle: &mut impl PrecompileHandle, +) -> EvmResult<()> { + let code_address = handle.code_address(); + let caller = handle.context().caller; + + // Check DELEGATECALL config. + let accept_delegate_call = C::accept_delegate_call().unwrap_or(false); + if !accept_delegate_call && code_address != handle.context().address { + return Err(revert("Cannot be called with DELEGATECALL or CALLCODE")); + } + + // Extract which selector is called. + let selector = handle.input().get(0..4).map(|bytes| { + let mut buffer = [0u8; 4]; + buffer.copy_from_slice(bytes); + u32::from_be_bytes(buffer) + }); + + // Is this selector callable from a smart contract? + let callable_by_smart_contract = + C::callable_by_smart_contract(caller, selector).unwrap_or(false); + if !callable_by_smart_contract { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + if !is_address_eoa_or_precompile::(caller) { + return Err(revert("Function not callable by smart contracts")); + } + } + + // Is this selector callable from a precompile? + let callable_by_precompile = C::callable_by_precompile(caller, selector).unwrap_or(false); + if !callable_by_precompile && is_precompile_or_fail::(caller, handle.remaining_gas())? { + return Err(revert("Function not callable by precompiles")); + } + + Ok(()) +} + +pub fn is_precompile_or_fail(address: H160, gas: u64) -> EvmResult { + match ::PrecompilesValue::get().is_precompile(address, gas) { + IsPrecompileResult::Answer { is_precompile, .. } => Ok(is_precompile), + IsPrecompileResult::OutOfGas => Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }), + } +} + +pub struct AddressU64; +impl Get for AddressU64 { + #[inline(always)] + fn get() -> H160 { + H160::from_low_u64_be(N) + } +} + +pub struct RestrictiveHandle<'a, H> { + handle: &'a mut H, + allow_subcalls: bool, +} + +impl<'a, H: PrecompileHandle> PrecompileHandle for RestrictiveHandle<'a, H> { + fn call( + &mut self, + address: H160, + transfer: Option, + input: Vec, + target_gas: Option, + is_static: bool, + context: &evm::Context, + ) -> (evm::ExitReason, Vec) { + if !self.allow_subcalls { + return ( + evm::ExitReason::Revert(evm::ExitRevert::Reverted), + String::from("subcalls disabled for this precompile").as_bytes().to_vec(), + ); + } + + self.handle + .call(address, transfer, input, target_gas, is_static, context) + } + + fn record_cost(&mut self, cost: u64) -> Result<(), evm::ExitError> { + self.handle.record_cost(cost) + } + + fn remaining_gas(&self) -> u64 { + self.handle.remaining_gas() + } + + fn log( + &mut self, + address: H160, + topics: Vec, + data: Vec, + ) -> Result<(), evm::ExitError> { + self.handle.log(address, topics, data) + } + + fn code_address(&self) -> H160 { + self.handle.code_address() + } + + fn input(&self) -> &[u8] { + self.handle.input() + } + + fn context(&self) -> &evm::Context { + self.handle.context() + } + + fn is_static(&self) -> bool { + self.handle.is_static() + } + + fn gas_limit(&self) -> Option { + self.handle.gas_limit() + } +} + +/// Allows to know if a precompile is active or not. +/// This allows to detect deactivated precompile, that are still considered precompiles by +/// the EVM but that will always revert when called. +pub trait IsActivePrecompile { + /// Is the provided address an active precompile, a precompile that has + /// not be deactivated. Note that a deactivated precompile is still considered a precompile + /// for the EVM, but it will always revert when called. + fn is_active_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult; +} + +// INDIVIDUAL PRECOMPILE(SET) + +/// A fragment of a PrecompileSet. Should be implemented as is it +/// was a PrecompileSet containing only the precompile(set) it wraps. +/// They can be combined into a real PrecompileSet using `PrecompileSetBuilder`. +pub trait PrecompileSetFragment { + /// Instanciate the fragment. + fn new() -> Self; + + /// Execute the fragment. + fn execute( + &self, + handle: &mut impl PrecompileHandle, + ) -> Option; + + /// Is the provided address a precompile in this fragment? + fn is_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult; + + /// Return the list of addresses covered by this fragment. + fn used_addresses(&self) -> Vec; + + /// Summarize + fn summarize_checks(&self) -> Vec; +} + +/// Wraps a stateless precompile: a type implementing the `Precompile` trait. +/// Type parameters allow to define: +/// - A: The address of the precompile +/// - R: The recursion limit (defaults to 1) +/// - D: If DELEGATECALL is supported (default to no) +pub struct PrecompileAt { + current_recursion_level: RefCell, + _phantom: PhantomData<(A, P, C)>, +} + +impl PrecompileSetFragment for PrecompileAt +where + A: Get, + P: Precompile, + C: PrecompileChecks, +{ + #[inline(always)] + fn new() -> Self { + Self { + current_recursion_level: RefCell::new(0), + _phantom: PhantomData, + } + } + + #[inline(always)] + fn execute( + &self, + handle: &mut impl PrecompileHandle, + ) -> Option { + let code_address = handle.code_address(); + + // Check if this is the address of the precompile. + if A::get() != code_address { + return None; + } + + // Perform common checks. + if let Err(err) = common_checks::(handle) { + return Some(Err(err)); + } + + // Check and increase recursion level if needed. + let recursion_limit = C::recursion_limit().unwrap_or(Some(0)); + if let Some(max_recursion_level) = recursion_limit { + match self.current_recursion_level.try_borrow_mut() { + Ok(mut recursion_level) => { + if *recursion_level > max_recursion_level { + return Some(Err( + revert("Precompile is called with too high nesting").into() + )); + } + + *recursion_level += 1; + } + // We don't hold the borrow and are in single-threaded code, thus we should + // not be able to fail borrowing in nested calls. + Err(_) => return Some(Err(revert("Couldn't check precompile nesting").into())), + } + } + + // Subcall protection. + let allow_subcalls = C::allow_subcalls().unwrap_or(false); + let mut handle = RestrictiveHandle { + handle, + allow_subcalls, + }; + + let res = P::execute(&mut handle); + + // Decrease recursion level if needed. + if recursion_limit.is_some() { + match self.current_recursion_level.try_borrow_mut() { + Ok(mut recursion_level) => { + *recursion_level -= 1; + } + // We don't hold the borrow and are in single-threaded code, thus we should + // not be able to fail borrowing in nested calls. + Err(_) => return Some(Err(revert("Couldn't check precompile nesting").into())), + } + } + + Some(res) + } + + #[inline(always)] + fn is_precompile(&self, address: H160, _gas: u64) -> IsPrecompileResult { + IsPrecompileResult::Answer { + is_precompile: address == A::get(), + extra_cost: 0, + } + } + + #[inline(always)] + fn used_addresses(&self) -> Vec { + vec![A::get()] + } + + fn summarize_checks(&self) -> Vec { + vec![PrecompileCheckSummary { + name: None, + precompile_kind: PrecompileKind::Single(A::get()), + recursion_limit: C::recursion_limit().unwrap_or(Some(0)), + accept_delegate_call: C::accept_delegate_call().unwrap_or(false), + callable_by_smart_contract: C::callable_by_smart_contract_summary() + .unwrap_or_else(|| "Not callable".into()), + callable_by_precompile: C::callable_by_precompile_summary() + .unwrap_or_else(|| "Not callable".into()), + }] + } +} + +impl IsActivePrecompile for PrecompileAt +where + A: Get, +{ + #[inline(always)] + fn is_active_precompile(&self, address: H160, _gas: u64) -> IsPrecompileResult { + IsPrecompileResult::Answer { + is_precompile: address == A::get(), + extra_cost: 0, + } + } +} + +/// Wraps an inner PrecompileSet with all its addresses starting with +/// a common prefix. +/// Type parameters allow to define: +/// - A: The common prefix +/// - D: If DELEGATECALL is supported (default to no) +pub struct PrecompileSetStartingWith { + precompile_set: P, + current_recursion_level: RefCell>, + _phantom: PhantomData<(A, C)>, +} + +impl PrecompileSetFragment for PrecompileSetStartingWith +where + A: Get<&'static [u8]>, + P: PrecompileSet + Default, + C: PrecompileChecks, +{ + #[inline(always)] + fn new() -> Self { + Self { + precompile_set: P::default(), + current_recursion_level: RefCell::new(BTreeMap::new()), + _phantom: PhantomData, + } + } + + #[inline(always)] + fn execute( + &self, + handle: &mut impl PrecompileHandle, + ) -> Option { + let code_address = handle.code_address(); + if !is_precompile_or_fail::(code_address, handle.remaining_gas()).ok()? { + return None; + } + // Perform common checks. + if let Err(err) = common_checks::(handle) { + return Some(Err(err)); + } + + // Check and increase recursion level if needed. + let recursion_limit = C::recursion_limit().unwrap_or(Some(0)); + if let Some(max_recursion_level) = recursion_limit { + match self.current_recursion_level.try_borrow_mut() { + Ok(mut recursion_level_map) => { + let recursion_level = recursion_level_map.entry(code_address).or_insert(0); + + if *recursion_level > max_recursion_level { + return Some(Err(revert("Precompile is called with too high nesting"))); + } + + *recursion_level += 1; + } + // We don't hold the borrow and are in single-threaded code, thus we should + // not be able to fail borrowing in nested calls. + Err(_) => return Some(Err(revert("Couldn't check precompile nesting"))), + } + } + + // Subcall protection. + let allow_subcalls = C::allow_subcalls().unwrap_or(false); + let mut handle = RestrictiveHandle { + handle, + allow_subcalls, + }; + + let res = self.precompile_set.execute(&mut handle); + + // Decrease recursion level if needed. + if recursion_limit.is_some() { + match self.current_recursion_level.try_borrow_mut() { + Ok(mut recursion_level_map) => { + let recursion_level = match recursion_level_map.get_mut(&code_address) { + Some(recursion_level) => recursion_level, + None => return Some(Err(revert("Couldn't retreive precompile nesting"))), + }; + + *recursion_level -= 1; + } + // We don't hold the borrow and are in single-threaded code, thus we should + // not be able to fail borrowing in nested calls. + Err(_) => return Some(Err(revert("Couldn't check precompile nesting"))), + } + } + + res + } + + #[inline(always)] + fn is_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult { + if address.as_bytes().starts_with(A::get()) { + return self.precompile_set.is_precompile(address, gas); + } + IsPrecompileResult::Answer { + is_precompile: false, + extra_cost: 0, + } + } + + #[inline(always)] + fn used_addresses(&self) -> Vec { + // TODO: We currently can't get the list of used addresses. + vec![] + } + + fn summarize_checks(&self) -> Vec { + let prefix = A::get(); + + vec![PrecompileCheckSummary { + name: None, + precompile_kind: PrecompileKind::Prefixed(prefix.to_vec()), + recursion_limit: C::recursion_limit().unwrap_or(Some(0)), + accept_delegate_call: C::accept_delegate_call().unwrap_or(false), + callable_by_smart_contract: C::callable_by_smart_contract_summary() + .unwrap_or_else(|| "Not callable".into()), + callable_by_precompile: C::callable_by_precompile_summary() + .unwrap_or_else(|| "Not callable".into()), + }] + } +} + +impl IsActivePrecompile for PrecompileSetStartingWith +where + Self: PrecompileSetFragment, +{ + #[inline(always)] + fn is_active_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult { + self.is_precompile(address, gas) + } +} + +/// Make a precompile that always revert. +/// Can be useful when writing tests. +pub struct RevertPrecompile(PhantomData); + +impl PrecompileSetFragment for RevertPrecompile +where + A: Get, +{ + #[inline(always)] + fn new() -> Self { + Self(PhantomData) + } + + #[inline(always)] + fn execute( + &self, + handle: &mut impl PrecompileHandle, + ) -> Option { + if A::get() == handle.code_address() { + Some(Err(revert("revert"))) + } else { + None + } + } + + #[inline(always)] + fn is_precompile(&self, address: H160, _gas: u64) -> IsPrecompileResult { + IsPrecompileResult::Answer { + is_precompile: address == A::get(), + extra_cost: 0, + } + } + + #[inline(always)] + fn used_addresses(&self) -> Vec { + vec![A::get()] + } + + fn summarize_checks(&self) -> Vec { + vec![PrecompileCheckSummary { + name: None, + precompile_kind: PrecompileKind::Single(A::get()), + recursion_limit: Some(0), + accept_delegate_call: true, + callable_by_smart_contract: "Reverts in all cases".into(), + callable_by_precompile: "Reverts in all cases".into(), + }] + } +} + +impl IsActivePrecompile for RevertPrecompile { + #[inline(always)] + fn is_active_precompile(&self, _address: H160, _gas: u64) -> IsPrecompileResult { + IsPrecompileResult::Answer { + is_precompile: true, + extra_cost: 0, + } + } +} + +/// A precompile that was removed from a precompile set. +/// Still considered a precompile but is inactive and always revert. +pub struct RemovedPrecompileAt(PhantomData); +impl PrecompileSetFragment for RemovedPrecompileAt +where + A: Get, +{ + #[inline(always)] + fn new() -> Self { + Self(PhantomData) + } + + #[inline(always)] + fn execute( + &self, + handle: &mut impl PrecompileHandle, + ) -> Option { + if A::get() == handle.code_address() { + Some(Err(revert("Removed precompile"))) + } else { + None + } + } + + #[inline(always)] + fn is_precompile(&self, address: H160, _gas: u64) -> IsPrecompileResult { + IsPrecompileResult::Answer { + is_precompile: address == A::get(), + extra_cost: 0, + } + } + + #[inline(always)] + fn used_addresses(&self) -> Vec { + vec![A::get()] + } + + fn summarize_checks(&self) -> Vec { + vec![PrecompileCheckSummary { + name: None, + precompile_kind: PrecompileKind::Single(A::get()), + recursion_limit: Some(0), + accept_delegate_call: true, + callable_by_smart_contract: "Reverts in all cases".into(), + callable_by_precompile: "Reverts in all cases".into(), + }] + } +} + +impl IsActivePrecompile for RemovedPrecompileAt { + #[inline(always)] + fn is_active_precompile(&self, _address: H160, _gas: u64) -> IsPrecompileResult { + IsPrecompileResult::Answer { + is_precompile: false, + extra_cost: 0, + } + } +} + +// COMPOSITION OF PARTS +#[impl_for_tuples(1, 100)] +impl PrecompileSetFragment for Tuple { + #[inline(always)] + fn new() -> Self { + (for_tuples!(#( + Tuple::new() + ),*)) + } + + #[inline(always)] + fn execute( + &self, + handle: &mut impl PrecompileHandle, + ) -> Option { + for_tuples!(#( + if let Some(res) = self.Tuple.execute::(handle) { + return Some(res); + } + )*); + + None + } + + #[inline(always)] + fn is_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult { + for_tuples!(#( + match self.Tuple.is_precompile(address, gas) { + IsPrecompileResult::Answer { + is_precompile: true, + .. + } => return IsPrecompileResult::Answer { + is_precompile: true, + extra_cost: 0, + }, + _ => {} + }; + )*); + IsPrecompileResult::Answer { + is_precompile: false, + extra_cost: 0, + } + } + + #[inline(always)] + fn used_addresses(&self) -> Vec { + let mut used_addresses = vec![]; + + for_tuples!(#( + let mut inner = self.Tuple.used_addresses(); + used_addresses.append(&mut inner); + )*); + + used_addresses + } + + fn summarize_checks(&self) -> Vec { + let mut checks = Vec::new(); + + for_tuples!(#( + let mut inner = self.Tuple.summarize_checks(); + checks.append(&mut inner); + )*); + + checks + } +} + +#[impl_for_tuples(1, 100)] +impl IsActivePrecompile for Tuple { + #[inline(always)] + fn is_active_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult { + for_tuples!(#( + match self.Tuple.is_active_precompile(address, gas) { + IsPrecompileResult::Answer { + is_precompile: true, + .. + } => return IsPrecompileResult::Answer { + is_precompile: true, + extra_cost: 0, + }, + _ => {} + }; + )*); + IsPrecompileResult::Answer { + is_precompile: false, + extra_cost: 0, + } + } +} + +/// Wraps a precompileset fragment into a range, and will skip processing it if the address +/// is out of the range. +pub struct PrecompilesInRangeInclusive { + inner: P, + range: RangeInclusive, + _phantom: PhantomData, +} + +impl PrecompileSetFragment for PrecompilesInRangeInclusive<(S, E), P> +where + S: Get, + E: Get, + P: PrecompileSetFragment, +{ + fn new() -> Self { + Self { + inner: P::new(), + range: RangeInclusive::new(S::get(), E::get()), + _phantom: PhantomData, + } + } + + fn execute( + &self, + handle: &mut impl PrecompileHandle, + ) -> Option { + if self.range.contains(&handle.code_address()) { + self.inner.execute::(handle) + } else { + None + } + } + + fn is_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult { + if self.range.contains(&address) { + self.inner.is_precompile(address, gas) + } else { + IsPrecompileResult::Answer { + is_precompile: false, + extra_cost: 0, + } + } + } + + fn used_addresses(&self) -> Vec { + self.inner.used_addresses() + } + + fn summarize_checks(&self) -> Vec { + self.inner.summarize_checks() + } +} + +impl IsActivePrecompile for PrecompilesInRangeInclusive<(S, E), P> +where + P: IsActivePrecompile, +{ + fn is_active_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult { + if self.range.contains(&address) { + self.inner.is_active_precompile(address, gas) + } else { + IsPrecompileResult::Answer { + is_precompile: false, + extra_cost: 0, + } + } + } +} + +/// Wraps a tuple of `PrecompileSetFragment` to make a real `PrecompileSet`. +pub struct PrecompileSetBuilder { + inner: P, + _phantom: PhantomData, +} + +impl PrecompileSet for PrecompileSetBuilder { + fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { + self.inner.execute::(handle) + } + + fn is_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult { + self.inner.is_precompile(address, gas) + } +} + +impl IsActivePrecompile for PrecompileSetBuilder { + fn is_active_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult { + self.inner.is_active_precompile(address, gas) + } +} + +impl PrecompileSetBuilder { + /// Create a new instance of the PrecompileSet. + pub fn new() -> Self { + Self { + inner: P::new(), + _phantom: PhantomData, + } + } + + /// Return the list of addresses contained in this PrecompileSet. + pub fn used_addresses() -> impl Iterator { + Self::new() + .inner + .used_addresses() + .into_iter() + .map(|x| R::AddressMapping::into_account_id(x)) + } + + pub fn summarize_checks(&self) -> Vec { + self.inner.summarize_checks() + } +} diff --git a/precompiles/utils/src/substrate.rs b/precompiles/utils/src/substrate.rs new file mode 100644 index 0000000000..7404a42a87 --- /dev/null +++ b/precompiles/utils/src/substrate.rs @@ -0,0 +1,119 @@ +// Copyright 2019-2022 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonbeam is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonbeam. If not, see . + +//! Utils related to Substrate features: +//! - Substrate call dispatch. +//! - Substrate DB read and write costs + +use { + crate::{evm::handle::using_precompile_handle,revert}, + core::marker::PhantomData, + fp_evm::{ExitError, PrecompileFailure, PrecompileHandle}, + frame_support::{ + dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, + pallet_prelude::DispatchError, + traits::Get, + }, + pallet_evm::GasWeightMapping, +}; + +#[derive(Debug)] +pub enum TryDispatchError { + Evm(ExitError), + Substrate(DispatchError), +} + +impl From for PrecompileFailure { + fn from(f: TryDispatchError) -> PrecompileFailure { + match f { + TryDispatchError::Evm(e) => PrecompileFailure::Error { exit_status: e }, + TryDispatchError::Substrate(e) => { + revert(alloc::format!("Dispatched call failed with error: {e:?}")) + } + } + } +} + +/// Helper functions requiring a Substrate runtime. +/// This runtime must of course implement `pallet_evm::Config`. +#[derive(Clone, Copy, Debug)] +pub struct RuntimeHelper(PhantomData); + +impl RuntimeHelper +where + Runtime: pallet_evm::Config, + Runtime::RuntimeCall: Dispatchable + GetDispatchInfo, +{ + /// Try to dispatch a Substrate call. + /// Return an error if there are not enough gas, or if the call fails. + /// If successful returns the used gas using the Runtime GasWeightMapping. + pub fn try_dispatch( + handle: &mut impl PrecompileHandle, + origin: ::RuntimeOrigin, + call: Call, + ) -> Result + where + Runtime::RuntimeCall: From, + { + let call = Runtime::RuntimeCall::from(call); + let dispatch_info = call.get_dispatch_info(); + + // Make sure there is enough gas. + let remaining_gas = handle.remaining_gas(); + let required_gas = Runtime::GasWeightMapping::weight_to_gas(dispatch_info.weight); + if required_gas > remaining_gas { + return Err(TryDispatchError::Evm(ExitError::OutOfGas)); + } + + // Dispatch call. + // It may be possible to not record gas cost if the call returns Pays::No. + // However while Substrate handle checking weight while not making the sender pay for it, + // the EVM doesn't. It seems this safer to always record the costs to avoid unmetered + // computations. + let post_dispatch_info = using_precompile_handle(handle, || call.dispatch(origin)) + .map_err(|e| TryDispatchError::Substrate(e.error))?; + + let used_weight = post_dispatch_info.actual_weight; + + let used_gas = + Runtime::GasWeightMapping::weight_to_gas(used_weight.unwrap_or(dispatch_info.weight)); + + handle + .record_cost(used_gas) + .map_err(|e| TryDispatchError::Evm(e))?; + + Ok(post_dispatch_info) + } +} + +impl RuntimeHelper +where + Runtime: pallet_evm::Config, +{ + /// Cost of a Substrate DB write in gas. + pub fn db_write_gas_cost() -> u64 { + ::GasWeightMapping::weight_to_gas( + ::DbWeight::get().writes(1), + ) + } + + /// Cost of a Substrate DB read in gas. + pub fn db_read_gas_cost() -> u64 { + ::GasWeightMapping::weight_to_gas( + ::DbWeight::get().reads(1), + ) + } +} diff --git a/precompiles/utils/src/testing/account.rs b/precompiles/utils/src/testing/account.rs new file mode 100644 index 0000000000..a75886cb8f --- /dev/null +++ b/precompiles/utils/src/testing/account.rs @@ -0,0 +1,181 @@ +// Copyright 2019-2022 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonbeam is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonbeam. If not, see . + +use { + pallet_evm::AddressMapping, + scale_info::TypeInfo, + serde::{Deserialize, Serialize}, + sp_core::{Decode, Encode, MaxEncodedLen, H160, H256}, +}; + +#[derive( + Eq, + PartialEq, + Ord, + PartialOrd, + Clone, + Encode, + Decode, + Debug, + MaxEncodedLen, + TypeInfo, + Serialize, + Deserialize, + derive_more::Display, +)] +pub struct MockAccount(pub H160); + +impl MockAccount { + pub fn from_u64(v: u64) -> Self { + H160::from_low_u64_be(v).into() + } + + pub fn zero() -> Self { + H160::zero().into() + } + + pub fn has_prefix(&self, prefix: &[u8]) -> bool { + &self.0[0..4] == prefix + } + + pub fn has_prefix_u32(&self, prefix: u32) -> bool { + self.0[0..4] == prefix.to_be_bytes() + } + + pub fn without_prefix(&self) -> u128 { + u128::from_be_bytes(<[u8; 16]>::try_from(&self.0[4..20]).expect("slice have len 16")) + } +} + +impl From for H160 { + fn from(account: MockAccount) -> H160 { + account.0 + } +} + +impl From for [u8; 20] { + fn from(account: MockAccount) -> [u8; 20] { + let x: H160 = account.into(); + x.into() + } +} + +impl From for H256 { + fn from(x: MockAccount) -> H256 { + let x: H160 = x.into(); + x.into() + } +} + +impl From for MockAccount { + fn from(address: H160) -> MockAccount { + MockAccount(address) + } +} + +impl From<[u8; 20]> for MockAccount { + fn from(address: [u8; 20]) -> MockAccount { + let x: H160 = address.into(); + MockAccount(x) + } +} + +impl AddressMapping for MockAccount { + fn into_account_id(address: H160) -> MockAccount { + address.into() + } +} + +#[macro_export] +macro_rules! mock_account { + ($name:ident, $convert:expr) => { + pub struct $name; + mock_account!(# $name, $convert); + }; + ($name:ident ( $($field:ty),* ), $convert:expr) => { + pub struct $name($(pub $field),*); + mock_account!(# $name, $convert); + }; + (# $name:ident, $convert:expr) => { + impl From<$name> for MockAccount { + fn from(value: $name) -> MockAccount { + $convert(value) + } + } + + impl From<$name> for sp_core::H160 { + fn from(value: $name) -> sp_core::H160 { + MockAccount::from(value).into() + } + } + + impl From<$name> for sp_core::H256 { + fn from(value: $name) -> sp_core::H256 { + MockAccount::from(value).into() + } + } + }; +} + +mock_account!(Zero, |_| MockAccount::zero()); +mock_account!(Alice, |_| H160::repeat_byte(0xAA).into()); +mock_account!(Bob, |_| H160::repeat_byte(0xBB).into()); +mock_account!(Charlie, |_| H160::repeat_byte(0xCC).into()); +mock_account!(David, |_| H160::repeat_byte(0xDD).into()); + +mock_account!(Precompile1, |_| MockAccount::from_u64(1)); + +mock_account!(CryptoAlith, |_| H160::from(hex_literal::hex!( + "f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac" +)) +.into()); +mock_account!(CryptoBaltathar, |_| H160::from(hex_literal::hex!( + "3Cd0A705a2DC65e5b1E1205896BaA2be8A07c6e0" +)) +.into()); +mock_account!(CryptoCarleth, |_| H160::from(hex_literal::hex!( + "798d4Ba9baf0064Ec19eB4F0a1a45785ae9D6DFc" +)) +.into()); + +mock_account!( + AddressInPrefixedSet(u32, u128), + |value: AddressInPrefixedSet| { + let prefix: u32 = value.0; + let index: u128 = value.1; + + let mut buffer = Vec::with_capacity(20); // 160 bits + + buffer.extend_from_slice(&prefix.to_be_bytes()); + buffer.extend_from_slice(&index.to_be_bytes()); + + assert_eq!(buffer.len(), 20, "address buffer should have len of 20"); + + H160::from_slice(&buffer).into() + } +); + +pub fn alith_secret_key() -> [u8; 32] { + hex_literal::hex!("5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133") +} + +pub fn baltathar_secret_key() -> [u8; 32] { + hex_literal::hex!("8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b") +} + +pub fn charleth_secret_key() -> [u8; 32] { + hex_literal::hex!("0b6e18cafb6ed99687ec547bd28139cafdd2bffe70e6b688025de6b445aa5c5b") +} diff --git a/precompiles/utils/src/testing/execution.rs b/precompiles/utils/src/testing/execution.rs new file mode 100644 index 0000000000..75332fd88c --- /dev/null +++ b/precompiles/utils/src/testing/execution.rs @@ -0,0 +1,238 @@ +use { + crate::{ + testing::{decode_revert_message, MockHandle, PrettyLog, SubcallHandle, SubcallTrait}, + data::EvmData, + }, + fp_evm::{ + Context, ExitError, ExitSucceed, Log, PrecompileFailure, PrecompileOutput, + PrecompileResult, PrecompileSet, + }, + sp_core::{H160, U256}, + sp_std::boxed::Box, +}; + +#[must_use] +pub struct PrecompilesTester<'p, P> { + precompiles: &'p P, + handle: MockHandle, + + target_gas: Option, + subcall_handle: Option, + + expected_cost: Option, + expected_logs: Option>, + static_call: bool, +} + +impl<'p, P: PrecompileSet> PrecompilesTester<'p, P> { + pub fn new( + precompiles: &'p P, + from: impl Into, + to: impl Into, + data: Vec, + ) -> Self { + let to = to.into(); + let mut handle = MockHandle::new( + to.clone(), + Context { + address: to, + caller: from.into(), + apparent_value: U256::zero(), + }, + ); + + handle.input = data; + + Self { + precompiles, + handle, + + target_gas: None, + subcall_handle: None, + + expected_cost: None, + expected_logs: None, + static_call: false, + } + } + + pub fn with_value(mut self, value: impl Into) -> Self { + self.handle.context.apparent_value = value.into(); + self + } + + pub fn with_subcall_handle(mut self, subcall_handle: impl SubcallTrait) -> Self { + self.subcall_handle = Some(Box::new(subcall_handle)); + self + } + + pub fn with_target_gas(mut self, target_gas: Option) -> Self { + self.target_gas = target_gas; + self + } + + pub fn with_static_call(mut self, static_call: bool) -> Self { + self.static_call = static_call; + self + } + + pub fn expect_cost(mut self, cost: u64) -> Self { + self.expected_cost = Some(cost); + self + } + + pub fn expect_no_logs(mut self) -> Self { + self.expected_logs = Some(vec![]); + self + } + + pub fn expect_log(mut self, log: Log) -> Self { + self.expected_logs = Some({ + let mut logs = self.expected_logs.unwrap_or_else(Vec::new); + logs.push(PrettyLog(log)); + logs + }); + self + } + + fn assert_optionals(&self) { + if let Some(cost) = &self.expected_cost { + assert_eq!(&self.handle.gas_used, cost); + } + + if let Some(logs) = &self.expected_logs { + similar_asserts::assert_eq!(&self.handle.logs, logs); + } + } + + fn execute(&mut self) -> Option { + let handle = &mut self.handle; + handle.subcall_handle = self.subcall_handle.take(); + handle.is_static = self.static_call; + + if let Some(gas_limit) = self.target_gas { + handle.gas_limit = gas_limit; + } + + let res = self.precompiles.execute(handle); + + self.subcall_handle = handle.subcall_handle.take(); + + res + } + + /// Execute the precompile set and expect some precompile to have been executed, regardless of the + /// result. + pub fn execute_some(mut self) { + let res = self.execute(); + assert!(res.is_some()); + self.assert_optionals(); + } + + /// Execute the precompile set and expect no precompile to have been executed. + pub fn execute_none(mut self) { + let res = self.execute(); + assert!(res.is_some()); + self.assert_optionals(); + } + + /// Execute the precompile set and check it returns provided output. + pub fn execute_returns_raw(mut self, output: Vec) { + let res = self.execute(); + + match res { + Some(Err(PrecompileFailure::Revert { output, .. })) => { + let decoded = decode_revert_message(&output); + eprintln!( + "Revert message (bytes): {:?}", + sp_core::hexdisplay::HexDisplay::from(&decoded) + ); + eprintln!( + "Revert message (string): {:?}", + core::str::from_utf8(decoded).ok() + ); + panic!("Shouldn't have reverted"); + } + Some(Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output: execution_output, + })) => { + if execution_output != output { + eprintln!( + "Output (bytes): {:?}", + sp_core::hexdisplay::HexDisplay::from(&execution_output) + ); + eprintln!( + "Output (string): {:?}", + core::str::from_utf8(&execution_output).ok() + ); + panic!("Output doesn't match"); + } + } + other => panic!("Unexpected result: {:?}", other), + } + + self.assert_optionals(); + } + + /// Execute the precompile set and check it returns provided Solidity encoded output. + pub fn execute_returns(self, output: impl EvmData) { + self.execute_returns_raw(crate::data::encode_return_value(output)) + } + + /// Execute the precompile set and check if it reverts. + /// Take a closure allowing to perform custom matching on the output. + pub fn execute_reverts(mut self, check: impl Fn(&[u8]) -> bool) { + let res = self.execute(); + + match res { + Some(Err(PrecompileFailure::Revert { output, .. })) => { + let decoded = decode_revert_message(&output); + if !check(decoded) { + eprintln!( + "Revert message (bytes): {:?}", + sp_core::hexdisplay::HexDisplay::from(&decoded) + ); + eprintln!( + "Revert message (string): {:?}", + core::str::from_utf8(decoded).ok() + ); + panic!("Revert reason doesn't match !"); + } + } + other => panic!("Didn't revert, instead returned {:?}", other), + } + + self.assert_optionals(); + } + + /// Execute the precompile set and check it returns provided output. + pub fn execute_error(mut self, error: ExitError) { + let res = self.execute(); + assert_eq!( + res, + Some(Err(PrecompileFailure::Error { exit_status: error })) + ); + self.assert_optionals(); + } +} + +pub trait PrecompileTesterExt: PrecompileSet + Sized { + fn prepare_test( + &self, + from: impl Into, + to: impl Into, + data: impl Into>, + ) -> PrecompilesTester; +} + +impl PrecompileTesterExt for T { + fn prepare_test( + &self, + from: impl Into, + to: impl Into, + data: impl Into>, + ) -> PrecompilesTester { + PrecompilesTester::new(self, from, to, data.into()) + } +} diff --git a/precompiles/utils/src/testing/handle.rs b/precompiles/utils/src/testing/handle.rs new file mode 100644 index 0000000000..c297fc8bca --- /dev/null +++ b/precompiles/utils/src/testing/handle.rs @@ -0,0 +1,205 @@ +// Copyright 2019-2022 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonbeam is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonbeam. If not, see . + +use { + crate::testing::PrettyLog, + evm::{ExitRevert, ExitSucceed}, + fp_evm::{Context, ExitError, ExitReason, Log, PrecompileHandle, Transfer}, + sp_core::{H160, H256}, + sp_std::boxed::Box, +}; + +#[derive(Debug, Clone)] +pub struct Subcall { + pub address: H160, + pub transfer: Option, + pub input: Vec, + pub target_gas: Option, + pub is_static: bool, + pub context: Context, +} + +#[derive(Debug, Clone)] +pub struct SubcallOutput { + pub reason: ExitReason, + pub output: Vec, + pub cost: u64, + pub logs: Vec, +} + +impl SubcallOutput { + pub fn revert() -> Self { + Self { + reason: ExitReason::Revert(ExitRevert::Reverted), + output: Vec::new(), + cost: 0, + logs: Vec::new(), + } + } + + pub fn succeed() -> Self { + Self { + reason: ExitReason::Succeed(ExitSucceed::Returned), + output: Vec::new(), + cost: 0, + logs: Vec::new(), + } + } + + pub fn out_of_gas() -> Self { + Self { + reason: ExitReason::Error(ExitError::OutOfGas), + output: Vec::new(), + cost: 0, + logs: Vec::new(), + } + } +} + +pub trait SubcallTrait: FnMut(Subcall) -> SubcallOutput + 'static {} + +impl SubcallOutput + 'static> SubcallTrait for T {} + +pub type SubcallHandle = Box; + +/// Mock handle to write tests for precompiles. +pub struct MockHandle { + pub gas_limit: u64, + pub gas_used: u64, + pub logs: Vec, + pub subcall_handle: Option, + pub code_address: H160, + pub input: Vec, + pub context: Context, + pub is_static: bool, +} + +impl MockHandle { + pub fn new(code_address: H160, context: Context) -> Self { + Self { + gas_limit: u64::MAX, + gas_used: 0, + logs: vec![], + subcall_handle: None, + code_address, + input: Vec::new(), + context, + is_static: false, + } + } +} + +impl PrecompileHandle for MockHandle { + /// Perform subcall in provided context. + /// Precompile specifies in which context the subcall is executed. + fn call( + &mut self, + address: H160, + transfer: Option, + input: Vec, + target_gas: Option, + is_static: bool, + context: &Context, + ) -> (ExitReason, Vec) { + if self + .record_cost(crate::evm::costs::call_cost( + context.apparent_value, + &evm::Config::london(), + )) + .is_err() + { + return (ExitReason::Error(ExitError::OutOfGas), vec![]); + } + + match &mut self.subcall_handle { + Some(handle) => { + let SubcallOutput { + reason, + output, + cost, + logs, + } = handle(Subcall { + address, + transfer, + input, + target_gas, + is_static, + context: context.clone(), + }); + + if self.record_cost(cost).is_err() { + return (ExitReason::Error(ExitError::OutOfGas), vec![]); + } + + for log in logs { + self.log(log.address, log.topics, log.data) + .expect("cannot fail"); + } + + (reason, output) + } + None => panic!("no subcall handle registered"), + } + } + + fn record_cost(&mut self, cost: u64) -> Result<(), ExitError> { + self.gas_used += cost; + + if self.gas_used > self.gas_limit { + Err(ExitError::OutOfGas) + } else { + Ok(()) + } + } + + fn remaining_gas(&self) -> u64 { + self.gas_limit - self.gas_used + } + + fn log(&mut self, address: H160, topics: Vec, data: Vec) -> Result<(), ExitError> { + self.logs.push(PrettyLog(Log { + address, + topics, + data, + })); + Ok(()) + } + + /// Retreive the code address (what is the address of the precompile being called). + fn code_address(&self) -> H160 { + self.code_address + } + + /// Retreive the input data the precompile is called with. + fn input(&self) -> &[u8] { + &self.input + } + + /// Retreive the context in which the precompile is executed. + fn context(&self) -> &Context { + &self.context + } + + /// Is the precompile call is done statically. + fn is_static(&self) -> bool { + self.is_static + } + + /// Retreive the gas limit of this call. + fn gas_limit(&self) -> Option { + Some(self.gas_limit) + } +} \ No newline at end of file diff --git a/precompiles/utils/src/testing/mod.rs b/precompiles/utils/src/testing/mod.rs new file mode 100644 index 0000000000..115807f08c --- /dev/null +++ b/precompiles/utils/src/testing/mod.rs @@ -0,0 +1,96 @@ +// Copyright 2019-2022 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonbeam is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonbeam. If not, see . + +pub mod account; +pub mod execution; +pub mod handle; + +pub use { + account::*, + execution::*, + handle::*, +}; + +use fp_evm::Log; + +pub fn decode_revert_message(encoded: &[u8]) -> &[u8] { + let encoded_len = encoded.len(); + // selector 4 + offset 32 + string length 32 + if encoded_len > 68 { + let message_len = encoded[36..68].iter().sum::(); + if encoded_len >= 68 + message_len as usize { + return &encoded[68..68 + message_len as usize]; + } + } + b"decode_revert_message: error" +} + +#[derive(Clone, PartialEq, Eq)] +pub struct PrettyLog(Log); + +impl core::fmt::Debug for PrettyLog { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { + let bytes = self + .0 + .data + .iter() + .map(|b| format!("{:02X}", b)) + .collect::>() + .join(""); + + let message = String::from_utf8(self.0.data.clone()).ok(); + + f.debug_struct("Log") + .field("address", &self.0.address) + .field("topics", &self.0.topics) + .field("data", &bytes) + .field("data_utf8", &message) + .finish() + } +} +/// Panics if an event is not found in the system log of events +#[macro_export] +macro_rules! assert_event_emitted { + ($event:expr) => { + match &$event { + e => { + assert!( + crate::mock::events().iter().find(|x| *x == e).is_some(), + "Event {:?} was not found in events: \n {:?}", + e, + crate::mock::events() + ); + } + } + }; +} + +// Panics if an event is found in the system log of events +#[macro_export] +macro_rules! assert_event_not_emitted { + ($event:expr) => { + match &$event { + e => { + assert!( + crate::mock::events().iter().find(|x| *x == e).is_none(), + "Event {:?} was found in events: \n {:?}", + e, + crate::mock::events() + ); + } + } + }; +} From 3ba87e7293bc69775ce130161ef2d775806df87a Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Mon, 31 Jul 2023 11:03:56 +0530 Subject: [PATCH 02/21] typo --- precompiles/batch/src/mock.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/precompiles/batch/src/mock.rs b/precompiles/batch/src/mock.rs index d5bf246a29..be0a573925 100644 --- a/precompiles/batch/src/mock.rs +++ b/precompiles/batch/src/mock.rs @@ -133,7 +133,7 @@ impl pallet_evm::Config for Runtime { type Currency = Balances; type RuntimeEvent = RuntimeEvent; type Runner = pallet_evm::runner::stack::Runner; - type PrecompilesType = Precopiles; + type PrecompilesType = Precompiles; type PrecompilesValue = PrecompilesValue; type Timestamp = Timestamp; type ChainId = (); From bb5e5cadeba1fe8ceaae097dd413152937d3d62f Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Wed, 2 Aug 2023 10:38:00 +0530 Subject: [PATCH 03/21] add tests --- Cargo.lock | 1 + Cargo.toml | 1 + precompiles/batch/Cargo.toml | 21 +- precompiles/batch/src/lib.rs | 16 +- precompiles/batch/src/mock.rs | 52 +- precompiles/batch/src/tests.rs | 698 ++++----------------- precompiles/utils/Cargo.toml | 14 +- precompiles/utils/src/evm/handle.rs | 287 +++++---- precompiles/utils/src/evm/mod.rs | 2 +- precompiles/utils/src/lib.rs | 7 +- precompiles/utils/src/old_testing.rs | 28 + precompiles/utils/src/substrate.rs | 150 ++--- precompiles/utils/src/testing/account.rs | 138 ++-- precompiles/utils/src/testing/execution.rs | 418 ++++++------ precompiles/utils/src/testing/handle.rs | 322 +++++----- precompiles/utils/src/testing/mod.rs | 104 ++- runtime/shibuya/Cargo.toml | 1 + runtime/shibuya/src/precompiles.rs | 7 +- 18 files changed, 925 insertions(+), 1342 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b6488c4dfa..e04995a757 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12104,6 +12104,7 @@ dependencies = [ "pallet-evm", "pallet-evm-chain-id", "pallet-evm-precompile-assets-erc20", + "pallet-evm-precompile-batch", "pallet-evm-precompile-blake2", "pallet-evm-precompile-bn128", "pallet-evm-precompile-dapps-staking", diff --git a/Cargo.toml b/Cargo.toml index e65c4b406c..20340e31d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -286,6 +286,7 @@ pallet-evm-precompile-substrate-ecdsa = { path = "./precompiles/substrate-ecdsa" pallet-evm-precompile-xcm = { path = "./precompiles/xcm", default-features = false } pallet-evm-precompile-xvm = { path = "./precompiles/xvm", default-features = false } pallet-evm-precompile-dapps-staking = { path = "./precompiles/dapps-staking", default-features = false } +pallet-evm-precompile-batch = { path = "./precompiles/batch", default-features = false } pallet-chain-extension-dapps-staking = { path = "./chain-extensions/dapps-staking", default-features = false } pallet-chain-extension-xvm = { path = "./chain-extensions/xvm", default-features = false } diff --git a/precompiles/batch/Cargo.toml b/precompiles/batch/Cargo.toml index 857a7aca84..718286e9bb 100644 --- a/precompiles/batch/Cargo.toml +++ b/precompiles/batch/Cargo.toml @@ -19,15 +19,15 @@ precompile-utils = { workspace = true } # Substrate frame-support = { workspace = true } frame-system = { workspace = true } -parity-scale-codec = { workspace = true, features = [ "max-encoded-len" ] } +parity-scale-codec = { workspace = true, features = ["max-encoded-len"] } sp-core = { workspace = true } sp-io = { workspace = true } sp-std = { workspace = true } # Frontier -evm = { workspace = true, features = [ "with-codec" ] } +evm = { workspace = true, features = ["with-codec"] } fp-evm = { workspace = true } -pallet-evm = { workspace = true, features = [ "forbid-evm-reentrancy" ] } +pallet-evm = { workspace = true, features = ["forbid-evm-reentrancy"] } [dev-dependencies] derive_more = { workspace = true } @@ -35,15 +35,15 @@ hex-literal = { workspace = true } serde = { workspace = true } sha3 = { workspace = true } -pallet-balances = { workspace = true, features = [ "std" ] } -pallet-timestamp = { workspace = true, features = [ "std" ] } -parity-scale-codec = { workspace = true, features = [ "max-encoded-len", "std" ] } -precompile-utils = { workspace = true, features = [ "std", "testing" ] } -scale-info = { workspace = true, features = [ "derive", "std" ] } -sp-runtime = { workspace = true, features = [ "std" ] } +pallet-balances = { workspace = true, features = ["std"] } +pallet-timestamp = { workspace = true, features = ["std"] } +parity-scale-codec = { workspace = true, features = ["max-encoded-len", "std"] } +precompile-utils = { workspace = true, features = ["std", "testing"] } +scale-info = { workspace = true, features = ["derive", "std"] } +sp-runtime = { workspace = true, features = ["std"] } [features] -default = [ "std" ] +default = ["std"] std = [ "fp-evm/std", "frame-support/std", @@ -55,4 +55,3 @@ std = [ "sp-io/std", "sp-std/std", ] - diff --git a/precompiles/batch/src/lib.rs b/precompiles/batch/src/lib.rs index b8a26beaa9..0010306199 100644 --- a/precompiles/batch/src/lib.rs +++ b/precompiles/batch/src/lib.rs @@ -20,13 +20,16 @@ use ::evm::{ExitError, ExitReason}; use fp_evm::{Context, Log, PrecompileFailure, PrecompileHandle, Transfer}; -use frame_support::traits::ConstU32; -use pallet_evm::PrecompileOutput; +use frame_support::{ + dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, + traits::ConstU32, +}; +use pallet_evm::{Precompile, PrecompileOutput}; use precompile_utils::{ bytes::BoundedBytes, data::BoundedVec, evm::{costs::call_cost, logs::log1}, - *, precompile_set::PrecompileSetFragment, + *, }; use sp_core::{H160, U256}; use sp_std::{iter::repeat, marker::PhantomData, vec, vec::Vec}; @@ -79,10 +82,10 @@ pub enum Action { #[derive(Debug, Clone)] pub struct BatchPrecompile(PhantomData); -impl BatchPrecompile +impl Precompile for BatchPrecompile where - Runtime: pallet_evm::Config + PrecompileSetFragment, - + Runtime: pallet_evm::Config, + Runtime::RuntimeCall: Dispatchable + GetDispatchInfo, { fn execute(handle: &mut impl PrecompileHandle) -> EvmResult { let selector = handle.read_selector()?; @@ -101,6 +104,7 @@ where impl BatchPrecompile where Runtime: pallet_evm::Config, + Runtime::RuntimeCall: Dispatchable + GetDispatchInfo, { fn batch_some(handle: &mut impl PrecompileHandle) -> EvmResult { let mut input = handle.read_input()?; diff --git a/precompiles/batch/src/mock.rs b/precompiles/batch/src/mock.rs index be0a573925..926314b7c2 100644 --- a/precompiles/batch/src/mock.rs +++ b/precompiles/batch/src/mock.rs @@ -17,10 +17,11 @@ //! Test utilities use super::*; +use fp_evm::IsPrecompileResult; use frame_support::traits::Everything; use frame_support::{construct_runtime, parameter_types, weights::Weight}; -use pallet_evm::{EnsureAddressNever, EnsureAddressRoot}; -use precompile_utils::{mock_account, precompile_set::*, testing::MockAccount}; +use pallet_evm::{EnsureAddressNever, EnsureAddressRoot, PrecompileResult, PrecompileSet}; +use precompile_utils::{mock_account, testing::MockAccount}; use sp_core::H256; use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, @@ -96,30 +97,40 @@ impl pallet_balances::Config for Runtime { type WeightInfo = (); } -pub type Precompiles = PrecompileSetBuilder< - R, - ( - PrecompileAt< - AddressU64<1>, - BatchPrecompile, - ( - SubcallWithMaxNesting<1>, - // Batch is the only precompile allowed to call Batch. - CallableByPrecompile>>, - ), - >, - RevertPrecompile>, - ), ->; +pub fn precompile_address() -> H160 { + H160::from_low_u64_be(0x5002) +} + +#[derive(Debug, Clone, Copy)] +pub struct BatchPrecompileMock(PhantomData); + +impl PrecompileSet for BatchPrecompileMock +where + R: pallet_evm::Config, + BatchPrecompile: Precompile, +{ + fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { + match handle.code_address() { + a if a == precompile_address() => Some(BatchPrecompile::::execute(handle)), + _ => None, + } + } + + fn is_precompile(&self, address: sp_core::H160, _gas: u64) -> IsPrecompileResult { + IsPrecompileResult::Answer { + is_precompile: address == precompile_address(), + extra_cost: 0, + } + } +} pub type PCall = BatchPrecompile; -mock_account!(Batch, |_| MockAccount::from_u64(1)); mock_account!(Revert, |_| MockAccount::from_u64(2)); parameter_types! { pub BlockGasLimit: U256 = U256::max_value(); - pub PrecompilesValue: Precompiles = Precompiles::new(); + pub PrecompilesValue: BatchPrecompileMock = BatchPrecompileMock(Default::default()); pub const WeightPerGas: Weight = Weight::from_parts(1, 0); } @@ -133,7 +144,7 @@ impl pallet_evm::Config for Runtime { type Currency = Balances; type RuntimeEvent = RuntimeEvent; type Runner = pallet_evm::runner::stack::Runner; - type PrecompilesType = Precompiles; + type PrecompilesType = BatchPrecompileMock; type PrecompilesValue = PrecompilesValue; type Timestamp = Timestamp; type ChainId = (); @@ -143,7 +154,6 @@ impl pallet_evm::Config for Runtime { type FindAuthor = (); type OnCreate = (); type WeightInfo = (); - } parameter_types! { diff --git a/precompiles/batch/src/tests.rs b/precompiles/batch/src/tests.rs index 91bb564e73..14aad8b556 100644 --- a/precompiles/batch/src/tests.rs +++ b/precompiles/batch/src/tests.rs @@ -15,30 +15,29 @@ // along with Moonbeam. If not, see . use crate::mock::{ - balance, Batch, ExtBuilder, PCall, Precompiles, PrecompilesValue, Revert, Runtime, RuntimeCall, - RuntimeOrigin, -}; -use crate::{ - log_subcall_failed, log_subcall_succeeded, Mode, LOG_SUBCALL_FAILED, LOG_SUBCALL_SUCCEEDED, + balance, precompile_address, BatchPrecompileMock, ExtBuilder, PCall, PrecompilesValue, Revert, + Runtime, RuntimeCall, RuntimeOrigin, }; +use crate::{log_subcall_failed, log_subcall_succeeded, Mode, *}; use fp_evm::ExitError; use frame_support::{ assert_ok, dispatch::{DispatchError, Dispatchable}, }; use pallet_evm::Call as EvmCall; -use precompile_utils::{evm::costs::call_cost, *, testing::*}; +use precompile_utils::{evm::costs::call_cost, testing::*, *}; +use sha3::digest::typenum::U25; use sp_core::{H160, H256, U256}; use sp_runtime::{DispatchErrorWithPostInfo, ModuleError}; -fn precompiles() -> Precompiles { +fn precompiles() -> BatchPrecompileMock { PrecompilesValue::get() } fn evm_call(from: impl Into, input: Vec) -> EvmCall { EvmCall::call { source: from.into(), - target: Batch.into(), + target: precompile_address().into(), input, value: U256::zero(), // No value sent in EVM gas_limit: u64::max_value(), @@ -50,29 +49,29 @@ fn evm_call(from: impl Into, input: Vec) -> EvmCall { } fn costs() -> (u64, u64) { - let return_log_cost = log_subcall_failed(Batch, 0).compute_cost().unwrap(); + let return_log_cost = log_subcall_failed(precompile_address(), 0) + .compute_cost() + .unwrap(); let call_cost = return_log_cost + call_cost(U256::one(), ::config()); (return_log_cost, call_cost) } - #[test] fn batch_some_empty() { ExtBuilder::default().build().execute_with(|| { precompiles() .prepare_test( Alice, - Batch, - PCall::batch_some { - to: vec![].into(), - value: vec![].into(), - call_data: vec![].into(), - gas_limit: vec![].into(), - }, + precompile_address(), + EvmDataWriter::new_with_selector(Action::BatchSome) + .write::>(vec![]) + .write::>(vec![]) + .write::>(vec![]) + .write::>(vec![]) + .build(), ) - .with_subcall_handle(|Subcall { .. }| panic!("there should be no subcall")) - .execute_returns(()) + .execute_returns(EvmDataWriter::new().write(true).build()); }) } @@ -82,16 +81,15 @@ fn batch_some_until_failure_empty() { precompiles() .prepare_test( Alice, - Batch, - PCall::batch_some_until_failure { - to: vec![].into(), - value: vec![].into(), - call_data: vec![].into(), - gas_limit: vec![].into(), - }, + precompile_address(), + EvmDataWriter::new_with_selector(Action::BatchSomeUntilFailure) + .write::>(vec![]) + .write::>(vec![]) + .write::>(vec![]) + .write::>(vec![]) + .build(), ) - .with_subcall_handle(|Subcall { .. }| panic!("there should be no subcall")) - .execute_returns(()) + .execute_returns(EvmDataWriter::new().write(true).build()); }) } @@ -101,38 +99,45 @@ fn batch_all_empty() { precompiles() .prepare_test( Alice, - Batch, - PCall::batch_all { - to: vec![].into(), - value: vec![].into(), - call_data: vec![].into(), - gas_limit: vec![].into(), - }, + precompile_address(), + EvmDataWriter::new_with_selector(Action::BatchAll) + .write::>(vec![]) + .write::>(vec![]) + .write::>(vec![]) + .write::>(vec![]) + .build(), ) - .with_subcall_handle(|Subcall { .. }| panic!("there should be no subcall")) - .execute_returns(()) + .execute_returns(EvmDataWriter::new().write(true).build()); }) } +fn check_mode(mode: Mode) -> Action { + match mode { + Mode::BatchAll => Action::BatchAll, + Mode::BatchSome => Action::BatchSome, + Mode::BatchSomeUntilFailure => Action::BatchSomeUntilFailure, + } +} + fn batch_returns( - precompiles: &Precompiles, + precompiles: &BatchPrecompileMock, mode: Mode, -) -> PrecompilesTester> { +) -> PrecompilesTester> { let mut counter = 0; - + let one = b"one"; + let two = b"two"; let (_, total_call_cost) = costs(); precompiles .prepare_test( Alice, - Batch, - PCall::batch_from_mode( - mode, - vec![Address(Bob.into()), Address(Charlie.into())], - vec![U256::from(1u8), U256::from(2u8)], - vec![b"one".to_vec(), b"two".to_vec()], - vec![], - ), + precompile_address(), + EvmDataWriter::new_with_selector(check_mode(mode)) + .write(vec![Address(Bob.into()), Address(Charlie.into())]) + .write(vec![U256::from(1u8), U256::from(2u8)]) + .write(vec![Bytes::from(&one[..]), Bytes::from(&two[..])]) + .write::>(vec![]) + .build(), ) .with_target_gas(Some(100_000)) .with_subcall_handle(move |subcall| { @@ -211,10 +216,10 @@ fn batch_some_returns() { ExtBuilder::default().build().execute_with(|| { batch_returns(&precompiles(), Mode::BatchSome) .expect_log(log1(Bob, H256::repeat_byte(0x11), vec![])) - .expect_log(log_subcall_succeeded(Batch, 0)) + .expect_log(log_subcall_succeeded(precompile_address(), 0)) .expect_log(log1(Charlie, H256::repeat_byte(0x22), vec![])) - .expect_log(log_subcall_succeeded(Batch, 1)) - .execute_returns(()) + .expect_log(log_subcall_succeeded(precompile_address(), 1)) + .execute_returns(EvmDataWriter::new().write(true).build()) }) } @@ -223,10 +228,10 @@ fn batch_some_until_failure_returns() { ExtBuilder::default().build().execute_with(|| { batch_returns(&precompiles(), Mode::BatchSomeUntilFailure) .expect_log(log1(Bob, H256::repeat_byte(0x11), vec![])) - .expect_log(log_subcall_succeeded(Batch, 0)) + .expect_log(log_subcall_succeeded(precompile_address(), 0)) .expect_log(log1(Charlie, H256::repeat_byte(0x22), vec![])) - .expect_log(log_subcall_succeeded(Batch, 1)) - .execute_returns(()) + .expect_log(log_subcall_succeeded(precompile_address(), 1)) + .execute_returns(EvmDataWriter::new().write(true).build()) }) } @@ -235,30 +240,29 @@ fn batch_all_returns() { ExtBuilder::default().build().execute_with(|| { batch_returns(&precompiles(), Mode::BatchAll) .expect_log(log1(Bob, H256::repeat_byte(0x11), vec![])) - .expect_log(log_subcall_succeeded(Batch, 0)) + .expect_log(log_subcall_succeeded(precompile_address(), 0)) .expect_log(log1(Charlie, H256::repeat_byte(0x22), vec![])) - .expect_log(log_subcall_succeeded(Batch, 1)) - .execute_returns(()) + .expect_log(log_subcall_succeeded(precompile_address(), 1)) + .execute_returns(EvmDataWriter::new().write(true).build()) }) } fn batch_out_of_gas( - precompiles: &Precompiles, + precompiles: &BatchPrecompileMock, mode: Mode, -) -> PrecompilesTester> { +) -> PrecompilesTester> { + let one = b"one"; let (_, total_call_cost) = costs(); - precompiles .prepare_test( Alice, - Batch, - PCall::batch_from_mode( - mode, - vec![Address(Bob.into())], - vec![U256::from(1u8)], - vec![b"one".to_vec()], - vec![], - ), + precompile_address(), + EvmDataWriter::new_with_selector(check_mode(mode)) + .write(vec![Address(Bob.into())]) + .write(vec![U256::from(1u8)]) + .write(vec![Bytes::from(&one[..])]) + .write::>(vec![]) + .build(), ) .with_target_gas(Some(50_000)) .with_subcall_handle(move |subcall| { @@ -306,8 +310,8 @@ fn batch_out_of_gas( fn batch_some_out_of_gas() { ExtBuilder::default().build().execute_with(|| { batch_out_of_gas(&precompiles(), Mode::BatchSome) - .expect_log(log_subcall_failed(Batch, 0)) - .execute_returns(()) + .expect_log(log_subcall_failed(precompile_address(), 0)) + .execute_returns(EvmDataWriter::new().write(true).build()) }) } @@ -315,8 +319,8 @@ fn batch_some_out_of_gas() { fn batch_some_until_failure_out_of_gas() { ExtBuilder::default().build().execute_with(|| { batch_out_of_gas(&precompiles(), Mode::BatchSomeUntilFailure) - .expect_log(log_subcall_failed(Batch, 0)) - .execute_returns(()) + .expect_log(log_subcall_failed(precompile_address(), 0)) + .execute_returns(EvmDataWriter::new().write(true).build()) }) } @@ -328,28 +332,28 @@ fn batch_all_out_of_gas() { } fn batch_incomplete( - precompiles: &Precompiles, + precompiles: &BatchPrecompileMock, mode: Mode, -) -> PrecompilesTester> { +) -> PrecompilesTester> { let mut counter = 0; + let one = b"one"; let (_, total_call_cost) = costs(); precompiles .prepare_test( Alice, - Batch, - PCall::batch_from_mode( - mode, - vec![ + precompile_address(), + EvmDataWriter::new_with_selector(check_mode(mode)) + .write(vec![ Address(Bob.into()), Address(Charlie.into()), Address(Alice.into()), - ], - vec![U256::from(1u8), U256::from(2u8), U256::from(3u8)], - vec![b"one".to_vec()], - vec![], - ), + ]) + .write(vec![U256::from(1u8), U256::from(2u8), U256::from(3u8)]) + .write(vec![Bytes::from(&one[..])]) + .write::>(vec![]) + .build(), ) .with_target_gas(Some(300_000)) .with_subcall_handle(move |subcall| { @@ -454,12 +458,12 @@ fn batch_some_incomplete() { batch_incomplete(&precompiles(), Mode::BatchSome) .expect_log(log1(Bob, H256::repeat_byte(0x11), vec![])) - .expect_log(log_subcall_succeeded(Batch, 0)) - .expect_log(log_subcall_failed(Batch, 1)) + .expect_log(log_subcall_succeeded(precompile_address(), 0)) + .expect_log(log_subcall_failed(precompile_address(), 1)) .expect_log(log1(Alice, H256::repeat_byte(0x33), vec![])) - .expect_log(log_subcall_succeeded(Batch, 2)) + .expect_log(log_subcall_succeeded(precompile_address(), 2)) .expect_cost(13 + 17 + 19 + total_call_cost * 3) - .execute_returns(()) + .execute_returns(EvmDataWriter::new().write(true).build()) }) } @@ -470,10 +474,10 @@ fn batch_some_until_failure_incomplete() { batch_incomplete(&precompiles(), Mode::BatchSomeUntilFailure) .expect_log(log1(Bob, H256::repeat_byte(0x11), vec![])) - .expect_log(log_subcall_succeeded(Batch, 0)) - .expect_log(log_subcall_failed(Batch, 1)) + .expect_log(log_subcall_succeeded(precompile_address(), 0)) + .expect_log(log_subcall_failed(precompile_address(), 1)) .expect_cost(13 + 17 + total_call_cost * 2) - .execute_returns(()) + .execute_returns(EvmDataWriter::new().write(true).build()) }) } @@ -486,22 +490,22 @@ fn batch_all_incomplete() { } fn batch_log_out_of_gas( - precompiles: &Precompiles, + precompiles: &BatchPrecompileMock, mode: Mode, -) -> PrecompilesTester> { +) -> PrecompilesTester> { let (log_cost, _) = costs(); + let one = b"one"; precompiles .prepare_test( Alice, - Batch, - PCall::batch_from_mode( - mode, - vec![Address(Bob.into())], - vec![U256::from(1u8)], - vec![b"one".to_vec()], - vec![], - ), + precompile_address(), + EvmDataWriter::new_with_selector(check_mode(mode)) + .write(vec![Address(Bob.into())]) + .write(vec![U256::from(1u8)]) + .write(vec![Bytes::from(&one[..])]) + .write::>(vec![]) + .build(), ) .with_target_gas(Some(log_cost - 1)) .with_subcall_handle(move |_subcall| panic!("there shouldn't be any subcalls")) @@ -519,7 +523,7 @@ fn batch_some_log_out_of_gas() { ExtBuilder::default().build().execute_with(|| { batch_log_out_of_gas(&precompiles(), Mode::BatchSome) .expect_no_logs() - .execute_returns(()); + .execute_returns(EvmDataWriter::new().write(true).build()); }) } @@ -528,27 +532,27 @@ fn batch_some_until_failure_log_out_of_gas() { ExtBuilder::default().build().execute_with(|| { batch_log_out_of_gas(&precompiles(), Mode::BatchSomeUntilFailure) .expect_no_logs() - .execute_returns(()); + .execute_returns(EvmDataWriter::new().write(true).build()); }) } fn batch_call_out_of_gas( - precompiles: &Precompiles, + precompiles: &BatchPrecompileMock, mode: Mode, -) -> PrecompilesTester> { +) -> PrecompilesTester> { let (_, total_call_cost) = costs(); + let one = b"one"; precompiles .prepare_test( Alice, - Batch, - PCall::batch_from_mode( - mode, - vec![Address(Bob.into())], - vec![U256::from(1u8)], - vec![b"one".to_vec()], - vec![], - ), + precompile_address(), + EvmDataWriter::new_with_selector(check_mode(mode)) + .write(vec![Address(Bob.into())]) + .write(vec![U256::from(1u8)]) + .write(vec![Bytes::from(&one[..])]) + .write::>(vec![]) + .build(), ) .with_target_gas(Some(total_call_cost - 1)) .with_subcall_handle(move |_subcall| panic!("there shouldn't be any subcalls")) @@ -565,8 +569,8 @@ fn batch_all_call_out_of_gas() { fn batch_some_call_out_of_gas() { ExtBuilder::default().build().execute_with(|| { batch_call_out_of_gas(&precompiles(), Mode::BatchSome) - .expect_log(log_subcall_failed(Batch, 0)) - .execute_returns(()); + .expect_log(log_subcall_failed(precompile_address(), 0)) + .execute_returns(EvmDataWriter::new().write(true).build()); }) } @@ -574,28 +578,28 @@ fn batch_some_call_out_of_gas() { fn batch_some_until_failure_call_out_of_gas() { ExtBuilder::default().build().execute_with(|| { batch_call_out_of_gas(&precompiles(), Mode::BatchSomeUntilFailure) - .expect_log(log_subcall_failed(Batch, 0)) - .execute_returns(()); + .expect_log(log_subcall_failed(precompile_address(), 0)) + .execute_returns(EvmDataWriter::new().write(true).build()); }) } fn batch_gas_limit( - precompiles: &Precompiles, + precompiles: &BatchPrecompileMock, mode: Mode, -) -> PrecompilesTester> { +) -> PrecompilesTester> { let (_, total_call_cost) = costs(); + let one = b"one"; precompiles .prepare_test( Alice, - Batch, - PCall::batch_from_mode( - mode, - vec![Address(Bob.into())], - vec![U256::from(1u8)], - vec![b"one".to_vec()], - vec![50_000 - total_call_cost + 1], - ), + precompile_address(), + EvmDataWriter::new_with_selector(check_mode(mode)) + .write(vec![Address(Bob.into())]) + .write(vec![U256::from(1u8)]) + .write(vec![Bytes::from(&one[..])]) + .write::>(vec![U256::from(50_000 - total_call_cost + 1)]) + .build(), ) .with_target_gas(Some(50_000)) .with_subcall_handle(move |_subcall| panic!("there shouldn't be any subcalls")) @@ -614,9 +618,9 @@ fn batch_some_gas_limit() { let (return_log_cost, _) = costs(); batch_gas_limit(&precompiles(), Mode::BatchSome) - .expect_log(log_subcall_failed(Batch, 0)) + .expect_log(log_subcall_failed(precompile_address(), 0)) .expect_cost(return_log_cost) - .execute_returns(()); + .execute_returns(EvmDataWriter::new().write(true).build()); }) } @@ -624,441 +628,7 @@ fn batch_some_gas_limit() { fn batch_some_until_failure_gas_limit() { ExtBuilder::default().build().execute_with(|| { batch_gas_limit(&precompiles(), Mode::BatchSomeUntilFailure) - .expect_log(log_subcall_failed(Batch, 0)) - .execute_returns(()); + .expect_log(log_subcall_failed(precompile_address(), 0)) + .execute_returns(EvmDataWriter::new().write(true).build()); }) } - -#[test] -fn evm_batch_some_transfers_enough() { - ExtBuilder::default() - .with_balances(vec![(Alice.into(), 10_000)]) - .build() - .execute_with(|| { - assert_ok!(RuntimeCall::Evm(evm_call( - Alice, - PCall::batch_some { - to: vec![Address(Bob.into()), Address(Charlie.into())].into(), - value: vec![U256::from(1_000u16), U256::from(2_000u16)].into(), - call_data: vec![].into(), - gas_limit: vec![].into(), - } - .into() - )) - .dispatch(RuntimeOrigin::root())); - }) -} - -#[test] -fn evm_batch_some_until_failure_transfers_enough() { - ExtBuilder::default() - .with_balances(vec![(Alice.into(), 10_000)]) - .build() - .execute_with(|| { - assert_ok!(RuntimeCall::Evm(evm_call( - Alice, - PCall::batch_some_until_failure { - to: vec![Address(Bob.into()), Address(Charlie.into())].into(), - value: vec![U256::from(1_000u16), U256::from(2_000u16)].into(), - call_data: vec![].into(), - gas_limit: vec![].into(), - } - .into() - )) - .dispatch(RuntimeOrigin::root())); - }) -} - -#[test] -fn evm_batch_all_transfers_enough() { - ExtBuilder::default() - .with_balances(vec![(Alice.into(), 10_000)]) - .build() - .execute_with(|| { - assert_ok!(RuntimeCall::Evm(evm_call( - Alice, - PCall::batch_all { - to: vec![Address(Bob.into()), Address(Charlie.into())].into(), - value: vec![U256::from(1_000u16), U256::from(2_000u16)].into(), - call_data: vec![].into(), - gas_limit: vec![].into(), - } - .into() - )) - .dispatch(RuntimeOrigin::root())); - - assert_eq!(balance(Bob), 1_000); - assert_eq!(balance(Charlie), 2_000); - }) -} - -#[test] -fn evm_batch_some_transfers_too_much() { - ExtBuilder::default() - .with_balances(vec![(Alice.into(), 10_000)]) - .build() - .execute_with(|| { - assert_ok!(RuntimeCall::Evm(evm_call( - Alice, - PCall::batch_some { - to: vec![ - Address(Bob.into()), - Address(Charlie.into()), - Address(David.into()), - ] - .into(), - value: vec![ - U256::from(9_000u16), - U256::from(2_000u16), - U256::from(500u16) - ] - .into(), - call_data: vec![].into(), - gas_limit: vec![].into() - } - .into() - )) - .dispatch(RuntimeOrigin::root())); - - assert_eq!(balance(Alice), 500); // gasprice = 0 - assert_eq!(balance(Bob), 9_000); - assert_eq!(balance(Charlie), 0); - assert_eq!(balance(David), 500); - }) -} - -#[test] -fn evm_batch_some_until_failure_transfers_too_much() { - ExtBuilder::default() - .with_balances(vec![(Alice.into(), 10_000)]) - .build() - .execute_with(|| { - assert_ok!(RuntimeCall::Evm(evm_call( - Alice, - PCall::batch_some_until_failure { - to: vec![ - Address(Bob.into()), - Address(Charlie.into()), - Address(David.into()), - ] - .into(), - value: vec![ - U256::from(9_000u16), - U256::from(2_000u16), - U256::from(500u16) - ] - .into(), - call_data: vec![].into(), - gas_limit: vec![].into() - } - .into() - )) - .dispatch(RuntimeOrigin::root())); - - assert_eq!(balance(Alice), 1_000); // gasprice = 0 - assert_eq!(balance(Bob), 9_000); - assert_eq!(balance(Charlie), 0); - assert_eq!(balance(David), 0); - }) -} - -#[test] -fn evm_batch_all_transfers_too_much() { - ExtBuilder::default() - .with_balances(vec![(Alice.into(), 10_000)]) - .build() - .execute_with(|| { - assert_ok!(RuntimeCall::Evm(evm_call( - Alice, - PCall::batch_all { - to: vec![ - Address(Bob.into()), - Address(Charlie.into()), - Address(David.into()), - ] - .into(), - value: vec![ - U256::from(9_000u16), - U256::from(2_000u16), - U256::from(500u16) - ] - .into(), - call_data: vec![].into(), - gas_limit: vec![].into() - } - .into() - )) - .dispatch(RuntimeOrigin::root())); - - assert_eq!(balance(Alice), 10_000); // gasprice = 0 - assert_eq!(balance(Bob), 0); - assert_eq!(balance(Charlie), 0); - assert_eq!(balance(David), 0); - }) -} - -#[test] -fn evm_batch_some_contract_revert() { - ExtBuilder::default() - .with_balances(vec![(Alice.into(), 10_000)]) - .build() - .execute_with(|| { - assert_ok!(RuntimeCall::Evm(evm_call( - Alice, - PCall::batch_some { - to: vec![ - Address(Bob.into()), - Address(Revert.into()), - Address(David.into()), - ] - .into(), - value: vec![ - U256::from(1_000u16), - U256::from(2_000), - U256::from(3_000u16) - ] - .into(), - call_data: vec![].into(), - gas_limit: vec![].into() - } - .into() - )) - .dispatch(RuntimeOrigin::root())); - - assert_eq!(balance(Alice), 6_000); // gasprice = 0 - assert_eq!(balance(Bob), 1_000); - assert_eq!(balance(Revert), 0); - assert_eq!(balance(David), 3_000); - }) -} - -#[test] -fn evm_batch_some_until_failure_contract_revert() { - ExtBuilder::default() - .with_balances(vec![(Alice.into(), 10_000)]) - .build() - .execute_with(|| { - assert_ok!(RuntimeCall::Evm(evm_call( - Alice, - PCall::batch_some_until_failure { - to: vec![ - Address(Bob.into()), - Address(Revert.into()), - Address(David.into()), - ] - .into(), - value: vec![ - U256::from(1_000u16), - U256::from(2_000), - U256::from(3_000u16) - ] - .into(), - call_data: vec![].into(), - gas_limit: vec![].into() - } - .into() - )) - .dispatch(RuntimeOrigin::root())); - - assert_eq!(balance(Alice), 9_000); // gasprice = 0 - assert_eq!(balance(Bob), 1_000); - assert_eq!(balance(Revert), 0); - assert_eq!(balance(David), 0); - }) -} - -#[test] -fn evm_batch_all_contract_revert() { - ExtBuilder::default() - .with_balances(vec![(Alice.into(), 10_000)]) - .build() - .execute_with(|| { - assert_ok!(RuntimeCall::Evm(evm_call( - Alice, - PCall::batch_all { - to: vec![ - Address(Bob.into()), - Address(Revert.into()), - Address(David.into()), - ] - .into(), - value: vec![ - U256::from(1_000u16), - U256::from(2_000), - U256::from(3_000u16) - ] - .into(), - call_data: vec![].into(), - gas_limit: vec![].into() - } - .into() - )) - .dispatch(RuntimeOrigin::root())); - - assert_eq!(balance(Alice), 10_000); // gasprice = 0 - assert_eq!(balance(Bob), 0); - assert_eq!(balance(Revert), 0); - assert_eq!(balance(David), 0); - }) -} - -#[test] -fn evm_batch_recursion_under_limit() { - ExtBuilder::default() - .with_balances(vec![(Alice.into(), 10_000)]) - .build() - .execute_with(|| { - // Mock sets the recursion limit to 2, and we 2 nested batch. - // Thus it succeeds. - - let input = PCall::batch_all { - to: vec![Address(Batch.into())].into(), - value: vec![].into(), - gas_limit: vec![].into(), - call_data: vec![PCall::batch_all { - to: vec![Address(Bob.into())].into(), - value: vec![1000_u32.into()].into(), - gas_limit: vec![].into(), - call_data: vec![].into(), - } - .encode() - .into()] - .into(), - } - .into(); - - assert_ok!(RuntimeCall::Evm(evm_call(Alice, input)).dispatch(RuntimeOrigin::root())); - - assert_eq!(balance(Alice), 9_000); // gasprice = 0 - assert_eq!(balance(Bob), 1_000); - }) -} - -#[test] -fn evm_batch_recursion_over_limit() { - ExtBuilder::default() - .with_balances(vec![(Alice.into(), 10_000)]) - .build() - .execute_with(|| { - // Mock sets the recursion limit to 2, and we 3 nested batch. - // Thus it reverts. - - let input = PCall::batch_from_mode( - Mode::BatchAll, - vec![Address(Batch.into())], - vec![], - vec![PCall::batch_from_mode( - Mode::BatchAll, - vec![Address(Batch.into())], - vec![], - vec![PCall::batch_from_mode( - Mode::BatchAll, - vec![Address(Bob.into())], - vec![1000_u32.into()], - vec![], - vec![].into(), - ) - .into()], - vec![].into(), - ) - .into()], - vec![], - ) - .into(); - - assert_ok!(RuntimeCall::Evm(evm_call(Alice, input)).dispatch(RuntimeOrigin::root())); - - assert_eq!(balance(Alice), 10_000); // gasprice = 0 - assert_eq!(balance(Bob), 0); - }) -} - -#[test] -fn batch_not_callable_by_smart_contract() { - ExtBuilder::default() - .with_balances(vec![(Alice.into(), 10_000)]) - .build() - .execute_with(|| { - // "deploy" SC to alice address - let alice_h160: H160 = Alice.into(); - pallet_evm::AccountCodes::::insert(alice_h160, vec![10u8]); - - // succeeds if not called by SC, see `evm_batch_recursion_under_limit` - let input = PCall::batch_all { - to: vec![Address(Batch.into())].into(), - value: vec![].into(), - gas_limit: vec![].into(), - call_data: vec![PCall::batch_all { - to: vec![Address(Bob.into())].into(), - value: vec![1000_u32.into()].into(), - gas_limit: vec![].into(), - call_data: vec![].into(), - } - .encode() - .into()] - .into(), - } - .into(); - - match RuntimeCall::Evm(evm_call(Alice, input)).dispatch(RuntimeOrigin::root()) { - Err(DispatchErrorWithPostInfo { - error: - DispatchError::Module(ModuleError { - message: Some(err_msg), - .. - }), - .. - }) => assert_eq!("TransactionMustComeFromEOA", err_msg), - _ => panic!("expected error 'TransactionMustComeFromEOA'"), - } - }) -} - -#[test] -fn batch_is_not_callable_by_dummy_code() { - ExtBuilder::default() - .with_balances(vec![(Alice.into(), 10_000)]) - .build() - .execute_with(|| { - // "deploy" dummy code to alice address - let alice_h160: H160 = Alice.into(); - pallet_evm::AccountCodes::::insert( - alice_h160, - [0x60, 0x00, 0x60, 0x00, 0xfd].to_vec(), - ); - - // succeeds if called by dummy code, see `evm_batch_recursion_under_limit` - let input = PCall::batch_all { - to: vec![Address(Batch.into())].into(), - value: vec![].into(), - gas_limit: vec![].into(), - call_data: vec![PCall::batch_all { - to: vec![Address(Bob.into())].into(), - value: vec![1000_u32.into()].into(), - gas_limit: vec![].into(), - call_data: vec![].into(), - } - .encode() - .into()] - .into(), - } - .into(); - - match RuntimeCall::Evm(evm_call(Alice, input)).dispatch(RuntimeOrigin::root()) { - Err(DispatchErrorWithPostInfo { - error: - DispatchError::Module(ModuleError { - message: Some(err_msg), - .. - }), - .. - }) => assert_eq!("TransactionMustComeFromEOA", err_msg), - _ => panic!("expected error 'TransactionMustComeFromEOA'"), - } - }) -} - -#[test] -fn test_solidity_interface_has_all_function_selectors_documented_and_implemented() { - check_precompile_implements_solidity_interfaces(&["Batch.sol"], PCall::supports_selector) -} diff --git a/precompiles/utils/Cargo.toml b/precompiles/utils/Cargo.toml index 1d9adf6699..8874ea1976 100644 --- a/precompiles/utils/Cargo.toml +++ b/precompiles/utils/Cargo.toml @@ -9,17 +9,17 @@ repository.workspace = true [dependencies] # There's a problem with --all-features when this is moved under dev-deps -evm = { workspace = true, features = [ "with-codec" ] } +derive_more = { workspace = true, optional = true } +environmental = { workspace = true } +evm = { workspace = true, features = ["with-codec"] } +hex-literal = { workspace = true, optional = true } impl-trait-for-tuples = { workspace = true } log = { workspace = true } num_enum = { workspace = true } +scale-info = { workspace = true, optional = true, features = ["derive"] } +serde = { workspace = true, optional = true } sha3 = { workspace = true } similar-asserts = { workspace = true, optional = true } -scale-info = { workspace = true, optional = true, features = [ "derive" ] } -serde = { workspace = true, optional = true } -derive_more = { workspace = true, optional = true } -hex-literal = { workspace = true, optional = true } -environmental = { workspace = true } precompile-utils-macro = { path = "macro" } @@ -60,4 +60,4 @@ std = [ "xcm/std", "environmental/std", ] -testing = ["similar-asserts", "std", "scale-info", "serde", "derive_more", "hex-literal" ] +testing = ["similar-asserts", "std", "scale-info", "serde", "derive_more", "hex-literal"] diff --git a/precompiles/utils/src/evm/handle.rs b/precompiles/utils/src/evm/handle.rs index 2d0d1c6973..05b7a8c717 100644 --- a/precompiles/utils/src/evm/handle.rs +++ b/precompiles/utils/src/evm/handle.rs @@ -15,170 +15,163 @@ // along with Moonbeam. If not, see . use { - crate::{ - data::{EvmDataReader}, FunctionModifier, - EvmResult, check_function_modifier - }, - fp_evm::{Log, PrecompileHandle}, + crate::{check_function_modifier, data::EvmDataReader, EvmResult, FunctionModifier}, + fp_evm::{Log, PrecompileHandle}, }; pub trait PrecompileHandleExt: PrecompileHandle { - /// Record cost of a log manually. - /// This can be useful to record log costs early when their content have static size. - #[must_use] - fn record_log_costs_manual(&mut self, topics: usize, data_len: usize) -> EvmResult; - - /// Record cost of logs. - #[must_use] - fn record_log_costs(&mut self, logs: &[&Log]) -> EvmResult; - - #[must_use] - /// Check that a function call is compatible with the context it is - /// called into. - fn check_function_modifier(&self, modifier: FunctionModifier) -> EvmResult; - - #[must_use] - /// Returns a reader of the input, skipping the selector. - fn read_after_selector(&self) -> EvmResult; + /// Record cost of a log manually. + /// This can be useful to record log costs early when their content have static size. + #[must_use] + fn record_log_costs_manual(&mut self, topics: usize, data_len: usize) -> EvmResult; + + /// Record cost of logs. + #[must_use] + fn record_log_costs(&mut self, logs: &[&Log]) -> EvmResult; + + #[must_use] + /// Check that a function call is compatible with the context it is + /// called into. + fn check_function_modifier(&self, modifier: FunctionModifier) -> EvmResult; + + #[must_use] + /// Returns a reader of the input, skipping the selector. + fn read_after_selector(&self) -> EvmResult; } impl PrecompileHandleExt for T { - /// Record cost of a log manualy. - /// This can be useful to record log costs early when their content have static size. - #[must_use] - fn record_log_costs_manual(&mut self, topics: usize, data_len: usize) -> EvmResult { - self.record_cost(crate::evm::costs::log_costs(topics, data_len)?)?; - - Ok(()) - } - - /// Record cost of logs. - #[must_use] - fn record_log_costs(&mut self, logs: &[&Log]) -> EvmResult { - for log in logs { - self.record_log_costs_manual(log.topics.len(), log.data.len())?; - } - - Ok(()) - } - - #[must_use] - /// Check that a function call is compatible with the context it is - /// called into. - fn check_function_modifier(&self, modifier: FunctionModifier) -> EvmResult { - check_function_modifier( - self.context(), - self.is_static(), - modifier, - ) - } - - #[must_use] - /// Returns a reader of the input, skipping the selector. - fn read_after_selector(&self) -> EvmResult { - EvmDataReader::new_skip_selector(self.input()) - } + /// Record cost of a log manualy. + /// This can be useful to record log costs early when their content have static size. + #[must_use] + fn record_log_costs_manual(&mut self, topics: usize, data_len: usize) -> EvmResult { + self.record_cost(crate::evm::costs::log_costs(topics, data_len)?)?; + + Ok(()) + } + + /// Record cost of logs. + #[must_use] + fn record_log_costs(&mut self, logs: &[&Log]) -> EvmResult { + for log in logs { + self.record_log_costs_manual(log.topics.len(), log.data.len())?; + } + + Ok(()) + } + + #[must_use] + /// Check that a function call is compatible with the context it is + /// called into. + fn check_function_modifier(&self, modifier: FunctionModifier) -> EvmResult { + check_function_modifier(self.context(), self.is_static(), modifier) + } + + #[must_use] + /// Returns a reader of the input, skipping the selector. + fn read_after_selector(&self) -> EvmResult { + EvmDataReader::new_skip_selector(self.input()) + } } environmental::environmental!(EVM_CONTEXT: trait PrecompileHandle); pub fn using_precompile_handle<'a, R, F: FnOnce() -> R>( - precompile_handle: &'a mut dyn PrecompileHandle, - mutator: F, + precompile_handle: &'a mut dyn PrecompileHandle, + mutator: F, ) -> R { - // # Safety - // - // unsafe rust does not mean unsafe, but "the compiler cannot guarantee the safety of the - // memory". - // - // The only risk here is that the lifetime 'a comes to its end while the global variable - // `EVM_CONTEXT` still contains the reference to the precompile handle. - // The `using` method guarantee that it can't happen because the global variable is freed right - // after the execution of the `mutator` closure (whatever the result of the execution). - unsafe { - EVM_CONTEXT::using( - core::mem::transmute::<&'a mut dyn PrecompileHandle, &'static mut dyn PrecompileHandle>( - precompile_handle, - ), - mutator, - ) - } + // # Safety + // + // unsafe rust does not mean unsafe, but "the compiler cannot guarantee the safety of the + // memory". + // + // The only risk here is that the lifetime 'a comes to its end while the global variable + // `EVM_CONTEXT` still contains the reference to the precompile handle. + // The `using` method guarantee that it can't happen because the global variable is freed right + // after the execution of the `mutator` closure (whatever the result of the execution). + unsafe { + EVM_CONTEXT::using( + core::mem::transmute::<&'a mut dyn PrecompileHandle, &'static mut dyn PrecompileHandle>( + precompile_handle, + ), + mutator, + ) + } } pub fn with_precompile_handle R>(f: F) -> Option { - EVM_CONTEXT::with(|precompile_handle| f(precompile_handle)) + EVM_CONTEXT::with(|precompile_handle| f(precompile_handle)) } #[cfg(test)] mod tests { - use super::*; - - struct MockPrecompileHandle; - impl PrecompileHandle for MockPrecompileHandle { - fn call( - &mut self, - _: sp_core::H160, - _: Option, - _: Vec, - _: Option, - _: bool, - _: &evm::Context, - ) -> (evm::ExitReason, Vec) { - unimplemented!() - } - - fn record_cost(&mut self, _: u64) -> Result<(), evm::ExitError> { - unimplemented!() - } - - fn remaining_gas(&self) -> u64 { - unimplemented!() - } - - fn log( - &mut self, - _: sp_core::H160, - _: Vec, - _: Vec, - ) -> Result<(), evm::ExitError> { - unimplemented!() - } - - fn code_address(&self) -> sp_core::H160 { - unimplemented!() - } - - fn input(&self) -> &[u8] { - unimplemented!() - } - - fn context(&self) -> &evm::Context { - unimplemented!() - } - - fn is_static(&self) -> bool { - true - } - - fn gas_limit(&self) -> Option { - unimplemented!() - } - } - - #[test] - fn with_precompile_handle_without_context() { - assert_eq!(with_precompile_handle(|_| {}), None); - } - - #[test] - fn with_precompile_handle_with_context() { - let mut precompile_handle = MockPrecompileHandle; - - assert_eq!( - using_precompile_handle(&mut precompile_handle, || with_precompile_handle( - |handle| handle.is_static() - )), - Some(true) - ); - } + use super::*; + + struct MockPrecompileHandle; + impl PrecompileHandle for MockPrecompileHandle { + fn call( + &mut self, + _: sp_core::H160, + _: Option, + _: Vec, + _: Option, + _: bool, + _: &evm::Context, + ) -> (evm::ExitReason, Vec) { + unimplemented!() + } + + fn record_cost(&mut self, _: u64) -> Result<(), evm::ExitError> { + unimplemented!() + } + + fn remaining_gas(&self) -> u64 { + unimplemented!() + } + + fn log( + &mut self, + _: sp_core::H160, + _: Vec, + _: Vec, + ) -> Result<(), evm::ExitError> { + unimplemented!() + } + + fn code_address(&self) -> sp_core::H160 { + unimplemented!() + } + + fn input(&self) -> &[u8] { + unimplemented!() + } + + fn context(&self) -> &evm::Context { + unimplemented!() + } + + fn is_static(&self) -> bool { + true + } + + fn gas_limit(&self) -> Option { + unimplemented!() + } + } + + #[test] + fn with_precompile_handle_without_context() { + assert_eq!(with_precompile_handle(|_| {}), None); + } + + #[test] + fn with_precompile_handle_with_context() { + let mut precompile_handle = MockPrecompileHandle; + + assert_eq!( + using_precompile_handle(&mut precompile_handle, || with_precompile_handle( + |handle| handle.is_static() + )), + Some(true) + ); + } } diff --git a/precompiles/utils/src/evm/mod.rs b/precompiles/utils/src/evm/mod.rs index e54f90eb39..6b4cc27f82 100644 --- a/precompiles/utils/src/evm/mod.rs +++ b/precompiles/utils/src/evm/mod.rs @@ -15,5 +15,5 @@ // along with Moonbeam. If not, see . pub mod costs; -pub mod logs; pub mod handle; +pub mod logs; diff --git a/precompiles/utils/src/lib.rs b/precompiles/utils/src/lib.rs index 15256ab373..40691a2d2f 100644 --- a/precompiles/utils/src/lib.rs +++ b/precompiles/utils/src/lib.rs @@ -25,6 +25,7 @@ extern crate alloc; use crate::alloc::borrow::ToOwned; +pub use alloc::string::String; use fp_evm::{ Context, ExitError, ExitRevert, ExitSucceed, PrecompileFailure, PrecompileHandle, PrecompileOutput, @@ -36,20 +37,18 @@ use frame_support::{ use pallet_evm::{GasWeightMapping, Log}; use sp_core::{H160, H256, U256}; use sp_std::{marker::PhantomData, vec, vec::Vec}; -pub use alloc::string::String; pub mod bytes; pub mod data; pub mod evm; -pub mod precompile_set; +// pub mod precompile_set; pub mod substrate; -pub mod testing; pub use data::{Address, Bytes, EvmData, EvmDataReader, EvmDataWriter}; pub use precompile_utils_macro::{generate_function_selector, keccak256}; #[cfg(feature = "testing")] -pub mod old_testing; +pub mod testing; #[cfg(test)] mod tests; diff --git a/precompiles/utils/src/old_testing.rs b/precompiles/utils/src/old_testing.rs index 3c45678657..56f0d3a0c7 100644 --- a/precompiles/utils/src/old_testing.rs +++ b/precompiles/utils/src/old_testing.rs @@ -43,6 +43,34 @@ pub struct SubcallOutput { pub logs: Vec, } +impl SubcallOutput { + pub fn revert() -> Self { + Self { + reason: ExitReason::Revert(ExitRevert::Reverted), + output: Vec::new(), + cost: 0, + logs: Vec::new(), + } + } + + pub fn succeed() -> Self { + Self { + reason: ExitReason::Succeed(ExitSucceed::Returned), + output: Vec::new(), + cost: 0, + logs: Vec::new(), + } + } + + pub fn out_of_gas() -> Self { + Self { + reason: ExitReason::Error(ExitError::OutOfGas), + output: Vec::new(), + cost: 0, + logs: Vec::new(), + } + } +} pub trait SubcallTrait: FnMut(Subcall) -> SubcallOutput + 'static {} impl SubcallOutput + 'static> SubcallTrait for T {} diff --git a/precompiles/utils/src/substrate.rs b/precompiles/utils/src/substrate.rs index 7404a42a87..d606920d2c 100644 --- a/precompiles/utils/src/substrate.rs +++ b/precompiles/utils/src/substrate.rs @@ -19,32 +19,32 @@ //! - Substrate DB read and write costs use { - crate::{evm::handle::using_precompile_handle,revert}, - core::marker::PhantomData, - fp_evm::{ExitError, PrecompileFailure, PrecompileHandle}, - frame_support::{ - dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, - pallet_prelude::DispatchError, - traits::Get, - }, - pallet_evm::GasWeightMapping, + crate::{evm::handle::using_precompile_handle, revert}, + core::marker::PhantomData, + fp_evm::{ExitError, PrecompileFailure, PrecompileHandle}, + frame_support::{ + dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, + pallet_prelude::DispatchError, + traits::Get, + }, + pallet_evm::GasWeightMapping, }; #[derive(Debug)] pub enum TryDispatchError { - Evm(ExitError), - Substrate(DispatchError), + Evm(ExitError), + Substrate(DispatchError), } impl From for PrecompileFailure { - fn from(f: TryDispatchError) -> PrecompileFailure { - match f { - TryDispatchError::Evm(e) => PrecompileFailure::Error { exit_status: e }, - TryDispatchError::Substrate(e) => { - revert(alloc::format!("Dispatched call failed with error: {e:?}")) - } - } - } + fn from(f: TryDispatchError) -> PrecompileFailure { + match f { + TryDispatchError::Evm(e) => PrecompileFailure::Error { exit_status: e }, + TryDispatchError::Substrate(e) => { + revert(alloc::format!("Dispatched call failed with error: {e:?}")) + } + } + } } /// Helper functions requiring a Substrate runtime. @@ -54,66 +54,66 @@ pub struct RuntimeHelper(PhantomData); impl RuntimeHelper where - Runtime: pallet_evm::Config, - Runtime::RuntimeCall: Dispatchable + GetDispatchInfo, + Runtime: pallet_evm::Config, + Runtime::RuntimeCall: Dispatchable + GetDispatchInfo, { - /// Try to dispatch a Substrate call. - /// Return an error if there are not enough gas, or if the call fails. - /// If successful returns the used gas using the Runtime GasWeightMapping. - pub fn try_dispatch( - handle: &mut impl PrecompileHandle, - origin: ::RuntimeOrigin, - call: Call, - ) -> Result - where - Runtime::RuntimeCall: From, - { - let call = Runtime::RuntimeCall::from(call); - let dispatch_info = call.get_dispatch_info(); - - // Make sure there is enough gas. - let remaining_gas = handle.remaining_gas(); - let required_gas = Runtime::GasWeightMapping::weight_to_gas(dispatch_info.weight); - if required_gas > remaining_gas { - return Err(TryDispatchError::Evm(ExitError::OutOfGas)); - } - - // Dispatch call. - // It may be possible to not record gas cost if the call returns Pays::No. - // However while Substrate handle checking weight while not making the sender pay for it, - // the EVM doesn't. It seems this safer to always record the costs to avoid unmetered - // computations. - let post_dispatch_info = using_precompile_handle(handle, || call.dispatch(origin)) - .map_err(|e| TryDispatchError::Substrate(e.error))?; - - let used_weight = post_dispatch_info.actual_weight; - - let used_gas = - Runtime::GasWeightMapping::weight_to_gas(used_weight.unwrap_or(dispatch_info.weight)); - - handle - .record_cost(used_gas) - .map_err(|e| TryDispatchError::Evm(e))?; - - Ok(post_dispatch_info) - } + /// Try to dispatch a Substrate call. + /// Return an error if there are not enough gas, or if the call fails. + /// If successful returns the used gas using the Runtime GasWeightMapping. + pub fn try_dispatch( + handle: &mut impl PrecompileHandle, + origin: ::RuntimeOrigin, + call: Call, + ) -> Result + where + Runtime::RuntimeCall: From, + { + let call = Runtime::RuntimeCall::from(call); + let dispatch_info = call.get_dispatch_info(); + + // Make sure there is enough gas. + let remaining_gas = handle.remaining_gas(); + let required_gas = Runtime::GasWeightMapping::weight_to_gas(dispatch_info.weight); + if required_gas > remaining_gas { + return Err(TryDispatchError::Evm(ExitError::OutOfGas)); + } + + // Dispatch call. + // It may be possible to not record gas cost if the call returns Pays::No. + // However while Substrate handle checking weight while not making the sender pay for it, + // the EVM doesn't. It seems this safer to always record the costs to avoid unmetered + // computations. + let post_dispatch_info = using_precompile_handle(handle, || call.dispatch(origin)) + .map_err(|e| TryDispatchError::Substrate(e.error))?; + + let used_weight = post_dispatch_info.actual_weight; + + let used_gas = + Runtime::GasWeightMapping::weight_to_gas(used_weight.unwrap_or(dispatch_info.weight)); + + handle + .record_cost(used_gas) + .map_err(|e| TryDispatchError::Evm(e))?; + + Ok(post_dispatch_info) + } } impl RuntimeHelper where - Runtime: pallet_evm::Config, + Runtime: pallet_evm::Config, { - /// Cost of a Substrate DB write in gas. - pub fn db_write_gas_cost() -> u64 { - ::GasWeightMapping::weight_to_gas( - ::DbWeight::get().writes(1), - ) - } - - /// Cost of a Substrate DB read in gas. - pub fn db_read_gas_cost() -> u64 { - ::GasWeightMapping::weight_to_gas( - ::DbWeight::get().reads(1), - ) - } + /// Cost of a Substrate DB write in gas. + pub fn db_write_gas_cost() -> u64 { + ::GasWeightMapping::weight_to_gas( + ::DbWeight::get().writes(1), + ) + } + + /// Cost of a Substrate DB read in gas. + pub fn db_read_gas_cost() -> u64 { + ::GasWeightMapping::weight_to_gas( + ::DbWeight::get().reads(1), + ) + } } diff --git a/precompiles/utils/src/testing/account.rs b/precompiles/utils/src/testing/account.rs index a75886cb8f..e348891faf 100644 --- a/precompiles/utils/src/testing/account.rs +++ b/precompiles/utils/src/testing/account.rs @@ -15,88 +15,88 @@ // along with Moonbeam. If not, see . use { - pallet_evm::AddressMapping, - scale_info::TypeInfo, - serde::{Deserialize, Serialize}, - sp_core::{Decode, Encode, MaxEncodedLen, H160, H256}, + pallet_evm::AddressMapping, + scale_info::TypeInfo, + serde::{Deserialize, Serialize}, + sp_core::{Decode, Encode, MaxEncodedLen, H160, H256}, }; #[derive( - Eq, - PartialEq, - Ord, - PartialOrd, - Clone, - Encode, - Decode, - Debug, - MaxEncodedLen, - TypeInfo, - Serialize, - Deserialize, - derive_more::Display, + Eq, + PartialEq, + Ord, + PartialOrd, + Clone, + Encode, + Decode, + Debug, + MaxEncodedLen, + TypeInfo, + Serialize, + Deserialize, + derive_more::Display, )] pub struct MockAccount(pub H160); impl MockAccount { - pub fn from_u64(v: u64) -> Self { - H160::from_low_u64_be(v).into() - } + pub fn from_u64(v: u64) -> Self { + H160::from_low_u64_be(v).into() + } - pub fn zero() -> Self { - H160::zero().into() - } + pub fn zero() -> Self { + H160::zero().into() + } - pub fn has_prefix(&self, prefix: &[u8]) -> bool { - &self.0[0..4] == prefix - } + pub fn has_prefix(&self, prefix: &[u8]) -> bool { + &self.0[0..4] == prefix + } - pub fn has_prefix_u32(&self, prefix: u32) -> bool { - self.0[0..4] == prefix.to_be_bytes() - } + pub fn has_prefix_u32(&self, prefix: u32) -> bool { + self.0[0..4] == prefix.to_be_bytes() + } - pub fn without_prefix(&self) -> u128 { - u128::from_be_bytes(<[u8; 16]>::try_from(&self.0[4..20]).expect("slice have len 16")) - } + pub fn without_prefix(&self) -> u128 { + u128::from_be_bytes(<[u8; 16]>::try_from(&self.0[4..20]).expect("slice have len 16")) + } } impl From for H160 { - fn from(account: MockAccount) -> H160 { - account.0 - } + fn from(account: MockAccount) -> H160 { + account.0 + } } impl From for [u8; 20] { - fn from(account: MockAccount) -> [u8; 20] { - let x: H160 = account.into(); - x.into() - } + fn from(account: MockAccount) -> [u8; 20] { + let x: H160 = account.into(); + x.into() + } } impl From for H256 { - fn from(x: MockAccount) -> H256 { - let x: H160 = x.into(); - x.into() - } + fn from(x: MockAccount) -> H256 { + let x: H160 = x.into(); + x.into() + } } impl From for MockAccount { - fn from(address: H160) -> MockAccount { - MockAccount(address) - } + fn from(address: H160) -> MockAccount { + MockAccount(address) + } } impl From<[u8; 20]> for MockAccount { - fn from(address: [u8; 20]) -> MockAccount { - let x: H160 = address.into(); - MockAccount(x) - } + fn from(address: [u8; 20]) -> MockAccount { + let x: H160 = address.into(); + MockAccount(x) + } } impl AddressMapping for MockAccount { - fn into_account_id(address: H160) -> MockAccount { - address.into() - } + fn into_account_id(address: H160) -> MockAccount { + address.into() + } } #[macro_export] @@ -139,43 +139,43 @@ mock_account!(David, |_| H160::repeat_byte(0xDD).into()); mock_account!(Precompile1, |_| MockAccount::from_u64(1)); mock_account!(CryptoAlith, |_| H160::from(hex_literal::hex!( - "f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac" + "f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac" )) .into()); mock_account!(CryptoBaltathar, |_| H160::from(hex_literal::hex!( - "3Cd0A705a2DC65e5b1E1205896BaA2be8A07c6e0" + "3Cd0A705a2DC65e5b1E1205896BaA2be8A07c6e0" )) .into()); mock_account!(CryptoCarleth, |_| H160::from(hex_literal::hex!( - "798d4Ba9baf0064Ec19eB4F0a1a45785ae9D6DFc" + "798d4Ba9baf0064Ec19eB4F0a1a45785ae9D6DFc" )) .into()); mock_account!( - AddressInPrefixedSet(u32, u128), - |value: AddressInPrefixedSet| { - let prefix: u32 = value.0; - let index: u128 = value.1; + AddressInPrefixedSet(u32, u128), + |value: AddressInPrefixedSet| { + let prefix: u32 = value.0; + let index: u128 = value.1; - let mut buffer = Vec::with_capacity(20); // 160 bits + let mut buffer = Vec::with_capacity(20); // 160 bits - buffer.extend_from_slice(&prefix.to_be_bytes()); - buffer.extend_from_slice(&index.to_be_bytes()); + buffer.extend_from_slice(&prefix.to_be_bytes()); + buffer.extend_from_slice(&index.to_be_bytes()); - assert_eq!(buffer.len(), 20, "address buffer should have len of 20"); + assert_eq!(buffer.len(), 20, "address buffer should have len of 20"); - H160::from_slice(&buffer).into() - } + H160::from_slice(&buffer).into() + } ); pub fn alith_secret_key() -> [u8; 32] { - hex_literal::hex!("5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133") + hex_literal::hex!("5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133") } pub fn baltathar_secret_key() -> [u8; 32] { - hex_literal::hex!("8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b") + hex_literal::hex!("8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b") } pub fn charleth_secret_key() -> [u8; 32] { - hex_literal::hex!("0b6e18cafb6ed99687ec547bd28139cafdd2bffe70e6b688025de6b445aa5c5b") + hex_literal::hex!("0b6e18cafb6ed99687ec547bd28139cafdd2bffe70e6b688025de6b445aa5c5b") } diff --git a/precompiles/utils/src/testing/execution.rs b/precompiles/utils/src/testing/execution.rs index 75332fd88c..dbe7188316 100644 --- a/precompiles/utils/src/testing/execution.rs +++ b/precompiles/utils/src/testing/execution.rs @@ -1,238 +1,214 @@ use { - crate::{ - testing::{decode_revert_message, MockHandle, PrettyLog, SubcallHandle, SubcallTrait}, - data::EvmData, - }, - fp_evm::{ - Context, ExitError, ExitSucceed, Log, PrecompileFailure, PrecompileOutput, - PrecompileResult, PrecompileSet, - }, - sp_core::{H160, U256}, - sp_std::boxed::Box, + crate::testing::{decode_revert_message, MockHandle, PrettyLog, SubcallHandle, SubcallTrait}, + assert_matches::assert_matches, + fp_evm::{ + Context, ExitError, ExitSucceed, Log, PrecompileFailure, PrecompileOutput, + PrecompileResult, PrecompileSet, + }, + sp_core::{H160, U256}, + sp_std::boxed::Box, }; #[must_use] pub struct PrecompilesTester<'p, P> { - precompiles: &'p P, - handle: MockHandle, + precompiles: &'p P, + handle: MockHandle, - target_gas: Option, - subcall_handle: Option, + target_gas: Option, + subcall_handle: Option, - expected_cost: Option, - expected_logs: Option>, - static_call: bool, + expected_cost: Option, + expected_logs: Option>, } impl<'p, P: PrecompileSet> PrecompilesTester<'p, P> { - pub fn new( - precompiles: &'p P, - from: impl Into, - to: impl Into, - data: Vec, - ) -> Self { - let to = to.into(); - let mut handle = MockHandle::new( - to.clone(), - Context { - address: to, - caller: from.into(), - apparent_value: U256::zero(), - }, - ); - - handle.input = data; - - Self { - precompiles, - handle, - - target_gas: None, - subcall_handle: None, - - expected_cost: None, - expected_logs: None, - static_call: false, - } - } - - pub fn with_value(mut self, value: impl Into) -> Self { - self.handle.context.apparent_value = value.into(); - self - } - - pub fn with_subcall_handle(mut self, subcall_handle: impl SubcallTrait) -> Self { - self.subcall_handle = Some(Box::new(subcall_handle)); - self - } - - pub fn with_target_gas(mut self, target_gas: Option) -> Self { - self.target_gas = target_gas; - self - } - - pub fn with_static_call(mut self, static_call: bool) -> Self { - self.static_call = static_call; - self - } - - pub fn expect_cost(mut self, cost: u64) -> Self { - self.expected_cost = Some(cost); - self - } - - pub fn expect_no_logs(mut self) -> Self { - self.expected_logs = Some(vec![]); - self - } - - pub fn expect_log(mut self, log: Log) -> Self { - self.expected_logs = Some({ - let mut logs = self.expected_logs.unwrap_or_else(Vec::new); - logs.push(PrettyLog(log)); - logs - }); - self - } - - fn assert_optionals(&self) { - if let Some(cost) = &self.expected_cost { - assert_eq!(&self.handle.gas_used, cost); - } - - if let Some(logs) = &self.expected_logs { - similar_asserts::assert_eq!(&self.handle.logs, logs); - } - } - - fn execute(&mut self) -> Option { - let handle = &mut self.handle; - handle.subcall_handle = self.subcall_handle.take(); - handle.is_static = self.static_call; - - if let Some(gas_limit) = self.target_gas { - handle.gas_limit = gas_limit; - } - - let res = self.precompiles.execute(handle); - - self.subcall_handle = handle.subcall_handle.take(); - - res - } - - /// Execute the precompile set and expect some precompile to have been executed, regardless of the - /// result. - pub fn execute_some(mut self) { - let res = self.execute(); - assert!(res.is_some()); - self.assert_optionals(); - } - - /// Execute the precompile set and expect no precompile to have been executed. - pub fn execute_none(mut self) { - let res = self.execute(); - assert!(res.is_some()); - self.assert_optionals(); - } - - /// Execute the precompile set and check it returns provided output. - pub fn execute_returns_raw(mut self, output: Vec) { - let res = self.execute(); - - match res { - Some(Err(PrecompileFailure::Revert { output, .. })) => { - let decoded = decode_revert_message(&output); - eprintln!( - "Revert message (bytes): {:?}", - sp_core::hexdisplay::HexDisplay::from(&decoded) - ); - eprintln!( - "Revert message (string): {:?}", - core::str::from_utf8(decoded).ok() - ); - panic!("Shouldn't have reverted"); - } - Some(Ok(PrecompileOutput { - exit_status: ExitSucceed::Returned, - output: execution_output, - })) => { - if execution_output != output { - eprintln!( - "Output (bytes): {:?}", - sp_core::hexdisplay::HexDisplay::from(&execution_output) - ); - eprintln!( - "Output (string): {:?}", - core::str::from_utf8(&execution_output).ok() - ); - panic!("Output doesn't match"); - } - } - other => panic!("Unexpected result: {:?}", other), - } - - self.assert_optionals(); - } - - /// Execute the precompile set and check it returns provided Solidity encoded output. - pub fn execute_returns(self, output: impl EvmData) { - self.execute_returns_raw(crate::data::encode_return_value(output)) - } - - /// Execute the precompile set and check if it reverts. - /// Take a closure allowing to perform custom matching on the output. - pub fn execute_reverts(mut self, check: impl Fn(&[u8]) -> bool) { - let res = self.execute(); - - match res { - Some(Err(PrecompileFailure::Revert { output, .. })) => { - let decoded = decode_revert_message(&output); - if !check(decoded) { - eprintln!( - "Revert message (bytes): {:?}", - sp_core::hexdisplay::HexDisplay::from(&decoded) - ); - eprintln!( - "Revert message (string): {:?}", - core::str::from_utf8(decoded).ok() - ); - panic!("Revert reason doesn't match !"); - } - } - other => panic!("Didn't revert, instead returned {:?}", other), - } - - self.assert_optionals(); - } - - /// Execute the precompile set and check it returns provided output. - pub fn execute_error(mut self, error: ExitError) { - let res = self.execute(); - assert_eq!( - res, - Some(Err(PrecompileFailure::Error { exit_status: error })) - ); - self.assert_optionals(); - } + pub fn new( + precompiles: &'p P, + from: impl Into, + to: impl Into, + data: Vec, + ) -> Self { + let to = to.into(); + let mut handle = MockHandle::new( + to.clone(), + Context { + address: to, + caller: from.into(), + apparent_value: U256::zero(), + }, + ); + + handle.input = data; + + Self { + precompiles, + handle, + + target_gas: None, + subcall_handle: None, + + expected_cost: None, + expected_logs: None, + } + } + + pub fn with_value(mut self, value: impl Into) -> Self { + self.handle.context.apparent_value = value.into(); + self + } + + pub fn with_subcall_handle(mut self, subcall_handle: impl SubcallTrait) -> Self { + self.subcall_handle = Some(Box::new(subcall_handle)); + self + } + + pub fn with_target_gas(mut self, target_gas: Option) -> Self { + self.target_gas = target_gas; + self + } + + pub fn expect_cost(mut self, cost: u64) -> Self { + self.expected_cost = Some(cost); + self + } + + pub fn expect_no_logs(mut self) -> Self { + self.expected_logs = Some(vec![]); + self + } + + pub fn expect_log(mut self, log: Log) -> Self { + self.expected_logs = Some({ + let mut logs = self.expected_logs.unwrap_or_else(Vec::new); + logs.push(PrettyLog(log)); + logs + }); + self + } + + fn assert_optionals(&self) { + if let Some(cost) = &self.expected_cost { + assert_eq!(&self.handle.gas_used, cost); + } + + if let Some(logs) = &self.expected_logs { + similar_asserts::assert_eq!(&self.handle.logs, logs); + } + } + + fn execute(&mut self) -> Option { + let handle = &mut self.handle; + handle.subcall_handle = self.subcall_handle.take(); + + if let Some(gas_limit) = self.target_gas { + handle.gas_limit = gas_limit; + } + + let res = self.precompiles.execute(handle); + + self.subcall_handle = handle.subcall_handle.take(); + + res + } + + /// Execute the precompile set and expect some precompile to have been executed, regardless of the + /// result. + pub fn execute_some(mut self) { + let res = self.execute(); + assert!(res.is_some()); + self.assert_optionals(); + } + + /// Execute the precompile set and expect no precompile to have been executed. + pub fn execute_none(mut self) { + let res = self.execute(); + assert!(res.is_some()); + self.assert_optionals(); + } + + /// Execute the precompile set and check it returns provided output. + pub fn execute_returns_raw(mut self, output: Vec) { + let res = self.execute(); + + match res { + Some(Err(PrecompileFailure::Revert { output, .. })) => { + let decoded = decode_revert_message(&output); + eprintln!( + "Revert message (bytes): {:?}", + sp_core::hexdisplay::HexDisplay::from(&decoded) + ); + eprintln!( + "Revert message (string): {:?}", + core::str::from_utf8(decoded).ok() + ); + panic!("Shouldn't have reverted"); + } + Some(Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output: execution_output, + })) => { + if execution_output != output { + eprintln!( + "Output (bytes): {:?}", + sp_core::hexdisplay::HexDisplay::from(&execution_output) + ); + eprintln!( + "Output (string): {:?}", + core::str::from_utf8(&execution_output).ok() + ); + panic!("Output doesn't match"); + } + } + other => panic!("Unexpected result: {:?}", other), + } + + self.assert_optionals(); + } + + /// Execute the precompile set and check it returns provided Solidity encoded output. + pub fn execute_returns(self, output: Vec) { + self.execute_returns_raw(output) + } + + /// Execute the precompile set and check if it reverts. + /// Take a closure allowing to perform custom matching on the output. + pub fn execute_reverts(mut self, check: impl Fn(&[u8]) -> bool) { + let res = self.execute(); + assert_matches!( + res, + Some(Err(PrecompileFailure::Revert { output, ..})) + if check(&output) + ); + self.assert_optionals(); + } + + /// Execute the precompile set and check it returns provided output. + pub fn execute_error(mut self, error: ExitError) { + let res = self.execute(); + assert_eq!( + res, + Some(Err(PrecompileFailure::Error { exit_status: error })) + ); + self.assert_optionals(); + } } pub trait PrecompileTesterExt: PrecompileSet + Sized { - fn prepare_test( - &self, - from: impl Into, - to: impl Into, - data: impl Into>, - ) -> PrecompilesTester; + fn prepare_test( + &self, + from: impl Into, + to: impl Into, + data: impl Into>, + ) -> PrecompilesTester; } impl PrecompileTesterExt for T { - fn prepare_test( - &self, - from: impl Into, - to: impl Into, - data: impl Into>, - ) -> PrecompilesTester { - PrecompilesTester::new(self, from, to, data.into()) - } + fn prepare_test( + &self, + from: impl Into, + to: impl Into, + data: impl Into>, + ) -> PrecompilesTester { + PrecompilesTester::new(self, from, to, data.into()) + } } diff --git a/precompiles/utils/src/testing/handle.rs b/precompiles/utils/src/testing/handle.rs index c297fc8bca..47771fe573 100644 --- a/precompiles/utils/src/testing/handle.rs +++ b/precompiles/utils/src/testing/handle.rs @@ -15,58 +15,58 @@ // along with Moonbeam. If not, see . use { - crate::testing::PrettyLog, - evm::{ExitRevert, ExitSucceed}, - fp_evm::{Context, ExitError, ExitReason, Log, PrecompileHandle, Transfer}, - sp_core::{H160, H256}, - sp_std::boxed::Box, + crate::testing::PrettyLog, + evm::{ExitRevert, ExitSucceed}, + fp_evm::{Context, ExitError, ExitReason, Log, PrecompileHandle, Transfer}, + sp_core::{H160, H256}, + sp_std::boxed::Box, }; #[derive(Debug, Clone)] pub struct Subcall { - pub address: H160, - pub transfer: Option, - pub input: Vec, - pub target_gas: Option, - pub is_static: bool, - pub context: Context, + pub address: H160, + pub transfer: Option, + pub input: Vec, + pub target_gas: Option, + pub is_static: bool, + pub context: Context, } #[derive(Debug, Clone)] pub struct SubcallOutput { - pub reason: ExitReason, - pub output: Vec, - pub cost: u64, - pub logs: Vec, + pub reason: ExitReason, + pub output: Vec, + pub cost: u64, + pub logs: Vec, } impl SubcallOutput { - pub fn revert() -> Self { - Self { - reason: ExitReason::Revert(ExitRevert::Reverted), - output: Vec::new(), - cost: 0, - logs: Vec::new(), - } - } - - pub fn succeed() -> Self { - Self { - reason: ExitReason::Succeed(ExitSucceed::Returned), - output: Vec::new(), - cost: 0, - logs: Vec::new(), - } - } - - pub fn out_of_gas() -> Self { - Self { - reason: ExitReason::Error(ExitError::OutOfGas), - output: Vec::new(), - cost: 0, - logs: Vec::new(), - } - } + pub fn revert() -> Self { + Self { + reason: ExitReason::Revert(ExitRevert::Reverted), + output: Vec::new(), + cost: 0, + logs: Vec::new(), + } + } + + pub fn succeed() -> Self { + Self { + reason: ExitReason::Succeed(ExitSucceed::Returned), + output: Vec::new(), + cost: 0, + logs: Vec::new(), + } + } + + pub fn out_of_gas() -> Self { + Self { + reason: ExitReason::Error(ExitError::OutOfGas), + output: Vec::new(), + cost: 0, + logs: Vec::new(), + } + } } pub trait SubcallTrait: FnMut(Subcall) -> SubcallOutput + 'static {} @@ -77,129 +77,129 @@ pub type SubcallHandle = Box; /// Mock handle to write tests for precompiles. pub struct MockHandle { - pub gas_limit: u64, - pub gas_used: u64, - pub logs: Vec, - pub subcall_handle: Option, - pub code_address: H160, - pub input: Vec, - pub context: Context, - pub is_static: bool, + pub gas_limit: u64, + pub gas_used: u64, + pub logs: Vec, + pub subcall_handle: Option, + pub code_address: H160, + pub input: Vec, + pub context: Context, + pub is_static: bool, } impl MockHandle { - pub fn new(code_address: H160, context: Context) -> Self { - Self { - gas_limit: u64::MAX, - gas_used: 0, - logs: vec![], - subcall_handle: None, - code_address, - input: Vec::new(), - context, - is_static: false, - } - } + pub fn new(code_address: H160, context: Context) -> Self { + Self { + gas_limit: u64::MAX, + gas_used: 0, + logs: vec![], + subcall_handle: None, + code_address, + input: Vec::new(), + context, + is_static: false, + } + } } impl PrecompileHandle for MockHandle { - /// Perform subcall in provided context. - /// Precompile specifies in which context the subcall is executed. - fn call( - &mut self, - address: H160, - transfer: Option, - input: Vec, - target_gas: Option, - is_static: bool, - context: &Context, - ) -> (ExitReason, Vec) { - if self - .record_cost(crate::evm::costs::call_cost( - context.apparent_value, - &evm::Config::london(), - )) - .is_err() - { - return (ExitReason::Error(ExitError::OutOfGas), vec![]); - } - - match &mut self.subcall_handle { - Some(handle) => { - let SubcallOutput { - reason, - output, - cost, - logs, - } = handle(Subcall { - address, - transfer, - input, - target_gas, - is_static, - context: context.clone(), - }); - - if self.record_cost(cost).is_err() { - return (ExitReason::Error(ExitError::OutOfGas), vec![]); - } - - for log in logs { - self.log(log.address, log.topics, log.data) - .expect("cannot fail"); - } - - (reason, output) - } - None => panic!("no subcall handle registered"), - } - } - - fn record_cost(&mut self, cost: u64) -> Result<(), ExitError> { - self.gas_used += cost; - - if self.gas_used > self.gas_limit { - Err(ExitError::OutOfGas) - } else { - Ok(()) - } - } - - fn remaining_gas(&self) -> u64 { - self.gas_limit - self.gas_used - } - - fn log(&mut self, address: H160, topics: Vec, data: Vec) -> Result<(), ExitError> { - self.logs.push(PrettyLog(Log { - address, - topics, - data, - })); - Ok(()) - } - - /// Retreive the code address (what is the address of the precompile being called). - fn code_address(&self) -> H160 { - self.code_address - } - - /// Retreive the input data the precompile is called with. - fn input(&self) -> &[u8] { - &self.input - } - - /// Retreive the context in which the precompile is executed. - fn context(&self) -> &Context { - &self.context - } - - /// Is the precompile call is done statically. - fn is_static(&self) -> bool { - self.is_static - } - - /// Retreive the gas limit of this call. - fn gas_limit(&self) -> Option { - Some(self.gas_limit) - } -} \ No newline at end of file + /// Perform subcall in provided context. + /// Precompile specifies in which context the subcall is executed. + fn call( + &mut self, + address: H160, + transfer: Option, + input: Vec, + target_gas: Option, + is_static: bool, + context: &Context, + ) -> (ExitReason, Vec) { + if self + .record_cost(crate::evm::costs::call_cost( + context.apparent_value, + &evm::Config::london(), + )) + .is_err() + { + return (ExitReason::Error(ExitError::OutOfGas), vec![]); + } + + match &mut self.subcall_handle { + Some(handle) => { + let SubcallOutput { + reason, + output, + cost, + logs, + } = handle(Subcall { + address, + transfer, + input, + target_gas, + is_static, + context: context.clone(), + }); + + if self.record_cost(cost).is_err() { + return (ExitReason::Error(ExitError::OutOfGas), vec![]); + } + + for log in logs { + self.log(log.address, log.topics, log.data) + .expect("cannot fail"); + } + + (reason, output) + } + None => panic!("no subcall handle registered"), + } + } + + fn record_cost(&mut self, cost: u64) -> Result<(), ExitError> { + self.gas_used += cost; + + if self.gas_used > self.gas_limit { + Err(ExitError::OutOfGas) + } else { + Ok(()) + } + } + + fn remaining_gas(&self) -> u64 { + self.gas_limit - self.gas_used + } + + fn log(&mut self, address: H160, topics: Vec, data: Vec) -> Result<(), ExitError> { + self.logs.push(PrettyLog(Log { + address, + topics, + data, + })); + Ok(()) + } + + /// Retreive the code address (what is the address of the precompile being called). + fn code_address(&self) -> H160 { + self.code_address + } + + /// Retreive the input data the precompile is called with. + fn input(&self) -> &[u8] { + &self.input + } + + /// Retreive the context in which the precompile is executed. + fn context(&self) -> &Context { + &self.context + } + + /// Is the precompile call is done statically. + fn is_static(&self) -> bool { + self.is_static + } + + /// Retreive the gas limit of this call. + fn gas_limit(&self) -> Option { + Some(self.gas_limit) + } +} diff --git a/precompiles/utils/src/testing/mod.rs b/precompiles/utils/src/testing/mod.rs index 115807f08c..c918c9b622 100644 --- a/precompiles/utils/src/testing/mod.rs +++ b/precompiles/utils/src/testing/mod.rs @@ -18,79 +18,75 @@ pub mod account; pub mod execution; pub mod handle; -pub use { - account::*, - execution::*, - handle::*, -}; +pub use {account::*, execution::*, handle::*}; use fp_evm::Log; pub fn decode_revert_message(encoded: &[u8]) -> &[u8] { - let encoded_len = encoded.len(); - // selector 4 + offset 32 + string length 32 - if encoded_len > 68 { - let message_len = encoded[36..68].iter().sum::(); - if encoded_len >= 68 + message_len as usize { - return &encoded[68..68 + message_len as usize]; - } - } - b"decode_revert_message: error" + let encoded_len = encoded.len(); + // selector 4 + offset 32 + string length 32 + if encoded_len > 68 { + let message_len = encoded[36..68].iter().sum::(); + if encoded_len >= 68 + message_len as usize { + return &encoded[68..68 + message_len as usize]; + } + } + b"decode_revert_message: error" } #[derive(Clone, PartialEq, Eq)] pub struct PrettyLog(Log); impl core::fmt::Debug for PrettyLog { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { - let bytes = self - .0 - .data - .iter() - .map(|b| format!("{:02X}", b)) - .collect::>() - .join(""); + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { + let bytes = self + .0 + .data + .iter() + .map(|b| format!("{:02X}", b)) + .collect::>() + .join(""); - let message = String::from_utf8(self.0.data.clone()).ok(); + let message = String::from_utf8(self.0.data.clone()).ok(); - f.debug_struct("Log") - .field("address", &self.0.address) - .field("topics", &self.0.topics) - .field("data", &bytes) - .field("data_utf8", &message) - .finish() - } + f.debug_struct("Log") + .field("address", &self.0.address) + .field("topics", &self.0.topics) + .field("data", &bytes) + .field("data_utf8", &message) + .finish() + } } /// Panics if an event is not found in the system log of events #[macro_export] macro_rules! assert_event_emitted { - ($event:expr) => { - match &$event { - e => { - assert!( - crate::mock::events().iter().find(|x| *x == e).is_some(), - "Event {:?} was not found in events: \n {:?}", - e, - crate::mock::events() - ); - } - } - }; + ($event:expr) => { + match &$event { + e => { + assert!( + crate::mock::events().iter().find(|x| *x == e).is_some(), + "Event {:?} was not found in events: \n {:?}", + e, + crate::mock::events() + ); + } + } + }; } // Panics if an event is found in the system log of events #[macro_export] macro_rules! assert_event_not_emitted { - ($event:expr) => { - match &$event { - e => { - assert!( - crate::mock::events().iter().find(|x| *x == e).is_none(), - "Event {:?} was found in events: \n {:?}", - e, - crate::mock::events() - ); - } - } - }; + ($event:expr) => { + match &$event { + e => { + assert!( + crate::mock::events().iter().find(|x| *x == e).is_none(), + "Event {:?} was found in events: \n {:?}", + e, + crate::mock::events() + ); + } + } + }; } diff --git a/runtime/shibuya/Cargo.toml b/runtime/shibuya/Cargo.toml index 12c2c14ea1..ac02dfc815 100644 --- a/runtime/shibuya/Cargo.toml +++ b/runtime/shibuya/Cargo.toml @@ -106,6 +106,7 @@ pallet-custom-signatures = { workspace = true } pallet-dapps-staking = { workspace = true } pallet-ethereum-checked = { workspace = true } pallet-evm-precompile-assets-erc20 = { workspace = true } +pallet-evm-precompile-batch = { workspace = true } pallet-evm-precompile-dapps-staking = { workspace = true } pallet-evm-precompile-sr25519 = { workspace = true } pallet-evm-precompile-substrate-ecdsa = { workspace = true } diff --git a/runtime/shibuya/src/precompiles.rs b/runtime/shibuya/src/precompiles.rs index bd8d1714c7..d81f8fd902 100644 --- a/runtime/shibuya/src/precompiles.rs +++ b/runtime/shibuya/src/precompiles.rs @@ -23,6 +23,7 @@ use pallet_evm::{ PrecompileResult, PrecompileSet, }; use pallet_evm_precompile_assets_erc20::{AddressToAssetId, Erc20AssetsPrecompileSet}; +use pallet_evm_precompile_batch::BatchPrecompile; use pallet_evm_precompile_blake2::Blake2F; use pallet_evm_precompile_bn128::{Bn128Add, Bn128Mul, Bn128Pairing}; use pallet_evm_precompile_dapps_staking::DappsStakingWrapper; @@ -58,7 +59,8 @@ impl ShibuyaNetworkPrecompiles { /// under the precompile. pub fn used_addresses() -> impl Iterator { sp_std::vec![ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 1024, 1025, 1026, 1027, 20481, 20482, 20483, 20484, 20485 + 1, 2, 3, 4, 5, 6, 7, 8, 9, 1024, 1025, 1026, 1027, 20481, 20482, 20483, 20484, 20485, + 20486 ] .into_iter() .map(hash) @@ -74,6 +76,7 @@ where DappsStakingWrapper: Precompile, XcmPrecompile: Precompile, XvmPrecompile: Precompile, + BatchPrecompile: Precompile, Dispatch: Precompile, R: pallet_evm::Config + pallet_assets::Config @@ -121,6 +124,8 @@ where a if a == hash(20484) => Some(XcmPrecompile::::execute(handle)), // Xvm 0x5005 a if a == hash(20485) => Some(XvmPrecompile::::execute(handle)), + // Batch 0x5006 + a if a == hash(20486) => Some(BatchPrecompile::::execute(handle)), // If the address matches asset prefix, the we route through the asset precompile set a if &a.to_fixed_bytes()[0..4] == ASSET_PRECOMPILE_ADDRESS_PREFIX => { Erc20AssetsPrecompileSet::::new().execute(handle) From 156f76d0a6cde969cd07fccb54d70ff27b99ccad Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Wed, 2 Aug 2023 16:16:31 +0530 Subject: [PATCH 04/21] minor fixes and updates --- precompiles/batch/Batch.sol | 20 +++++--------------- precompiles/batch/src/mock.rs | 2 -- precompiles/batch/src/tests.rs | 28 +++------------------------- 3 files changed, 8 insertions(+), 42 deletions(-) diff --git a/precompiles/batch/Batch.sol b/precompiles/batch/Batch.sol index d4069d1b27..2406fc9d32 100644 --- a/precompiles/batch/Batch.sol +++ b/precompiles/batch/Batch.sol @@ -1,17 +1,15 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity >=0.8.3; -/// @dev The Batch contract's address. -address constant BATCH_ADDRESS = 0x0000000000000000000000000000000000000808; +/// Interface to the precompiled contract on Shibuya/Shiden/Astar +/// Predeployed at the address 0x0000000000000000000000000000000000005006 +/// For better understanding check the source code: +/// repo: https://github.com/AstarNetwork/astar +/// code: frame/dapps-staking/src/pallet -/// @dev The Batch contract's instance. -Batch constant BATCH_CONTRACT = Batch(BATCH_ADDRESS); - -/// @author The Moonbeam Team /// @title Batch precompile /// @dev Allows to perform multiple calls throught one call to the precompile. /// Can be used by EOA to do multiple calls in a single transaction. -/// @custom:address 0x0000000000000000000000000000000000000808 interface Batch { /// @dev Batch multiple calls into a single transaction. /// All calls are performed from the address calling this precompile. @@ -25,7 +23,6 @@ interface Batch { /// additional calls will be performed with an empty call data. /// @param gasLimit Gas limit for each `to` address. Use 0 to forward all the remaining gas. /// If array is shorter than "to" then the remaining gas available will be used. - /// @custom:selector 79df4b9c function batchSome( address[] memory to, uint256[] memory value, @@ -46,7 +43,6 @@ interface Batch { /// additional calls will be performed with an empty call data. /// @param gasLimit Gas limit for each `to` address. Use 0 to forward all the remaining gas. /// If array is shorter than "to" then the remaining gas available will be used. - /// @custom:selector cf0491c7 function batchSomeUntilFailure( address[] memory to, uint256[] memory value, @@ -66,7 +62,6 @@ interface Batch { /// additional calls will be performed with an empty call data. /// @param gasLimit Gas limit for each `to` address. Use 0 to forward all the remaining gas. /// If array is shorter than "to" then the remaining gas available will be used. - /// @custom:selector 96e292b8 function batchAll( address[] memory to, uint256[] memory value, @@ -74,9 +69,4 @@ interface Batch { uint64[] memory gasLimit ) external; - /// Emitted when a subcall succeeds. - event SubcallSucceeded(uint256 index); - - /// Emitted when a subcall fails. - event SubcallFailed(uint256 index); } diff --git a/precompiles/batch/src/mock.rs b/precompiles/batch/src/mock.rs index 926314b7c2..db7e4f72ef 100644 --- a/precompiles/batch/src/mock.rs +++ b/precompiles/batch/src/mock.rs @@ -124,8 +124,6 @@ where } } -pub type PCall = BatchPrecompile; - mock_account!(Revert, |_| MockAccount::from_u64(2)); parameter_types! { diff --git a/precompiles/batch/src/tests.rs b/precompiles/batch/src/tests.rs index 14aad8b556..000996f9d4 100644 --- a/precompiles/batch/src/tests.rs +++ b/precompiles/batch/src/tests.rs @@ -14,19 +14,11 @@ // You should have received a copy of the GNU General Public License // along with Moonbeam. If not, see . -use crate::mock::{ - balance, precompile_address, BatchPrecompileMock, ExtBuilder, PCall, PrecompilesValue, Revert, - Runtime, RuntimeCall, RuntimeOrigin, -}; +use crate::mock::{precompile_address, BatchPrecompileMock, ExtBuilder, PrecompilesValue, Runtime}; use crate::{log_subcall_failed, log_subcall_succeeded, Mode, *}; use fp_evm::ExitError; -use frame_support::{ - assert_ok, - dispatch::{DispatchError, Dispatchable}, -}; -use pallet_evm::Call as EvmCall; -use precompile_utils::{evm::costs::call_cost, testing::*, *}; -use sha3::digest::typenum::U25; +use frame_support::dispatch::{DispatchError, Dispatchable}; +use precompile_utils::{evm::costs::call_cost, testing::*}; use sp_core::{H160, H256, U256}; use sp_runtime::{DispatchErrorWithPostInfo, ModuleError}; @@ -34,20 +26,6 @@ fn precompiles() -> BatchPrecompileMock { PrecompilesValue::get() } -fn evm_call(from: impl Into, input: Vec) -> EvmCall { - EvmCall::call { - source: from.into(), - target: precompile_address().into(), - input, - value: U256::zero(), // No value sent in EVM - gas_limit: u64::max_value(), - max_fee_per_gas: 0.into(), - max_priority_fee_per_gas: Some(U256::zero()), - nonce: None, // Use the next nonce - access_list: Vec::new(), - } -} - fn costs() -> (u64, u64) { let return_log_cost = log_subcall_failed(precompile_address(), 0) .compute_cost() From 35c8eb4b08cf28fc806ccd996084adee012ca697 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Thu, 3 Aug 2023 13:47:00 +0530 Subject: [PATCH 05/21] refactor, add batch to local --- Cargo.lock | 1 + precompiles/batch/src/mock.rs | 9 - precompiles/batch/src/tests.rs | 4 +- precompiles/utils/src/lib.rs | 2 - precompiles/utils/src/old_testing.rs | 462 ---------- precompiles/utils/src/precompile_set.rs | 1079 ----------------------- precompiles/utils/src/substrate.rs | 119 --- runtime/local/Cargo.toml | 1 + runtime/local/src/precompiles.rs | 4 + 9 files changed, 7 insertions(+), 1674 deletions(-) delete mode 100644 precompiles/utils/src/old_testing.rs delete mode 100644 precompiles/utils/src/precompile_set.rs delete mode 100644 precompiles/utils/src/substrate.rs diff --git a/Cargo.lock b/Cargo.lock index e04995a757..85b0107bcb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5379,6 +5379,7 @@ dependencies = [ "pallet-ethereum-checked", "pallet-evm", "pallet-evm-precompile-assets-erc20", + "pallet-evm-precompile-batch", "pallet-evm-precompile-blake2", "pallet-evm-precompile-bn128", "pallet-evm-precompile-dapps-staking", diff --git a/precompiles/batch/src/mock.rs b/precompiles/batch/src/mock.rs index db7e4f72ef..2ee775bd55 100644 --- a/precompiles/batch/src/mock.rs +++ b/precompiles/batch/src/mock.rs @@ -176,11 +176,6 @@ impl Default for ExtBuilder { } impl ExtBuilder { - pub(crate) fn with_balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self { - self.balances = balances; - self - } - pub(crate) fn build(self) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default() .build_storage::() @@ -203,7 +198,3 @@ impl ExtBuilder { ext } } - -pub fn balance(account: impl Into) -> Balance { - pallet_balances::Pallet::::usable_balance(account.into()) -} diff --git a/precompiles/batch/src/tests.rs b/precompiles/batch/src/tests.rs index 000996f9d4..0b7cb50771 100644 --- a/precompiles/batch/src/tests.rs +++ b/precompiles/batch/src/tests.rs @@ -17,10 +17,8 @@ use crate::mock::{precompile_address, BatchPrecompileMock, ExtBuilder, PrecompilesValue, Runtime}; use crate::{log_subcall_failed, log_subcall_succeeded, Mode, *}; use fp_evm::ExitError; -use frame_support::dispatch::{DispatchError, Dispatchable}; use precompile_utils::{evm::costs::call_cost, testing::*}; -use sp_core::{H160, H256, U256}; -use sp_runtime::{DispatchErrorWithPostInfo, ModuleError}; +use sp_core::{H256, U256}; fn precompiles() -> BatchPrecompileMock { PrecompilesValue::get() diff --git a/precompiles/utils/src/lib.rs b/precompiles/utils/src/lib.rs index 40691a2d2f..3a663032e9 100644 --- a/precompiles/utils/src/lib.rs +++ b/precompiles/utils/src/lib.rs @@ -41,8 +41,6 @@ use sp_std::{marker::PhantomData, vec, vec::Vec}; pub mod bytes; pub mod data; pub mod evm; -// pub mod precompile_set; -pub mod substrate; pub use data::{Address, Bytes, EvmData, EvmDataReader, EvmDataWriter}; pub use precompile_utils_macro::{generate_function_selector, keccak256}; diff --git a/precompiles/utils/src/old_testing.rs b/precompiles/utils/src/old_testing.rs deleted file mode 100644 index 56f0d3a0c7..0000000000 --- a/precompiles/utils/src/old_testing.rs +++ /dev/null @@ -1,462 +0,0 @@ -// This file is part of Astar. - -// Copyright 2019-2022 PureStake Inc. -// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later -// -// This file is part of Utils package, originally developed by Purestake Inc. -// Utils package used in Astar Network in terms of GPLv3. -// -// Utils is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Utils is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Utils. If not, see . -use super::*; -use assert_matches::assert_matches; -use fp_evm::{ - ExitReason, ExitSucceed, PrecompileOutput, PrecompileResult, PrecompileSet, Transfer, -}; -use sp_std::boxed::Box; -extern crate evm; - -pub struct Subcall { - pub address: H160, - pub transfer: Option, - pub input: Vec, - pub target_gas: Option, - pub is_static: bool, - pub context: Context, -} - -pub struct SubcallOutput { - pub reason: ExitReason, - pub output: Vec, - pub cost: u64, - pub logs: Vec, -} - -impl SubcallOutput { - pub fn revert() -> Self { - Self { - reason: ExitReason::Revert(ExitRevert::Reverted), - output: Vec::new(), - cost: 0, - logs: Vec::new(), - } - } - - pub fn succeed() -> Self { - Self { - reason: ExitReason::Succeed(ExitSucceed::Returned), - output: Vec::new(), - cost: 0, - logs: Vec::new(), - } - } - - pub fn out_of_gas() -> Self { - Self { - reason: ExitReason::Error(ExitError::OutOfGas), - output: Vec::new(), - cost: 0, - logs: Vec::new(), - } - } -} -pub trait SubcallTrait: FnMut(Subcall) -> SubcallOutput + 'static {} - -impl SubcallOutput + 'static> SubcallTrait for T {} - -pub type SubcallHandle = Box; - -/// Mock handle to write tests for precompiles. -pub struct MockHandle { - pub gas_limit: u64, - pub gas_used: u64, - pub logs: Vec, - pub subcall_handle: Option, - pub code_address: H160, - pub input: Vec, - pub context: Context, - pub is_static: bool, -} - -impl MockHandle { - pub fn new(code_address: H160, context: Context) -> Self { - Self { - gas_limit: u64::MAX, - gas_used: 0, - logs: vec![], - subcall_handle: None, - code_address, - input: Vec::new(), - context, - is_static: false, - } - } -} - -// Compute the cost of doing a subcall. -// Some parameters cannot be known in advance, so we estimate the worst possible cost. -pub fn call_cost(value: U256, config: &evm::Config) -> u64 { - // Copied from EVM code since not public. - pub const G_CALLVALUE: u64 = 9000; - pub const G_NEWACCOUNT: u64 = 25000; - - fn address_access_cost(is_cold: bool, regular_value: u64, config: &evm::Config) -> u64 { - if config.increase_state_access_gas { - if is_cold { - config.gas_account_access_cold - } else { - config.gas_storage_read_warm - } - } else { - regular_value - } - } - - fn xfer_cost(is_call_or_callcode: bool, transfers_value: bool) -> u64 { - if is_call_or_callcode && transfers_value { - G_CALLVALUE - } else { - 0 - } - } - - fn new_cost( - is_call_or_staticcall: bool, - new_account: bool, - transfers_value: bool, - config: &evm::Config, - ) -> u64 { - let eip161 = !config.empty_considered_exists; - if is_call_or_staticcall { - if eip161 { - if transfers_value && new_account { - G_NEWACCOUNT - } else { - 0 - } - } else if new_account { - G_NEWACCOUNT - } else { - 0 - } - } else { - 0 - } - } - - let transfers_value = value != U256::default(); - let is_cold = true; - let is_call_or_callcode = true; - let is_call_or_staticcall = true; - let new_account = true; - - address_access_cost(is_cold, config.gas_call, config) - + xfer_cost(is_call_or_callcode, transfers_value) - + new_cost(is_call_or_staticcall, new_account, transfers_value, config) -} - -impl PrecompileHandle for MockHandle { - /// Perform subcall in provided context. - /// Precompile specifies in which context the subcall is executed. - fn call( - &mut self, - address: H160, - transfer: Option, - input: Vec, - target_gas: Option, - is_static: bool, - context: &Context, - ) -> (ExitReason, Vec) { - if self - .record_cost(call_cost(context.apparent_value, &evm::Config::london())) - .is_err() - { - return (ExitReason::Error(ExitError::OutOfGas), vec![]); - } - - match &mut self.subcall_handle { - Some(handle) => { - let SubcallOutput { - reason, - output, - cost, - logs, - } = handle(Subcall { - address, - transfer, - input, - target_gas, - is_static, - context: context.clone(), - }); - - if self.record_cost(cost).is_err() { - return (ExitReason::Error(ExitError::OutOfGas), vec![]); - } - - for log in logs { - self.log(log.address, log.topics, log.data) - .expect("cannot fail"); - } - - (reason, output) - } - None => panic!("no subcall handle registered"), - } - } - - fn record_cost(&mut self, cost: u64) -> Result<(), ExitError> { - self.gas_used += cost; - - if self.gas_used > self.gas_limit { - Err(ExitError::OutOfGas) - } else { - Ok(()) - } - } - - fn remaining_gas(&self) -> u64 { - self.gas_limit - self.gas_used - } - - fn log(&mut self, address: H160, topics: Vec, data: Vec) -> Result<(), ExitError> { - self.logs.push(PrettyLog(Log { - address, - topics, - data, - })); - Ok(()) - } - - /// Retreive the code address (what is the address of the precompile being called). - fn code_address(&self) -> H160 { - self.code_address - } - - /// Retreive the input data the precompile is called with. - fn input(&self) -> &[u8] { - &self.input - } - - /// Retreive the context in which the precompile is executed. - fn context(&self) -> &Context { - &self.context - } - - /// Is the precompile call is done statically. - fn is_static(&self) -> bool { - self.is_static - } - - /// Retreive the gas limit of this call. - fn gas_limit(&self) -> Option { - Some(self.gas_limit) - } -} - -pub struct PrecompilesTester<'p, P> { - precompiles: &'p P, - handle: MockHandle, - - target_gas: Option, - subcall_handle: Option, - - expected_cost: Option, - expected_logs: Option>, -} - -impl<'p, P: PrecompileSet> PrecompilesTester<'p, P> { - pub fn new( - precompiles: &'p P, - from: impl Into, - to: impl Into, - data: Vec, - ) -> Self { - let to = to.into(); - let mut handle = MockHandle::new( - to, - Context { - address: to, - caller: from.into(), - apparent_value: U256::zero(), - }, - ); - - handle.input = data; - - Self { - precompiles, - handle, - - target_gas: None, - subcall_handle: None, - - expected_cost: None, - expected_logs: None, - } - } - - pub fn with_value(mut self, value: impl Into) -> Self { - self.handle.context.apparent_value = value.into(); - self - } - - pub fn with_subcall_handle(mut self, subcall_handle: impl SubcallTrait) -> Self { - self.subcall_handle = Some(Box::new(subcall_handle)); - self - } - - pub fn with_target_gas(mut self, target_gas: Option) -> Self { - self.target_gas = target_gas; - self - } - - pub fn expect_cost(mut self, cost: u64) -> Self { - self.expected_cost = Some(cost); - self - } - - pub fn expect_no_logs(mut self) -> Self { - self.expected_logs = Some(vec![]); - self - } - - pub fn expect_log(mut self, log: Log) -> Self { - self.expected_logs = Some({ - let mut logs = self.expected_logs.unwrap_or_default(); - logs.push(PrettyLog(log)); - logs - }); - self - } - - fn assert_optionals(&self) { - if let Some(cost) = &self.expected_cost { - assert_eq!(&self.handle.gas_used, cost); - } - - if let Some(logs) = &self.expected_logs { - similar_asserts::assert_eq!(&self.handle.logs, logs); - } - } - - fn execute(&mut self) -> Option { - let handle = &mut self.handle; - handle.subcall_handle = self.subcall_handle.take(); - - if let Some(gas_limit) = self.target_gas { - handle.gas_limit = gas_limit; - } - - let res = self.precompiles.execute(handle); - - self.subcall_handle = handle.subcall_handle.take(); - - res - } - - /// Execute the precompile set and expect some precompile to have been executed, regardless of the - /// result. - pub fn execute_some(mut self) { - let res = self.execute(); - assert!(res.is_some()); - self.assert_optionals(); - } - - /// Execute the precompile set and expect no precompile to have been executed. - pub fn execute_none(mut self) { - let res = self.execute(); - assert!(res.is_none()); - self.assert_optionals(); - } - - /// Execute the precompile set and check it returns provided output. - pub fn execute_returns(mut self, output: Vec) { - let res = self.execute(); - assert_eq!( - res, - Some(Ok(PrecompileOutput { - exit_status: ExitSucceed::Returned, - output - })) - ); - self.assert_optionals(); - } - - /// Execute the precompile set and check if it reverts. - /// Take a closure allowing to perform custom matching on the output. - pub fn execute_reverts(mut self, check: impl Fn(&[u8]) -> bool) { - let res = self.execute(); - assert_matches!( - res, - Some(Err(PrecompileFailure::Revert { output, ..})) - if check(&output) - ); - self.assert_optionals(); - } - - /// Execute the precompile set and check it returns provided output. - pub fn execute_error(mut self, error: ExitError) { - let res = self.execute(); - assert_eq!( - res, - Some(Err(PrecompileFailure::Error { exit_status: error })) - ); - self.assert_optionals(); - } -} - -pub trait PrecompileTesterExt: PrecompileSet + Sized { - fn prepare_test( - &self, - from: impl Into, - to: impl Into, - data: Vec, - ) -> PrecompilesTester; -} - -impl PrecompileTesterExt for T { - fn prepare_test( - &self, - from: impl Into, - to: impl Into, - data: Vec, - ) -> PrecompilesTester { - PrecompilesTester::new(self, from, to, data) - } -} - -#[derive(Clone, PartialEq, Eq)] -pub struct PrettyLog(Log); - -impl core::fmt::Debug for PrettyLog { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { - let bytes = self - .0 - .data - .iter() - .map(|b| format!("{:02X}", b)) - .collect::>() - .join(""); - - let message = String::from_utf8(self.0.data.clone()).ok(); - - f.debug_struct("Log") - .field("address", &self.0.address) - .field("topics", &self.0.topics) - .field("data", &bytes) - .field("data_utf8", &message) - .finish() - } -} diff --git a/precompiles/utils/src/precompile_set.rs b/precompiles/utils/src/precompile_set.rs deleted file mode 100644 index e3eb53905c..0000000000 --- a/precompiles/utils/src/precompile_set.rs +++ /dev/null @@ -1,1079 +0,0 @@ -// Copyright 2019-2022 PureStake Inc. -// This file is part of Moonbeam. - -// Moonbeam is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Moonbeam is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Moonbeam. If not, see . - -//! Provide utils to assemble precompiles and precompilesets into a -//! final precompile set with security checks. All security checks are enabled by -//! default and must be disabled explicely throught type annotations. - -use crate::{ - revert,String, - substrate::RuntimeHelper, - EvmResult, -}; -use fp_evm::{ - ExitError, IsPrecompileResult, Precompile, PrecompileFailure, PrecompileHandle, - PrecompileResult, PrecompileSet, -}; -use frame_support::pallet_prelude::Get; -use impl_trait_for_tuples::impl_for_tuples; -use pallet_evm::AddressMapping; -use sp_core::{H160, H256}; -use sp_std::{ - cell::RefCell, collections::btree_map::BTreeMap, marker::PhantomData, ops::RangeInclusive, vec, - vec::Vec, -}; - -/// Trait representing checks that can be made on a precompile call. -/// Types implementing this trait are made to be chained in a tuple. -/// -/// For that reason every method returns an Option, None meaning that -/// the implementor have no constraint and the decision is left to -/// latter elements in the chain. If None is returned by all elements of -/// the chain then sensible defaults are used. -/// -/// Both `PrecompileAt` and `PrecompileSetStartingWith` have a type parameter that must -/// implement this trait to configure the checks of the precompile(set) it represents. -pub trait PrecompileChecks { - #[inline(always)] - /// Is there a limit to the amount of recursions this precompile - /// can make using subcalls? 0 means this specific precompile will not - /// be callable as a subcall of itself, 1 will allow one level of recursion, - /// etc... - /// - /// If all checks return None, defaults to `Some(0)` (no recursion allowed). - fn recursion_limit() -> Option> { - None - } - - #[inline(always)] - /// Does this precompile supports being called with DELEGATECALL or CALLCODE? - /// - /// If all checks return None, defaults to `false`. - fn accept_delegate_call() -> Option { - None - } - - #[inline(always)] - /// Is this precompile callable by a smart contract? - /// - /// If all checks return None, defaults to `false`. - fn callable_by_smart_contract(_caller: H160, _called_selector: Option) -> Option { - None - } - - #[inline(always)] - /// Is this precompile callable by a precompile? - /// - /// If all checks return None, defaults to `false`. - fn callable_by_precompile(_caller: H160, _called_selector: Option) -> Option { - None - } - - #[inline(always)] - /// Is this precompile able to do subcalls? - /// - /// If all checks return None, defaults to `false`. - fn allow_subcalls() -> Option { - None - } - - /// Summarize the checks when being called by a smart contract. - fn callable_by_smart_contract_summary() -> Option { - None - } - - /// Summarize the checks when being called by a precompile. - fn callable_by_precompile_summary() -> Option { - None - } -} - -#[derive(Debug, Clone)] -pub enum DiscriminantResult { - Some(T, u64), - None(u64), - OutOfGas, -} - -impl Into for DiscriminantResult { - fn into(self) -> IsPrecompileResult { - match self { - Self::Some(_, extra_cost) => IsPrecompileResult::Answer { - is_precompile: true, - extra_cost, - }, - Self::None(extra_cost) => IsPrecompileResult::Answer { - is_precompile: false, - extra_cost, - }, - Self::OutOfGas => IsPrecompileResult::OutOfGas, - } - } -} - -#[derive(Debug, Clone)] -#[cfg_attr(feature = "testing", derive(serde::Serialize, serde::Deserialize))] -pub enum PrecompileKind { - Single(H160), - Prefixed(Vec), -} - -#[derive(Debug, Clone)] -#[cfg_attr(feature = "testing", derive(serde::Serialize, serde::Deserialize))] -pub struct PrecompileCheckSummary { - pub name: Option, - pub precompile_kind: PrecompileKind, - pub recursion_limit: Option, - pub accept_delegate_call: bool, - pub callable_by_smart_contract: String, - pub callable_by_precompile: String, -} - -#[impl_for_tuples(0, 20)] -impl PrecompileChecks for Tuple { - #[inline(always)] - fn recursion_limit() -> Option> { - for_tuples!(#( - if let Some(check) = Tuple::recursion_limit() { - return Some(check); - } - )*); - - None - } - - #[inline(always)] - fn accept_delegate_call() -> Option { - for_tuples!(#( - if let Some(check) = Tuple::accept_delegate_call() { - return Some(check); - } - )*); - - None - } - - #[inline(always)] - fn callable_by_smart_contract(caller: H160, called_selector: Option) -> Option { - for_tuples!(#( - if let Some(check) = Tuple::callable_by_smart_contract(caller, called_selector) { - return Some(check); - } - )*); - - None - } - - #[inline(always)] - fn callable_by_precompile(caller: H160, called_selector: Option) -> Option { - for_tuples!(#( - if let Some(check) = Tuple::callable_by_precompile(caller, called_selector) { - return Some(check); - } - )*); - - None - } - - #[inline(always)] - fn allow_subcalls() -> Option { - for_tuples!(#( - if let Some(check) = Tuple::allow_subcalls() { - return Some(check); - } - )*); - - None - } - - fn callable_by_smart_contract_summary() -> Option { - for_tuples!(#( - if let Some(check) = Tuple::callable_by_smart_contract_summary() { - return Some(check); - } - )*); - - None - } - - fn callable_by_precompile_summary() -> Option { - for_tuples!(#( - if let Some(check) = Tuple::callable_by_precompile_summary() { - return Some(check); - } - )*); - - None - } -} - -/// Precompile can be called using DELEGATECALL/CALLCODE. -pub struct AcceptDelegateCall; - -impl PrecompileChecks for AcceptDelegateCall { - #[inline(always)] - fn accept_delegate_call() -> Option { - Some(true) - } -} - -/// Precompile is able to do subcalls with provided nesting limit. -pub struct SubcallWithMaxNesting; - -impl PrecompileChecks for SubcallWithMaxNesting { - #[inline(always)] - fn recursion_limit() -> Option> { - Some(Some(R)) - } - - #[inline(always)] - fn allow_subcalls() -> Option { - Some(true) - } -} - -pub trait SelectorFilter { - fn is_allowed(_caller: H160, _selector: Option) -> bool; - - fn description() -> String; -} -pub struct ForAllSelectors; -impl SelectorFilter for ForAllSelectors { - fn is_allowed(_caller: H160, _selector: Option) -> bool { - true - } - - fn description() -> String { - "Allowed for all selectors and callers".into() - } -} - -pub struct OnlyFrom(PhantomData); -impl> SelectorFilter for OnlyFrom { - fn is_allowed(caller: H160, _selector: Option) -> bool { - caller == T::get() - } - - fn description() -> String { - alloc::format!("Allowed for all selectors only if called from {}", T::get()) - } -} - -pub struct CallableByContract(PhantomData); - -impl PrecompileChecks for CallableByContract { - #[inline(always)] - fn callable_by_smart_contract(caller: H160, called_selector: Option) -> Option { - Some(T::is_allowed(caller, called_selector)) - } - - fn callable_by_smart_contract_summary() -> Option { - Some(T::description()) - } -} - -/// Precompiles are allowed to call this precompile. -pub struct CallableByPrecompile(PhantomData); - -impl PrecompileChecks for CallableByPrecompile { - #[inline(always)] - fn callable_by_precompile(caller: H160, called_selector: Option) -> Option { - Some(T::is_allowed(caller, called_selector)) - } - - fn callable_by_precompile_summary() -> Option { - Some(T::description()) - } -} - -/// The type of EVM address. -#[derive(PartialEq)] -#[cfg_attr(feature = "std", derive(Debug))] -pub enum AddressType { - /// The code stored at the address is less than 5 bytes, but not well known. - Unknown, - /// No code is stored at the address, therefore is EOA. - EOA, - /// The 5-byte magic constant for a precompile is stored at the address. - Precompile, - /// The code is greater than 5-bytes, potentially a Smart Contract. - Contract, -} - -/// Retrieves the type of address demarcated by `AddressType`. -pub fn get_address_type(address: H160) -> AddressType { - let code_len = pallet_evm::AccountCodes::::decode_len(address).unwrap_or(0); - - // 0 => either EOA or precompile without dummy code - if code_len == 0 { - return AddressType::EOA; - } - - // dummy code is 5 bytes long, so any other len means it is a contract. - if code_len != 5 { - return AddressType::Contract; - } - - // check code matches dummy code - let code = pallet_evm::AccountCodes::::get(address); - if &code == &[0x60, 0x00, 0x60, 0x00, 0xfd] { - return AddressType::Precompile; - } - - AddressType::Unknown -} - -fn is_address_eoa_or_precompile(address: H160) -> bool { - match get_address_type::(address) { - AddressType::EOA | AddressType::Precompile => true, - _ => false, - } -} - -/// Common checks for precompile and precompile sets. -/// Don't contain recursion check as precompile sets have recursion check for each member. -fn common_checks( - handle: &mut impl PrecompileHandle, -) -> EvmResult<()> { - let code_address = handle.code_address(); - let caller = handle.context().caller; - - // Check DELEGATECALL config. - let accept_delegate_call = C::accept_delegate_call().unwrap_or(false); - if !accept_delegate_call && code_address != handle.context().address { - return Err(revert("Cannot be called with DELEGATECALL or CALLCODE")); - } - - // Extract which selector is called. - let selector = handle.input().get(0..4).map(|bytes| { - let mut buffer = [0u8; 4]; - buffer.copy_from_slice(bytes); - u32::from_be_bytes(buffer) - }); - - // Is this selector callable from a smart contract? - let callable_by_smart_contract = - C::callable_by_smart_contract(caller, selector).unwrap_or(false); - if !callable_by_smart_contract { - handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - if !is_address_eoa_or_precompile::(caller) { - return Err(revert("Function not callable by smart contracts")); - } - } - - // Is this selector callable from a precompile? - let callable_by_precompile = C::callable_by_precompile(caller, selector).unwrap_or(false); - if !callable_by_precompile && is_precompile_or_fail::(caller, handle.remaining_gas())? { - return Err(revert("Function not callable by precompiles")); - } - - Ok(()) -} - -pub fn is_precompile_or_fail(address: H160, gas: u64) -> EvmResult { - match ::PrecompilesValue::get().is_precompile(address, gas) { - IsPrecompileResult::Answer { is_precompile, .. } => Ok(is_precompile), - IsPrecompileResult::OutOfGas => Err(PrecompileFailure::Error { - exit_status: ExitError::OutOfGas, - }), - } -} - -pub struct AddressU64; -impl Get for AddressU64 { - #[inline(always)] - fn get() -> H160 { - H160::from_low_u64_be(N) - } -} - -pub struct RestrictiveHandle<'a, H> { - handle: &'a mut H, - allow_subcalls: bool, -} - -impl<'a, H: PrecompileHandle> PrecompileHandle for RestrictiveHandle<'a, H> { - fn call( - &mut self, - address: H160, - transfer: Option, - input: Vec, - target_gas: Option, - is_static: bool, - context: &evm::Context, - ) -> (evm::ExitReason, Vec) { - if !self.allow_subcalls { - return ( - evm::ExitReason::Revert(evm::ExitRevert::Reverted), - String::from("subcalls disabled for this precompile").as_bytes().to_vec(), - ); - } - - self.handle - .call(address, transfer, input, target_gas, is_static, context) - } - - fn record_cost(&mut self, cost: u64) -> Result<(), evm::ExitError> { - self.handle.record_cost(cost) - } - - fn remaining_gas(&self) -> u64 { - self.handle.remaining_gas() - } - - fn log( - &mut self, - address: H160, - topics: Vec, - data: Vec, - ) -> Result<(), evm::ExitError> { - self.handle.log(address, topics, data) - } - - fn code_address(&self) -> H160 { - self.handle.code_address() - } - - fn input(&self) -> &[u8] { - self.handle.input() - } - - fn context(&self) -> &evm::Context { - self.handle.context() - } - - fn is_static(&self) -> bool { - self.handle.is_static() - } - - fn gas_limit(&self) -> Option { - self.handle.gas_limit() - } -} - -/// Allows to know if a precompile is active or not. -/// This allows to detect deactivated precompile, that are still considered precompiles by -/// the EVM but that will always revert when called. -pub trait IsActivePrecompile { - /// Is the provided address an active precompile, a precompile that has - /// not be deactivated. Note that a deactivated precompile is still considered a precompile - /// for the EVM, but it will always revert when called. - fn is_active_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult; -} - -// INDIVIDUAL PRECOMPILE(SET) - -/// A fragment of a PrecompileSet. Should be implemented as is it -/// was a PrecompileSet containing only the precompile(set) it wraps. -/// They can be combined into a real PrecompileSet using `PrecompileSetBuilder`. -pub trait PrecompileSetFragment { - /// Instanciate the fragment. - fn new() -> Self; - - /// Execute the fragment. - fn execute( - &self, - handle: &mut impl PrecompileHandle, - ) -> Option; - - /// Is the provided address a precompile in this fragment? - fn is_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult; - - /// Return the list of addresses covered by this fragment. - fn used_addresses(&self) -> Vec; - - /// Summarize - fn summarize_checks(&self) -> Vec; -} - -/// Wraps a stateless precompile: a type implementing the `Precompile` trait. -/// Type parameters allow to define: -/// - A: The address of the precompile -/// - R: The recursion limit (defaults to 1) -/// - D: If DELEGATECALL is supported (default to no) -pub struct PrecompileAt { - current_recursion_level: RefCell, - _phantom: PhantomData<(A, P, C)>, -} - -impl PrecompileSetFragment for PrecompileAt -where - A: Get, - P: Precompile, - C: PrecompileChecks, -{ - #[inline(always)] - fn new() -> Self { - Self { - current_recursion_level: RefCell::new(0), - _phantom: PhantomData, - } - } - - #[inline(always)] - fn execute( - &self, - handle: &mut impl PrecompileHandle, - ) -> Option { - let code_address = handle.code_address(); - - // Check if this is the address of the precompile. - if A::get() != code_address { - return None; - } - - // Perform common checks. - if let Err(err) = common_checks::(handle) { - return Some(Err(err)); - } - - // Check and increase recursion level if needed. - let recursion_limit = C::recursion_limit().unwrap_or(Some(0)); - if let Some(max_recursion_level) = recursion_limit { - match self.current_recursion_level.try_borrow_mut() { - Ok(mut recursion_level) => { - if *recursion_level > max_recursion_level { - return Some(Err( - revert("Precompile is called with too high nesting").into() - )); - } - - *recursion_level += 1; - } - // We don't hold the borrow and are in single-threaded code, thus we should - // not be able to fail borrowing in nested calls. - Err(_) => return Some(Err(revert("Couldn't check precompile nesting").into())), - } - } - - // Subcall protection. - let allow_subcalls = C::allow_subcalls().unwrap_or(false); - let mut handle = RestrictiveHandle { - handle, - allow_subcalls, - }; - - let res = P::execute(&mut handle); - - // Decrease recursion level if needed. - if recursion_limit.is_some() { - match self.current_recursion_level.try_borrow_mut() { - Ok(mut recursion_level) => { - *recursion_level -= 1; - } - // We don't hold the borrow and are in single-threaded code, thus we should - // not be able to fail borrowing in nested calls. - Err(_) => return Some(Err(revert("Couldn't check precompile nesting").into())), - } - } - - Some(res) - } - - #[inline(always)] - fn is_precompile(&self, address: H160, _gas: u64) -> IsPrecompileResult { - IsPrecompileResult::Answer { - is_precompile: address == A::get(), - extra_cost: 0, - } - } - - #[inline(always)] - fn used_addresses(&self) -> Vec { - vec![A::get()] - } - - fn summarize_checks(&self) -> Vec { - vec![PrecompileCheckSummary { - name: None, - precompile_kind: PrecompileKind::Single(A::get()), - recursion_limit: C::recursion_limit().unwrap_or(Some(0)), - accept_delegate_call: C::accept_delegate_call().unwrap_or(false), - callable_by_smart_contract: C::callable_by_smart_contract_summary() - .unwrap_or_else(|| "Not callable".into()), - callable_by_precompile: C::callable_by_precompile_summary() - .unwrap_or_else(|| "Not callable".into()), - }] - } -} - -impl IsActivePrecompile for PrecompileAt -where - A: Get, -{ - #[inline(always)] - fn is_active_precompile(&self, address: H160, _gas: u64) -> IsPrecompileResult { - IsPrecompileResult::Answer { - is_precompile: address == A::get(), - extra_cost: 0, - } - } -} - -/// Wraps an inner PrecompileSet with all its addresses starting with -/// a common prefix. -/// Type parameters allow to define: -/// - A: The common prefix -/// - D: If DELEGATECALL is supported (default to no) -pub struct PrecompileSetStartingWith { - precompile_set: P, - current_recursion_level: RefCell>, - _phantom: PhantomData<(A, C)>, -} - -impl PrecompileSetFragment for PrecompileSetStartingWith -where - A: Get<&'static [u8]>, - P: PrecompileSet + Default, - C: PrecompileChecks, -{ - #[inline(always)] - fn new() -> Self { - Self { - precompile_set: P::default(), - current_recursion_level: RefCell::new(BTreeMap::new()), - _phantom: PhantomData, - } - } - - #[inline(always)] - fn execute( - &self, - handle: &mut impl PrecompileHandle, - ) -> Option { - let code_address = handle.code_address(); - if !is_precompile_or_fail::(code_address, handle.remaining_gas()).ok()? { - return None; - } - // Perform common checks. - if let Err(err) = common_checks::(handle) { - return Some(Err(err)); - } - - // Check and increase recursion level if needed. - let recursion_limit = C::recursion_limit().unwrap_or(Some(0)); - if let Some(max_recursion_level) = recursion_limit { - match self.current_recursion_level.try_borrow_mut() { - Ok(mut recursion_level_map) => { - let recursion_level = recursion_level_map.entry(code_address).or_insert(0); - - if *recursion_level > max_recursion_level { - return Some(Err(revert("Precompile is called with too high nesting"))); - } - - *recursion_level += 1; - } - // We don't hold the borrow and are in single-threaded code, thus we should - // not be able to fail borrowing in nested calls. - Err(_) => return Some(Err(revert("Couldn't check precompile nesting"))), - } - } - - // Subcall protection. - let allow_subcalls = C::allow_subcalls().unwrap_or(false); - let mut handle = RestrictiveHandle { - handle, - allow_subcalls, - }; - - let res = self.precompile_set.execute(&mut handle); - - // Decrease recursion level if needed. - if recursion_limit.is_some() { - match self.current_recursion_level.try_borrow_mut() { - Ok(mut recursion_level_map) => { - let recursion_level = match recursion_level_map.get_mut(&code_address) { - Some(recursion_level) => recursion_level, - None => return Some(Err(revert("Couldn't retreive precompile nesting"))), - }; - - *recursion_level -= 1; - } - // We don't hold the borrow and are in single-threaded code, thus we should - // not be able to fail borrowing in nested calls. - Err(_) => return Some(Err(revert("Couldn't check precompile nesting"))), - } - } - - res - } - - #[inline(always)] - fn is_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult { - if address.as_bytes().starts_with(A::get()) { - return self.precompile_set.is_precompile(address, gas); - } - IsPrecompileResult::Answer { - is_precompile: false, - extra_cost: 0, - } - } - - #[inline(always)] - fn used_addresses(&self) -> Vec { - // TODO: We currently can't get the list of used addresses. - vec![] - } - - fn summarize_checks(&self) -> Vec { - let prefix = A::get(); - - vec![PrecompileCheckSummary { - name: None, - precompile_kind: PrecompileKind::Prefixed(prefix.to_vec()), - recursion_limit: C::recursion_limit().unwrap_or(Some(0)), - accept_delegate_call: C::accept_delegate_call().unwrap_or(false), - callable_by_smart_contract: C::callable_by_smart_contract_summary() - .unwrap_or_else(|| "Not callable".into()), - callable_by_precompile: C::callable_by_precompile_summary() - .unwrap_or_else(|| "Not callable".into()), - }] - } -} - -impl IsActivePrecompile for PrecompileSetStartingWith -where - Self: PrecompileSetFragment, -{ - #[inline(always)] - fn is_active_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult { - self.is_precompile(address, gas) - } -} - -/// Make a precompile that always revert. -/// Can be useful when writing tests. -pub struct RevertPrecompile(PhantomData); - -impl PrecompileSetFragment for RevertPrecompile -where - A: Get, -{ - #[inline(always)] - fn new() -> Self { - Self(PhantomData) - } - - #[inline(always)] - fn execute( - &self, - handle: &mut impl PrecompileHandle, - ) -> Option { - if A::get() == handle.code_address() { - Some(Err(revert("revert"))) - } else { - None - } - } - - #[inline(always)] - fn is_precompile(&self, address: H160, _gas: u64) -> IsPrecompileResult { - IsPrecompileResult::Answer { - is_precompile: address == A::get(), - extra_cost: 0, - } - } - - #[inline(always)] - fn used_addresses(&self) -> Vec { - vec![A::get()] - } - - fn summarize_checks(&self) -> Vec { - vec![PrecompileCheckSummary { - name: None, - precompile_kind: PrecompileKind::Single(A::get()), - recursion_limit: Some(0), - accept_delegate_call: true, - callable_by_smart_contract: "Reverts in all cases".into(), - callable_by_precompile: "Reverts in all cases".into(), - }] - } -} - -impl IsActivePrecompile for RevertPrecompile { - #[inline(always)] - fn is_active_precompile(&self, _address: H160, _gas: u64) -> IsPrecompileResult { - IsPrecompileResult::Answer { - is_precompile: true, - extra_cost: 0, - } - } -} - -/// A precompile that was removed from a precompile set. -/// Still considered a precompile but is inactive and always revert. -pub struct RemovedPrecompileAt(PhantomData); -impl PrecompileSetFragment for RemovedPrecompileAt -where - A: Get, -{ - #[inline(always)] - fn new() -> Self { - Self(PhantomData) - } - - #[inline(always)] - fn execute( - &self, - handle: &mut impl PrecompileHandle, - ) -> Option { - if A::get() == handle.code_address() { - Some(Err(revert("Removed precompile"))) - } else { - None - } - } - - #[inline(always)] - fn is_precompile(&self, address: H160, _gas: u64) -> IsPrecompileResult { - IsPrecompileResult::Answer { - is_precompile: address == A::get(), - extra_cost: 0, - } - } - - #[inline(always)] - fn used_addresses(&self) -> Vec { - vec![A::get()] - } - - fn summarize_checks(&self) -> Vec { - vec![PrecompileCheckSummary { - name: None, - precompile_kind: PrecompileKind::Single(A::get()), - recursion_limit: Some(0), - accept_delegate_call: true, - callable_by_smart_contract: "Reverts in all cases".into(), - callable_by_precompile: "Reverts in all cases".into(), - }] - } -} - -impl IsActivePrecompile for RemovedPrecompileAt { - #[inline(always)] - fn is_active_precompile(&self, _address: H160, _gas: u64) -> IsPrecompileResult { - IsPrecompileResult::Answer { - is_precompile: false, - extra_cost: 0, - } - } -} - -// COMPOSITION OF PARTS -#[impl_for_tuples(1, 100)] -impl PrecompileSetFragment for Tuple { - #[inline(always)] - fn new() -> Self { - (for_tuples!(#( - Tuple::new() - ),*)) - } - - #[inline(always)] - fn execute( - &self, - handle: &mut impl PrecompileHandle, - ) -> Option { - for_tuples!(#( - if let Some(res) = self.Tuple.execute::(handle) { - return Some(res); - } - )*); - - None - } - - #[inline(always)] - fn is_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult { - for_tuples!(#( - match self.Tuple.is_precompile(address, gas) { - IsPrecompileResult::Answer { - is_precompile: true, - .. - } => return IsPrecompileResult::Answer { - is_precompile: true, - extra_cost: 0, - }, - _ => {} - }; - )*); - IsPrecompileResult::Answer { - is_precompile: false, - extra_cost: 0, - } - } - - #[inline(always)] - fn used_addresses(&self) -> Vec { - let mut used_addresses = vec![]; - - for_tuples!(#( - let mut inner = self.Tuple.used_addresses(); - used_addresses.append(&mut inner); - )*); - - used_addresses - } - - fn summarize_checks(&self) -> Vec { - let mut checks = Vec::new(); - - for_tuples!(#( - let mut inner = self.Tuple.summarize_checks(); - checks.append(&mut inner); - )*); - - checks - } -} - -#[impl_for_tuples(1, 100)] -impl IsActivePrecompile for Tuple { - #[inline(always)] - fn is_active_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult { - for_tuples!(#( - match self.Tuple.is_active_precompile(address, gas) { - IsPrecompileResult::Answer { - is_precompile: true, - .. - } => return IsPrecompileResult::Answer { - is_precompile: true, - extra_cost: 0, - }, - _ => {} - }; - )*); - IsPrecompileResult::Answer { - is_precompile: false, - extra_cost: 0, - } - } -} - -/// Wraps a precompileset fragment into a range, and will skip processing it if the address -/// is out of the range. -pub struct PrecompilesInRangeInclusive { - inner: P, - range: RangeInclusive, - _phantom: PhantomData, -} - -impl PrecompileSetFragment for PrecompilesInRangeInclusive<(S, E), P> -where - S: Get, - E: Get, - P: PrecompileSetFragment, -{ - fn new() -> Self { - Self { - inner: P::new(), - range: RangeInclusive::new(S::get(), E::get()), - _phantom: PhantomData, - } - } - - fn execute( - &self, - handle: &mut impl PrecompileHandle, - ) -> Option { - if self.range.contains(&handle.code_address()) { - self.inner.execute::(handle) - } else { - None - } - } - - fn is_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult { - if self.range.contains(&address) { - self.inner.is_precompile(address, gas) - } else { - IsPrecompileResult::Answer { - is_precompile: false, - extra_cost: 0, - } - } - } - - fn used_addresses(&self) -> Vec { - self.inner.used_addresses() - } - - fn summarize_checks(&self) -> Vec { - self.inner.summarize_checks() - } -} - -impl IsActivePrecompile for PrecompilesInRangeInclusive<(S, E), P> -where - P: IsActivePrecompile, -{ - fn is_active_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult { - if self.range.contains(&address) { - self.inner.is_active_precompile(address, gas) - } else { - IsPrecompileResult::Answer { - is_precompile: false, - extra_cost: 0, - } - } - } -} - -/// Wraps a tuple of `PrecompileSetFragment` to make a real `PrecompileSet`. -pub struct PrecompileSetBuilder { - inner: P, - _phantom: PhantomData, -} - -impl PrecompileSet for PrecompileSetBuilder { - fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { - self.inner.execute::(handle) - } - - fn is_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult { - self.inner.is_precompile(address, gas) - } -} - -impl IsActivePrecompile for PrecompileSetBuilder { - fn is_active_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult { - self.inner.is_active_precompile(address, gas) - } -} - -impl PrecompileSetBuilder { - /// Create a new instance of the PrecompileSet. - pub fn new() -> Self { - Self { - inner: P::new(), - _phantom: PhantomData, - } - } - - /// Return the list of addresses contained in this PrecompileSet. - pub fn used_addresses() -> impl Iterator { - Self::new() - .inner - .used_addresses() - .into_iter() - .map(|x| R::AddressMapping::into_account_id(x)) - } - - pub fn summarize_checks(&self) -> Vec { - self.inner.summarize_checks() - } -} diff --git a/precompiles/utils/src/substrate.rs b/precompiles/utils/src/substrate.rs deleted file mode 100644 index d606920d2c..0000000000 --- a/precompiles/utils/src/substrate.rs +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright 2019-2022 PureStake Inc. -// This file is part of Moonbeam. - -// Moonbeam is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Moonbeam is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Moonbeam. If not, see . - -//! Utils related to Substrate features: -//! - Substrate call dispatch. -//! - Substrate DB read and write costs - -use { - crate::{evm::handle::using_precompile_handle, revert}, - core::marker::PhantomData, - fp_evm::{ExitError, PrecompileFailure, PrecompileHandle}, - frame_support::{ - dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, - pallet_prelude::DispatchError, - traits::Get, - }, - pallet_evm::GasWeightMapping, -}; - -#[derive(Debug)] -pub enum TryDispatchError { - Evm(ExitError), - Substrate(DispatchError), -} - -impl From for PrecompileFailure { - fn from(f: TryDispatchError) -> PrecompileFailure { - match f { - TryDispatchError::Evm(e) => PrecompileFailure::Error { exit_status: e }, - TryDispatchError::Substrate(e) => { - revert(alloc::format!("Dispatched call failed with error: {e:?}")) - } - } - } -} - -/// Helper functions requiring a Substrate runtime. -/// This runtime must of course implement `pallet_evm::Config`. -#[derive(Clone, Copy, Debug)] -pub struct RuntimeHelper(PhantomData); - -impl RuntimeHelper -where - Runtime: pallet_evm::Config, - Runtime::RuntimeCall: Dispatchable + GetDispatchInfo, -{ - /// Try to dispatch a Substrate call. - /// Return an error if there are not enough gas, or if the call fails. - /// If successful returns the used gas using the Runtime GasWeightMapping. - pub fn try_dispatch( - handle: &mut impl PrecompileHandle, - origin: ::RuntimeOrigin, - call: Call, - ) -> Result - where - Runtime::RuntimeCall: From, - { - let call = Runtime::RuntimeCall::from(call); - let dispatch_info = call.get_dispatch_info(); - - // Make sure there is enough gas. - let remaining_gas = handle.remaining_gas(); - let required_gas = Runtime::GasWeightMapping::weight_to_gas(dispatch_info.weight); - if required_gas > remaining_gas { - return Err(TryDispatchError::Evm(ExitError::OutOfGas)); - } - - // Dispatch call. - // It may be possible to not record gas cost if the call returns Pays::No. - // However while Substrate handle checking weight while not making the sender pay for it, - // the EVM doesn't. It seems this safer to always record the costs to avoid unmetered - // computations. - let post_dispatch_info = using_precompile_handle(handle, || call.dispatch(origin)) - .map_err(|e| TryDispatchError::Substrate(e.error))?; - - let used_weight = post_dispatch_info.actual_weight; - - let used_gas = - Runtime::GasWeightMapping::weight_to_gas(used_weight.unwrap_or(dispatch_info.weight)); - - handle - .record_cost(used_gas) - .map_err(|e| TryDispatchError::Evm(e))?; - - Ok(post_dispatch_info) - } -} - -impl RuntimeHelper -where - Runtime: pallet_evm::Config, -{ - /// Cost of a Substrate DB write in gas. - pub fn db_write_gas_cost() -> u64 { - ::GasWeightMapping::weight_to_gas( - ::DbWeight::get().writes(1), - ) - } - - /// Cost of a Substrate DB read in gas. - pub fn db_read_gas_cost() -> u64 { - ::GasWeightMapping::weight_to_gas( - ::DbWeight::get().reads(1), - ) - } -} diff --git a/runtime/local/Cargo.toml b/runtime/local/Cargo.toml index b5873f0088..de0b16403c 100644 --- a/runtime/local/Cargo.toml +++ b/runtime/local/Cargo.toml @@ -29,6 +29,7 @@ pallet-ethereum = { workspace = true } pallet-ethereum-checked = { workspace = true } pallet-evm = { workspace = true } pallet-evm-precompile-blake2 = { workspace = true } +pallet-evm-precompile-batch = { workspace = true } pallet-evm-precompile-bn128 = { workspace = true } pallet-evm-precompile-dispatch = { workspace = true } pallet-evm-precompile-ed25519 = { workspace = true } diff --git a/runtime/local/src/precompiles.rs b/runtime/local/src/precompiles.rs index 6eddf6629e..32e83a8667 100644 --- a/runtime/local/src/precompiles.rs +++ b/runtime/local/src/precompiles.rs @@ -23,6 +23,7 @@ use pallet_evm::{ PrecompileResult, PrecompileSet, }; use pallet_evm_precompile_assets_erc20::{AddressToAssetId, Erc20AssetsPrecompileSet}; +use pallet_evm_precompile_batch::BatchPrecompile; use pallet_evm_precompile_blake2::Blake2F; use pallet_evm_precompile_bn128::{Bn128Add, Bn128Mul, Bn128Pairing}; use pallet_evm_precompile_dapps_staking::DappsStakingWrapper; @@ -67,6 +68,7 @@ impl PrecompileSet for LocalNetworkPrecompiles where Erc20AssetsPrecompileSet: PrecompileSet, DappsStakingWrapper: Precompile, + BatchPrecompile: Precompile, XvmPrecompile: Precompile, Dispatch: Precompile, R: pallet_evm::Config @@ -111,6 +113,8 @@ where a if a == hash(20483) => Some(SubstrateEcdsaPrecompile::::execute(handle)), // Xvm 0x5005 a if a == hash(20485) => Some(XvmPrecompile::::execute(handle)), + // Batch 0x5006 + a if a == hash(20486) => Some(BatchPrecompile::::execute(handle)), // If the address matches asset prefix, the we route through the asset precompile set a if &a.to_fixed_bytes()[0..4] == ASSET_PRECOMPILE_ADDRESS_PREFIX => { Erc20AssetsPrecompileSet::::new().execute(handle) From 0dd68f1d83469ce0ef78295213df038c3f114a21 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Fri, 4 Aug 2023 13:52:32 +0530 Subject: [PATCH 06/21] fix expect_arguments --- precompiles/batch/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/precompiles/batch/src/lib.rs b/precompiles/batch/src/lib.rs index 0010306199..b54e22def9 100644 --- a/precompiles/batch/src/lib.rs +++ b/precompiles/batch/src/lib.rs @@ -108,7 +108,7 @@ where { fn batch_some(handle: &mut impl PrecompileHandle) -> EvmResult { let mut input = handle.read_input()?; - input.expect_arguments(6)?; + input.expect_arguments(4)?; let to = input.read::>()?; let value = input.read::>()?; let call_data = @@ -119,7 +119,7 @@ where fn batch_some_until_failure(handle: &mut impl PrecompileHandle) -> EvmResult { let mut input = handle.read_input()?; - input.expect_arguments(6)?; + input.expect_arguments(4)?; let to = input.read::>()?; let value = input.read::>()?; let call_data = @@ -137,7 +137,7 @@ where fn batch_all(handle: &mut impl PrecompileHandle) -> EvmResult { let mut input = handle.read_input()?; - input.expect_arguments(6)?; + input.expect_arguments(4)?; let to = input.read::>()?; let value = input.read::>()?; let call_data = From da6782553980da4457b93ad5bd2d6e3caa63d069 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Fri, 4 Aug 2023 14:36:20 +0530 Subject: [PATCH 07/21] add logs --- precompiles/batch/src/lib.rs | 3 +++ runtime/local/src/precompiles.rs | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/precompiles/batch/src/lib.rs b/precompiles/batch/src/lib.rs index b54e22def9..c6ba72bae8 100644 --- a/precompiles/batch/src/lib.rs +++ b/precompiles/batch/src/lib.rs @@ -114,6 +114,7 @@ where let call_data = input.read::, GetArrayLimit>>()?; let gas_limit = input.read::>()?; + log::trace!(target: "batch-precompile", "batch_some\n to address(s) {:?}, value(s) {:?} call_data(s) {:?}, gas_limit(s) {:?}", to, value,call_data, gas_limit); Self::inner_batch(Mode::BatchSome, handle, to, value, call_data, gas_limit) } @@ -125,6 +126,7 @@ where let call_data = input.read::, GetArrayLimit>>()?; let gas_limit = input.read::>()?; + log::trace!(target: "batch-precompile", "batch_some_until_failure\n to address(s) {:?}, value(s) {:?} call_data(s) {:?}, gas_limit(s) {:?}", to, value,call_data, gas_limit); Self::inner_batch( Mode::BatchSomeUntilFailure, handle, @@ -143,6 +145,7 @@ where let call_data = input.read::, GetArrayLimit>>()?; let gas_limit = input.read::>()?; + log::trace!(target: "batch-precompile", "batch_all\n to address(s) {:?}, value(s) {:?} call_data(s) {:?}, gas_limit(s) {:?}", to, value,call_data, gas_limit); Self::inner_batch(Mode::BatchAll, handle, to, value, call_data, gas_limit) } diff --git a/runtime/local/src/precompiles.rs b/runtime/local/src/precompiles.rs index 32e83a8667..2ddb7fbf9f 100644 --- a/runtime/local/src/precompiles.rs +++ b/runtime/local/src/precompiles.rs @@ -113,8 +113,8 @@ where a if a == hash(20483) => Some(SubstrateEcdsaPrecompile::::execute(handle)), // Xvm 0x5005 a if a == hash(20485) => Some(XvmPrecompile::::execute(handle)), - // Batch 0x5006 - a if a == hash(20486) => Some(BatchPrecompile::::execute(handle)), + // Batch 0x5006 + a if a == hash(20486) => Some(BatchPrecompile::::execute(handle)), // If the address matches asset prefix, the we route through the asset precompile set a if &a.to_fixed_bytes()[0..4] == ASSET_PRECOMPILE_ADDRESS_PREFIX => { Erc20AssetsPrecompileSet::::new().execute(handle) From a2f2ec2c8f05c439d95f4404641e1ec1b16b50a4 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Sat, 5 Aug 2023 07:42:24 +0530 Subject: [PATCH 08/21] update license --- precompiles/batch/Batch.sol | 1 - precompiles/batch/Cargo.toml | 2 +- precompiles/batch/src/lib.rs | 60 +++------------------- precompiles/batch/src/mock.rs | 13 +++-- precompiles/batch/src/tests.rs | 13 +++-- precompiles/utils/src/evm/costs.rs | 20 ++++---- precompiles/utils/src/evm/handle.rs | 16 ++++-- precompiles/utils/src/evm/logs.rs | 16 ++++-- precompiles/utils/src/evm/mod.rs | 16 ++++-- precompiles/utils/src/testing/account.rs | 17 +++--- precompiles/utils/src/testing/execution.rs | 43 ++++++++++++++++ precompiles/utils/src/testing/handle.rs | 16 ++++-- precompiles/utils/src/testing/mod.rs | 16 ++++-- 13 files changed, 145 insertions(+), 104 deletions(-) diff --git a/precompiles/batch/Batch.sol b/precompiles/batch/Batch.sol index 2406fc9d32..f303ad040c 100644 --- a/precompiles/batch/Batch.sol +++ b/precompiles/batch/Batch.sol @@ -5,7 +5,6 @@ pragma solidity >=0.8.3; /// Predeployed at the address 0x0000000000000000000000000000000000005006 /// For better understanding check the source code: /// repo: https://github.com/AstarNetwork/astar -/// code: frame/dapps-staking/src/pallet /// @title Batch precompile /// @dev Allows to perform multiple calls throught one call to the precompile. diff --git a/precompiles/batch/Cargo.toml b/precompiles/batch/Cargo.toml index 718286e9bb..f8d9c87b3e 100644 --- a/precompiles/batch/Cargo.toml +++ b/precompiles/batch/Cargo.toml @@ -2,7 +2,7 @@ name = "pallet-evm-precompile-batch" description = "A Precompile to batch multiple calls." version = "0.1.0" -authors.workspace = true +authors = ["StakeTechnologies", "PureStake"] edition.workspace = true homepage.workspace = true repository.workspace = true diff --git a/precompiles/batch/src/lib.rs b/precompiles/batch/src/lib.rs index c6ba72bae8..4faddbd0fb 100644 --- a/precompiles/batch/src/lib.rs +++ b/precompiles/batch/src/lib.rs @@ -1,20 +1,21 @@ -// Copyright 2019-2022 PureStake Inc. -// This file is part of Moonbeam. +// This file is part of Astar. -// Moonbeam is free software: you can redistribute it and/or modify +// Copyright 2019-2022 PureStake Inc. +// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// Astar is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Moonbeam is distributed in the hope that it will be useful, +// Astar is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Moonbeam. If not, see . - -//! Precompile to interact with pallet_balances instances using the ERC20 interface standard. +// along with Astar. If not, see . #![cfg_attr(not(feature = "std"), no_std)] @@ -325,48 +326,3 @@ where Ok(succeed(EvmDataWriter::new().write(true).build())) } } - -// // The enum is generated by the macro above. -// // We add this method to simplify writing tests generic over the mode. -// impl BatchPrecompileCall -// where -// Runtime: pallet_evm::Config, -// { -// pub fn batch_from_mode( -// mode: Mode, -// to: Vec
, -// value: Vec, -// call_data: Vec>, -// gas_limit: Vec, -// ) -> Self { -// // Convert Vecs into their bounded versions. -// // This is mainly a convenient function to write tests. -// // Bounds are only checked when parsing from call data. -// 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, -// }, -// } -// } -// } diff --git a/precompiles/batch/src/mock.rs b/precompiles/batch/src/mock.rs index 2ee775bd55..3db4b497e2 100644 --- a/precompiles/batch/src/mock.rs +++ b/precompiles/batch/src/mock.rs @@ -1,18 +1,21 @@ -// Copyright 2019-2022 PureStake Inc. -// This file is part of Moonbeam. +// This file is part of Astar. -// Moonbeam is free software: you can redistribute it and/or modify +// Copyright 2019-2022 PureStake Inc. +// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// Astar is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Moonbeam is distributed in the hope that it will be useful, +// Astar is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Moonbeam. If not, see . +// along with Astar. If not, see . //! Test utilities use super::*; diff --git a/precompiles/batch/src/tests.rs b/precompiles/batch/src/tests.rs index 0b7cb50771..0677848a33 100644 --- a/precompiles/batch/src/tests.rs +++ b/precompiles/batch/src/tests.rs @@ -1,18 +1,21 @@ -// Copyright 2019-2022 PureStake Inc. -// This file is part of Moonbeam. +// This file is part of Astar. -// Moonbeam is free software: you can redistribute it and/or modify +// Copyright 2019-2022 PureStake Inc. +// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// Astar is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Moonbeam is distributed in the hope that it will be useful, +// Astar is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Moonbeam. If not, see . +// along with Astar. If not, see . use crate::mock::{precompile_address, BatchPrecompileMock, ExtBuilder, PrecompilesValue, Runtime}; use crate::{log_subcall_failed, log_subcall_succeeded, Mode, *}; diff --git a/precompiles/utils/src/evm/costs.rs b/precompiles/utils/src/evm/costs.rs index cbdbffa0af..82f90bbe7b 100644 --- a/precompiles/utils/src/evm/costs.rs +++ b/precompiles/utils/src/evm/costs.rs @@ -1,22 +1,24 @@ -// Copyright 2019-2022 PureStake Inc. -// This file is part of Moonbeam. +// This file is part of Astar. -// Moonbeam is free software: you can redistribute it and/or modify +// Copyright 2019-2022 PureStake Inc. +// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Utils package, originally developed by Purestake Inc. +// Utils package used in Astar Network in terms of GPLv3. +// +// Utils is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Moonbeam is distributed in the hope that it will be useful, +// Utils is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Moonbeam. If not, see . - -//! Cost calculations. -//! TODO: PR EVM to make those cost calculations public. - +// along with Utils. If not, see . use { crate::EvmResult, fp_evm::{ExitError, PrecompileFailure}, diff --git a/precompiles/utils/src/evm/handle.rs b/precompiles/utils/src/evm/handle.rs index 05b7a8c717..f0c30dfdbf 100644 --- a/precompiles/utils/src/evm/handle.rs +++ b/precompiles/utils/src/evm/handle.rs @@ -1,18 +1,24 @@ -// Copyright 2019-2022 PureStake Inc. -// This file is part of Moonbeam. +// This file is part of Astar. -// Moonbeam is free software: you can redistribute it and/or modify +// Copyright 2019-2022 PureStake Inc. +// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Utils package, originally developed by Purestake Inc. +// Utils package used in Astar Network in terms of GPLv3. +// +// Utils is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Moonbeam is distributed in the hope that it will be useful, +// Utils is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Moonbeam. If not, see . +// along with Utils. If not, see . use { crate::{check_function_modifier, data::EvmDataReader, EvmResult, FunctionModifier}, diff --git a/precompiles/utils/src/evm/logs.rs b/precompiles/utils/src/evm/logs.rs index fdbd567f7d..8a322b7832 100644 --- a/precompiles/utils/src/evm/logs.rs +++ b/precompiles/utils/src/evm/logs.rs @@ -1,18 +1,24 @@ -// Copyright 2019-2022 PureStake Inc. -// This file is part of Moonbeam. +// This file is part of Astar. -// Moonbeam is free software: you can redistribute it and/or modify +// Copyright 2019-2022 PureStake Inc. +// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Utils package, originally developed by Purestake Inc. +// Utils package used in Astar Network in terms of GPLv3. +// +// Utils is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Moonbeam is distributed in the hope that it will be useful, +// Utils is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Moonbeam. If not, see . +// along with Utils. If not, see . use { crate::EvmResult, diff --git a/precompiles/utils/src/evm/mod.rs b/precompiles/utils/src/evm/mod.rs index 6b4cc27f82..d4182d44cc 100644 --- a/precompiles/utils/src/evm/mod.rs +++ b/precompiles/utils/src/evm/mod.rs @@ -1,18 +1,24 @@ -// Copyright 2019-2023 PureStake Inc. -// This file is part of Moonbeam. +// This file is part of Astar. -// Moonbeam is free software: you can redistribute it and/or modify +// Copyright 2019-2022 PureStake Inc. +// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Utils package, originally developed by Purestake Inc. +// Utils package used in Astar Network in terms of GPLv3. +// +// Utils is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Moonbeam is distributed in the hope that it will be useful, +// Utils is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Moonbeam. If not, see . +// along with Utils. If not, see . pub mod costs; pub mod handle; diff --git a/precompiles/utils/src/testing/account.rs b/precompiles/utils/src/testing/account.rs index e348891faf..66c5a13714 100644 --- a/precompiles/utils/src/testing/account.rs +++ b/precompiles/utils/src/testing/account.rs @@ -1,19 +1,24 @@ -// Copyright 2019-2022 PureStake Inc. -// This file is part of Moonbeam. +// This file is part of Astar. -// Moonbeam is free software: you can redistribute it and/or modify +// Copyright 2019-2022 PureStake Inc. +// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Utils package, originally developed by Purestake Inc. +// Utils package used in Astar Network in terms of GPLv3. +// +// Utils is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Moonbeam is distributed in the hope that it will be useful, +// Utils is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Moonbeam. If not, see . - +// along with Utils. If not, see . use { pallet_evm::AddressMapping, scale_info::TypeInfo, diff --git a/precompiles/utils/src/testing/execution.rs b/precompiles/utils/src/testing/execution.rs index dbe7188316..067b6b70de 100644 --- a/precompiles/utils/src/testing/execution.rs +++ b/precompiles/utils/src/testing/execution.rs @@ -1,3 +1,46 @@ +// This file is part of Astar. + +// Copyright 2019-2022 PureStake Inc. +// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Utils package, originally developed by Purestake Inc. +// Utils package used in Astar Network in terms of GPLv3. +// +// Utils is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Utils is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Utils. If not, see . +// This file is part of Astar. + +// Copyright 2019-2022 PureStake Inc. +// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Utils package, originally developed by Purestake Inc. +// Utils package used in Astar Network in terms of GPLv3. +// +// Utils is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Utils is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Utils. If not, see . + use { crate::testing::{decode_revert_message, MockHandle, PrettyLog, SubcallHandle, SubcallTrait}, assert_matches::assert_matches, diff --git a/precompiles/utils/src/testing/handle.rs b/precompiles/utils/src/testing/handle.rs index 47771fe573..6b02ea5c00 100644 --- a/precompiles/utils/src/testing/handle.rs +++ b/precompiles/utils/src/testing/handle.rs @@ -1,18 +1,24 @@ -// Copyright 2019-2022 PureStake Inc. -// This file is part of Moonbeam. +// This file is part of Astar. -// Moonbeam is free software: you can redistribute it and/or modify +// Copyright 2019-2022 PureStake Inc. +// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Utils package, originally developed by Purestake Inc. +// Utils package used in Astar Network in terms of GPLv3. +// +// Utils is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Moonbeam is distributed in the hope that it will be useful, +// Utils is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Moonbeam. If not, see . +// along with Utils. If not, see . use { crate::testing::PrettyLog, diff --git a/precompiles/utils/src/testing/mod.rs b/precompiles/utils/src/testing/mod.rs index c918c9b622..be6c5a881c 100644 --- a/precompiles/utils/src/testing/mod.rs +++ b/precompiles/utils/src/testing/mod.rs @@ -1,18 +1,24 @@ -// Copyright 2019-2022 PureStake Inc. -// This file is part of Moonbeam. +// This file is part of Astar. -// Moonbeam is free software: you can redistribute it and/or modify +// Copyright 2019-2022 PureStake Inc. +// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Utils package, originally developed by Purestake Inc. +// Utils package used in Astar Network in terms of GPLv3. +// +// Utils is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Moonbeam is distributed in the hope that it will be useful, +// Utils is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Moonbeam. If not, see . +// along with Utils. If not, see . pub mod account; pub mod execution; From f0b7b69fa96011bddbc0308353ef61ba6421ee9d Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Sat, 5 Aug 2023 07:43:08 +0530 Subject: [PATCH 09/21] taplo fix --- runtime/local/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/local/Cargo.toml b/runtime/local/Cargo.toml index de0b16403c..963a2d75ee 100644 --- a/runtime/local/Cargo.toml +++ b/runtime/local/Cargo.toml @@ -28,8 +28,8 @@ pallet-democracy = { workspace = true } pallet-ethereum = { workspace = true } pallet-ethereum-checked = { workspace = true } pallet-evm = { workspace = true } -pallet-evm-precompile-blake2 = { workspace = true } pallet-evm-precompile-batch = { workspace = true } +pallet-evm-precompile-blake2 = { workspace = true } pallet-evm-precompile-bn128 = { workspace = true } pallet-evm-precompile-dispatch = { workspace = true } pallet-evm-precompile-ed25519 = { workspace = true } From 22bd42da698dacb3cdb17d089b04faeaca550e77 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Sun, 6 Aug 2023 10:41:50 +0530 Subject: [PATCH 10/21] more refactor, remote evm crate --- precompiles/batch/Batch.sol | 6 +++ precompiles/batch/src/lib.rs | 13 ++--- precompiles/batch/src/tests.rs | 36 ++++++++------ precompiles/utils/src/lib.rs | 64 ++++++++++++++++++++++++- precompiles/utils/src/testing/handle.rs | 2 +- 5 files changed, 95 insertions(+), 26 deletions(-) diff --git a/precompiles/batch/Batch.sol b/precompiles/batch/Batch.sol index f303ad040c..4aae0557ff 100644 --- a/precompiles/batch/Batch.sol +++ b/precompiles/batch/Batch.sol @@ -68,4 +68,10 @@ interface Batch { uint64[] memory gasLimit ) external; + /// Emitted when a subcall succeeds. + event SubcallSucceeded(uint256 index); + + /// Emitted when a subcall fails. + event SubcallFailed(uint256 index); + } diff --git a/precompiles/batch/src/lib.rs b/precompiles/batch/src/lib.rs index 4faddbd0fb..8cbef4d430 100644 --- a/precompiles/batch/src/lib.rs +++ b/precompiles/batch/src/lib.rs @@ -26,12 +26,7 @@ use frame_support::{ traits::ConstU32, }; use pallet_evm::{Precompile, PrecompileOutput}; -use precompile_utils::{ - bytes::BoundedBytes, - data::BoundedVec, - evm::{costs::call_cost, logs::log1}, - *, -}; +use precompile_utils::{bytes::BoundedBytes, data::BoundedVec, *}; use sp_core::{H160, U256}; use sp_std::{iter::repeat, marker::PhantomData, vec, vec::Vec}; @@ -56,16 +51,14 @@ type GetCallDataLimit = ConstU32; type GetArrayLimit = ConstU32; pub fn log_subcall_succeeded(address: impl Into, index: usize) -> Log { - log1( - address, + LogsBuilder::new(address.into()).log1( LOG_SUBCALL_SUCCEEDED, data::encode_event_data(U256::from(index)), ) } pub fn log_subcall_failed(address: impl Into, index: usize) -> Log { - log1( - address, + LogsBuilder::new(address.into()).log1( LOG_SUBCALL_FAILED, data::encode_event_data(U256::from(index)), ) diff --git a/precompiles/batch/src/tests.rs b/precompiles/batch/src/tests.rs index 0677848a33..c025220664 100644 --- a/precompiles/batch/src/tests.rs +++ b/precompiles/batch/src/tests.rs @@ -20,7 +20,7 @@ use crate::mock::{precompile_address, BatchPrecompileMock, ExtBuilder, PrecompilesValue, Runtime}; use crate::{log_subcall_failed, log_subcall_succeeded, Mode, *}; use fp_evm::ExitError; -use precompile_utils::{evm::costs::call_cost, testing::*}; +use precompile_utils::{call_cost, testing::*, LogsBuilder}; use sp_core::{H256, U256}; fn precompiles() -> BatchPrecompileMock { @@ -155,7 +155,9 @@ fn batch_returns( SubcallOutput { cost: 13, - logs: vec![log1(Bob, H256::repeat_byte(0x11), vec![])], + logs: vec![ + LogsBuilder::new(Bob.into()).log1(H256::repeat_byte(0x11), vec![]) + ], ..SubcallOutput::succeed() } } @@ -180,7 +182,9 @@ fn batch_returns( SubcallOutput { cost: 17, - logs: vec![log1(Charlie, H256::repeat_byte(0x22), vec![])], + logs: vec![ + LogsBuilder::new(Charlie.into()).log1(H256::repeat_byte(0x22), vec![]) + ], ..SubcallOutput::succeed() } } @@ -194,9 +198,9 @@ fn batch_returns( fn batch_some_returns() { ExtBuilder::default().build().execute_with(|| { batch_returns(&precompiles(), Mode::BatchSome) - .expect_log(log1(Bob, H256::repeat_byte(0x11), vec![])) + .expect_log(LogsBuilder::new(Bob.into()).log1(H256::repeat_byte(0x11), vec![])) .expect_log(log_subcall_succeeded(precompile_address(), 0)) - .expect_log(log1(Charlie, H256::repeat_byte(0x22), vec![])) + .expect_log(LogsBuilder::new(Charlie.into()).log1(H256::repeat_byte(0x22), vec![])) .expect_log(log_subcall_succeeded(precompile_address(), 1)) .execute_returns(EvmDataWriter::new().write(true).build()) }) @@ -206,9 +210,9 @@ fn batch_some_returns() { fn batch_some_until_failure_returns() { ExtBuilder::default().build().execute_with(|| { batch_returns(&precompiles(), Mode::BatchSomeUntilFailure) - .expect_log(log1(Bob, H256::repeat_byte(0x11), vec![])) + .expect_log(LogsBuilder::new(Bob.into()).log1(H256::repeat_byte(0x11), vec![])) .expect_log(log_subcall_succeeded(precompile_address(), 0)) - .expect_log(log1(Charlie, H256::repeat_byte(0x22), vec![])) + .expect_log(LogsBuilder::new(Charlie.into()).log1(H256::repeat_byte(0x22), vec![])) .expect_log(log_subcall_succeeded(precompile_address(), 1)) .execute_returns(EvmDataWriter::new().write(true).build()) }) @@ -218,9 +222,9 @@ fn batch_some_until_failure_returns() { fn batch_all_returns() { ExtBuilder::default().build().execute_with(|| { batch_returns(&precompiles(), Mode::BatchAll) - .expect_log(log1(Bob, H256::repeat_byte(0x11), vec![])) + .expect_log(LogsBuilder::new(Bob.into()).log1(H256::repeat_byte(0x11), vec![])) .expect_log(log_subcall_succeeded(precompile_address(), 0)) - .expect_log(log1(Charlie, H256::repeat_byte(0x22), vec![])) + .expect_log(LogsBuilder::new(Charlie.into()).log1(H256::repeat_byte(0x22), vec![])) .expect_log(log_subcall_succeeded(precompile_address(), 1)) .execute_returns(EvmDataWriter::new().write(true).build()) }) @@ -371,7 +375,9 @@ fn batch_incomplete( SubcallOutput { cost: 13, - logs: vec![log1(Bob, H256::repeat_byte(0x11), vec![])], + logs: vec![ + LogsBuilder::new(Bob.into()).log1(H256::repeat_byte(0x11), vec![]) + ], ..SubcallOutput::succeed() } } @@ -421,7 +427,9 @@ fn batch_incomplete( SubcallOutput { cost: 19, - logs: vec![log1(Alice, H256::repeat_byte(0x33), vec![])], + logs: vec![ + LogsBuilder::new(Alice.into()).log1(H256::repeat_byte(0x33), vec![]) + ], ..SubcallOutput::succeed() } } @@ -436,10 +444,10 @@ fn batch_some_incomplete() { let (_, total_call_cost) = costs(); batch_incomplete(&precompiles(), Mode::BatchSome) - .expect_log(log1(Bob, H256::repeat_byte(0x11), vec![])) + .expect_log(LogsBuilder::new(Bob.into()).log1(H256::repeat_byte(0x11), vec![])) .expect_log(log_subcall_succeeded(precompile_address(), 0)) .expect_log(log_subcall_failed(precompile_address(), 1)) - .expect_log(log1(Alice, H256::repeat_byte(0x33), vec![])) + .expect_log(LogsBuilder::new(Alice.into()).log1(H256::repeat_byte(0x33), vec![])) .expect_log(log_subcall_succeeded(precompile_address(), 2)) .expect_cost(13 + 17 + 19 + total_call_cost * 3) .execute_returns(EvmDataWriter::new().write(true).build()) @@ -452,7 +460,7 @@ fn batch_some_until_failure_incomplete() { let (_, total_call_cost) = costs(); batch_incomplete(&precompiles(), Mode::BatchSomeUntilFailure) - .expect_log(log1(Bob, H256::repeat_byte(0x11), vec![])) + .expect_log(LogsBuilder::new(Bob.into()).log1(H256::repeat_byte(0x11), vec![])) .expect_log(log_subcall_succeeded(precompile_address(), 0)) .expect_log(log_subcall_failed(precompile_address(), 1)) .expect_cost(13 + 17 + total_call_cost * 2) diff --git a/precompiles/utils/src/lib.rs b/precompiles/utils/src/lib.rs index 3a663032e9..706a30a19e 100644 --- a/precompiles/utils/src/lib.rs +++ b/precompiles/utils/src/lib.rs @@ -40,7 +40,7 @@ use sp_std::{marker::PhantomData, vec, vec::Vec}; pub mod bytes; pub mod data; -pub mod evm; +// pub mod evm; pub use data::{Address, Bytes, EvmData, EvmDataReader, EvmDataWriter}; pub use precompile_utils_macro::{generate_function_selector, keccak256}; @@ -304,6 +304,68 @@ pub fn log_costs(topics: usize, data_len: usize) -> EvmResult { }) } +// Compute the cost of doing a subcall. +// Some parameters cannot be known in advance, so we estimate the worst possible cost. +pub fn call_cost(value: U256, config: &evm::Config) -> u64 { + // Copied from EVM code since not public. + pub const G_CALLVALUE: u64 = 9000; + pub const G_NEWACCOUNT: u64 = 25000; + + fn address_access_cost(is_cold: bool, regular_value: u64, config: &evm::Config) -> u64 { + if config.increase_state_access_gas { + if is_cold { + config.gas_account_access_cold + } else { + config.gas_storage_read_warm + } + } else { + regular_value + } + } + + fn xfer_cost(is_call_or_callcode: bool, transfers_value: bool) -> u64 { + if is_call_or_callcode && transfers_value { + G_CALLVALUE + } else { + 0 + } + } + + fn new_cost( + is_call_or_staticcall: bool, + new_account: bool, + transfers_value: bool, + config: &evm::Config, + ) -> u64 { + let eip161 = !config.empty_considered_exists; + if is_call_or_staticcall { + if eip161 { + if transfers_value && new_account { + G_NEWACCOUNT + } else { + 0 + } + } else if new_account { + G_NEWACCOUNT + } else { + 0 + } + } else { + 0 + } + } + + let transfers_value = value != U256::default(); + let is_cold = true; + let is_call_or_callcode = true; + let is_call_or_staticcall = true; + let new_account = true; + + address_access_cost(is_cold, config.gas_call, config) + + xfer_cost(is_call_or_callcode, transfers_value) + + new_cost(is_call_or_staticcall, new_account, transfers_value, config) +} + impl PrecompileHandleExt for T { #[must_use] /// Record cost of a log manualy. diff --git a/precompiles/utils/src/testing/handle.rs b/precompiles/utils/src/testing/handle.rs index 6b02ea5c00..27b1f1e071 100644 --- a/precompiles/utils/src/testing/handle.rs +++ b/precompiles/utils/src/testing/handle.rs @@ -121,7 +121,7 @@ impl PrecompileHandle for MockHandle { context: &Context, ) -> (ExitReason, Vec) { if self - .record_cost(crate::evm::costs::call_cost( + .record_cost(crate::call_cost( context.apparent_value, &evm::Config::london(), )) From 915f6df0b84b646733d859cb145f6c34f0117730 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Sun, 6 Aug 2023 11:01:57 +0530 Subject: [PATCH 11/21] remove evm crate, update utils version, add std in dappsstaking --- Cargo.lock | 2 +- precompiles/dapps-staking/Cargo.toml | 2 +- precompiles/utils/Cargo.toml | 2 +- precompiles/utils/src/evm/costs.rs | 119 ----------------- precompiles/utils/src/evm/handle.rs | 183 --------------------------- precompiles/utils/src/evm/logs.rs | 114 ----------------- precompiles/utils/src/evm/mod.rs | 25 ---- 7 files changed, 3 insertions(+), 444 deletions(-) delete mode 100644 precompiles/utils/src/evm/costs.rs delete mode 100644 precompiles/utils/src/evm/handle.rs delete mode 100644 precompiles/utils/src/evm/logs.rs delete mode 100644 precompiles/utils/src/evm/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 85b0107bcb..7914c6350b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9590,7 +9590,7 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "precompile-utils" -version = "0.4.3" +version = "0.4.4" dependencies = [ "assert_matches", "derive_more", diff --git a/precompiles/dapps-staking/Cargo.toml b/precompiles/dapps-staking/Cargo.toml index dfd46ac32a..76f9483fc2 100644 --- a/precompiles/dapps-staking/Cargo.toml +++ b/precompiles/dapps-staking/Cargo.toml @@ -31,7 +31,7 @@ pallet-evm = { workspace = true } [dev-dependencies] derive_more = { workspace = true } -pallet-balances = { workspace = true } +pallet-balances = { workspace = true, features = ["std"] } pallet-timestamp = { workspace = true } precompile-utils = { workspace = true, features = ["testing"] } serde = { workspace = true } diff --git a/precompiles/utils/Cargo.toml b/precompiles/utils/Cargo.toml index 8874ea1976..6102900fd6 100644 --- a/precompiles/utils/Cargo.toml +++ b/precompiles/utils/Cargo.toml @@ -2,7 +2,7 @@ name = "precompile-utils" authors = ["StakeTechnologies", "PureStake"] description = "Utils to write EVM precompiles." -version = "0.4.3" +version = "0.4.4" edition.workspace = true homepage.workspace = true repository.workspace = true diff --git a/precompiles/utils/src/evm/costs.rs b/precompiles/utils/src/evm/costs.rs deleted file mode 100644 index 82f90bbe7b..0000000000 --- a/precompiles/utils/src/evm/costs.rs +++ /dev/null @@ -1,119 +0,0 @@ -// This file is part of Astar. - -// Copyright 2019-2022 PureStake Inc. -// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later -// -// This file is part of Utils package, originally developed by Purestake Inc. -// Utils package used in Astar Network in terms of GPLv3. -// -// Utils is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Utils is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Utils. If not, see . -use { - crate::EvmResult, - fp_evm::{ExitError, PrecompileFailure}, - sp_core::U256, -}; - -pub fn log_costs(topics: usize, data_len: usize) -> EvmResult { - // Cost calculation is copied from EVM code that is not publicly exposed by the crates. - // https://github.com/rust-blockchain/evm/blob/master/gasometer/src/costs.rs#L148 - - const G_LOG: u64 = 375; - const G_LOGDATA: u64 = 8; - const G_LOGTOPIC: u64 = 375; - - let topic_cost = G_LOGTOPIC - .checked_mul(topics as u64) - .ok_or(PrecompileFailure::Error { - exit_status: ExitError::OutOfGas, - })?; - - let data_cost = G_LOGDATA - .checked_mul(data_len as u64) - .ok_or(PrecompileFailure::Error { - exit_status: ExitError::OutOfGas, - })?; - - G_LOG - .checked_add(topic_cost) - .ok_or(PrecompileFailure::Error { - exit_status: ExitError::OutOfGas, - })? - .checked_add(data_cost) - .ok_or(PrecompileFailure::Error { - exit_status: ExitError::OutOfGas, - }) -} - -// Compute the cost of doing a subcall. -// Some parameters cannot be known in advance, so we estimate the worst possible cost. -pub fn call_cost(value: U256, config: &evm::Config) -> u64 { - // Copied from EVM code since not public. - pub const G_CALLVALUE: u64 = 9000; - pub const G_NEWACCOUNT: u64 = 25000; - - fn address_access_cost(is_cold: bool, regular_value: u64, config: &evm::Config) -> u64 { - if config.increase_state_access_gas { - if is_cold { - config.gas_account_access_cold - } else { - config.gas_storage_read_warm - } - } else { - regular_value - } - } - - fn xfer_cost(is_call_or_callcode: bool, transfers_value: bool) -> u64 { - if is_call_or_callcode && transfers_value { - G_CALLVALUE - } else { - 0 - } - } - - fn new_cost( - is_call_or_staticcall: bool, - new_account: bool, - transfers_value: bool, - config: &evm::Config, - ) -> u64 { - let eip161 = !config.empty_considered_exists; - if is_call_or_staticcall { - if eip161 { - if transfers_value && new_account { - G_NEWACCOUNT - } else { - 0 - } - } else if new_account { - G_NEWACCOUNT - } else { - 0 - } - } else { - 0 - } - } - - let transfers_value = value != U256::default(); - let is_cold = true; - let is_call_or_callcode = true; - let is_call_or_staticcall = true; - let new_account = true; - - address_access_cost(is_cold, config.gas_call, config) - + xfer_cost(is_call_or_callcode, transfers_value) - + new_cost(is_call_or_staticcall, new_account, transfers_value, config) -} diff --git a/precompiles/utils/src/evm/handle.rs b/precompiles/utils/src/evm/handle.rs deleted file mode 100644 index f0c30dfdbf..0000000000 --- a/precompiles/utils/src/evm/handle.rs +++ /dev/null @@ -1,183 +0,0 @@ -// This file is part of Astar. - -// Copyright 2019-2022 PureStake Inc. -// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later -// -// This file is part of Utils package, originally developed by Purestake Inc. -// Utils package used in Astar Network in terms of GPLv3. -// -// Utils is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Utils is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Utils. If not, see . - -use { - crate::{check_function_modifier, data::EvmDataReader, EvmResult, FunctionModifier}, - fp_evm::{Log, PrecompileHandle}, -}; - -pub trait PrecompileHandleExt: PrecompileHandle { - /// Record cost of a log manually. - /// This can be useful to record log costs early when their content have static size. - #[must_use] - fn record_log_costs_manual(&mut self, topics: usize, data_len: usize) -> EvmResult; - - /// Record cost of logs. - #[must_use] - fn record_log_costs(&mut self, logs: &[&Log]) -> EvmResult; - - #[must_use] - /// Check that a function call is compatible with the context it is - /// called into. - fn check_function_modifier(&self, modifier: FunctionModifier) -> EvmResult; - - #[must_use] - /// Returns a reader of the input, skipping the selector. - fn read_after_selector(&self) -> EvmResult; -} - -impl PrecompileHandleExt for T { - /// Record cost of a log manualy. - /// This can be useful to record log costs early when their content have static size. - #[must_use] - fn record_log_costs_manual(&mut self, topics: usize, data_len: usize) -> EvmResult { - self.record_cost(crate::evm::costs::log_costs(topics, data_len)?)?; - - Ok(()) - } - - /// Record cost of logs. - #[must_use] - fn record_log_costs(&mut self, logs: &[&Log]) -> EvmResult { - for log in logs { - self.record_log_costs_manual(log.topics.len(), log.data.len())?; - } - - Ok(()) - } - - #[must_use] - /// Check that a function call is compatible with the context it is - /// called into. - fn check_function_modifier(&self, modifier: FunctionModifier) -> EvmResult { - check_function_modifier(self.context(), self.is_static(), modifier) - } - - #[must_use] - /// Returns a reader of the input, skipping the selector. - fn read_after_selector(&self) -> EvmResult { - EvmDataReader::new_skip_selector(self.input()) - } -} - -environmental::environmental!(EVM_CONTEXT: trait PrecompileHandle); - -pub fn using_precompile_handle<'a, R, F: FnOnce() -> R>( - precompile_handle: &'a mut dyn PrecompileHandle, - mutator: F, -) -> R { - // # Safety - // - // unsafe rust does not mean unsafe, but "the compiler cannot guarantee the safety of the - // memory". - // - // The only risk here is that the lifetime 'a comes to its end while the global variable - // `EVM_CONTEXT` still contains the reference to the precompile handle. - // The `using` method guarantee that it can't happen because the global variable is freed right - // after the execution of the `mutator` closure (whatever the result of the execution). - unsafe { - EVM_CONTEXT::using( - core::mem::transmute::<&'a mut dyn PrecompileHandle, &'static mut dyn PrecompileHandle>( - precompile_handle, - ), - mutator, - ) - } -} - -pub fn with_precompile_handle R>(f: F) -> Option { - EVM_CONTEXT::with(|precompile_handle| f(precompile_handle)) -} - -#[cfg(test)] -mod tests { - use super::*; - - struct MockPrecompileHandle; - impl PrecompileHandle for MockPrecompileHandle { - fn call( - &mut self, - _: sp_core::H160, - _: Option, - _: Vec, - _: Option, - _: bool, - _: &evm::Context, - ) -> (evm::ExitReason, Vec) { - unimplemented!() - } - - fn record_cost(&mut self, _: u64) -> Result<(), evm::ExitError> { - unimplemented!() - } - - fn remaining_gas(&self) -> u64 { - unimplemented!() - } - - fn log( - &mut self, - _: sp_core::H160, - _: Vec, - _: Vec, - ) -> Result<(), evm::ExitError> { - unimplemented!() - } - - fn code_address(&self) -> sp_core::H160 { - unimplemented!() - } - - fn input(&self) -> &[u8] { - unimplemented!() - } - - fn context(&self) -> &evm::Context { - unimplemented!() - } - - fn is_static(&self) -> bool { - true - } - - fn gas_limit(&self) -> Option { - unimplemented!() - } - } - - #[test] - fn with_precompile_handle_without_context() { - assert_eq!(with_precompile_handle(|_| {}), None); - } - - #[test] - fn with_precompile_handle_with_context() { - let mut precompile_handle = MockPrecompileHandle; - - assert_eq!( - using_precompile_handle(&mut precompile_handle, || with_precompile_handle( - |handle| handle.is_static() - )), - Some(true) - ); - } -} diff --git a/precompiles/utils/src/evm/logs.rs b/precompiles/utils/src/evm/logs.rs deleted file mode 100644 index 8a322b7832..0000000000 --- a/precompiles/utils/src/evm/logs.rs +++ /dev/null @@ -1,114 +0,0 @@ -// This file is part of Astar. - -// Copyright 2019-2022 PureStake Inc. -// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later -// -// This file is part of Utils package, originally developed by Purestake Inc. -// Utils package used in Astar Network in terms of GPLv3. -// -// Utils is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Utils is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Utils. If not, see . - -use { - crate::EvmResult, - pallet_evm::{Log, PrecompileHandle}, - sp_core::{H160, H256}, - sp_std::{vec, vec::Vec}, -}; - -/// Create a 0-topic log. -#[must_use] -pub fn log0(address: impl Into, data: impl Into>) -> Log { - Log { - address: address.into(), - topics: vec![], - data: data.into(), - } -} - -/// Create a 1-topic log. -#[must_use] -pub fn log1(address: impl Into, topic0: impl Into, data: impl Into>) -> Log { - Log { - address: address.into(), - topics: vec![topic0.into()], - data: data.into(), - } -} - -/// Create a 2-topics log. -#[must_use] -pub fn log2( - address: impl Into, - topic0: impl Into, - topic1: impl Into, - data: impl Into>, -) -> Log { - Log { - address: address.into(), - topics: vec![topic0.into(), topic1.into()], - data: data.into(), - } -} - -/// Create a 3-topics log. -#[must_use] -pub fn log3( - address: impl Into, - topic0: impl Into, - topic1: impl Into, - topic2: impl Into, - data: impl Into>, -) -> Log { - Log { - address: address.into(), - topics: vec![topic0.into(), topic1.into(), topic2.into()], - data: data.into(), - } -} - -/// Create a 4-topics log. -#[must_use] -pub fn log4( - address: impl Into, - topic0: impl Into, - topic1: impl Into, - topic2: impl Into, - topic3: impl Into, - data: impl Into>, -) -> Log { - Log { - address: address.into(), - topics: vec![topic0.into(), topic1.into(), topic2.into(), topic3.into()], - data: data.into(), - } -} - -/// Extension trait allowing to record logs into a PrecompileHandle. -pub trait LogExt { - fn record(self, handle: &mut impl PrecompileHandle) -> EvmResult; - - fn compute_cost(&self) -> EvmResult; -} - -impl LogExt for Log { - fn record(self, handle: &mut impl PrecompileHandle) -> EvmResult { - handle.log(self.address, self.topics, self.data)?; - Ok(()) - } - - fn compute_cost(&self) -> EvmResult { - crate::evm::costs::log_costs(self.topics.len(), self.data.len()) - } -} diff --git a/precompiles/utils/src/evm/mod.rs b/precompiles/utils/src/evm/mod.rs deleted file mode 100644 index d4182d44cc..0000000000 --- a/precompiles/utils/src/evm/mod.rs +++ /dev/null @@ -1,25 +0,0 @@ -// This file is part of Astar. - -// Copyright 2019-2022 PureStake Inc. -// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later -// -// This file is part of Utils package, originally developed by Purestake Inc. -// Utils package used in Astar Network in terms of GPLv3. -// -// Utils is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Utils is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Utils. If not, see . - -pub mod costs; -pub mod handle; -pub mod logs; From b04e1a808e9489c2d6f1e43a50775d9ca557f250 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Sun, 6 Aug 2023 21:26:05 +0530 Subject: [PATCH 12/21] update pallet_balances::config in mock --- precompiles/batch/src/mock.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/precompiles/batch/src/mock.rs b/precompiles/batch/src/mock.rs index 3db4b497e2..54764f9359 100644 --- a/precompiles/batch/src/mock.rs +++ b/precompiles/batch/src/mock.rs @@ -98,6 +98,10 @@ impl pallet_balances::Config for Runtime { type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; type WeightInfo = (); + type HoldIdentifier = (); + type FreezeIdentifier = (); + type MaxHolds = (); + type MaxFreezes = (); } pub fn precompile_address() -> H160 { From 785e5ca2171badfec20b51d0964756f81c6c863e Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Sun, 6 Aug 2023 21:45:25 +0530 Subject: [PATCH 13/21] update pallet_evm config --- precompiles/batch/src/mock.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/precompiles/batch/src/mock.rs b/precompiles/batch/src/mock.rs index 54764f9359..cd6a5fe694 100644 --- a/precompiles/batch/src/mock.rs +++ b/precompiles/batch/src/mock.rs @@ -21,7 +21,7 @@ use super::*; use fp_evm::IsPrecompileResult; -use frame_support::traits::Everything; +use frame_support::traits::{ConstU64, Everything}; use frame_support::{construct_runtime, parameter_types, weights::Weight}; use pallet_evm::{EnsureAddressNever, EnsureAddressRoot, PrecompileResult, PrecompileSet}; use precompile_utils::{mock_account, testing::MockAccount}; @@ -159,6 +159,7 @@ impl pallet_evm::Config for Runtime { type FindAuthor = (); type OnCreate = (); type WeightInfo = (); + type GasLimitPovSizeRatio = ConstU64<4>; } parameter_types! { From 4044dbfd147cc390646398faf5aca223cf5317bb Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Mon, 7 Aug 2023 14:26:05 +0530 Subject: [PATCH 14/21] update license --- precompiles/batch/src/lib.rs | 22 +++++++++++++++++++--- precompiles/batch/src/mock.rs | 23 ++++++++++++++++++++--- precompiles/batch/src/tests.rs | 23 ++++++++++++++++++++--- 3 files changed, 59 insertions(+), 9 deletions(-) diff --git a/precompiles/batch/src/lib.rs b/precompiles/batch/src/lib.rs index 8cbef4d430..51d951440a 100644 --- a/precompiles/batch/src/lib.rs +++ b/precompiles/batch/src/lib.rs @@ -1,9 +1,8 @@ // This file is part of Astar. -// Copyright 2019-2022 PureStake Inc. -// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd. +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. // SPDX-License-Identifier: GPL-3.0-or-later -// + // Astar is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or @@ -17,6 +16,23 @@ // You should have received a copy of the GNU General Public License // along with Astar. If not, see . +// Copyright 2019-2022 PureStake Inc. +// Copyright 2022 Stake Technologies +// This file is part of pallet-evm-precompile-batch package, originally developed by Purestake Inc. +// pallet-evm-precompile-batch package used in Astar Network in terms of GPLv3. +// +// pallet-evm-precompile-batch is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// pallet-evm-precompile-batch is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with pallet-evm-precompile-batch. If not, see . #![cfg_attr(not(feature = "std"), no_std)] use ::evm::{ExitError, ExitReason}; diff --git a/precompiles/batch/src/mock.rs b/precompiles/batch/src/mock.rs index cd6a5fe694..4ac61de881 100644 --- a/precompiles/batch/src/mock.rs +++ b/precompiles/batch/src/mock.rs @@ -1,9 +1,8 @@ // This file is part of Astar. -// Copyright 2019-2022 PureStake Inc. -// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd. +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. // SPDX-License-Identifier: GPL-3.0-or-later -// + // Astar is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or @@ -17,6 +16,24 @@ // You should have received a copy of the GNU General Public License // along with Astar. If not, see . +// Copyright 2019-2022 PureStake Inc. +// Copyright 2022 Stake Technologies +// This file is part of pallet-evm-precompile-batch package, originally developed by Purestake Inc. +// pallet-evm-precompile-batch package used in Astar Network in terms of GPLv3. +// +// pallet-evm-precompile-batch is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// pallet-evm-precompile-batch is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with pallet-evm-precompile-batch. If not, see . + //! Test utilities use super::*; diff --git a/precompiles/batch/src/tests.rs b/precompiles/batch/src/tests.rs index c025220664..55241fe08a 100644 --- a/precompiles/batch/src/tests.rs +++ b/precompiles/batch/src/tests.rs @@ -1,9 +1,8 @@ // This file is part of Astar. -// Copyright 2019-2022 PureStake Inc. -// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd. +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. // SPDX-License-Identifier: GPL-3.0-or-later -// + // Astar is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or @@ -17,6 +16,24 @@ // You should have received a copy of the GNU General Public License // along with Astar. If not, see . +// Copyright 2019-2022 PureStake Inc. +// Copyright 2022 Stake Technologies +// This file is part of pallet-evm-precompile-batch package, originally developed by Purestake Inc. +// pallet-evm-precompile-batch package used in Astar Network in terms of GPLv3. +// +// pallet-evm-precompile-batch is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// pallet-evm-precompile-batch is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with pallet-evm-precompile-batch. If not, see . + use crate::mock::{precompile_address, BatchPrecompileMock, ExtBuilder, PrecompilesValue, Runtime}; use crate::{log_subcall_failed, log_subcall_succeeded, Mode, *}; use fp_evm::ExitError; From f5a439ab8ed42903a1e799f5e3fccd6b06d923fa Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Wed, 9 Aug 2023 12:46:04 +0530 Subject: [PATCH 15/21] add to std-feature --- runtime/local/Cargo.toml | 1 + runtime/local/src/precompiles.rs | 8 +++++--- runtime/shibuya/Cargo.toml | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/runtime/local/Cargo.toml b/runtime/local/Cargo.toml index 5d8b8dd258..77a300febf 100644 --- a/runtime/local/Cargo.toml +++ b/runtime/local/Cargo.toml @@ -128,6 +128,7 @@ std = [ "pallet-evm-precompile-modexp/std", "pallet-evm-precompile-sha3fips/std", "pallet-evm-precompile-dapps-staking/std", + "pallet-evm-precompile-batch/std", "pallet-evm-precompile-sr25519/std", "pallet-evm-precompile-substrate-ecdsa/std", "pallet-evm-precompile-xvm/std", diff --git a/runtime/local/src/precompiles.rs b/runtime/local/src/precompiles.rs index ceec2e119e..8520a7522a 100644 --- a/runtime/local/src/precompiles.rs +++ b/runtime/local/src/precompiles.rs @@ -55,9 +55,11 @@ impl LocalNetworkPrecompiles { /// Return all addresses that contain precompiles. This can be used to populate dummy code /// under the precompile. pub fn used_addresses() -> impl Iterator { - sp_std::vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 1024, 1025, 1026, 1027, 20481, 20482, 20483, 20485] - .into_iter() - .map(hash) + sp_std::vec![ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 1024, 1025, 1026, 1027, 20481, 20482, 20483, 20485, 20846 + ] + .into_iter() + .map(hash) } } diff --git a/runtime/shibuya/Cargo.toml b/runtime/shibuya/Cargo.toml index 2e16191f11..5658d2e833 100644 --- a/runtime/shibuya/Cargo.toml +++ b/runtime/shibuya/Cargo.toml @@ -181,6 +181,7 @@ std = [ "pallet-evm-precompile-modexp/std", "pallet-evm-precompile-sha3fips/std", "pallet-evm-precompile-dapps-staking/std", + "pallet-evm-precompile-batch/std", "pallet-evm-precompile-sr25519/std", "pallet-evm-precompile-substrate-ecdsa/std", "pallet-evm-precompile-assets-erc20/std", From 9078d63be6a87b384404a14412ec473a3fdb8f9c Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Thu, 10 Aug 2023 09:50:31 +0530 Subject: [PATCH 16/21] utils: rectify patch to minor update --- Cargo.lock | 2 +- precompiles/utils/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c836b403a6..076c50ee5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9834,7 +9834,7 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "precompile-utils" -version = "0.4.4" +version = "0.5.0" dependencies = [ "assert_matches", "derive_more", diff --git a/precompiles/utils/Cargo.toml b/precompiles/utils/Cargo.toml index 6102900fd6..a97a1a81ff 100644 --- a/precompiles/utils/Cargo.toml +++ b/precompiles/utils/Cargo.toml @@ -2,7 +2,7 @@ name = "precompile-utils" authors = ["StakeTechnologies", "PureStake"] description = "Utils to write EVM precompiles." -version = "0.4.4" +version = "0.5.0" edition.workspace = true homepage.workspace = true repository.workspace = true From d76419f737d613b329b1983fa6ca228d57c4c783 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Thu, 10 Aug 2023 09:59:31 +0530 Subject: [PATCH 17/21] remove pub from logs function --- precompiles/batch/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/precompiles/batch/src/lib.rs b/precompiles/batch/src/lib.rs index 51d951440a..029c2e09a1 100644 --- a/precompiles/batch/src/lib.rs +++ b/precompiles/batch/src/lib.rs @@ -66,14 +66,14 @@ pub const ARRAY_LIMIT: u32 = 2u32.pow(9); type GetCallDataLimit = ConstU32; type GetArrayLimit = ConstU32; -pub fn log_subcall_succeeded(address: impl Into, index: usize) -> Log { +fn log_subcall_succeeded(address: impl Into, index: usize) -> Log { LogsBuilder::new(address.into()).log1( LOG_SUBCALL_SUCCEEDED, data::encode_event_data(U256::from(index)), ) } -pub fn log_subcall_failed(address: impl Into, index: usize) -> Log { +fn log_subcall_failed(address: impl Into, index: usize) -> Log { LogsBuilder::new(address.into()).log1( LOG_SUBCALL_FAILED, data::encode_event_data(U256::from(index)), From 4e6c2d9e4f6a14793c11832a055236b985ba4b76 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Thu, 10 Aug 2023 10:22:14 +0530 Subject: [PATCH 18/21] fix typos --- precompiles/batch/Batch.sol | 22 +++++++++++----------- precompiles/batch/Cargo.toml | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/precompiles/batch/Batch.sol b/precompiles/batch/Batch.sol index 4aae0557ff..b2884de6d3 100644 --- a/precompiles/batch/Batch.sol +++ b/precompiles/batch/Batch.sol @@ -7,7 +7,7 @@ pragma solidity >=0.8.3; /// repo: https://github.com/AstarNetwork/astar /// @title Batch precompile -/// @dev Allows to perform multiple calls throught one call to the precompile. +/// @dev Allows to perform multiple calls through one call to the precompile. /// Can be used by EOA to do multiple calls in a single transaction. interface Batch { /// @dev Batch multiple calls into a single transaction. @@ -16,12 +16,12 @@ interface Batch { /// In case of one subcall reverting following subcalls will still be attempted. /// /// @param to List of addresses to call. - /// @param value List of values for each subcall. If array is shorter than "to" then additional + /// @param value List of values for each subcall. If array is shorter than `to` then additional /// calls will be performed with a value of 0. - /// @param callData Call data for each `to` address. If array is shorter than "to" then + /// @param callData Call data for each `to` address. If array is shorter than `to` then /// additional calls will be performed with an empty call data. /// @param gasLimit Gas limit for each `to` address. Use 0 to forward all the remaining gas. - /// If array is shorter than "to" then the remaining gas available will be used. + /// If array is shorter than `to` then the remaining gas available will be used. function batchSome( address[] memory to, uint256[] memory value, @@ -33,15 +33,15 @@ interface Batch { /// All calls are performed from the address calling this precompile. /// /// In case of one subcall reverting, no more subcalls will be executed but - /// the batch transaction will succeed. Use batchAll to revert on any subcall revert. + /// the batch transaction will succeed. Use "batchAll" to revert on any subcall revert. /// /// @param to List of addresses to call. - /// @param value List of values for each subcall. If array is shorter than "to" then additional + /// @param value List of values for each subcall. If array is shorter than `to` then additional /// calls will be performed with a value of 0. - /// @param callData Call data for each `to` address. If array is shorter than "to" then + /// @param callData Call data for each `to` address. If array is shorter than `to` then /// additional calls will be performed with an empty call data. /// @param gasLimit Gas limit for each `to` address. Use 0 to forward all the remaining gas. - /// If array is shorter than "to" then the remaining gas available will be used. + /// If array is shorter than `to` then the remaining gas available will be used. function batchSomeUntilFailure( address[] memory to, uint256[] memory value, @@ -55,12 +55,12 @@ interface Batch { /// In case of one subcall reverting, the entire batch will revert. /// /// @param to List of addresses to call. - /// @param value List of values for each subcall. If array is shorter than "to" then additional + /// @param value List of values for each subcall. If array is shorter than `to` then additional /// calls will be performed with a value of 0. - /// @param callData Call data for each `to` address. If array is shorter than "to" then + /// @param callData Call data for each `to` address. If array is shorter than `to` then /// additional calls will be performed with an empty call data. /// @param gasLimit Gas limit for each `to` address. Use 0 to forward all the remaining gas. - /// If array is shorter than "to" then the remaining gas available will be used. + /// If array is shorter than `to` then the remaining gas available will be used. function batchAll( address[] memory to, uint256[] memory value, diff --git a/precompiles/batch/Cargo.toml b/precompiles/batch/Cargo.toml index f8d9c87b3e..56a366b43e 100644 --- a/precompiles/batch/Cargo.toml +++ b/precompiles/batch/Cargo.toml @@ -27,7 +27,7 @@ sp-std = { workspace = true } # Frontier evm = { workspace = true, features = ["with-codec"] } fp-evm = { workspace = true } -pallet-evm = { workspace = true, features = ["forbid-evm-reentrancy"] } +pallet-evm = { workspace = true } [dev-dependencies] derive_more = { workspace = true } From a34e00dbaaabe6a47c8e503970d7433432c758af Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Thu, 10 Aug 2023 10:22:55 +0530 Subject: [PATCH 19/21] remove comments --- precompiles/utils/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/precompiles/utils/src/lib.rs b/precompiles/utils/src/lib.rs index 1b67ce0c0d..ac7cfce9e3 100644 --- a/precompiles/utils/src/lib.rs +++ b/precompiles/utils/src/lib.rs @@ -41,7 +41,6 @@ use sp_std::{marker::PhantomData, vec, vec::Vec}; pub mod bytes; pub mod data; -// pub mod evm; pub use data::{Address, Bytes, EvmData, EvmDataReader, EvmDataWriter}; pub use precompile_utils_macro::{generate_function_selector, keccak256}; From 75c6db48ef9ec4c96c0dd268b125fe4f9881d421 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Fri, 11 Aug 2023 10:45:10 +0530 Subject: [PATCH 20/21] refactor --- precompiles/utils/src/bytes.rs | 9 ++------- precompiles/utils/src/data.rs | 8 ++------ 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/precompiles/utils/src/bytes.rs b/precompiles/utils/src/bytes.rs index cfeb020a99..ba86df9297 100644 --- a/precompiles/utils/src/bytes.rs +++ b/precompiles/utils/src/bytes.rs @@ -106,12 +106,7 @@ impl> EvmData for BoundedBytesString { return Err(revert("length, value too large").into()); } - // Get valid range over the bytes data. - let range = inner_reader.move_cursor(array_size)?; - - let data = inner_reader - .get_input_from_range(range) - .ok_or_else(|| revert(K::signature()))?; + let data = inner_reader.read_raw_bytes(array_size)?; let bytes = Self { data: data.to_owned(), @@ -140,7 +135,7 @@ impl> EvmData for BoundedBytesString { writer.write_pointer( EvmDataWriter::new() .write(U256::from(length)) - .write_raw_bytes(&value) + .write(value) .build(), ); } diff --git a/precompiles/utils/src/data.rs b/precompiles/utils/src/data.rs index e6bb0363c5..ef910ec537 100644 --- a/precompiles/utils/src/data.rs +++ b/precompiles/utils/src/data.rs @@ -187,15 +187,11 @@ impl<'a> EvmDataReader<'a> { Ok(data) } - /// Return Option<&[u8]> from a given range for EvmDataReader - pub fn get_input_from_range(&self, range: Range) -> Option<&[u8]> { - self.input.get(range) - } /// Move the reading cursor with provided length, and return a range from the previous cursor /// location to the new one. /// Checks cursor overflows. - pub fn move_cursor(&mut self, len: usize) -> EvmResult> { + fn move_cursor(&mut self, len: usize) -> EvmResult> { let start = self.cursor; let end = self .cursor @@ -290,7 +286,7 @@ impl EvmDataWriter { /// Write arbitrary bytes. /// Doesn't handle any alignement checks, prefer using `write` instead if possible. - pub fn write_raw_bytes(mut self, value: &[u8]) -> Self { + fn write_raw_bytes(mut self, value: &[u8]) -> Self { self.data.extend_from_slice(value); self } From f5ae2902e37188fb823eed0ddd3dcf42f30fb4ed Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Fri, 11 Aug 2023 10:47:47 +0530 Subject: [PATCH 21/21] fmt --- precompiles/utils/src/data.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/precompiles/utils/src/data.rs b/precompiles/utils/src/data.rs index ef910ec537..2504171b33 100644 --- a/precompiles/utils/src/data.rs +++ b/precompiles/utils/src/data.rs @@ -187,7 +187,6 @@ impl<'a> EvmDataReader<'a> { Ok(data) } - /// Move the reading cursor with provided length, and return a range from the previous cursor /// location to the new one. /// Checks cursor overflows.