diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index 399cd52da9..3e20b99cab 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -47,7 +47,7 @@ jobs: uses: actions-rs/tarpaulin@v0.1 with: version: 0.22.0 - args: '--workspace -e astar-collator xcm-tools local-runtime shibuya-runtime shiden-runtime astar-runtime integration-tests --exclude-files vendor/* bin/* runtime/* tests/* **/mock.rs **/weights.rs precompiles/utils_v2/*' + args: '--workspace -e astar-collator xcm-tools local-runtime shibuya-runtime shiden-runtime astar-runtime integration-tests --exclude-files vendor/* bin/* runtime/* tests/* **/mock.rs **/weights.rs precompiles/utils/*' out-type: Xml - name: Code Coverage Summary Report diff --git a/Cargo.lock b/Cargo.lock index 2440baf174..a7674adddf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -569,6 +569,7 @@ dependencies = [ "moonbeam-evm-tracer", "moonbeam-rpc-primitives-debug", "moonbeam-rpc-primitives-txpool", + "num_enum 0.5.11", "orml-xcm-support", "orml-xtokens", "pallet-assets", @@ -613,6 +614,7 @@ dependencies = [ "polkadot-parachain", "polkadot-primitives", "polkadot-runtime-common", + "precompile-utils", "scale-info", "smallvec 1.11.0", "sp-api", @@ -6032,6 +6034,7 @@ dependencies = [ "moonbeam-evm-tracer", "moonbeam-rpc-primitives-debug", "moonbeam-rpc-primitives-txpool", + "num_enum 0.5.11", "pallet-assets", "pallet-aura", "pallet-balances", @@ -6080,6 +6083,7 @@ dependencies = [ "pallet-vesting", "pallet-xvm", "parity-scale-codec", + "precompile-utils", "scale-info", "smallvec 1.11.0", "sp-api", @@ -10540,25 +10544,24 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "precompile-utils" -version = "0.5.0" +version = "0.1.0" dependencies = [ - "assert_matches", "derive_more", "environmental", "evm", "fp-evm", "frame-support", "frame-system", + "hex", "hex-literal", "impl-trait-for-tuples", "log", "num_enum 0.5.11", "pallet-evm", "parity-scale-codec", - "precompile-utils-macro", + "precompile-utils-macro-v2", "scale-info", "serde", - "sha3", "similar-asserts", "sp-core", "sp-io", @@ -10567,17 +10570,6 @@ dependencies = [ "xcm", ] -[[package]] -name = "precompile-utils-macro" -version = "0.1.0" -dependencies = [ - "num_enum 0.5.11", - "proc-macro2", - "quote", - "sha3", - "syn 1.0.109", -] - [[package]] name = "precompile-utils-macro-v2" version = "0.1.0" @@ -10587,7 +10579,6 @@ dependencies = [ "frame-support", "macrotest", "num_enum 0.5.11", - "precompile-utils-v2", "prettyplease 0.2.15", "proc-macro2", "quote", @@ -10597,33 +10588,6 @@ dependencies = [ "trybuild", ] -[[package]] -name = "precompile-utils-v2" -version = "0.1.0" -dependencies = [ - "derive_more", - "environmental", - "evm", - "fp-evm", - "frame-support", - "frame-system", - "hex", - "hex-literal", - "impl-trait-for-tuples", - "log", - "num_enum 0.5.11", - "pallet-evm", - "parity-scale-codec", - "precompile-utils-macro-v2", - "scale-info", - "serde", - "similar-asserts", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std", -] - [[package]] name = "predicates" version = "2.1.5" @@ -13200,6 +13164,7 @@ dependencies = [ "moonbeam-evm-tracer", "moonbeam-rpc-primitives-debug", "moonbeam-rpc-primitives-txpool", + "num_enum 0.5.11", "orml-xcm-support", "orml-xtokens", "pallet-assets", @@ -13259,6 +13224,7 @@ dependencies = [ "polkadot-parachain", "polkadot-primitives", "polkadot-runtime-common", + "precompile-utils", "scale-info", "smallvec 1.11.0", "sp-api", @@ -13308,6 +13274,7 @@ dependencies = [ "moonbeam-evm-tracer", "moonbeam-rpc-primitives-debug", "moonbeam-rpc-primitives-txpool", + "num_enum 0.5.11", "orml-xcm-support", "orml-xtokens", "pallet-assets", @@ -13353,6 +13320,7 @@ dependencies = [ "polkadot-parachain", "polkadot-primitives", "polkadot-runtime-common", + "precompile-utils", "scale-info", "smallvec 1.11.0", "sp-api", diff --git a/Cargo.toml b/Cargo.toml index 447d45f612..41a2dead91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -308,7 +308,6 @@ assets-chain-extension-types = { path = "./chain-extensions/types/assets", defau unified-accounts-chain-extension-types = { path = "./chain-extensions/types/unified-accounts", default-features = false } precompile-utils = { path = "./precompiles/utils", default-features = false } -precompile-utils-v2 = { path = "./precompiles/utils_v2", default-features = false } local-runtime = { path = "./runtime/local", default-features = false } shibuya-runtime = { path = "./runtime/shibuya", default-features = false } diff --git a/bin/collator/src/parachain/chain_spec/astar.rs b/bin/collator/src/parachain/chain_spec/astar.rs index fe2b8f0535..cce4ef9514 100644 --- a/bin/collator/src/parachain/chain_spec/astar.rs +++ b/bin/collator/src/parachain/chain_spec/astar.rs @@ -141,7 +141,6 @@ fn make_genesis( // We need _some_ code inserted at the precompile address so that // the evm will actually call the address. accounts: Precompiles::used_addresses() - .filter(|addr| !Precompiles::is_blacklisted(addr)) .map(|addr| { ( addr, diff --git a/bin/collator/src/parachain/chain_spec/shiden.rs b/bin/collator/src/parachain/chain_spec/shiden.rs index bc9a888db7..e305f994c4 100644 --- a/bin/collator/src/parachain/chain_spec/shiden.rs +++ b/bin/collator/src/parachain/chain_spec/shiden.rs @@ -142,7 +142,6 @@ fn make_genesis( // We need _some_ code inserted at the precompile address so that // the evm will actually call the address. accounts: Precompiles::used_addresses() - .filter(|addr| !Precompiles::is_blacklisted(addr)) .map(|addr| { ( addr, diff --git a/precompiles/assets-erc20/Cargo.toml b/precompiles/assets-erc20/Cargo.toml index f70e155468..c4f5f65cba 100644 --- a/precompiles/assets-erc20/Cargo.toml +++ b/precompiles/assets-erc20/Cargo.toml @@ -12,7 +12,7 @@ log = { workspace = true } num_enum = { workspace = true } slices = { workspace = true } -precompile-utils = { workspace = true } +precompile-utils = { workspace = true, default-features = false } # Substrate frame-support = { workspace = true } diff --git a/precompiles/assets-erc20/src/lib.rs b/precompiles/assets-erc20/src/lib.rs index cc6a70fce2..c5bc8cb1ff 100644 --- a/precompiles/assets-erc20/src/lib.rs +++ b/precompiles/assets-erc20/src/lib.rs @@ -36,21 +36,19 @@ #![cfg_attr(not(feature = "std"), no_std)] -use fp_evm::{IsPrecompileResult, PrecompileFailure, PrecompileHandle, PrecompileOutput}; +use fp_evm::{ExitError, PrecompileHandle}; use frame_support::traits::fungibles::approvals::Inspect as ApprovalInspect; use frame_support::traits::fungibles::metadata::Inspect as MetadataInspect; use frame_support::traits::fungibles::Inspect; use frame_support::traits::OriginTrait; +use frame_support::DefaultNoBound; use frame_support::{ dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, sp_runtime::traits::StaticLookup, }; -use pallet_evm::{AddressMapping, PrecompileSet}; -use precompile_utils::{ - keccak256, revert, succeed, Address, Bytes, EvmData, EvmDataWriter, EvmResult, - FunctionModifier, LogExt, LogsBuilder, PrecompileHandleExt, RuntimeHelper, -}; -use sp_runtime::traits::{Bounded, Zero}; +use pallet_evm::AddressMapping; +use precompile_utils::prelude::*; +use sp_runtime::traits::Bounded; use sp_core::{Get, MaxEncodedLen, H160, U256}; use sp_std::{ @@ -75,23 +73,6 @@ pub type BalanceOf = = >::AssetId; -#[precompile_utils::generate_function_selector] -#[derive(Debug, PartialEq)] -pub enum Action { - TotalSupply = "totalSupply()", - BalanceOf = "balanceOf(address)", - Allowance = "allowance(address,address)", - Transfer = "transfer(address,uint256)", - Approve = "approve(address,uint256)", - TransferFrom = "transferFrom(address,address,uint256)", - Name = "name()", - Symbol = "symbol()", - Decimals = "decimals()", - MinimumBalance = "minimumBalance()", - Mint = "mint(address,uint256)", - Burn = "burn(address,uint256)", -} - /// This trait ensure we can convert EVM address to AssetIds /// We will require Runtime to have this trait implemented pub trait AddressToAssetId { @@ -116,6 +97,7 @@ pub trait AddressToAssetId { /// This means that every address that starts with 0xFFFFFFFF will go through an additional db read, /// but the probability for this to happen is 2^-32 for random addresses +#[derive(Clone, DefaultNoBound)] pub struct Erc20AssetsPrecompileSet( PhantomData<(Runtime, Instance)>, ); @@ -126,118 +108,63 @@ impl Erc20AssetsPrecompileSet { } } -impl PrecompileSet for Erc20AssetsPrecompileSet +#[precompile_utils::precompile] +#[precompile::precompile_set] +#[precompile::test_concrete_types(mock::Runtime, ())] +impl Erc20AssetsPrecompileSet where Instance: 'static, Runtime: pallet_assets::Config + pallet_evm::Config + frame_system::Config, Runtime::RuntimeCall: Dispatchable + GetDispatchInfo, Runtime::RuntimeCall: From>, ::RuntimeOrigin: From>, - BalanceOf: TryFrom + Into + EvmData, + BalanceOf: TryFrom + Into + solidity::Codec, Runtime: AddressToAssetId>, <::RuntimeCall as Dispatchable>::RuntimeOrigin: OriginTrait, + AssetIdOf: Copy, { - fn execute(&self, handle: &mut impl PrecompileHandle) -> Option> { - let address = handle.code_address(); - - if let Some(asset_id) = Runtime::address_to_asset_id(address) { - // We check maybe_total_supply. This function returns Some if the asset exists, - // which is all we care about at this point - if pallet_assets::Pallet::::maybe_total_supply(asset_id.clone()) - .is_some() - { - let result = { - let selector = match handle.read_selector() { - Ok(selector) => selector, - Err(e) => return Some(Err(e)), - }; - - if let Err(err) = handle.check_function_modifier(match selector { - Action::Approve - | Action::Transfer - | Action::TransferFrom - | Action::Mint - | Action::Burn => FunctionModifier::NonPayable, - _ => FunctionModifier::View, - }) { - return Some(Err(err)); - } - - match selector { - // XC20 - Action::TotalSupply => Self::total_supply(asset_id, handle), - Action::BalanceOf => Self::balance_of(asset_id, handle), - Action::Allowance => Self::allowance(asset_id, handle), - Action::Approve => Self::approve(asset_id, handle), - Action::Transfer => Self::transfer(asset_id, handle), - Action::TransferFrom => Self::transfer_from(asset_id, handle), - Action::Name => Self::name(asset_id, handle), - Action::Symbol => Self::symbol(asset_id, handle), - Action::Decimals => Self::decimals(asset_id, handle), - // XC20+ - Action::MinimumBalance => Self::minimum_balance(asset_id, handle), - Action::Mint => Self::mint(asset_id, handle), - Action::Burn => Self::burn(asset_id, handle), - } - }; - return Some(result); - } + /// PrecompileSet discriminant. Allows to knows if the address maps to an asset id, + /// and if this is the case which one. + #[precompile::discriminant] + fn discriminant(address: H160, gas: u64) -> DiscriminantResult> { + let extra_cost = RuntimeHelper::::db_read_gas_cost(); + if gas < extra_cost { + return DiscriminantResult::OutOfGas; } - None - } - fn is_precompile(&self, address: H160, _gas: u64) -> IsPrecompileResult { - let is_precompile = if let Some(asset_id) = Runtime::address_to_asset_id(address) { - // If the assetId has non-zero supply - // "total_supply" returns both 0 if the assetId does not exist or if the supply is 0 - // The assumption I am making here is that a 0 supply asset is not interesting from - // the perspective of the precompiles. Once pallet-assets has more publicly accesible - // storage we can use another function for this, like check_asset_existence. - // The other options is to check the asset existence in pallet-asset-manager, but - // this makes the precompiles dependent on such a pallet, which is not ideal - !pallet_assets::Pallet::::total_supply(asset_id).is_zero() - } else { - false + let asset_id = match Runtime::address_to_asset_id(address) { + Some(asset_id) => asset_id, + None => return DiscriminantResult::None(extra_cost), }; - IsPrecompileResult::Answer { - is_precompile, - extra_cost: 0, + if pallet_assets::Pallet::::maybe_total_supply(asset_id).is_some() { + DiscriminantResult::Some(asset_id, extra_cost) + } else { + DiscriminantResult::None(extra_cost) } } -} -impl Erc20AssetsPrecompileSet -where - Instance: 'static, - Runtime: pallet_assets::Config + pallet_evm::Config + frame_system::Config, - Runtime::RuntimeCall: Dispatchable + GetDispatchInfo, - Runtime::RuntimeCall: From>, - ::RuntimeOrigin: From>, - BalanceOf: TryFrom + Into + EvmData, - Runtime: AddressToAssetId>, - <::RuntimeCall as Dispatchable>::RuntimeOrigin: OriginTrait, -{ + #[precompile::public("totalSupply()")] + #[precompile::view] fn total_supply( asset_id: AssetIdOf, handle: &mut impl PrecompileHandle, - ) -> EvmResult { + ) -> EvmResult { // TODO: benchmark this function so we can measure ref time & PoV correctly // Storage item: Asset: // Blake2_128(16) + AssetId(16) + AssetDetails((4 * AccountId(32)) + (3 * Balance(16)) + 15) handle.record_db_read::(223)?; - // Fetch info. - let amount: U256 = - pallet_assets::Pallet::::total_issuance(asset_id).into(); - - Ok(succeed(EvmDataWriter::new().write(amount).build())) + Ok(pallet_assets::Pallet::::total_issuance(asset_id).into()) } + #[precompile::public("balanceOf(address)")] + #[precompile::view] fn balance_of( asset_id: AssetIdOf, handle: &mut impl PrecompileHandle, - ) -> EvmResult { + who: Address, + ) -> EvmResult { // TODO: benchmark this function so we can measure ref time & PoV correctly // Storage item: Account: // Blake2_128(16) + AssetId(16) + Blake2_128(16) + AccountId(32) + AssetAccount(19 + Extra) @@ -245,34 +172,33 @@ where 99 + >::Extra::max_encoded_len(), )?; - let mut input = handle.read_input()?; - input.expect_arguments(1)?; - - let owner: H160 = input.read::
()?.into(); + let who: H160 = who.into(); // Fetch info. let amount: U256 = { - let owner: Runtime::AccountId = Runtime::AddressMapping::into_account_id(owner); - pallet_assets::Pallet::::balance(asset_id, &owner).into() + let who: Runtime::AccountId = Runtime::AddressMapping::into_account_id(who); + pallet_assets::Pallet::::balance(asset_id, &who).into() }; - Ok(succeed(EvmDataWriter::new().write(amount).build())) + // Build output. + Ok(amount) } + #[precompile::public("allowance(address,address)")] + #[precompile::view] fn allowance( asset_id: AssetIdOf, handle: &mut impl PrecompileHandle, - ) -> EvmResult { + owner: Address, + spender: Address, + ) -> EvmResult { // TODO: benchmark this function so we can measure ref time & PoV correctly // Storage item: Approvals: - // Blake2_128(16) + AssetId(16) + (2 * Blake2_128(16) + AccountId(20)) + Approval(32) + // Blake2_128(16) + AssetId(16) + (2 * Blake2_128(16) + AccountId(32)) + Approval(32) handle.record_db_read::(148)?; - let mut input = handle.read_input()?; - input.expect_arguments(2)?; - - let owner: H160 = input.read::
()?.into(); - let spender: H160 = input.read::
()?.into(); + let owner: H160 = owner.into(); + let spender: H160 = spender.into(); // Fetch info. let amount: U256 = { @@ -283,88 +209,96 @@ where pallet_assets::Pallet::::allowance(asset_id, &owner, &spender).into() }; - Ok(succeed(EvmDataWriter::new().write(amount).build())) + // Build output. + Ok(amount) } + #[precompile::public("approve(address,uint256)")] fn approve( asset_id: AssetIdOf, handle: &mut impl PrecompileHandle, - ) -> EvmResult { + spender: Address, + value: U256, + ) -> EvmResult { handle.record_log_costs_manual(3, 32)?; - let mut input = handle.read_input()?; - input.expect_arguments(2)?; + let spender: H160 = spender.into(); - let spender: H160 = input.read::
()?.into(); - let amount: U256 = input.read()?; + Self::approve_inner(asset_id, handle, handle.context().caller, spender, value)?; + log3( + handle.context().address, + SELECTOR_LOG_APPROVAL, + handle.context().caller, + spender, + solidity::encode_event_data(value), + ) + .record(handle)?; + + // Build output. + Ok(true) + } + + fn approve_inner( + asset_id: AssetIdOf, + handle: &mut impl PrecompileHandle, + owner: H160, + spender: H160, + value: U256, + ) -> EvmResult { + let owner = Runtime::AddressMapping::into_account_id(owner); + let spender: Runtime::AccountId = Runtime::AddressMapping::into_account_id(spender); + // Amount saturate if too high. + let amount: BalanceOf = + value.try_into().unwrap_or_else(|_| Bounded::max_value()); + + // Storage item: Approvals: + // Blake2_128(16) + AssetId(16) + (2 * Blake2_128(16) + AccountId(20)) + Approval(32) + handle.record_db_read::(136)?; + + // If previous approval exists, we need to clean it + if pallet_assets::Pallet::::allowance(asset_id, &owner, &spender) + != 0u32.into() { - let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); - let spender: Runtime::AccountId = Runtime::AddressMapping::into_account_id(spender); - // Amount saturate if too high. - let amount: BalanceOf = - amount.try_into().unwrap_or_else(|_| Bounded::max_value()); - - // Allowance read - handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - - // If previous approval exists, we need to clean it - if pallet_assets::Pallet::::allowance( - asset_id.clone(), - &origin, - &spender, - ) != 0u32.into() - { - RuntimeHelper::::try_dispatch( - handle, - Some(origin.clone()).into(), - pallet_assets::Call::::cancel_approval { - id: asset_id.clone().into(), - delegate: Runtime::Lookup::unlookup(spender.clone()), - }, - )?; - } - // Dispatch call (if enough gas). RuntimeHelper::::try_dispatch( handle, - Some(origin).into(), - pallet_assets::Call::::approve_transfer { + Some(owner.clone()).into(), + pallet_assets::Call::::cancel_approval { id: asset_id.into(), - delegate: Runtime::Lookup::unlookup(spender), - amount, + delegate: Runtime::Lookup::unlookup(spender.clone()), }, )?; } + // Dispatch call (if enough gas). + RuntimeHelper::::try_dispatch( + handle, + Some(owner).into(), + pallet_assets::Call::::approve_transfer { + id: asset_id.into(), + delegate: Runtime::Lookup::unlookup(spender), + amount, + }, + )?; - LogsBuilder::new(handle.context().address) - .log3( - SELECTOR_LOG_APPROVAL, - handle.context().caller, - spender, - EvmDataWriter::new().write(amount).build(), - ) - .record(handle)?; - - Ok(succeed(EvmDataWriter::new().write(true).build())) + Ok(()) } + #[precompile::public("transfer(address,uint256)")] fn transfer( asset_id: AssetIdOf, handle: &mut impl PrecompileHandle, - ) -> EvmResult { + to: Address, + value: U256, + ) -> EvmResult { handle.record_log_costs_manual(3, 32)?; - let mut input = handle.read_input()?; - input.expect_arguments(2)?; - - let to: H160 = input.read::
()?.into(); - let amount: U256 = input.read()?; + let to: H160 = to.into(); + let value = Self::u256_to_amount(value).in_field("value")?; // Build call with origin. { let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); let to = Runtime::AddressMapping::into_account_id(to); - let amount = Self::u256_to_amount(amount)?; // Dispatch call (if enough gas). RuntimeHelper::::try_dispatch( @@ -373,42 +307,42 @@ where pallet_assets::Call::::transfer { id: asset_id.into(), target: Runtime::Lookup::unlookup(to), - amount, + amount: value, }, )?; } - LogsBuilder::new(handle.context().address) - .log3( - SELECTOR_LOG_TRANSFER, - handle.context().caller, - to, - EvmDataWriter::new().write(amount).build(), - ) - .record(handle)?; + log3( + handle.context().address, + SELECTOR_LOG_TRANSFER, + handle.context().caller, + to, + solidity::encode_event_data(value), + ) + .record(handle)?; - Ok(succeed(EvmDataWriter::new().write(true).build())) + Ok(true) } + #[precompile::public("transferFrom(address,address,uint256)")] fn transfer_from( asset_id: AssetIdOf, handle: &mut impl PrecompileHandle, - ) -> EvmResult { + from: Address, + to: Address, + value: U256, + ) -> EvmResult { handle.record_log_costs_manual(3, 32)?; - let mut input = handle.read_input()?; - input.expect_arguments(3)?; - - let from: H160 = input.read::
()?.into(); - let to: H160 = input.read::
()?.into(); - let amount: U256 = input.read()?; + let from: H160 = from.into(); + let to: H160 = to.into(); + let value = Self::u256_to_amount(value).in_field("value")?; { let caller: Runtime::AccountId = Runtime::AddressMapping::into_account_id(handle.context().caller); - let from: Runtime::AccountId = Runtime::AddressMapping::into_account_id(from); + let from: Runtime::AccountId = Runtime::AddressMapping::into_account_id(from.clone()); let to: Runtime::AccountId = Runtime::AddressMapping::into_account_id(to); - let amount = Self::u256_to_amount(amount)?; // If caller is "from", it can spend as much as it wants from its own balance. if caller != from { @@ -420,7 +354,7 @@ where id: asset_id.into(), owner: Runtime::Lookup::unlookup(from), destination: Runtime::Lookup::unlookup(to), - amount, + amount: value, }, )?; } else { @@ -431,29 +365,31 @@ where pallet_assets::Call::::transfer { id: asset_id.into(), target: Runtime::Lookup::unlookup(to), - amount, + amount: value, }, )?; } } - LogsBuilder::new(handle.context().address) - .log3( - SELECTOR_LOG_TRANSFER, - from, - to, - EvmDataWriter::new().write(amount).build(), - ) - .record(handle)?; + log3( + handle.context().address, + SELECTOR_LOG_TRANSFER, + from, + to, + solidity::encode_event_data(value), + ) + .record(handle)?; - Ok(succeed(EvmDataWriter::new().write(true).build())) + // Build output. + Ok(true) } + #[precompile::public("name()")] + #[precompile::view] fn name( asset_id: AssetIdOf, handle: &mut impl PrecompileHandle, - ) -> EvmResult { - // TODO: benchmark this function so we can measure ref time & PoV correctly + ) -> EvmResult { // Storage item: Metadata: // Blake2_128(16) + AssetId(16) + AssetMetadata[deposit(16) + name(StringLimit) // + symbol(StringLimit) + decimals(1) + is_frozen(1)] @@ -461,22 +397,19 @@ where 50 + (2 * >::StringLimit::get()) as usize, )?; - Ok(succeed( - EvmDataWriter::new() - .write::( - pallet_assets::Pallet::::name(asset_id) - .as_slice() - .into(), - ) - .build(), - )) + let name = pallet_assets::Pallet::::name(asset_id) + .as_slice() + .into(); + + Ok(name) } + #[precompile::public("symbol()")] + #[precompile::view] fn symbol( asset_id: AssetIdOf, handle: &mut impl PrecompileHandle, - ) -> EvmResult { - // TODO: benchmark this function so we can measure ref time & PoV correctly + ) -> EvmResult { // Storage item: Metadata: // Blake2_128(16) + AssetId(16) + AssetMetadata[deposit(16) + name(StringLimit) // + symbol(StringLimit) + decimals(1) + is_frozen(1)] @@ -484,23 +417,19 @@ where 50 + (2 * >::StringLimit::get()) as usize, )?; - // Build output. - Ok(succeed( - EvmDataWriter::new() - .write::( - pallet_assets::Pallet::::symbol(asset_id) - .as_slice() - .into(), - ) - .build(), - )) + let symbol = pallet_assets::Pallet::::symbol(asset_id) + .as_slice() + .into(); + + Ok(symbol) } + #[precompile::public("decimals()")] + #[precompile::view] fn decimals( asset_id: AssetIdOf, handle: &mut impl PrecompileHandle, - ) -> EvmResult { - // TODO: benchmark this function so we can measure ref time & PoV correctly + ) -> EvmResult { // Storage item: Metadata: // Blake2_128(16) + AssetId(16) + AssetMetadata[deposit(16) + name(StringLimit) // + symbol(StringLimit) + decimals(1) + is_frozen(1)] @@ -508,88 +437,110 @@ where 50 + (2 * >::StringLimit::get()) as usize, )?; - // Build output. - Ok(succeed( - EvmDataWriter::new() - .write::(pallet_assets::Pallet::::decimals( - asset_id, - )) - .build(), + Ok(pallet_assets::Pallet::::decimals( + asset_id, )) } + #[precompile::public("minimumBalance()")] + #[precompile::view] fn minimum_balance( asset_id: AssetIdOf, handle: &mut impl PrecompileHandle, - ) -> EvmResult { + ) -> EvmResult { // TODO: benchmark this function so we can measure ref time & PoV correctly // Storage item: AssetDetails: // Blake2_128(16) + AssetDetails((4 * AccountId(32)) + (3 * Balance(16)) + 15) handle.record_db_read::(207)?; - let min_balance: U256 = - pallet_assets::Pallet::::minimum_balance(asset_id).into(); - - Ok(succeed(EvmDataWriter::new().write(min_balance).build())) + Ok(pallet_assets::Pallet::::minimum_balance(asset_id).into()) } + #[precompile::public("mint(address,uint256)")] fn mint( asset_id: AssetIdOf, handle: &mut impl PrecompileHandle, - ) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(2)?; + to: Address, + value: U256, + ) -> EvmResult { + handle.record_log_costs_manual(3, 32)?; - let beneficiary: H160 = input.read::
()?.into(); - let amount = Self::u256_to_amount(input.read::()?)?; + let to: H160 = to.into(); + let value = Self::u256_to_amount(value).in_field("value")?; - let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); - let beneficiary = Runtime::AddressMapping::into_account_id(beneficiary); + // Build call with origin. + { + let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); + let to = Runtime::AddressMapping::into_account_id(to); - // Dispatch call (if enough gas). - RuntimeHelper::::try_dispatch( - handle, - Some(origin).into(), - pallet_assets::Call::::mint { - id: asset_id.into(), - beneficiary: Runtime::Lookup::unlookup(beneficiary), - amount, - }, - )?; + // Dispatch call (if enough gas). + RuntimeHelper::::try_dispatch( + handle, + Some(origin).into(), + pallet_assets::Call::::mint { + id: asset_id.into(), + beneficiary: Runtime::Lookup::unlookup(to), + amount: value, + }, + )?; + } + + log3( + handle.context().address, + SELECTOR_LOG_TRANSFER, + H160::default(), + to, + solidity::encode_event_data(value), + ) + .record(handle)?; - Ok(succeed(EvmDataWriter::new().write(true).build())) + Ok(true) } + #[precompile::public("burn(address,uint256)")] fn burn( asset_id: AssetIdOf, handle: &mut impl PrecompileHandle, - ) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(2)?; + from: Address, + value: U256, + ) -> EvmResult { + handle.record_log_costs_manual(3, 32)?; - let who: H160 = input.read::
()?.into(); - let amount = Self::u256_to_amount(input.read::()?)?; + let from: H160 = from.into(); + let value = Self::u256_to_amount(value).in_field("value")?; - let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); - let who = Runtime::AddressMapping::into_account_id(who); + // Build call with origin. + { + let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); + let from = Runtime::AddressMapping::into_account_id(from); - // Dispatch call (if enough gas). - RuntimeHelper::::try_dispatch( - handle, - Some(origin).into(), - pallet_assets::Call::::burn { - id: asset_id.into(), - who: Runtime::Lookup::unlookup(who), - amount, - }, - )?; + // Dispatch call (if enough gas). + RuntimeHelper::::try_dispatch( + handle, + Some(origin).into(), + pallet_assets::Call::::burn { + id: asset_id.into(), + who: Runtime::Lookup::unlookup(from), + amount: value, + }, + )?; + } + + log3( + handle.context().address, + SELECTOR_LOG_TRANSFER, + from, + H160::default(), + solidity::encode_event_data(value), + ) + .record(handle)?; - Ok(succeed(EvmDataWriter::new().write(true).build())) + Ok(true) } - fn u256_to_amount(value: U256) -> Result, PrecompileFailure> { + fn u256_to_amount(value: U256) -> MayRevert> { value .try_into() - .map_err(|_| revert("Error processing amount")) + .map_err(|_| RevertReason::value_is_too_large("balance type").into()) } } diff --git a/precompiles/assets-erc20/src/mock.rs b/precompiles/assets-erc20/src/mock.rs index 47c4ba498b..d79adc7afa 100644 --- a/precompiles/assets-erc20/src/mock.rs +++ b/precompiles/assets-erc20/src/mock.rs @@ -42,122 +42,50 @@ use frame_support::{ traits::{AsEnsureOriginWithArg, ConstU64, Everything}, weights::Weight, }; -use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use frame_system::EnsureRoot; -use pallet_evm::{AddressMapping, EnsureAddressNever, EnsureAddressRoot}; -use scale_info::TypeInfo; -use serde::{Deserialize, Serialize}; +use pallet_evm::{EnsureAddressNever, EnsureAddressRoot}; +use precompile_utils::{ + mock_account, + testing::{AddressInPrefixedSet, MockAccount}, +}; + use sp_core::{ConstU32, H160, H256}; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, }; -pub type AccountId = Account; +pub type AccountId = MockAccount; pub type AssetId = u128; pub type Balance = u128; pub type BlockNumber = u64; pub type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; pub type Block = frame_system::mocking::MockBlock; -/// A simple account type. -#[derive( - Eq, - PartialEq, - Ord, - PartialOrd, - Clone, - Encode, - Decode, - Debug, - MaxEncodedLen, - Serialize, - Deserialize, - derive_more::Display, - TypeInfo, -)] -pub enum Account { - Alice, - Bob, - Charlie, - Bogus, - AssetId(AssetId), -} - -impl Default for Account { - fn default() -> Self { - Self::Bogus - } -} - -impl AddressMapping for Account { - fn into_account_id(h160_account: H160) -> Account { - match h160_account { - a if a == H160::repeat_byte(0xAA) => Self::Alice, - a if a == H160::repeat_byte(0xBB) => Self::Bob, - a if a == H160::repeat_byte(0xCC) => Self::Charlie, - _ => { - let mut data = [0u8; 16]; - let (prefix_part, id_part) = h160_account.as_fixed_bytes().split_at(4); - if prefix_part == &[255u8; 4] { - data.copy_from_slice(id_part); - - return Self::AssetId(u128::from_be_bytes(data)); - } - Self::Bogus - } - } - } -} +/// The local asset precompile address prefix. Addresses that match against this prefix will +/// be routed to Erc20AssetsPrecompileSet being marked as local +pub const ASSET_PRECOMPILE_ADDRESS_PREFIX: u32 = 0xfffffffe; -pub const ASSET_PRECOMPILE_ADDRESS_PREFIX: &[u8] = &[255u8; 4]; +mock_account!(LocalAssetId(AssetId), |value: LocalAssetId| { + AddressInPrefixedSet(ASSET_PRECOMPILE_ADDRESS_PREFIX, value.0).into() +}); // Implement the trait, where we convert AccountId to AssetID impl AddressToAssetId for Runtime { /// The way to convert an account to assetId is by ensuring that the prefix is 0XFFFFFFFF /// and by taking the lowest 128 bits as the assetId fn address_to_asset_id(address: H160) -> Option { - let mut data = [0u8; 16]; - let address_bytes: [u8; 20] = address.into(); - if ASSET_PRECOMPILE_ADDRESS_PREFIX.eq(&address_bytes[0..4]) { - data.copy_from_slice(&address_bytes[4..20]); - Some(u128::from_be_bytes(data)) + let address: MockAccount = address.into(); + if address.has_prefix_u32(ASSET_PRECOMPILE_ADDRESS_PREFIX) { + return Some(address.without_prefix()); } else { None } } fn asset_id_to_address(asset_id: AssetId) -> H160 { - let mut data = [0u8; 20]; - data[0..4].copy_from_slice(ASSET_PRECOMPILE_ADDRESS_PREFIX); - data[4..20].copy_from_slice(&asset_id.to_be_bytes()); - H160::from(data) - } -} - -impl From for H160 { - fn from(x: Account) -> H160 { - match x { - Account::Alice => H160::repeat_byte(0xAA), - Account::Bob => H160::repeat_byte(0xBB), - Account::Charlie => H160::repeat_byte(0xCC), - Account::AssetId(asset_id) => { - let mut data = [0u8; 20]; - let id_as_bytes = asset_id.to_be_bytes(); - data[0..4].copy_from_slice(&[255u8; 4]); - data[4..20].copy_from_slice(&id_as_bytes); - H160::from_slice(&data) - } - Account::Bogus => Default::default(), - } - } -} - -impl From for H256 { - fn from(x: Account) -> H256 { - let x: H160 = x.into(); - x.into() + LocalAssetId(asset_id).into() } } @@ -230,6 +158,8 @@ parameter_types! { pub WeightPerGas: Weight = Weight::from_parts(1, 0); } +pub type PrecompileCall = Erc20AssetsPrecompileSetCall; + impl pallet_evm::Config for Runtime { type FeeCalculator = (); type GasWeightMapping = pallet_evm::FixedGasWeightMapping; diff --git a/precompiles/assets-erc20/src/tests.rs b/precompiles/assets-erc20/src/tests.rs index a7af894de7..ab0cf49a5b 100644 --- a/precompiles/assets-erc20/src/tests.rs +++ b/precompiles/assets-erc20/src/tests.rs @@ -34,12 +34,13 @@ // You should have received a copy of the GNU General Public License // along with AssetsERC20. If not, see . use frame_support::assert_ok; +use sp_runtime::traits::Zero; use std::str::from_utf8; use crate::mock::*; use crate::*; -use precompile_utils::{testing::*, EvmDataWriter, LogsBuilder}; +use precompile_utils::testing::*; use sha3::{Digest, Keccak256}; fn precompiles() -> Erc20AssetsPrecompileSet { @@ -52,14 +53,14 @@ fn selector_less_than_four_bytes() { assert_ok!(Assets::force_create( RuntimeOrigin::root(), 0u128, - Account::Alice.into(), + CryptoAlith.into(), true, 1 )); // This selector is only three bytes long when four are required. precompiles() - .prepare_test(Account::Alice, Account::AssetId(0u128), vec![1u8, 2u8, 3u8]) - .execute_reverts(|output| output == b"tried to parse selector out of bounds"); + .prepare_test(CryptoAlith, LocalAssetId(0u128), vec![1u8, 2u8, 3u8]) + .execute_reverts(|output| output == b"Tried to read selector out of bounds"); }); } @@ -69,35 +70,31 @@ fn no_selector_exists_but_length_is_right() { assert_ok!(Assets::force_create( RuntimeOrigin::root(), 0u128, - Account::Alice.into(), + CryptoAlith.into(), true, 1 )); precompiles() - .prepare_test( - Account::Alice, - Account::AssetId(0u128), - vec![1u8, 2u8, 3u8, 4u8], - ) - .execute_reverts(|output| output == b"unknown selector"); + .prepare_test(CryptoAlith, LocalAssetId(0u128), vec![1u8, 2u8, 3u8, 4u8]) + .execute_reverts(|output| output == b"Unknown selector"); }); } #[test] fn selectors() { - assert_eq!(Action::BalanceOf as u32, 0x70a08231); - assert_eq!(Action::TotalSupply as u32, 0x18160ddd); - assert_eq!(Action::Approve as u32, 0x095ea7b3); - assert_eq!(Action::Allowance as u32, 0xdd62ed3e); - assert_eq!(Action::Transfer as u32, 0xa9059cbb); - assert_eq!(Action::TransferFrom as u32, 0x23b872dd); - assert_eq!(Action::Name as u32, 0x06fdde03); - assert_eq!(Action::Symbol as u32, 0x95d89b41); - assert_eq!(Action::Decimals as u32, 0x313ce567); - assert_eq!(Action::MinimumBalance as u32, 0xb9d1d49b); - assert_eq!(Action::Mint as u32, 0x40c10f19); - assert_eq!(Action::Burn as u32, 0x9dc29fac); + assert!(PrecompileCall::balance_of_selectors().contains(&0x70a08231)); + assert!(PrecompileCall::total_supply_selectors().contains(&0x18160ddd)); + assert!(PrecompileCall::approve_selectors().contains(&0x095ea7b3)); + assert!(PrecompileCall::allowance_selectors().contains(&0xdd62ed3e)); + assert!(PrecompileCall::transfer_selectors().contains(&0xa9059cbb)); + assert!(PrecompileCall::transfer_from_selectors().contains(&0x23b872dd)); + assert!(PrecompileCall::name_selectors().contains(&0x06fdde03)); + assert!(PrecompileCall::symbol_selectors().contains(&0x95d89b41)); + assert!(PrecompileCall::decimals_selectors().contains(&0x313ce567)); + + assert!(PrecompileCall::mint_selectors().contains(&0x40c10f19)); + assert!(PrecompileCall::burn_selectors().contains(&0x9dc29fac)); assert_eq!( crate::SELECTOR_LOG_TRANSFER, @@ -110,97 +107,128 @@ fn selectors() { ); } +#[test] +fn modifiers() { + ExtBuilder::default() + .with_balances(vec![(CryptoAlith.into(), 1000)]) + .build() + .execute_with(|| { + assert_ok!(Assets::force_create( + RuntimeOrigin::root(), + 0u128, + CryptoAlith.into(), + true, + 1 + )); + let mut tester = + PrecompilesModifierTester::new(precompiles(), CryptoAlith, LocalAssetId(0u128)); + + tester.test_view_modifier(PrecompileCall::balance_of_selectors()); + tester.test_view_modifier(PrecompileCall::total_supply_selectors()); + tester.test_default_modifier(PrecompileCall::approve_selectors()); + tester.test_view_modifier(PrecompileCall::allowance_selectors()); + tester.test_default_modifier(PrecompileCall::transfer_selectors()); + tester.test_default_modifier(PrecompileCall::transfer_from_selectors()); + tester.test_view_modifier(PrecompileCall::name_selectors()); + tester.test_view_modifier(PrecompileCall::symbol_selectors()); + tester.test_view_modifier(PrecompileCall::decimals_selectors()); + + tester.test_default_modifier(PrecompileCall::mint_selectors()); + tester.test_default_modifier(PrecompileCall::burn_selectors()); + }); +} + #[test] fn get_total_supply() { ExtBuilder::default() - .with_balances(vec![(Account::Alice, 1000), (Account::Bob, 2500)]) + .with_balances(vec![(CryptoAlith.into(), 1000), (Bob.into(), 2500)]) .build() .execute_with(|| { assert_ok!(Assets::force_create( RuntimeOrigin::root(), 0u128, - Account::Alice.into(), + CryptoAlith.into(), true, 1 )); assert_ok!(Assets::mint( - RuntimeOrigin::signed(Account::Alice), + RuntimeOrigin::signed(CryptoAlith.into()), 0u128, - Account::Alice.into(), + CryptoAlith.into(), 1000 )); precompiles() .prepare_test( - Account::Alice, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::TotalSupply).build(), + CryptoAlith, + LocalAssetId(0u128), + PrecompileCall::total_supply {}, ) .expect_cost(0) // TODO: Test db read/write costs .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(U256::from(1000u64)).build()); + .execute_returns(U256::from(1000u64)); }); } #[test] fn get_balances_known_user() { ExtBuilder::default() - .with_balances(vec![(Account::Alice, 1000)]) + .with_balances(vec![(CryptoAlith.into(), 1000)]) .build() .execute_with(|| { assert_ok!(Assets::force_create( RuntimeOrigin::root(), 0u128, - Account::Alice.into(), + CryptoAlith.into(), true, 1 )); assert_ok!(Assets::mint( - RuntimeOrigin::signed(Account::Alice), + RuntimeOrigin::signed(CryptoAlith.into()), 0u128, - Account::Alice.into(), + CryptoAlith.into(), 1000 )); precompiles() .prepare_test( - Account::Alice, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::BalanceOf) - .write(Address(Account::Alice.into())) - .build(), + CryptoAlith, + LocalAssetId(0u128), + PrecompileCall::balance_of { + who: Address(CryptoAlith.into()), + }, ) .expect_cost(0) // TODO: Test db read/write costs .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(U256::from(1000u64)).build()); + .execute_returns(U256::from(1000u64)); }); } #[test] fn get_balances_unknown_user() { ExtBuilder::default() - .with_balances(vec![(Account::Alice, 1000)]) + .with_balances(vec![(CryptoAlith.into(), 1000)]) .build() .execute_with(|| { assert_ok!(Assets::force_create( RuntimeOrigin::root(), 0u128, - Account::Alice.into(), + CryptoAlith.into(), true, 1 )); precompiles() .prepare_test( - Account::Alice, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::BalanceOf) - .write(Address(Account::Bob.into())) - .build(), + CryptoAlith, + LocalAssetId(0u128), + PrecompileCall::balance_of { + who: Address(Bob.into()), + }, ) .expect_cost(0) // TODO: Test db read/write costs .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(U256::from(0u64)).build()); + .execute_returns(U256::from(0u64)); }); } @@ -211,30 +239,37 @@ fn mint_is_ok() { assert_ok!(Assets::force_create( RuntimeOrigin::root(), asset_id, - Account::Alice.into(), + CryptoAlith.into(), true, 1, )); // Sanity check, Bob should be without assets - assert!(Assets::balance(asset_id, &Account::Bob.into()).is_zero()); + assert!(Assets::balance(asset_id, &Bob.into()).is_zero()); // Mint some assets for Bob let mint_amount = 7 * 11 * 19; precompiles() .prepare_test( - Account::Alice, - Account::AssetId(asset_id), - EvmDataWriter::new_with_selector(Action::Mint) - .write(Address(Account::Bob.into())) - .write(U256::from(mint_amount)) - .build(), + CryptoAlith, + LocalAssetId(asset_id), + PrecompileCall::mint { + to: Address(Bob.into()), + value: mint_amount.into(), + }, ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .expect_cost(28770756) // 1 weight => 1 gas in mock + .expect_log(log3( + LocalAssetId(0u128), + SELECTOR_LOG_TRANSFER, + Zero, + Bob, + solidity::encode_event_data(U256::from(mint_amount)), + )) + .execute_returns(true); // Ensure Bob's asset balance was increased - assert_eq!(Assets::balance(asset_id, &Account::Bob.into()), mint_amount); + assert_eq!(Assets::balance(asset_id, &Bob.into()), mint_amount); }); } @@ -245,36 +280,36 @@ fn mint_non_admin_is_not_ok() { assert_ok!(Assets::force_create( RuntimeOrigin::root(), asset_id, - Account::Alice.into(), + CryptoAlith.into(), true, 1, )); precompiles() .prepare_test( - Account::Bob, - Account::AssetId(asset_id), - EvmDataWriter::new_with_selector(Action::Mint) - .write(Address(Account::Bob.into())) - .write(U256::from(42)) - .build(), + Bob, + LocalAssetId(asset_id), + PrecompileCall::mint { + to: Address(Bob.into()), + value: 42.into(), + }, ) .expect_no_logs() .execute_reverts(|output| from_utf8(&output).unwrap().contains("NoPermission")); precompiles() .prepare_test( - Account::Alice, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::Mint) - .write(Address(Account::Alice.into())) - .write(U256::from(1) << 128) - .build(), + CryptoAlith, + LocalAssetId(0u128), + PrecompileCall::mint { + to: Address(CryptoAlith.into()), + value: U256::from(1) << 128, + }, ) .execute_reverts(|output| { from_utf8(&output) .unwrap() - .contains("Error processing amount") + .contains("value: Value is too large for balance type") }); }); } @@ -286,7 +321,7 @@ fn burn_is_ok() { assert_ok!(Assets::force_create( RuntimeOrigin::root(), asset_id, - Account::Alice.into(), + CryptoAlith.into(), true, 1, )); @@ -294,30 +329,37 @@ fn burn_is_ok() { // Issue some initial assets for Bob let init_amount = 123; assert_ok!(Assets::mint( - RuntimeOrigin::signed(Account::Alice), + RuntimeOrigin::signed(CryptoAlith.into()), asset_id, - Account::Bob.into(), + Bob.into(), init_amount, )); - assert_eq!(Assets::balance(asset_id, &Account::Bob.into()), init_amount); + assert_eq!(Assets::balance(asset_id, &Bob.into()), init_amount); // Burn some assets from Bob let burn_amount = 19; precompiles() .prepare_test( - Account::Alice, - Account::AssetId(asset_id), - EvmDataWriter::new_with_selector(Action::Burn) - .write(Address(Account::Bob.into())) - .write(U256::from(burn_amount)) - .build(), + CryptoAlith, + LocalAssetId(asset_id), + PrecompileCall::burn { + from: Address(Bob.into()), + value: burn_amount.into(), + }, ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .expect_cost(34903756) // 1 weight => 1 gas in mock + .expect_log(log3( + LocalAssetId(0u128), + SELECTOR_LOG_TRANSFER, + Bob, + Zero, + solidity::encode_event_data(U256::from(burn_amount)), + )) + .execute_returns(true); // Ensure Bob's asset balance was decreased assert_eq!( - Assets::balance(asset_id, &Account::Bob.into()), + Assets::balance(asset_id, &Bob.into()), init_amount - burn_amount ); }); @@ -330,42 +372,42 @@ fn burn_non_admin_is_not_ok() { assert_ok!(Assets::force_create( RuntimeOrigin::root(), asset_id, - Account::Alice.into(), + CryptoAlith.into(), true, 1, )); assert_ok!(Assets::mint( - RuntimeOrigin::signed(Account::Alice), + RuntimeOrigin::signed(CryptoAlith.into()), asset_id, - Account::Bob.into(), + Bob.into(), 1000000, )); precompiles() .prepare_test( - Account::Bob, - Account::AssetId(asset_id), - EvmDataWriter::new_with_selector(Action::Burn) - .write(Address(Account::Bob.into())) - .write(U256::from(42)) - .build(), + Bob, + LocalAssetId(asset_id), + PrecompileCall::burn { + from: Address(Bob.into()), + value: 42.into(), + }, ) .expect_no_logs() .execute_reverts(|output| from_utf8(&output).unwrap().contains("NoPermission")); precompiles() .prepare_test( - Account::Alice, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::Burn) - .write(Address(Account::Alice.into())) - .write(U256::from(1) << 128) - .build(), + CryptoAlith, + LocalAssetId(0u128), + PrecompileCall::burn { + from: Address(CryptoAlith.into()), + value: U256::from(1) << 128, + }, ) .execute_reverts(|output| { from_utf8(&output) .unwrap() - .contains("Error processing amount") + .contains("Value is too large for balance type") }); }); } @@ -373,281 +415,284 @@ fn burn_non_admin_is_not_ok() { #[test] fn approve() { ExtBuilder::default() - .with_balances(vec![(Account::Alice, 1000)]) + .with_balances(vec![(CryptoAlith.into(), 1000)]) .build() .execute_with(|| { assert_ok!(Assets::force_create( RuntimeOrigin::root(), 0u128, - Account::Alice.into(), + CryptoAlith.into(), true, 1 )); assert_ok!(Assets::mint( - RuntimeOrigin::signed(Account::Alice), + RuntimeOrigin::signed(CryptoAlith.into()), 0u128, - Account::Alice.into(), + CryptoAlith.into(), 1000 )); precompiles() .prepare_test( - Account::Alice, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::Approve) - .write(Address(Account::Bob.into())) - .write(U256::from(500)) - .build(), + CryptoAlith, + LocalAssetId(0u128), + PrecompileCall::approve { + spender: Address(Bob.into()), + value: 500.into(), + }, ) - .expect_log(LogsBuilder::new(Account::AssetId(0u128).into()).log3( + .expect_log(log3( + LocalAssetId(0u128), SELECTOR_LOG_APPROVAL, - Account::Alice, - Account::Bob, - EvmDataWriter::new().write(U256::from(500)).build(), + CryptoAlith, + Bob, + solidity::encode_event_data(U256::from(500)), )) - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); }); } #[test] fn approve_saturating() { ExtBuilder::default() - .with_balances(vec![(Account::Alice, 1000)]) + .with_balances(vec![(CryptoAlith.into(), 1000)]) .build() .execute_with(|| { assert_ok!(Assets::force_create( RuntimeOrigin::root(), 0u128, - Account::Alice.into(), + CryptoAlith.into(), true, 1 )); assert_ok!(Assets::mint( - RuntimeOrigin::signed(Account::Alice), + RuntimeOrigin::signed(CryptoAlith.into()), 0u128, - Account::Alice.into(), + CryptoAlith.into(), 1000 )); precompiles() .prepare_test( - Account::Alice, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::Approve) - .write(Address(Account::Bob.into())) - .write(U256::MAX) - .build(), + CryptoAlith, + LocalAssetId(0u128), + PrecompileCall::approve { + spender: Address(Bob.into()), + value: U256::MAX, + }, ) - .expect_log(LogsBuilder::new(Account::AssetId(0u128).into()).log3( + .expect_log(log3( + LocalAssetId(0u128), SELECTOR_LOG_APPROVAL, - Account::Alice, - Account::Bob, - EvmDataWriter::new().write(U256::MAX).build(), + CryptoAlith, + Bob, + solidity::encode_event_data(U256::MAX), )) - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); precompiles() .prepare_test( - Account::Alice, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::Allowance) - .write(Address(Account::Alice.into())) - .write(Address(Account::Bob.into())) - .build(), + CryptoAlith, + LocalAssetId(0u128), + PrecompileCall::allowance { + owner: Address(CryptoAlith.into()), + spender: Address(Bob.into()), + }, ) .expect_cost(0u64) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(U256::from(u128::MAX)).build()); + .execute_returns(U256::from(u128::MAX)); }); } #[test] fn check_allowance_existing() { ExtBuilder::default() - .with_balances(vec![(Account::Alice, 1000)]) + .with_balances(vec![(CryptoAlith.into(), 1000)]) .build() .execute_with(|| { assert_ok!(Assets::force_create( RuntimeOrigin::root(), 0u128, - Account::Alice.into(), + CryptoAlith.into(), true, 1 )); assert_ok!(Assets::mint( - RuntimeOrigin::signed(Account::Alice), + RuntimeOrigin::signed(CryptoAlith.into()), 0u128, - Account::Alice.into(), + CryptoAlith.into(), 1000 )); precompiles() .prepare_test( - Account::Alice, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::Approve) - .write(Address(Account::Bob.into())) - .write(U256::from(500)) - .build(), + CryptoAlith, + LocalAssetId(0u128), + PrecompileCall::approve { + spender: Address(Bob.into()), + value: 500.into(), + }, ) .execute_some(); precompiles() .prepare_test( - Account::Alice, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::Allowance) - .write(Address(Account::Alice.into())) - .write(Address(Account::Bob.into())) - .build(), + CryptoAlith, + LocalAssetId(0u128), + PrecompileCall::allowance { + owner: Address(CryptoAlith.into()), + spender: Address(Bob.into()), + }, ) .expect_cost(0) // TODO: Test db read/write costs .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(U256::from(500u64)).build()); + .execute_returns(U256::from(500u64)); }); } #[test] fn check_allowance_not_existing() { ExtBuilder::default() - .with_balances(vec![(Account::Alice, 1000)]) + .with_balances(vec![(CryptoAlith.into(), 1000)]) .build() .execute_with(|| { assert_ok!(Assets::force_create( RuntimeOrigin::root(), 0u128, - Account::Alice.into(), + CryptoAlith.into(), true, 1 )); precompiles() .prepare_test( - Account::Alice, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::Allowance) - .write(Address(Account::Alice.into())) - .write(Address(Account::Bob.into())) - .build(), + CryptoAlith, + LocalAssetId(0u128), + PrecompileCall::allowance { + owner: Address(CryptoAlith.into()), + spender: Address(Bob.into()), + }, ) .expect_cost(0) // TODO: Test db read/write costs .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(U256::from(0u64)).build()); + .execute_returns(U256::from(0u64)); }); } #[test] fn transfer() { ExtBuilder::default() - .with_balances(vec![(Account::Alice, 1000)]) + .with_balances(vec![(CryptoAlith.into(), 1000)]) .build() .execute_with(|| { assert_ok!(Assets::force_create( RuntimeOrigin::root(), 0u128, - Account::Alice.into(), + CryptoAlith.into(), true, 1 )); assert_ok!(Assets::mint( - RuntimeOrigin::signed(Account::Alice), + RuntimeOrigin::signed(CryptoAlith.into()), 0u128, - Account::Alice.into(), + CryptoAlith.into(), 1000 )); precompiles() .prepare_test( - Account::Alice, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::Transfer) - .write(Address(Account::Bob.into())) - .write(U256::from(400)) - .build(), + CryptoAlith, + LocalAssetId(0u128), + PrecompileCall::transfer { + to: Address(Bob.into()), + value: 400.into(), + }, ) - .expect_log(LogsBuilder::new(Account::AssetId(0u128).into()).log3( + .expect_log(log3( + LocalAssetId(0u128), SELECTOR_LOG_TRANSFER, - Account::Alice, - Account::Bob, - EvmDataWriter::new().write(U256::from(400)).build(), + CryptoAlith, + Bob, + solidity::encode_event_data(U256::from(400)), )) - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); precompiles() .prepare_test( - Account::Bob, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::BalanceOf) - .write(Address(Account::Bob.into())) - .build(), + Bob, + LocalAssetId(0u128), + PrecompileCall::balance_of { + who: Address(Bob.into()), + }, ) .expect_cost(0) // TODO: Test db read/write costs .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(U256::from(400)).build()); + .execute_returns(U256::from(400)); precompiles() .prepare_test( - Account::Alice, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::BalanceOf) - .write(Address(Account::Alice.into())) - .build(), + CryptoAlith, + LocalAssetId(0u128), + PrecompileCall::balance_of { + who: Address(CryptoAlith.into()), + }, ) .expect_cost(0) // TODO: Test db read/write costs .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(U256::from(600)).build()); + .execute_returns(U256::from(600)); }); } #[test] fn transfer_not_enough_founds() { ExtBuilder::default() - .with_balances(vec![(Account::Alice, 1000)]) + .with_balances(vec![(CryptoAlith.into(), 1000)]) .build() .execute_with(|| { assert_ok!(Assets::force_create( RuntimeOrigin::root(), 0u128, - Account::Alice.into(), + CryptoAlith.into(), true, 1 )); assert_ok!(Assets::mint( - RuntimeOrigin::signed(Account::Alice), + RuntimeOrigin::signed(CryptoAlith.into()), 0u128, - Account::Alice.into(), + CryptoAlith.into(), 1 )); precompiles() .prepare_test( - Account::Alice, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::Transfer) - .write(Address(Account::Charlie.into())) - .write(U256::from(50)) - .build(), + CryptoAlith, + LocalAssetId(0u128), + PrecompileCall::transfer { + to: Address(Charlie.into()), + value: 50.into(), + }, ) .execute_reverts(|output| { from_utf8(&output) .unwrap() - .contains("Dispatched call failed with error: DispatchErrorWithPostInfo") + .contains("Dispatched call failed with error: Module(ModuleError") && from_utf8(&output).unwrap().contains("BalanceLow") }); precompiles() .prepare_test( - Account::Alice, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::Transfer) - .write(Address(Account::Charlie.into())) - .write(U256::from(1) << 128) - .build(), + CryptoAlith, + LocalAssetId(0u128), + PrecompileCall::transfer { + to: Address(Charlie.into()), + value: U256::from(1) << 128, + }, ) .execute_reverts(|output| { from_utf8(&output) .unwrap() - .contains("Error processing amount") + .contains("Value is too large for balance type") }); }); } @@ -655,138 +700,140 @@ fn transfer_not_enough_founds() { #[test] fn transfer_from() { ExtBuilder::default() - .with_balances(vec![(Account::Alice, 1000)]) + .with_balances(vec![(CryptoAlith.into(), 1000)]) .build() .execute_with(|| { assert_ok!(Assets::force_create( RuntimeOrigin::root(), 0u128, - Account::Alice.into(), + CryptoAlith.into(), true, 1 )); assert_ok!(Assets::mint( - RuntimeOrigin::signed(Account::Alice), + RuntimeOrigin::signed(CryptoAlith.into()), 0u128, - Account::Alice.into(), + CryptoAlith.into(), 1000 )); precompiles() .prepare_test( - Account::Alice, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::Approve) - .write(Address(Account::Bob.into())) - .write(U256::from(500)) - .build(), + CryptoAlith, + LocalAssetId(0u128), + PrecompileCall::approve { + spender: Address(Bob.into()), + value: 500.into(), + }, ) .execute_some(); precompiles() .prepare_test( - Account::Alice, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::Approve) - .write(Address(Account::Bob.into())) - .write(U256::from(500)) - .build(), + CryptoAlith, + LocalAssetId(0u128), + PrecompileCall::approve { + spender: Address(Bob.into()), + value: 500.into(), + }, ) .execute_some(); precompiles() .prepare_test( - Account::Bob, // Bob is the one sending transferFrom! - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::TransferFrom) - .write(Address(Account::Alice.into())) - .write(Address(Account::Charlie.into())) - .write(U256::from(400)) - .build(), + Bob, // Bob is the one sending transferFrom! + LocalAssetId(0u128), + PrecompileCall::transfer_from { + from: Address(CryptoAlith.into()), + to: Address(Charlie.into()), + value: 400.into(), + }, ) - .expect_log(LogsBuilder::new(Account::AssetId(0u128).into()).log3( + .expect_log(log3( + LocalAssetId(0u128), SELECTOR_LOG_TRANSFER, - Account::Alice, - Account::Charlie, - EvmDataWriter::new().write(U256::from(400)).build(), + CryptoAlith, + Charlie, + solidity::encode_event_data(U256::from(400)), )) - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); precompiles() .prepare_test( - Account::Alice, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::BalanceOf) - .write(Address(Account::Alice.into())) - .build(), + CryptoAlith, + LocalAssetId(0u128), + PrecompileCall::balance_of { + who: Address(CryptoAlith.into()), + }, ) .expect_cost(0) // TODO: Test db read/write costs .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(U256::from(600)).build()); + .execute_returns(U256::from(600)); precompiles() .prepare_test( - Account::Bob, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::BalanceOf) - .write(Address(Account::Bob.into())) - .build(), + Bob, + LocalAssetId(0u128), + PrecompileCall::balance_of { + who: Address(Bob.into()), + }, ) .expect_cost(0) // TODO: Test db read/write costs .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(U256::from(0)).build()); + .execute_returns(U256::from(0)); precompiles() .prepare_test( - Account::Charlie, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::BalanceOf) - .write(Address(Account::Charlie.into())) - .build(), + Charlie, + LocalAssetId(0u128), + PrecompileCall::balance_of { + who: Address(Charlie.into()), + }, ) .expect_cost(0) // TODO: Test db read/write costs .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(U256::from(400)).build()); + .execute_returns(U256::from(400)); }); } #[test] fn transfer_from_non_incremental_approval() { ExtBuilder::default() - .with_balances(vec![(Account::Alice, 1000)]) + .with_balances(vec![(CryptoAlith.into(), 1000)]) .build() .execute_with(|| { assert_ok!(Assets::force_create( RuntimeOrigin::root(), 0u128, - Account::Alice.into(), + CryptoAlith.into(), true, 1 )); assert_ok!(Assets::mint( - RuntimeOrigin::signed(Account::Alice), + RuntimeOrigin::signed(CryptoAlith.into()), 0u128, - Account::Alice.into(), + CryptoAlith.into(), 1000 )); // We first approve 500 precompiles() .prepare_test( - Account::Alice, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::Approve) - .write(Address(Account::Bob.into())) - .write(U256::from(500)) - .build(), + CryptoAlith, + LocalAssetId(0u128), + PrecompileCall::approve { + spender: Address(Bob.into()), + value: 500.into(), + }, ) - .expect_log(LogsBuilder::new(Account::AssetId(0u128).into()).log3( + .expect_log(log3( + LocalAssetId(0u128), SELECTOR_LOG_APPROVAL, - Account::Alice, - Account::Bob, - EvmDataWriter::new().write(U256::from(500)).build(), + CryptoAlith, + Bob, + solidity::encode_event_data(U256::from(500)), )) - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); // We then approve 300. Non-incremental, so this is // the approved new value @@ -794,38 +841,37 @@ fn transfer_from_non_incremental_approval() { // need to clear the previous one precompiles() .prepare_test( - Account::Alice, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::Approve) - .write(Address(Account::Bob.into())) - .write(U256::from(300)) - .build(), + CryptoAlith, + LocalAssetId(0u128), + PrecompileCall::approve { + spender: Address(Bob.into()), + value: 300.into(), + }, ) - .expect_log(LogsBuilder::new(Account::AssetId(0u128).into()).log3( + .expect_log(log3( + LocalAssetId(0u128), SELECTOR_LOG_APPROVAL, - Account::Alice, - Account::Bob, - EvmDataWriter::new().write(U256::from(300)).build(), + CryptoAlith, + Bob, + solidity::encode_event_data(U256::from(300)), )) - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); // This should fail, as now the new approved quantity is 300 precompiles() .prepare_test( - Account::Bob, // Bob is the one sending transferFrom! - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::TransferFrom) - .write(Address(Account::Alice.into())) - .write(Address(Account::Bob.into())) - .write(U256::from(500)) - .build(), + Bob, // Bob is the one sending transferFrom! + LocalAssetId(0u128), + PrecompileCall::transfer_from { + from: Address(CryptoAlith.into()), + to: Address(Bob.into()), + value: 500.into(), + }, ) .execute_reverts(|output| { output - == b"Dispatched call failed with error: DispatchErrorWithPostInfo { \ - post_info: PostDispatchInfo { actual_weight: None, pays_fee: Pays::Yes }, \ - error: Module(ModuleError { index: 2, error: [10, 0, 0, 0], \ - message: Some(\"Unapproved\") }) }" + == b"Dispatched call failed with error: Module(ModuleError \ + { index: 2, error: [10, 0, 0, 0], message: Some(\"Unapproved\") })" }); }); } @@ -833,66 +879,64 @@ fn transfer_from_non_incremental_approval() { #[test] fn transfer_from_above_allowance() { ExtBuilder::default() - .with_balances(vec![(Account::Alice, 1000)]) + .with_balances(vec![(CryptoAlith.into(), 1000)]) .build() .execute_with(|| { assert_ok!(Assets::force_create( RuntimeOrigin::root(), 0u128, - Account::Alice.into(), + CryptoAlith.into(), true, 1 )); assert_ok!(Assets::mint( - RuntimeOrigin::signed(Account::Alice), + RuntimeOrigin::signed(CryptoAlith.into()), 0u128, - Account::Alice.into(), + CryptoAlith.into(), 1000 )); precompiles() .prepare_test( - Account::Alice, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::Approve) - .write(Address(Account::Bob.into())) - .write(U256::from(300)) - .build(), + CryptoAlith, + LocalAssetId(0u128), + PrecompileCall::approve { + spender: Address(Bob.into()), + value: 300.into(), + }, ) .execute_some(); precompiles() .prepare_test( - Account::Bob, // Bob is the one sending transferFrom! - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::TransferFrom) - .write(Address(Account::Alice.into())) - .write(Address(Account::Bob.into())) - .write(U256::from(400)) - .build(), + Bob, // Bob is the one sending transferFrom! + LocalAssetId(0u128), + PrecompileCall::transfer_from { + from: Address(CryptoAlith.into()), + to: Address(Bob.into()), + value: 400.into(), + }, ) .execute_reverts(|output| { output - == b"Dispatched call failed with error: DispatchErrorWithPostInfo { \ - post_info: PostDispatchInfo { actual_weight: None, pays_fee: Pays::Yes }, \ - error: Module(ModuleError { index: 2, error: [10, 0, 0, 0], \ - message: Some(\"Unapproved\") }) }" + == b"Dispatched call failed with error: Module(ModuleError \ + { index: 2, error: [10, 0, 0, 0], message: Some(\"Unapproved\") })" }); precompiles() .prepare_test( - Account::Alice, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::TransferFrom) - .write(Address(Account::Alice.into())) - .write(Address(Account::Bob.into())) - .write(U256::from(1) << 128) - .build(), + CryptoAlith, + LocalAssetId(0u128), + PrecompileCall::transfer_from { + from: Address(CryptoAlith.into()), + to: Address(Bob.into()), + value: U256::from(1) << 128, + }, ) .execute_reverts(|output| { from_utf8(&output) .unwrap() - .contains("Error processing amount") + .contains("Value is too large for balance type") }); }); } @@ -900,77 +944,78 @@ fn transfer_from_above_allowance() { #[test] fn transfer_from_self() { ExtBuilder::default() - .with_balances(vec![(Account::Alice, 1000)]) + .with_balances(vec![(CryptoAlith.into(), 1000)]) .build() .execute_with(|| { assert_ok!(Assets::force_create( RuntimeOrigin::root(), 0u128, - Account::Alice.into(), + CryptoAlith.into(), true, 1 )); assert_ok!(Assets::mint( - RuntimeOrigin::signed(Account::Alice), + RuntimeOrigin::signed(CryptoAlith.into()), 0u128, - Account::Alice.into(), + CryptoAlith.into(), 1000 )); precompiles() .prepare_test( - Account::Alice, // Alice sending transferFrom herself, no need for allowance. - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::TransferFrom) - .write(Address(Account::Alice.into())) - .write(Address(Account::Bob.into())) - .write(U256::from(400)) - .build(), + CryptoAlith, // Alice sending transferFrom herself, no need for allowance. + LocalAssetId(0u128), + PrecompileCall::transfer_from { + from: Address(CryptoAlith.into()), + to: Address(Bob.into()), + value: 400.into(), + }, ) - .expect_log(LogsBuilder::new(Account::AssetId(0u128).into()).log3( + .expect_log(log3( + LocalAssetId(0u128), SELECTOR_LOG_TRANSFER, - Account::Alice, - Account::Bob, - EvmDataWriter::new().write(U256::from(400)).build(), + CryptoAlith, + Bob, + solidity::encode_event_data(U256::from(400)), )) - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); precompiles() .prepare_test( - Account::Alice, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::BalanceOf) - .write(Address(Account::Alice.into())) - .build(), + CryptoAlith, + LocalAssetId(0u128), + PrecompileCall::balance_of { + who: Address(CryptoAlith.into()), + }, ) .expect_cost(0) // TODO: Test db read/write costs .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(U256::from(600)).build()); + .execute_returns(U256::from(600)); precompiles() .prepare_test( - Account::Alice, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::BalanceOf) - .write(Address(Account::Bob.into())) - .build(), + CryptoAlith, + LocalAssetId(0u128), + PrecompileCall::balance_of { + who: Address(Bob.into()), + }, ) .expect_cost(0) // TODO: Test db read/write costs .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(U256::from(400)).build()); + .execute_returns(U256::from(400)); }); } #[test] fn get_metadata() { ExtBuilder::default() - .with_balances(vec![(Account::Alice, 1000), (Account::Bob, 2500)]) + .with_balances(vec![(CryptoAlith.into(), 1000), (Bob.into(), 2500)]) .build() .execute_with(|| { assert_ok!(Assets::force_create( RuntimeOrigin::root(), 0u128, - Account::Alice.into(), + CryptoAlith.into(), true, 1 )); @@ -984,38 +1029,26 @@ fn get_metadata() { )); precompiles() - .prepare_test( - Account::Alice, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::Name).build(), - ) + .prepare_test(CryptoAlith, LocalAssetId(0u128), PrecompileCall::name {}) .expect_cost(0) // TODO: Test db read/write costs .expect_no_logs() - .execute_returns( - EvmDataWriter::new() - .write::("TestToken".into()) - .build(), - ); + .execute_returns(UnboundedBytes::from("TestToken")); precompiles() - .prepare_test( - Account::Alice, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::Symbol).build(), - ) + .prepare_test(CryptoAlith, LocalAssetId(0u128), PrecompileCall::symbol {}) .expect_cost(0) // TODO: Test db read/write costs .expect_no_logs() - .execute_returns(EvmDataWriter::new().write::("Test".into()).build()); + .execute_returns(UnboundedBytes::from("Test")); precompiles() .prepare_test( - Account::Alice, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::Decimals).build(), + CryptoAlith, + LocalAssetId(0u128), + PrecompileCall::decimals {}, ) .expect_cost(0) // TODO: Test db read/write costs .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(12u8).build()); + .execute_returns(12u8); }); } @@ -1026,19 +1059,19 @@ fn minimum_balance_is_right() { assert_ok!(Assets::force_create( RuntimeOrigin::root(), 0u128, - Account::Alice.into(), + CryptoAlith.into(), true, expected_min_balance, )); precompiles() .prepare_test( - Account::Alice, - Account::AssetId(0u128), - EvmDataWriter::new_with_selector(Action::MinimumBalance).build(), + CryptoAlith, + LocalAssetId(0u128), + PrecompileCall::minimum_balance {}, ) .expect_cost(0) // TODO: Test db read/write costs .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(expected_min_balance).build()); + .execute_returns(U256::from(expected_min_balance)); }); } diff --git a/precompiles/dapps-staking/DappsStaking.sol b/precompiles/dapps-staking/DappsStaking.sol index b6967d9247..83b5af5f6d 100644 --- a/precompiles/dapps-staking/DappsStaking.sol +++ b/precompiles/dapps-staking/DappsStaking.sol @@ -47,27 +47,27 @@ interface DappsStaking { /// @notice Register is root origin only and not allowed via evm precompile. /// This should always fail. - function register(address) external; + function register(address) external returns (bool); /// @notice Stake provided amount on the contract. - function bond_and_stake(address, uint128) external; + function bond_and_stake(address, uint128) external returns (bool); /// @notice Start unbonding process and unstake balance from the contract. - function unbond_and_unstake(address, uint128) external; + function unbond_and_unstake(address, uint128) external returns (bool); /// @notice Withdraw all funds that have completed the unbonding process. - function withdraw_unbonded() external; + function withdraw_unbonded() external returns (bool); /// @notice Claim earned staker rewards for the oldest unclaimed era. /// In order to claim multiple eras, this call has to be called multiple times. /// Staker account is derived from the caller address. /// @param smart_contract: The smart contract address used for staking - function claim_staker(address smart_contract) external; + function claim_staker(address smart_contract) external returns (bool); /// @notice Claim one era of unclaimed dapp rewards for the specified contract and era. /// @param smart_contract: The smart contract address used for staking /// @param era: The era to be claimed - function claim_dapp(address smart_contract, uint128 era) external; + function claim_dapp(address smart_contract, uint128 era) external returns (bool); /// Instruction how to handle reward payout for staker. /// `FreeBalance` - Reward will be paid out to the staker (free balance). @@ -76,15 +76,15 @@ interface DappsStaking { /// @notice Set reward destination for staker rewards /// @param reward_destination: The instruction on how the reward payout should be handled - function set_reward_destination(RewardDestination reward_destination) external; + function set_reward_destination(RewardDestination reward_destination) external returns (bool); /// @notice Withdraw staked funds from an unregistered contract. /// @param smart_contract: The smart contract address used for staking - function withdraw_from_unregistered(address smart_contract) external; + function withdraw_from_unregistered(address smart_contract) external returns (bool); /// @notice Transfer part or entire nomination from origin smart contract to target smart contract /// @param origin_smart_contract: The origin smart contract address /// @param amount: The amount to transfer from origin to target /// @param target_smart_contract: The target smart contract address - function nomination_transfer(address origin_smart_contract, uint128 amount, address target_smart_contract) external; + function nomination_transfer(address origin_smart_contract, uint128 amount, address target_smart_contract) external returns (bool); } diff --git a/precompiles/dapps-staking/src/lib.rs b/precompiles/dapps-staking/src/lib.rs index c6a371d53a..a408e1c4a5 100644 --- a/precompiles/dapps-staking/src/lib.rs +++ b/precompiles/dapps-staking/src/lib.rs @@ -20,7 +20,7 @@ #![cfg_attr(not(feature = "std"), no_std)] -use fp_evm::{PrecompileHandle, PrecompileOutput}; +use fp_evm::PrecompileHandle; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ @@ -28,12 +28,9 @@ use frame_support::{ traits::{Currency, Get}, }; use pallet_dapps_staking::RewardDestination; -use pallet_evm::{AddressMapping, Precompile}; -use precompile_utils::{ - error, revert, succeed, Address, Bytes, EvmData, EvmDataWriter, EvmResult, FunctionModifier, - PrecompileHandleExt, RuntimeHelper, -}; -use sp_core::H160; +use pallet_evm::AddressMapping; +use precompile_utils::prelude::*; +use sp_core::{ConstU32, H160, U256}; use sp_runtime::traits::Zero; use sp_std::marker::PhantomData; use sp_std::prelude::*; @@ -43,6 +40,9 @@ type BalanceOf = <::Currency a ::AccountId, >>::Balance; +pub const STAKER_BYTES_LIMIT: u32 = 32; +type GetStakerBytesLimit = ConstU32; + #[cfg(test)] mod mock; #[cfg(test)] @@ -59,105 +59,105 @@ pub enum Contract { pub struct DappsStakingWrapper(PhantomData); +#[precompile_utils::precompile] impl DappsStakingWrapper where R: pallet_evm::Config + pallet_dapps_staking::Config, - BalanceOf: EvmData, + BalanceOf: solidity::Codec, ::RuntimeOrigin: From>, R::RuntimeCall: Dispatchable + GetDispatchInfo, R::RuntimeCall: From>, R::AccountId: From<[u8; 32]>, { /// Fetch current era from CurrentEra storage map - fn read_current_era(handle: &mut impl PrecompileHandle) -> EvmResult { + #[precompile::public("read_current_era()")] + #[precompile::view] + fn read_current_era(handle: &mut impl PrecompileHandle) -> EvmResult { // TODO: benchmark this function so we can measure ref time & PoV correctly // Storage item: CurrentEra: // Twox64(8) + EraIndex(4) handle.record_db_read::(12)?; let current_era = pallet_dapps_staking::CurrentEra::::get(); - - Ok(succeed(EvmDataWriter::new().write(current_era).build())) + Ok(current_era.into()) } /// Fetch unbonding period - fn read_unbonding_period(_: &impl PrecompileHandle) -> EvmResult { + #[precompile::public("read_unbonding_period()")] + #[precompile::view] + fn read_unbonding_period(_: &mut impl PrecompileHandle) -> EvmResult { // constant, no DB read let unbonding_period = R::UnbondingPeriod::get(); - Ok(succeed( - EvmDataWriter::new().write(unbonding_period).build(), - )) + Ok(unbonding_period.into()) } /// Fetch reward from EraRewardsAndStakes storage map - fn read_era_reward(handle: &mut impl PrecompileHandle) -> EvmResult { + #[precompile::public("read_era_reward(uint32)")] + #[precompile::view] + fn read_era_reward(handle: &mut impl PrecompileHandle, era: u32) -> EvmResult { // TODO: benchmark this function so we can measure ref time & PoV correctly // Storage item: GeneralEraInfo: // Twox64Concat(8) + EraIndex(4) + EraInfo::max_encoded_len handle.record_db_read::(12 + pallet_dapps_staking::EraInfo::max_encoded_len())?; - let mut input = handle.read_input()?; - input.expect_arguments(1)?; - - // parse input parameters for pallet-dapps-staking call - let era: u32 = input.read::()?; - // call pallet-dapps-staking let read_reward = pallet_dapps_staking::GeneralEraInfo::::get(era); let reward = read_reward.map_or(Zero::zero(), |r| { r.rewards.stakers.saturating_add(r.rewards.dapps) }); - Ok(succeed(EvmDataWriter::new().write(reward).build())) + Ok(reward.into()) } /// Fetch total staked amount from EraRewardsAndStakes storage map - fn read_era_staked(handle: &mut impl PrecompileHandle) -> EvmResult { + #[precompile::public("read_era_staked(uint32)")] + #[precompile::view] + fn read_era_staked(handle: &mut impl PrecompileHandle, era: u32) -> EvmResult { // TODO: benchmark this function so we can measure ref time & PoV correctly // Storage item: GeneralEraInfo: // Twox64Concat(8) + EraIndex(4) + EraInfo::max_encoded_len handle.record_db_read::(12 + pallet_dapps_staking::EraInfo::max_encoded_len())?; - // parse input parameters for pallet-dapps-staking call - let mut input = handle.read_input()?; - input.expect_arguments(1)?; - let era: u32 = input.read::()?; - // call pallet-dapps-staking let reward_and_stake = pallet_dapps_staking::GeneralEraInfo::::get(era); // compose output let staked = reward_and_stake.map_or(Zero::zero(), |r| r.staked); let staked = TryInto::::try_into(staked).unwrap_or(0); - Ok(succeed(EvmDataWriter::new().write(staked).build())) + Ok(staked.into()) } /// Fetch Ledger storage map for an account - fn read_staked_amount(handle: &mut impl PrecompileHandle) -> EvmResult { + #[precompile::public("read_staked_amount(bytes)")] + #[precompile::view] + fn read_staked_amount( + handle: &mut impl PrecompileHandle, + staker: BoundedBytes, + ) -> EvmResult { // TODO: benchmark this function so we can measure ref time & PoV correctly // Storage item: Ledger: // Blake2_128Concat(16 + 32) + Ledger::max_encoded_len handle.record_db_read::(48 + pallet_dapps_staking::AccountLedger::max_encoded_len())?; - let mut input = handle.read_input()?; - input.expect_arguments(1)?; - // parse input parameters for pallet-dapps-staking call - let staker_vec: Vec = input.read::()?.into(); - let staker = Self::parse_input_address(staker_vec)?; + let staker = Self::parse_input_address(staker.into())?; // call pallet-dapps-staking let ledger = pallet_dapps_staking::Ledger::::get(&staker); log::trace!(target: "ds-precompile", "read_staked_amount for account:{:?}, ledger.locked:{:?}", staker, ledger.locked); - Ok(succeed(EvmDataWriter::new().write(ledger.locked).build())) + Ok(ledger.locked.into()) } /// Read GeneralStakerInfo for account/contract + #[precompile::public("read_staked_amount_on_contract(address,bytes)")] + #[precompile::view] fn read_staked_amount_on_contract( handle: &mut impl PrecompileHandle, - ) -> EvmResult { + contract_h160: Address, + staker: BoundedBytes, + ) -> EvmResult { // TODO: benchmark this function so we can measure ref time & PoV correctly // Storage item: GeneralStakerInfo: // Blake2_128Concat(16 + 32) + Blake2_128Concat(16 + SmartContract::max_encoded_len) + StakerInfo::max_encoded_len @@ -166,27 +166,27 @@ where + pallet_dapps_staking::StakerInfo::max_encoded_len(), )?; - let mut input = handle.read_input()?; - input.expect_arguments(2)?; - // parse contract address - let contract_h160 = input.read::
()?.0; - let contract_id = Self::decode_smart_contract(contract_h160)?; + let contract_id = Self::decode_smart_contract(contract_h160.into())?; // parse input parameters for pallet-dapps-staking call - let staker_vec: Vec = input.read::()?.into(); - let staker = Self::parse_input_address(staker_vec)?; + let staker = Self::parse_input_address(staker.into())?; // call pallet-dapps-staking let staking_info = pallet_dapps_staking::GeneralStakerInfo::::get(&staker, &contract_id); let staked_amount = staking_info.latest_staked_value(); log::trace!(target: "ds-precompile", "read_staked_amount_on_contract for account:{:?}, contract: {:?} => staked_amount:{:?}", staker, contract_id, staked_amount); - Ok(succeed(EvmDataWriter::new().write(staked_amount).build())) + Ok(staked_amount.into()) } /// Read the amount staked on contract in the given era - fn read_contract_stake(handle: &mut impl PrecompileHandle) -> EvmResult { + #[precompile::public("read_contract_stake(address)")] + #[precompile::view] + fn read_contract_stake( + handle: &mut impl PrecompileHandle, + contract_h160: Address, + ) -> EvmResult { // TODO: benchmark this function so we can measure ref time & PoV correctly // Storage item: CurrentEra: // Twox64(8) + EraIndex(4) @@ -199,12 +199,7 @@ where + pallet_dapps_staking::ContractStakeInfo::max_encoded_len(), )?; - let mut input = handle.read_input()?; - input.expect_arguments(1)?; - - // parse input parameters for pallet-dapps-staking call - let contract_h160 = input.read::
()?.0; - let contract_id = Self::decode_smart_contract(contract_h160)?; + let contract_id = Self::decode_smart_contract(contract_h160.into())?; let current_era = pallet_dapps_staking::CurrentEra::::get(); // call pallet-dapps-staking @@ -214,28 +209,27 @@ where // encode output with total let total = TryInto::::try_into(staking_info.total).unwrap_or(0); - - Ok(succeed(EvmDataWriter::new().write(total).build())) + log::trace!(target: "ds-precompile", "read_contract_stake for contract: {:?} => staked_amount:{:?}", contract_id, total); + Ok(total.into()) } /// Register contract with the dapp-staking pallet /// Register is root origin only. This should always fail when called via evm precompile. - fn register(_: &mut impl PrecompileHandle) -> EvmResult { + #[precompile::public("register(address)")] + fn register(_: &mut impl PrecompileHandle, _address: Address) -> EvmResult { // register is root-origin call. it should always fail when called via evm precompiles. - Err(error("register via evm precompile is not allowed")) + Err(RevertReason::custom("register via evm precompile is not allowed").into()) } /// Lock up and stake balance of the origin account. - fn bond_and_stake(handle: &mut impl PrecompileHandle) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(2)?; - + #[precompile::public("bond_and_stake(address,uint128)")] + fn bond_and_stake( + handle: &mut impl PrecompileHandle, + contract_h160: Address, + value: u128, + ) -> EvmResult { // parse contract's address - let contract_h160 = input.read::
()?.0; - let contract_id = Self::decode_smart_contract(contract_h160)?; - - // parse balance to be staked - let value: BalanceOf = input.read()?; + let contract_id = Self::decode_smart_contract(contract_h160.into())?; log::trace!(target: "ds-precompile", "bond_and_stake {:?}, {:?}", contract_id, value); @@ -245,20 +239,19 @@ where RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - Ok(succeed(EvmDataWriter::new().write(true).build())) + Ok(true) } /// Start unbonding process and unstake balance from the contract. - fn unbond_and_unstake(handle: &mut impl PrecompileHandle) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(2)?; - + #[precompile::public("unbond_and_unstake(address,uint128)")] + fn unbond_and_unstake( + handle: &mut impl PrecompileHandle, + contract_h160: Address, + value: u128, + ) -> EvmResult { // parse contract's address - let contract_h160 = input.read::
()?.0; - let contract_id = Self::decode_smart_contract(contract_h160)?; + let contract_id = Self::decode_smart_contract(contract_h160.into())?; - // parse balance to be unstaked - let value: BalanceOf = input.read()?; log::trace!(target: "ds-precompile", "unbond_and_unstake {:?}, {:?}", contract_id, value); // Build call with origin. @@ -267,31 +260,37 @@ where RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - Ok(succeed(EvmDataWriter::new().write(true).build())) + Ok(true) } /// Start unbonding process and unstake balance from the contract. - fn withdraw_unbonded(handle: &mut impl PrecompileHandle) -> EvmResult { + #[precompile::public("withdraw_unbonded()")] + fn withdraw_unbonded(handle: &mut impl PrecompileHandle) -> EvmResult { // Build call with origin. let origin = R::AddressMapping::into_account_id(handle.context().caller); let call = pallet_dapps_staking::Call::::withdraw_unbonded {}; RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - Ok(succeed(EvmDataWriter::new().write(true).build())) + Ok(true) } /// Claim rewards for the contract in the dapps-staking pallet - fn claim_dapp(handle: &mut impl PrecompileHandle) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(2)?; - + #[precompile::public("claim_dapp(address,uint128)")] + fn claim_dapp( + handle: &mut impl PrecompileHandle, + contract_h160: Address, + era: u128, + ) -> EvmResult { // parse contract's address - let contract_h160 = input.read::
()?.0; - let contract_id = Self::decode_smart_contract(contract_h160)?; + let contract_id = Self::decode_smart_contract(contract_h160.into())?; // parse era - let era: u32 = input.read::()?; + let era = era + .try_into() + .map_err::(|_| RevertReason::value_is_too_large("era type").into()) + .in_field("era")?; + log::trace!(target: "ds-precompile", "claim_dapp {:?}, era {:?}", contract_id, era); // Build call with origin. @@ -300,17 +299,14 @@ where RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - Ok(succeed(EvmDataWriter::new().write(true).build())) + Ok(true) } /// Claim rewards for the contract in the dapps-staking pallet - fn claim_staker(handle: &mut impl PrecompileHandle) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(1)?; - + #[precompile::public("claim_staker(address)")] + fn claim_staker(handle: &mut impl PrecompileHandle, contract_h160: Address) -> EvmResult { // parse contract's address - let contract_h160 = input.read::
()?.0; - let contract_id = Self::decode_smart_contract(contract_h160)?; + let contract_id = Self::decode_smart_contract(contract_h160.into())?; log::trace!(target: "ds-precompile", "claim_staker {:?}", contract_id); // Build call with origin. @@ -319,24 +315,22 @@ where RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - Ok(succeed(EvmDataWriter::new().write(true).build())) + Ok(true) } /// Set claim reward destination for the caller - fn set_reward_destination(handle: &mut impl PrecompileHandle) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(1)?; - - // raw solidity representation of enum - let reward_destination_raw = input.read::()?; - + #[precompile::public("set_reward_destination(uint8)")] + fn set_reward_destination( + handle: &mut impl PrecompileHandle, + reward_destination_raw: u8, + ) -> EvmResult { // Transform raw value into dapps staking enum let reward_destination = if reward_destination_raw == 0 { RewardDestination::FreeBalance } else if reward_destination_raw == 1 { RewardDestination::StakeBalance } else { - return Err(error("Unexpected reward destination value.")); + return Err(RevertReason::custom("Unexpected reward destination value.").into()); }; // Build call with origin. @@ -347,18 +341,17 @@ where RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - Ok(succeed(EvmDataWriter::new().write(true).build())) + Ok(true) } + /// Withdraw staked funds from the unregistered contract + #[precompile::public("withdraw_from_unregistered(address)")] fn withdraw_from_unregistered( handle: &mut impl PrecompileHandle, - ) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(1)?; - + contract_h160: Address, + ) -> EvmResult { // parse contract's address - let contract_h160 = input.read::
()?.0; - let contract_id = Self::decode_smart_contract(contract_h160)?; + let contract_id = Self::decode_smart_contract(contract_h160.into())?; log::trace!(target: "ds-precompile", "withdraw_from_unregistered {:?}", contract_id); // Build call with origin. @@ -367,24 +360,22 @@ where RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - Ok(succeed(EvmDataWriter::new().write(true).build())) + Ok(true) } /// Claim rewards for the contract in the dapps-staking pallet - fn nomination_transfer(handle: &mut impl PrecompileHandle) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(3)?; - + #[precompile::public("nomination_transfer(address,uint128,address)")] + fn nomination_transfer( + handle: &mut impl PrecompileHandle, + origin_contract_h160: Address, + value: u128, + target_contract_h160: Address, + ) -> EvmResult { // parse origin contract's address - let origin_contract_h160 = input.read::
()?.0; - let origin_contract_id = Self::decode_smart_contract(origin_contract_h160)?; - - // parse balance to be transferred - let value = input.read::>()?; + let origin_contract_id = Self::decode_smart_contract(origin_contract_h160.into())?; // parse target contract's address - let target_contract_h160 = input.read::
()?.0; - let target_contract_id = Self::decode_smart_contract(target_contract_h160)?; + let target_contract_id = Self::decode_smart_contract(target_contract_h160.into())?; log::trace!(target: "ds-precompile", "nomination_transfer {:?} {:?} {:?}", origin_contract_id, value, target_contract_id); @@ -398,7 +389,7 @@ where RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - Ok(succeed(EvmDataWriter::new().write(true).build())) + Ok(true) } /// Helper method to decode type SmartContract enum @@ -446,73 +437,3 @@ where Ok(staker) } } - -#[precompile_utils::generate_function_selector] -#[derive(Debug, PartialEq)] -pub enum Action { - ReadCurrentEra = "read_current_era()", - ReadUnbondingPeriod = "read_unbonding_period()", - ReadEraReward = "read_era_reward(uint32)", - ReadEraStaked = "read_era_staked(uint32)", - ReadStakedAmount = "read_staked_amount(bytes)", - ReadStakedAmountOnContract = "read_staked_amount_on_contract(address,bytes)", - ReadContractStake = "read_contract_stake(address)", - Register = "register(address)", - BondAndStake = "bond_and_stake(address,uint128)", - UnbondAndUnstake = "unbond_and_unstake(address,uint128)", - WithdrawUnbounded = "withdraw_unbonded()", - ClaimDapp = "claim_dapp(address,uint128)", - ClaimStaker = "claim_staker(address)", - SetRewardDestination = "set_reward_destination(uint8)", - WithdrawFromUnregistered = "withdraw_from_unregistered(address)", - NominationTransfer = "nomination_transfer(address,uint128,address)", -} - -impl Precompile for DappsStakingWrapper -where - R: pallet_evm::Config + pallet_dapps_staking::Config, - R::RuntimeCall: From> - + Dispatchable - + GetDispatchInfo, - ::RuntimeOrigin: From>, - BalanceOf: EvmData, - R::AccountId: From<[u8; 32]>, -{ - fn execute(handle: &mut impl PrecompileHandle) -> EvmResult { - log::trace!(target: "ds-precompile", "Execute input = {:?}", handle.input()); - - let selector = handle.read_selector()?; - - handle.check_function_modifier(match selector { - Action::ReadCurrentEra - | Action::ReadUnbondingPeriod - | Action::ReadEraReward - | Action::ReadEraStaked - | Action::ReadStakedAmount - | Action::ReadStakedAmountOnContract - | Action::ReadContractStake => FunctionModifier::View, - _ => FunctionModifier::NonPayable, - })?; - - match selector { - // read storage - Action::ReadCurrentEra => Self::read_current_era(handle), - Action::ReadUnbondingPeriod => Self::read_unbonding_period(handle), - Action::ReadEraReward => Self::read_era_reward(handle), - Action::ReadEraStaked => Self::read_era_staked(handle), - Action::ReadStakedAmount => Self::read_staked_amount(handle), - Action::ReadStakedAmountOnContract => Self::read_staked_amount_on_contract(handle), - Action::ReadContractStake => Self::read_contract_stake(handle), - // Dispatchables - Action::Register => Self::register(handle), - Action::BondAndStake => Self::bond_and_stake(handle), - Action::UnbondAndUnstake => Self::unbond_and_unstake(handle), - Action::WithdrawUnbounded => Self::withdraw_unbonded(handle), - Action::ClaimDapp => Self::claim_dapp(handle), - Action::ClaimStaker => Self::claim_staker(handle), - Action::SetRewardDestination => Self::set_reward_destination(handle), - Action::WithdrawFromUnregistered => Self::withdraw_from_unregistered(handle), - Action::NominationTransfer => Self::nomination_transfer(handle), - } - } -} diff --git a/precompiles/dapps-staking/src/mock.rs b/precompiles/dapps-staking/src/mock.rs index c6fe131511..e85d563580 100644 --- a/precompiles/dapps-staking/src/mock.rs +++ b/precompiles/dapps-staking/src/mock.rs @@ -18,7 +18,7 @@ use super::*; -use fp_evm::IsPrecompileResult; +use fp_evm::{IsPrecompileResult, Precompile}; use frame_support::{ construct_runtime, parameter_types, traits::{ConstBool, ConstU64, Currency, OnFinalize, OnInitialize}, @@ -120,14 +120,14 @@ impl From for H160 { } } -trait H160Conversion { - fn to_h160(&self) -> H160; -} - -impl H160Conversion for AccountId32 { - fn to_h160(&self) -> H160 { - let x = self.encode()[31]; - H160::repeat_byte(x) +impl From for TestAccount { + fn from(address: H160) -> TestAccount { + match address { + a if a == H160::repeat_byte(0x01) => TestAccount::Alex, + a if a == H160::repeat_byte(0x02) => TestAccount::Bobo, + a if a == H160::repeat_byte(0x03) => TestAccount::Dino, + _ => Default::default(), + } } } @@ -228,6 +228,8 @@ where } } +pub type PrecompileCall = DappsStakingWrapperCall; + parameter_types! { pub PrecompilesValue: DappPrecompile = DappPrecompile(Default::default()); pub WeightPerGas: Weight = Weight::from_parts(1, 0); diff --git a/precompiles/dapps-staking/src/tests.rs b/precompiles/dapps-staking/src/tests.rs index 1285673b40..8844574de2 100644 --- a/precompiles/dapps-staking/src/tests.rs +++ b/precompiles/dapps-staking/src/tests.rs @@ -24,7 +24,6 @@ use crate::{ }, *, }; -use fp_evm::ExitError; use frame_support::assert_ok; use pallet_dapps_staking::RewardDestination; use precompile_utils::testing::*; @@ -46,11 +45,11 @@ fn current_era_is_ok() { .prepare_test( TestAccount::Alex, precompile_address(), - EvmDataWriter::new_with_selector(Action::ReadCurrentEra).build(), + PrecompileCall::read_current_era {}, ) .expect_cost(READ_WEIGHT) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(current_era).build()); + .execute_returns(current_era); // advance to era 5 and check output advance_to_era(5); @@ -60,11 +59,11 @@ fn current_era_is_ok() { .prepare_test( TestAccount::Alex, precompile_address(), - EvmDataWriter::new_with_selector(Action::ReadCurrentEra).build(), + PrecompileCall::read_current_era {}, ) .expect_cost(READ_WEIGHT) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(current_era).build()); + .execute_returns(current_era); }); } @@ -77,11 +76,11 @@ fn read_unbonding_period_is_ok() { .prepare_test( TestAccount::Alex, precompile_address(), - EvmDataWriter::new_with_selector(Action::ReadUnbondingPeriod).build(), + PrecompileCall::read_unbonding_period {}, ) .expect_cost(0) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(UNBONDING_PERIOD).build()); + .execute_returns(UNBONDING_PERIOD as u128); }); } @@ -98,13 +97,13 @@ fn read_era_reward_is_ok() { .prepare_test( TestAccount::Alex, precompile_address(), - EvmDataWriter::new_with_selector(Action::ReadEraReward) - .write(second_era) - .build(), + PrecompileCall::read_era_reward { + era: second_era.into(), + }, ) .expect_cost(READ_WEIGHT) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(era_reward).build()); + .execute_returns(era_reward); }); } @@ -120,13 +119,11 @@ fn read_era_staked_is_ok() { .prepare_test( TestAccount::Alex, precompile_address(), - EvmDataWriter::new_with_selector(Action::ReadEraStaked) - .write(zero_era) - .build(), + PrecompileCall::read_era_staked { era: zero_era }, ) .expect_cost(READ_WEIGHT) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(staked).build()); + .execute_returns(staked); }); } @@ -142,14 +139,12 @@ fn register_via_precompile_fails() { .prepare_test( TestAccount::Alex, precompile_address(), - EvmDataWriter::new_with_selector(Action::Register) - .write(Address(TEST_CONTRACT.clone())) - .build(), + PrecompileCall::register { + _address: Address(TEST_CONTRACT.clone()), + }, ) .expect_no_logs() - .execute_error(ExitError::Other(alloc::borrow::Cow::Borrowed( - "register via evm precompile is not allowed", - ))); + .execute_reverts(|output| output == b"register via evm precompile is not allowed"); }); } @@ -426,30 +421,26 @@ fn read_staked_amount_h160_verify(staker: TestAccount, amount: u128) { .prepare_test( staker.clone(), precompile_address(), - EvmDataWriter::new_with_selector(Action::ReadStakedAmount) - .write(Bytes( - Into::::into(staker.clone()).to_fixed_bytes().to_vec(), - )) - .build(), + PrecompileCall::read_staked_amount { + staker: H160::from(staker).to_fixed_bytes().into(), + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(amount).build()); + .execute_returns(amount); } /// helper function to read ledger storage item for ss58 account fn read_staked_amount_ss58_verify(staker: TestAccount, amount: u128) { - let staker_acc_id: AccountId32 = staker.clone().into(); - precompiles() .prepare_test( staker.clone(), precompile_address(), - EvmDataWriter::new_with_selector(Action::ReadStakedAmount) - .write(Bytes(staker_acc_id.encode())) - .build(), + PrecompileCall::read_staked_amount { + staker: AccountId32::from(staker).encode().into(), + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(amount).build()); + .execute_returns(amount); } /// helper function to bond, stake and verify if resulet is OK @@ -458,13 +449,13 @@ fn bond_stake_and_verify(staker: TestAccount, contract: H160, amount: u128) { .prepare_test( staker.clone(), precompile_address(), - EvmDataWriter::new_with_selector(Action::BondAndStake) - .write(Address(contract.clone())) - .write(amount) - .build(), + PrecompileCall::bond_and_stake { + contract_h160: Address(contract), + value: amount, + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); read_staked_amount_h160_verify(staker.clone(), amount); read_staked_amount_ss58_verify(staker, amount); @@ -476,13 +467,13 @@ fn unbond_unstake_and_verify(staker: TestAccount, contract: H160, amount: u128) .prepare_test( staker.clone(), precompile_address(), - EvmDataWriter::new_with_selector(Action::UnbondAndUnstake) - .write(Address(contract.clone())) - .write(amount) - .build(), + PrecompileCall::unbond_and_unstake { + contract_h160: Address(contract), + value: amount, + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); } /// helper function to withdraw unstaked funds and verify if result is OK @@ -499,10 +490,10 @@ fn withdraw_unbonded_verify(staker: TestAccount) { .prepare_test( staker.clone(), precompile_address(), - EvmDataWriter::new_with_selector(Action::WithdrawUnbounded).build(), + PrecompileCall::withdraw_unbonded {}, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); assert_eq!( ::Currency::free_balance(&staker_acc_id), @@ -526,12 +517,12 @@ fn set_reward_destination_verify(staker: TestAccount, reward_destination: Reward .prepare_test( staker.clone(), precompile_address(), - EvmDataWriter::new_with_selector(Action::SetRewardDestination) - .write(reward_destination_raw) - .build(), + PrecompileCall::set_reward_destination { + reward_destination_raw, + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); let final_ledger = DappsStaking::ledger(&staker_acc_id); assert_eq!(final_ledger.reward_destination(), reward_destination); @@ -549,12 +540,12 @@ fn withdraw_from_unregistered_verify(staker: TestAccount, contract: H160) { .prepare_test( staker.clone(), precompile_address(), - EvmDataWriter::new_with_selector(Action::WithdrawFromUnregistered) - .write(Address(contract.clone())) - .build(), + PrecompileCall::withdraw_from_unregistered { + contract_h160: Address(contract), + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); let final_staker_info = DappsStaking::staker_info(&staker_acc_id, &smart_contract); assert!(final_staker_info.latest_staked_value().is_zero()); @@ -581,14 +572,14 @@ fn nomination_transfer_verify( .prepare_test( staker.clone(), precompile_address(), - EvmDataWriter::new_with_selector(Action::NominationTransfer) - .write(Address(origin_contract.clone())) - .write(amount) - .write(Address(target_contract.clone())) - .build(), + PrecompileCall::nomination_transfer { + origin_contract_h160: Address(origin_contract), + value: amount, + target_contract_h160: Address(target_contract), + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); let final_origin_staker_info = DappsStaking::staker_info(&staker_acc_id, &origin_smart_contract); @@ -622,13 +613,13 @@ fn claim_dapp_and_verify(contract: H160, era: EraIndex) { .prepare_test( TestAccount::Bobo, precompile_address(), - EvmDataWriter::new_with_selector(Action::ClaimDapp) - .write(Address(contract.clone())) - .write(era) - .build(), + PrecompileCall::claim_dapp { + contract_h160: Address(contract), + era: era as u128, + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); } /// helper function to bond, stake and verify if the result is OK @@ -637,12 +628,12 @@ fn claim_staker_and_verify(staker: TestAccount, contract: H160) { .prepare_test( staker, precompile_address(), - EvmDataWriter::new_with_selector(Action::ClaimStaker) - .write(Address(contract.clone())) - .build(), + PrecompileCall::claim_staker { + contract_h160: Address(contract), + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); } fn contract_era_stake_verify(contract: H160, amount: Balance) { @@ -650,13 +641,13 @@ fn contract_era_stake_verify(contract: H160, amount: Balance) { .prepare_test( TestAccount::Alex, precompile_address(), - EvmDataWriter::new_with_selector(Action::ReadContractStake) - .write(Address(contract.clone())) - .build(), + PrecompileCall::read_contract_stake { + contract_h160: Address(contract), + }, ) .expect_cost(2 * READ_WEIGHT) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(amount).build()); + .execute_returns(amount); } /// helper function to verify latest staked amount @@ -665,16 +656,14 @@ fn verify_staked_amount(contract: H160, staker: TestAccount, amount: Balance) { .prepare_test( staker.clone(), precompile_address(), - EvmDataWriter::new_with_selector(Action::ReadStakedAmountOnContract) - .write(Address(contract.clone())) - .write(Bytes( - Into::::into(staker.clone()).to_fixed_bytes().to_vec(), - )) - .build(), + PrecompileCall::read_staked_amount_on_contract { + contract_h160: Address(contract), + staker: H160::from(staker).to_fixed_bytes().into(), + }, ) .expect_cost(READ_WEIGHT) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(amount).build()); + .execute_returns(amount); } /// Helper method to decode type SmartContract enum from [u8; 20] diff --git a/precompiles/sr25519/Cargo.toml b/precompiles/sr25519/Cargo.toml index 9a0741b480..a5abe88ec7 100644 --- a/precompiles/sr25519/Cargo.toml +++ b/precompiles/sr25519/Cargo.toml @@ -32,7 +32,7 @@ precompile-utils = { workspace = true, features = ["testing"] } frame-support = { workspace = true } frame-system = { workspace = true } -pallet-balances = { workspace = true } +pallet-balances = { workspace = true, features = ["std"] } pallet-timestamp = { workspace = true } sp-runtime = { workspace = true } diff --git a/precompiles/sr25519/src/lib.rs b/precompiles/sr25519/src/lib.rs index f7f08a1e38..a303a7e2a3 100644 --- a/precompiles/sr25519/src/lib.rs +++ b/precompiles/sr25519/src/lib.rs @@ -18,63 +18,42 @@ #![cfg_attr(not(feature = "std"), no_std)] -use fp_evm::{PrecompileHandle, PrecompileOutput}; -use pallet_evm::Precompile; +use fp_evm::PrecompileHandle; +use sp_core::ConstU32; use sp_core::{crypto::UncheckedFrom, sr25519, H256}; use sp_std::marker::PhantomData; -use sp_std::prelude::*; -use precompile_utils::{ - succeed, Bytes, EvmDataWriter, EvmResult, FunctionModifier, PrecompileHandleExt, -}; +use precompile_utils::prelude::*; #[cfg(test)] mod mock; #[cfg(test)] mod tests; -#[precompile_utils::generate_function_selector] -#[derive(Debug, PartialEq)] -pub enum Action { - Verify = "verify(bytes32,bytes,bytes)", -} +// SR25519 signature bytes +type SR25519SignatureBytes = ConstU32<64>; /// A precompile to wrap substrate sr25519 functions. pub struct Sr25519Precompile(PhantomData); -impl Precompile for Sr25519Precompile { - fn execute(handle: &mut impl PrecompileHandle) -> EvmResult { - log::trace!(target: "sr25519-precompile", "In sr25519 precompile"); - - let selector = handle.read_selector()?; - - handle.check_function_modifier(FunctionModifier::View)?; - - match selector { - // Dispatchables - Action::Verify => Self::verify(handle), - } - } -} - +#[precompile_utils::precompile] impl Sr25519Precompile { - fn verify(handle: &mut impl PrecompileHandle) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(3)?; - - // Parse arguments - let public: sr25519::Public = sr25519::Public::unchecked_from(input.read::()?); - let signature_bytes: Vec = input.read::()?.into(); - let message: Vec = input.read::()?.into(); - + #[precompile::public("verify(bytes32,bytes,bytes)")] + #[precompile::view] + fn verify( + _: &mut impl PrecompileHandle, + public: H256, + signature: BoundedBytes, + message: UnboundedBytes, + ) -> EvmResult { + // Parse pub key + let public = sr25519::Public::unchecked_from(public); // Parse signature - let signature_opt = sr25519::Signature::from_slice(&signature_bytes[..]); - - let signature = if let Some(sig) = signature_opt { + let signature = if let Some(sig) = sr25519::Signature::from_slice(&signature.as_bytes()) { sig } else { // Return `false` if signature length is wrong - return Ok(succeed(EvmDataWriter::new().write(false).build())); + return Ok(false); }; log::trace!( @@ -83,7 +62,8 @@ impl Sr25519Precompile { signature, public, message, ); - let is_confirmed = sp_io::crypto::sr25519_verify(&signature, &message[..], &public); + let is_confirmed = + sp_io::crypto::sr25519_verify(&signature, &message.as_bytes(), &public.into()); log::trace!( target: "sr25519-precompile", @@ -91,6 +71,6 @@ impl Sr25519Precompile { signature, is_confirmed, ); - Ok(succeed(EvmDataWriter::new().write(is_confirmed).build())) + Ok(is_confirmed) } } diff --git a/precompiles/sr25519/src/mock.rs b/precompiles/sr25519/src/mock.rs index 914889ee31..f8450a4192 100644 --- a/precompiles/sr25519/src/mock.rs +++ b/precompiles/sr25519/src/mock.rs @@ -20,7 +20,7 @@ use super::*; -use fp_evm::IsPrecompileResult; +use fp_evm::{IsPrecompileResult, Precompile}; use frame_support::{ construct_runtime, parameter_types, traits::{ConstU32, ConstU64, Everything}, @@ -198,6 +198,8 @@ parameter_types! { pub const WeightPerGas: Weight = Weight::from_parts(1, 0); } +pub type PrecompileCall = Sr25519PrecompileCall; + impl pallet_evm::Config for Runtime { type FeeCalculator = (); type GasWeightMapping = pallet_evm::FixedGasWeightMapping; diff --git a/precompiles/sr25519/src/tests.rs b/precompiles/sr25519/src/tests.rs index 0774ae7078..f8a15a3470 100644 --- a/precompiles/sr25519/src/tests.rs +++ b/precompiles/sr25519/src/tests.rs @@ -19,10 +19,9 @@ use hex_literal::hex; use crate::mock::*; -use crate::*; use precompile_utils::testing::*; -use sp_core::{sr25519, Pair, H256}; +use sp_core::{sr25519, Pair}; fn precompiles() -> TestPrecompileSet { PrecompilesValue::get() @@ -40,14 +39,14 @@ fn wrong_signature_length_returns_false() { .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::Verify) - .write(H256::from(public)) - .write(Bytes::from(&signature[..])) - .write(Bytes::from(&message[..])) - .build(), + PrecompileCall::verify { + public: public.into(), + signature: signature.into(), + message: message.into(), + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(false).build()); + .execute_returns(false); }); } @@ -66,14 +65,14 @@ fn bad_signature_returns_false() { .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::Verify) - .write(H256::from(public)) - .write(Bytes::from(>::as_ref(&signature))) - .write(Bytes::from(&bad_message[..])) - .build(), + PrecompileCall::verify { + public: public.into(), + signature: >::as_ref(&signature).into(), + message: bad_message.into(), + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(false).build()); + .execute_returns(false); }); } @@ -96,13 +95,13 @@ fn substrate_test_vector_works() { .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::Verify) - .write(H256::from(public)) - .write(Bytes::from(>::as_ref(&signature))) - .write(Bytes::from(&message[..])) - .build(), + PrecompileCall::verify { + public: public.into(), + signature: >::as_ref(&signature).into(), + message: message.into(), + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); }); } diff --git a/precompiles/substrate-ecdsa/Cargo.toml b/precompiles/substrate-ecdsa/Cargo.toml index df7b1015e0..388283b9e9 100644 --- a/precompiles/substrate-ecdsa/Cargo.toml +++ b/precompiles/substrate-ecdsa/Cargo.toml @@ -33,7 +33,7 @@ precompile-utils = { workspace = true, features = ["testing"] } frame-support = { workspace = true } frame-system = { workspace = true } -pallet-balances = { workspace = true } +pallet-balances = { workspace = true, features = ["std"] } pallet-timestamp = { workspace = true } sp-runtime = { workspace = true } diff --git a/precompiles/substrate-ecdsa/src/lib.rs b/precompiles/substrate-ecdsa/src/lib.rs index 855ac7ca41..e128854ca6 100644 --- a/precompiles/substrate-ecdsa/src/lib.rs +++ b/precompiles/substrate-ecdsa/src/lib.rs @@ -18,61 +18,47 @@ #![cfg_attr(not(feature = "std"), no_std)] -use fp_evm::{PrecompileHandle, PrecompileOutput}; -use pallet_evm::Precompile; -use sp_core::ecdsa; +use fp_evm::PrecompileHandle; +use sp_core::{ecdsa, ConstU32}; use sp_std::marker::PhantomData; use sp_std::prelude::*; -use precompile_utils::{ - succeed, Bytes, EvmDataWriter, EvmResult, FunctionModifier, PrecompileHandleExt, -}; +use precompile_utils::prelude::*; #[cfg(test)] mod mock; #[cfg(test)] mod tests; -#[precompile_utils::generate_function_selector] -#[derive(Debug, PartialEq)] -pub enum Action { - Verify = "verify(bytes,bytes,bytes)", -} +// ECDSA pub key bytes +type ECDSAPubKeyBytes = ConstU32<33>; +// ECDSA signature bytes +type ECDSASignatureBytes = ConstU32<65>; /// A precompile to wrap substrate ecdsa functions. pub struct SubstrateEcdsaPrecompile(PhantomData); -impl Precompile for SubstrateEcdsaPrecompile { - fn execute(handle: &mut impl PrecompileHandle) -> EvmResult { - log::trace!(target: "substrate-ecdsa-precompile", "In SubstrateEcdsa precompile"); - - let selector = handle.read_selector()?; - - handle.check_function_modifier(FunctionModifier::View)?; - - match selector { - // Dispatchables - Action::Verify => Self::verify(handle), - } - } -} - +#[precompile_utils::precompile] impl SubstrateEcdsaPrecompile { - fn verify(handle: &mut impl PrecompileHandle) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(3)?; - + #[precompile::public("verify(bytes,bytes,bytes)")] + #[precompile::view] + fn verify( + _handle: &mut impl PrecompileHandle, + public_bytes: BoundedBytes, + signature_bytes: BoundedBytes, + message: UnboundedBytes, + ) -> EvmResult { // Parse arguments - let public_bytes: Vec = input.read::()?.into(); - let signature_bytes: Vec = input.read::()?.into(); - let message: Vec = input.read::()?.into(); + let public_bytes: Vec = public_bytes.into(); + let signature_bytes: Vec = signature_bytes.into(); + let message: Vec = message.into(); // Parse public key let public = if let Ok(public) = ecdsa::Public::try_from(&public_bytes[..]) { public } else { // Return `false` if public key length is wrong - return Ok(succeed(EvmDataWriter::new().write(false).build())); + return Ok(false); }; // Parse signature @@ -82,7 +68,7 @@ impl SubstrateEcdsaPrecompile { sig } else { // Return `false` if signature length is wrong - return Ok(succeed(EvmDataWriter::new().write(false).build())); + return Ok(false); }; log::trace!( @@ -99,6 +85,6 @@ impl SubstrateEcdsaPrecompile { signature, is_confirmed, ); - Ok(succeed(EvmDataWriter::new().write(is_confirmed).build())) + Ok(is_confirmed) } } diff --git a/precompiles/substrate-ecdsa/src/mock.rs b/precompiles/substrate-ecdsa/src/mock.rs index 295dd2e3bf..d0037b65f5 100644 --- a/precompiles/substrate-ecdsa/src/mock.rs +++ b/precompiles/substrate-ecdsa/src/mock.rs @@ -20,7 +20,7 @@ use super::*; -use fp_evm::IsPrecompileResult; +use fp_evm::{IsPrecompileResult, Precompile}; use frame_support::{ construct_runtime, parameter_types, traits::{ConstU32, ConstU64, Everything}, @@ -198,6 +198,8 @@ parameter_types! { pub WeightPerGas: Weight = Weight::from_parts(1, 0); } +pub type PrecompileCall = SubstrateEcdsaPrecompileCall; + impl pallet_evm::Config for Runtime { type FeeCalculator = (); type GasWeightMapping = pallet_evm::FixedGasWeightMapping; diff --git a/precompiles/substrate-ecdsa/src/tests.rs b/precompiles/substrate-ecdsa/src/tests.rs index f5d940f0e3..99ca075fca 100644 --- a/precompiles/substrate-ecdsa/src/tests.rs +++ b/precompiles/substrate-ecdsa/src/tests.rs @@ -19,7 +19,6 @@ use hex_literal::hex; use crate::mock::*; -use crate::*; use precompile_utils::testing::*; use sp_core::{ecdsa, Pair}; @@ -40,14 +39,14 @@ fn wrong_signature_length_returns_false() { .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::Verify) - .write(Bytes::from(>::as_ref(&public))) - .write(Bytes::from(&signature[..])) - .write(Bytes::from(&message[..])) - .build(), + PrecompileCall::verify { + public_bytes: >::as_ref(&public).into(), + signature_bytes: signature.into(), + message: message.into(), + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(false).build()); + .execute_returns(false); }); } @@ -66,14 +65,14 @@ fn bad_signature_returns_false() { .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::Verify) - .write(Bytes::from(>::as_ref(&public))) - .write(Bytes::from(>::as_ref(&signature))) - .write(Bytes::from(&bad_message[..])) - .build(), + PrecompileCall::verify { + public_bytes: >::as_ref(&public).into(), + signature_bytes: >::as_ref(&signature).into(), + message: bad_message.into(), + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(false).build()); + .execute_returns(false); }); } @@ -98,13 +97,13 @@ fn substrate_test_vector_works() { .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::Verify) - .write(Bytes::from(>::as_ref(&public))) - .write(Bytes::from(>::as_ref(&signature))) - .write(Bytes::from(&message[..])) - .build(), + PrecompileCall::verify { + public_bytes: >::as_ref(&public).into(), + signature_bytes: >::as_ref(&signature).into(), + message: message.into(), + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); }); } diff --git a/precompiles/unified-accounts/src/lib.rs b/precompiles/unified-accounts/src/lib.rs index d1dbf3dbf3..031bdb1d49 100644 --- a/precompiles/unified-accounts/src/lib.rs +++ b/precompiles/unified-accounts/src/lib.rs @@ -20,54 +20,21 @@ use astar_primitives::evm::{UnifiedAddress, UnifiedAddressMapper}; use core::marker::PhantomData; -use fp_evm::Precompile; -use fp_evm::{PrecompileHandle, PrecompileOutput}; +use fp_evm::PrecompileHandle; use frame_support::dispatch::Dispatchable; use frame_support::traits::IsType; -use precompile_utils::{ - succeed, Address, EvmDataWriter, EvmResult, FunctionModifier, PrecompileHandleExt, -}; +use precompile_utils::prelude::*; use sp_core::{crypto::AccountId32, H256}; -use sp_std::prelude::*; #[cfg(test)] mod mock; #[cfg(test)] mod tests; -#[precompile_utils::generate_function_selector] -#[derive(Debug, PartialEq)] -pub enum Action { - GetEvmAddressOrDefault = "get_evm_address_or_default(bytes32)", - GetNativeAddressOrDefault = "get_native_address_or_default(address)", -} - /// A precompile that expose AU related functions. pub struct UnifiedAccountsPrecompile(PhantomData<(T, UA)>); -impl Precompile for UnifiedAccountsPrecompile -where - R: pallet_evm::Config + pallet_unified_accounts::Config, - <::RuntimeCall as Dispatchable>::RuntimeOrigin: - From>, - ::AccountId: IsType, - UA: UnifiedAddressMapper, -{ - fn execute(handle: &mut impl PrecompileHandle) -> EvmResult { - log::trace!(target: "au-precompile", "Execute input = {:?}", handle.input()); - - let selector = handle.read_selector()?; - - handle.check_function_modifier(FunctionModifier::View)?; - - match selector { - // Dispatchables - Action::GetEvmAddressOrDefault => Self::get_evm_address_or_default(handle), - Action::GetNativeAddressOrDefault => Self::get_native_address_or_default(handle), - } - } -} - +#[precompile_utils::precompile] impl UnifiedAccountsPrecompile where R: pallet_evm::Config + pallet_unified_accounts::Config, @@ -76,31 +43,31 @@ where ::AccountId: IsType, UA: UnifiedAddressMapper, { + #[precompile::public("get_evm_address_or_default(bytes32)")] + #[precompile::view] fn get_evm_address_or_default( - handle: &mut impl PrecompileHandle, - ) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(1)?; - let account_id = AccountId32::new(input.read::()?.into()).into(); + _: &mut impl PrecompileHandle, + account_id: H256, + ) -> EvmResult<(Address, bool)> { + let account_id = AccountId32::new(account_id.into()).into(); let output: (Address, bool) = match UA::to_h160_or_default(&account_id) { UnifiedAddress::Mapped(address) => (address.into(), true), UnifiedAddress::Default(address) => (address.into(), false), }; - Ok(succeed(EvmDataWriter::new().write(output).build())) + Ok(output) } + #[precompile::public("get_native_address_or_default(address)")] + #[precompile::view] fn get_native_address_or_default( - handle: &mut impl PrecompileHandle, - ) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(1)?; - let evm_address = input.read::
()?; - + _: &mut impl PrecompileHandle, + evm_address: Address, + ) -> EvmResult<(H256, bool)> { let output: (H256, bool) = match UA::to_account_id_or_default(&evm_address.into()) { UnifiedAddress::Mapped(account_id) => (H256::from(account_id.into().as_ref()), true), UnifiedAddress::Default(account_id) => (H256::from(account_id.into().as_ref()), false), }; - Ok(succeed(EvmDataWriter::new().write(output).build())) + Ok(output) } } diff --git a/precompiles/unified-accounts/src/mock.rs b/precompiles/unified-accounts/src/mock.rs index 9b1eeb3a30..384f293fbb 100644 --- a/precompiles/unified-accounts/src/mock.rs +++ b/precompiles/unified-accounts/src/mock.rs @@ -18,7 +18,7 @@ use super::*; -use fp_evm::IsPrecompileResult; +use fp_evm::{IsPrecompileResult, Precompile}; use frame_support::{construct_runtime, parameter_types, traits::ConstU64, weights::Weight}; pub use pallet_evm::{ AddressMapping, EnsureAddressNever, EnsureAddressRoot, PrecompileResult, PrecompileSet, @@ -206,6 +206,8 @@ parameter_types! { pub WeightPerGas: Weight = Weight::from_parts(1, 0); } +pub type PrecompileCall = UnifiedAccountsPrecompileCall; + impl pallet_evm::Config for TestRuntime { type FeeCalculator = (); type GasWeightMapping = pallet_evm::FixedGasWeightMapping; diff --git a/precompiles/unified-accounts/src/tests.rs b/precompiles/unified-accounts/src/tests.rs index 598f38d8b4..59f52ff66c 100644 --- a/precompiles/unified-accounts/src/tests.rs +++ b/precompiles/unified-accounts/src/tests.rs @@ -21,7 +21,6 @@ use crate::*; use frame_support::assert_ok; use precompile_utils::testing::*; -use precompile_utils::EvmDataWriter; fn precompiles() -> TestPrecompileSet { PrecompilesValue::get() @@ -41,12 +40,12 @@ fn test_get_evm_address() { .prepare_test( TestAccount::Viktor, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::GetEvmAddressOrDefault) - .write(H256::zero()) // Alice's Address - .build(), + PrecompileCall::get_evm_address_or_default { + account_id: H256::zero(), // Alice's Address + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(res).build()); + .execute_returns(res); }); // Case 2 : Address Mapped @@ -66,12 +65,12 @@ fn test_get_evm_address() { .prepare_test( TestAccount::Viktor, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::GetEvmAddressOrDefault) - .write(H256::zero()) // Alice's Address - .build(), + PrecompileCall::get_evm_address_or_default { + account_id: H256::zero(), // Alice's Address + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(res).build()); + .execute_returns(res); }); } @@ -95,12 +94,10 @@ fn test_get_native_address() { .prepare_test( TestAccount::Viktor, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::GetNativeAddressOrDefault) - .write(alice_eth_address) // Alice's Address - .build(), + PrecompileCall::get_native_address_or_default { evm_address: alice_eth_address } ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(res).build()); + .execute_returns(res); }); // Case 2 : mapped address @@ -123,11 +120,11 @@ fn test_get_native_address() { .prepare_test( TestAccount::Viktor, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::GetNativeAddressOrDefault) - .write(alice_eth) // Alice's Address - .build(), + PrecompileCall::get_native_address_or_default { + evm_address: alice_eth, + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(res).build()); + .execute_returns(res); }); } diff --git a/precompiles/utils/Cargo.toml b/precompiles/utils/Cargo.toml index a97a1a81ff..2c821e09a9 100644 --- a/precompiles/utils/Cargo.toml +++ b/precompiles/utils/Cargo.toml @@ -1,27 +1,24 @@ [package] name = "precompile-utils" -authors = ["StakeTechnologies", "PureStake"] +authors = { workspace = true } description = "Utils to write EVM precompiles." -version = "0.5.0" -edition.workspace = true -homepage.workspace = true -repository.workspace = true +edition = "2021" +version = "0.1.0" [dependencies] -# There's a problem with --all-features when this is moved under dev-deps derive_more = { workspace = true, optional = true } environmental = { workspace = true } -evm = { workspace = true, features = ["with-codec"] } +hex = { workspace = true } 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 } -precompile-utils-macro = { path = "macro" } +# Frontier +precompile-utils-macro-v2 = { path = "macro" } # Substrate frame-support = { workspace = true } @@ -33,31 +30,29 @@ sp-runtime = { workspace = true } sp-std = { workspace = true } # Frontier +evm = { workspace = true, features = ["with-codec"] } fp-evm = { workspace = true } -pallet-evm = { workspace = true } +pallet-evm = { workspace = true, features = ["forbid-evm-reentrancy"] } # Polkadot / XCM -xcm = { workspace = true } - -assert_matches = { workspace = true } +xcm = { workspace = true, optional = true } [dev-dependencies] hex-literal = { workspace = true } +xcm = { workspace = true } [features] default = ["std"] std = [ - "evm/std", - "parity-scale-codec/std", + "environmental/std", "fp-evm/std", "frame-support/std", "frame-system/std", "pallet-evm/std", + "parity-scale-codec/std", "sp-core/std", "sp-io/std", "sp-std/std", - "sp-runtime/std", - "xcm/std", - "environmental/std", ] -testing = ["similar-asserts", "std", "scale-info", "serde", "derive_more", "hex-literal"] +codec-xcm = ["xcm"] +testing = ["derive_more", "hex-literal", "scale-info", "serde", "similar-asserts", "std"] diff --git a/precompiles/utils/macro/Cargo.toml b/precompiles/utils/macro/Cargo.toml index 86c80db831..d55db0c220 100644 --- a/precompiles/utils/macro/Cargo.toml +++ b/precompiles/utils/macro/Cargo.toml @@ -1,11 +1,9 @@ [package] -name = "precompile-utils-macro" -authors = ["StakeTechnologies", "PureStake"] +name = "precompile-utils-macro-v2" +authors = { workspace = true } description = "" +edition = "2021" version = "0.1.0" -edition.workspace = true -homepage.workspace = true -repository.workspace = true [lib] proc-macro = true @@ -15,8 +13,21 @@ name = "tests" path = "tests/tests.rs" [dependencies] +case = "1.0" num_enum = { workspace = true } -proc-macro2 = { workspace = true } -quote = { workspace = true } -sha3 = { workspace = true } -syn = { workspace = true, features = ["extra-traits", "fold", "full", "visit"] } +prettyplease = "0.2.12" +proc-macro2 = "1.0" +quote = "1.0" +sp-core-hashing = { workspace = true } +syn = { version = "1.0", features = ["extra-traits", "fold", "full", "visit"] } + +[dev-dependencies] +macrotest = "1.0.9" +trybuild = "1.0" + +# precompile-utils = { path = "../", features = ["testing"] } + +fp-evm = { workspace = true } +frame-support = { workspace = true } +sp-core-hashing = { workspace = true } +sp-std = { workspace = true } diff --git a/precompiles/utils_v2/macro/docs/precompile_macro.md b/precompiles/utils/macro/docs/precompile_macro.md similarity index 100% rename from precompiles/utils_v2/macro/docs/precompile_macro.md rename to precompiles/utils/macro/docs/precompile_macro.md diff --git a/precompiles/utils_v2/macro/src/derive_codec.rs b/precompiles/utils/macro/src/derive_codec.rs similarity index 100% rename from precompiles/utils_v2/macro/src/derive_codec.rs rename to precompiles/utils/macro/src/derive_codec.rs diff --git a/precompiles/utils_v2/macro/src/generate_function_selector.rs b/precompiles/utils/macro/src/generate_function_selector.rs similarity index 100% rename from precompiles/utils_v2/macro/src/generate_function_selector.rs rename to precompiles/utils/macro/src/generate_function_selector.rs diff --git a/precompiles/utils/macro/src/lib.rs b/precompiles/utils/macro/src/lib.rs index fb66972c73..0b595ca042 100644 --- a/precompiles/utils/macro/src/lib.rs +++ b/precompiles/utils/macro/src/lib.rs @@ -1,33 +1,33 @@ -// 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 +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. // -// This file is part of Utils package, originally developed by Purestake Inc. -// Utils package used in Astar Network in terms of GPLv3. +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. // -// Utils is free software: you can redistribute it and/or modify +// This program 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, +// +// This program 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 +// 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 . +// along with this program. If not, see . #![crate_type = "proc-macro"] extern crate proc_macro; use proc_macro::TokenStream; -use proc_macro2::Literal; use quote::{quote, quote_spanned}; -use sha3::{Digest, Keccak256}; -use syn::{parse_macro_input, spanned::Spanned, Expr, ExprLit, Ident, ItemEnum, Lit, LitStr}; +use sp_core_hashing::keccak_256; +use syn::{parse_macro_input, spanned::Spanned, Expr, Ident, ItemType, Lit, LitStr}; + +mod derive_codec; +mod precompile; +mod precompile_name_from_address; struct Bytes(Vec); @@ -50,7 +50,7 @@ impl ::std::fmt::Debug for Bytes { pub fn keccak256(input: TokenStream) -> TokenStream { let lit_str = parse_macro_input!(input as LitStr); - let hash = Keccak256::digest(lit_str.value().as_bytes()); + let hash = keccak_256(lit_str.value().as_bytes()); let bytes = Bytes(hash.to_vec()); let eval_str = format!("{:?}", bytes); @@ -63,88 +63,17 @@ pub fn keccak256(input: TokenStream) -> TokenStream { quote!(#eval_ts).into() } -/// This macro allows to associate to each variant of an enumeration a discriminant (of type u32 -/// whose value corresponds to the first 4 bytes of the Hash Keccak256 of the character string -///indicated by the user of this macro. -/// -/// Usage: -/// -/// ```ignore -/// #[generate_function_selector] -/// enum Action { -/// Toto = "toto()", -/// Tata = "tata()", -/// } -/// ``` -/// -/// Extanded to: -/// -/// ```rust -/// #[repr(u32)] -/// enum Action { -/// Toto = 119097542u32, -/// Tata = 1414311903u32, -/// } -/// ``` -/// #[proc_macro_attribute] -pub fn generate_function_selector(_: TokenStream, input: TokenStream) -> TokenStream { - let item = parse_macro_input!(input as ItemEnum); - - let ItemEnum { - attrs, - vis, - enum_token, - ident, - variants, - .. - } = item; - - let mut ident_expressions: Vec = vec![]; - let mut variant_expressions: Vec = vec![]; - for variant in variants { - match variant.discriminant { - Some((_, Expr::Lit(ExprLit { lit, .. }))) => { - if let Lit::Str(lit_str) = lit { - let digest = Keccak256::digest(lit_str.value().as_bytes()); - let selector = u32::from_be_bytes([digest[0], digest[1], digest[2], digest[3]]); +pub fn precompile(attr: TokenStream, input: TokenStream) -> TokenStream { + precompile::main(attr, input) +} - ident_expressions.push(variant.ident); - variant_expressions.push(Expr::Lit(ExprLit { - lit: Lit::Verbatim(Literal::u32_suffixed(selector)), - attrs: Default::default(), - })); - } else { - return quote_spanned! { - lit.span() => compile_error("Expected literal string"); - } - .into(); - } - } - Some((_eg, expr)) => { - return quote_spanned! { - expr.span() => compile_error("Expected literal"); - } - .into() - } - None => { - return quote_spanned! { - variant.span() => compile_error("Each variant must have a discriminant"); - } - .into() - } - } - } +#[proc_macro_attribute] +pub fn precompile_name_from_address(attr: TokenStream, input: TokenStream) -> TokenStream { + precompile_name_from_address::main(attr, input) +} - (quote! { - #(#attrs)* - #[derive(num_enum::TryFromPrimitive, num_enum::IntoPrimitive)] - #[repr(u32)] - #vis #enum_token #ident { - #( - #ident_expressions = #variant_expressions, - )* - } - }) - .into() +#[proc_macro_derive(Codec)] +pub fn derive_codec(input: TokenStream) -> TokenStream { + derive_codec::main(input) } diff --git a/precompiles/utils_v2/macro/src/precompile/attr.rs b/precompiles/utils/macro/src/precompile/attr.rs similarity index 100% rename from precompiles/utils_v2/macro/src/precompile/attr.rs rename to precompiles/utils/macro/src/precompile/attr.rs diff --git a/precompiles/utils_v2/macro/src/precompile/expand.rs b/precompiles/utils/macro/src/precompile/expand.rs similarity index 100% rename from precompiles/utils_v2/macro/src/precompile/expand.rs rename to precompiles/utils/macro/src/precompile/expand.rs diff --git a/precompiles/utils_v2/macro/src/precompile/mod.rs b/precompiles/utils/macro/src/precompile/mod.rs similarity index 100% rename from precompiles/utils_v2/macro/src/precompile/mod.rs rename to precompiles/utils/macro/src/precompile/mod.rs diff --git a/precompiles/utils_v2/macro/src/precompile/parse.rs b/precompiles/utils/macro/src/precompile/parse.rs similarity index 100% rename from precompiles/utils_v2/macro/src/precompile/parse.rs rename to precompiles/utils/macro/src/precompile/parse.rs diff --git a/precompiles/utils_v2/macro/src/precompile_name_from_address.rs b/precompiles/utils/macro/src/precompile_name_from_address.rs similarity index 100% rename from precompiles/utils_v2/macro/src/precompile_name_from_address.rs rename to precompiles/utils/macro/src/precompile_name_from_address.rs diff --git a/precompiles/utils_v2/macro/tests/compile-fail/derive_codec/empty_struct.rs b/precompiles/utils/macro/tests/compile-fail/derive_codec/empty_struct.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/derive_codec/empty_struct.rs rename to precompiles/utils/macro/tests/compile-fail/derive_codec/empty_struct.rs diff --git a/precompiles/utils_v2/macro/tests/compile-fail/derive_codec/empty_struct.stderr b/precompiles/utils/macro/tests/compile-fail/derive_codec/empty_struct.stderr similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/derive_codec/empty_struct.stderr rename to precompiles/utils/macro/tests/compile-fail/derive_codec/empty_struct.stderr diff --git a/precompiles/utils_v2/macro/tests/compile-fail/derive_codec/enum.rs b/precompiles/utils/macro/tests/compile-fail/derive_codec/enum.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/derive_codec/enum.rs rename to precompiles/utils/macro/tests/compile-fail/derive_codec/enum.rs diff --git a/precompiles/utils_v2/macro/tests/compile-fail/derive_codec/enum.stderr b/precompiles/utils/macro/tests/compile-fail/derive_codec/enum.stderr similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/derive_codec/enum.stderr rename to precompiles/utils/macro/tests/compile-fail/derive_codec/enum.stderr diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/arg-dont-impl-codec.rs b/precompiles/utils/macro/tests/compile-fail/precompile/codec/arg-dont-impl-codec.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/arg-dont-impl-codec.rs rename to precompiles/utils/macro/tests/compile-fail/precompile/codec/arg-dont-impl-codec.rs diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/arg-dont-impl-codec.stderr b/precompiles/utils/macro/tests/compile-fail/precompile/codec/arg-dont-impl-codec.stderr similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/arg-dont-impl-codec.stderr rename to precompiles/utils/macro/tests/compile-fail/precompile/codec/arg-dont-impl-codec.stderr diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/no-output.rs b/precompiles/utils/macro/tests/compile-fail/precompile/codec/no-output.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/no-output.rs rename to precompiles/utils/macro/tests/compile-fail/precompile/codec/no-output.rs diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/no-output.stderr b/precompiles/utils/macro/tests/compile-fail/precompile/codec/no-output.stderr similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/no-output.stderr rename to precompiles/utils/macro/tests/compile-fail/precompile/codec/no-output.stderr diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-dont-impl-codec.rs b/precompiles/utils/macro/tests/compile-fail/precompile/codec/output-dont-impl-codec.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-dont-impl-codec.rs rename to precompiles/utils/macro/tests/compile-fail/precompile/codec/output-dont-impl-codec.rs diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-dont-impl-codec.stderr b/precompiles/utils/macro/tests/compile-fail/precompile/codec/output-dont-impl-codec.stderr similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-dont-impl-codec.stderr rename to precompiles/utils/macro/tests/compile-fail/precompile/codec/output-dont-impl-codec.stderr diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-not-result.rs b/precompiles/utils/macro/tests/compile-fail/precompile/codec/output-not-result.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-not-result.rs rename to precompiles/utils/macro/tests/compile-fail/precompile/codec/output-not-result.rs diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-not-result.stderr b/precompiles/utils/macro/tests/compile-fail/precompile/codec/output-not-result.stderr similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-not-result.stderr rename to precompiles/utils/macro/tests/compile-fail/precompile/codec/output-not-result.stderr diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-wrong-error-result.rs b/precompiles/utils/macro/tests/compile-fail/precompile/codec/output-wrong-error-result.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-wrong-error-result.rs rename to precompiles/utils/macro/tests/compile-fail/precompile/codec/output-wrong-error-result.rs diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-wrong-error-result.stderr b/precompiles/utils/macro/tests/compile-fail/precompile/codec/output-wrong-error-result.stderr similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/codec/output-wrong-error-result.stderr rename to precompiles/utils/macro/tests/compile-fail/precompile/codec/output-wrong-error-result.stderr diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/dont-return-option.rs b/precompiles/utils/macro/tests/compile-fail/precompile/discriminant/dont-return-option.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/dont-return-option.rs rename to precompiles/utils/macro/tests/compile-fail/precompile/discriminant/dont-return-option.rs diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/dont-return-option.stderr b/precompiles/utils/macro/tests/compile-fail/precompile/discriminant/dont-return-option.stderr similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/dont-return-option.stderr rename to precompiles/utils/macro/tests/compile-fail/precompile/discriminant/dont-return-option.stderr diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/missing-fn.rs b/precompiles/utils/macro/tests/compile-fail/precompile/discriminant/missing-fn.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/missing-fn.rs rename to precompiles/utils/macro/tests/compile-fail/precompile/discriminant/missing-fn.rs diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/missing-fn.stderr b/precompiles/utils/macro/tests/compile-fail/precompile/discriminant/missing-fn.stderr similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/missing-fn.stderr rename to precompiles/utils/macro/tests/compile-fail/precompile/discriminant/missing-fn.stderr diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/missing-param.rs b/precompiles/utils/macro/tests/compile-fail/precompile/discriminant/missing-param.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/missing-param.rs rename to precompiles/utils/macro/tests/compile-fail/precompile/discriminant/missing-param.rs diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/missing-param.stderr b/precompiles/utils/macro/tests/compile-fail/precompile/discriminant/missing-param.stderr similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/missing-param.stderr rename to precompiles/utils/macro/tests/compile-fail/precompile/discriminant/missing-param.stderr diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/return-incomplete-option.rs b/precompiles/utils/macro/tests/compile-fail/precompile/discriminant/return-incomplete-option.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/return-incomplete-option.rs rename to precompiles/utils/macro/tests/compile-fail/precompile/discriminant/return-incomplete-option.rs diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/return-incomplete-option.stderr b/precompiles/utils/macro/tests/compile-fail/precompile/discriminant/return-incomplete-option.stderr similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/return-incomplete-option.stderr rename to precompiles/utils/macro/tests/compile-fail/precompile/discriminant/return-incomplete-option.stderr diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/too-many-arguments.rs b/precompiles/utils/macro/tests/compile-fail/precompile/discriminant/too-many-arguments.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/too-many-arguments.rs rename to precompiles/utils/macro/tests/compile-fail/precompile/discriminant/too-many-arguments.rs diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/too-many-arguments.stderr b/precompiles/utils/macro/tests/compile-fail/precompile/discriminant/too-many-arguments.stderr similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/too-many-arguments.stderr rename to precompiles/utils/macro/tests/compile-fail/precompile/discriminant/too-many-arguments.stderr diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/type-mismatch-1.rs b/precompiles/utils/macro/tests/compile-fail/precompile/discriminant/type-mismatch-1.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/type-mismatch-1.rs rename to precompiles/utils/macro/tests/compile-fail/precompile/discriminant/type-mismatch-1.rs diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/type-mismatch-1.stderr b/precompiles/utils/macro/tests/compile-fail/precompile/discriminant/type-mismatch-1.stderr similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/type-mismatch-1.stderr rename to precompiles/utils/macro/tests/compile-fail/precompile/discriminant/type-mismatch-1.stderr diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/type-mismatch-2.rs b/precompiles/utils/macro/tests/compile-fail/precompile/discriminant/type-mismatch-2.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/type-mismatch-2.rs rename to precompiles/utils/macro/tests/compile-fail/precompile/discriminant/type-mismatch-2.rs diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/type-mismatch-2.stderr b/precompiles/utils/macro/tests/compile-fail/precompile/discriminant/type-mismatch-2.stderr similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/discriminant/type-mismatch-2.stderr rename to precompiles/utils/macro/tests/compile-fail/precompile/discriminant/type-mismatch-2.stderr diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/discriminant-multiple.rs b/precompiles/utils/macro/tests/compile-fail/precompile/fn-modifiers/discriminant-multiple.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/discriminant-multiple.rs rename to precompiles/utils/macro/tests/compile-fail/precompile/fn-modifiers/discriminant-multiple.rs diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/discriminant-multiple.stderr b/precompiles/utils/macro/tests/compile-fail/precompile/fn-modifiers/discriminant-multiple.stderr similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/discriminant-multiple.stderr rename to precompiles/utils/macro/tests/compile-fail/precompile/fn-modifiers/discriminant-multiple.stderr diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/multiple-modifiers.rs b/precompiles/utils/macro/tests/compile-fail/precompile/fn-modifiers/multiple-modifiers.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/multiple-modifiers.rs rename to precompiles/utils/macro/tests/compile-fail/precompile/fn-modifiers/multiple-modifiers.rs diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/multiple-modifiers.stderr b/precompiles/utils/macro/tests/compile-fail/precompile/fn-modifiers/multiple-modifiers.stderr similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/multiple-modifiers.stderr rename to precompiles/utils/macro/tests/compile-fail/precompile/fn-modifiers/multiple-modifiers.stderr diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/pre-check-multiple.rs b/precompiles/utils/macro/tests/compile-fail/precompile/fn-modifiers/pre-check-multiple.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/pre-check-multiple.rs rename to precompiles/utils/macro/tests/compile-fail/precompile/fn-modifiers/pre-check-multiple.rs diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/pre-check-multiple.stderr b/precompiles/utils/macro/tests/compile-fail/precompile/fn-modifiers/pre-check-multiple.stderr similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/fn-modifiers/pre-check-multiple.stderr rename to precompiles/utils/macro/tests/compile-fail/precompile/fn-modifiers/pre-check-multiple.stderr diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/missing.rs b/precompiles/utils/macro/tests/compile-fail/precompile/handle/missing.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/missing.rs rename to precompiles/utils/macro/tests/compile-fail/precompile/handle/missing.rs diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/missing.stderr b/precompiles/utils/macro/tests/compile-fail/precompile/handle/missing.stderr similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/missing.stderr rename to precompiles/utils/macro/tests/compile-fail/precompile/handle/missing.stderr diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/set-missing.rs b/precompiles/utils/macro/tests/compile-fail/precompile/handle/set-missing.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/set-missing.rs rename to precompiles/utils/macro/tests/compile-fail/precompile/handle/set-missing.rs diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/set-missing.stderr b/precompiles/utils/macro/tests/compile-fail/precompile/handle/set-missing.stderr similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/set-missing.stderr rename to precompiles/utils/macro/tests/compile-fail/precompile/handle/set-missing.stderr diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/set-wrong-type.rs b/precompiles/utils/macro/tests/compile-fail/precompile/handle/set-wrong-type.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/set-wrong-type.rs rename to precompiles/utils/macro/tests/compile-fail/precompile/handle/set-wrong-type.rs diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/set-wrong-type.stderr b/precompiles/utils/macro/tests/compile-fail/precompile/handle/set-wrong-type.stderr similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/set-wrong-type.stderr rename to precompiles/utils/macro/tests/compile-fail/precompile/handle/set-wrong-type.stderr diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/wrong-type.rs b/precompiles/utils/macro/tests/compile-fail/precompile/handle/wrong-type.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/wrong-type.rs rename to precompiles/utils/macro/tests/compile-fail/precompile/handle/wrong-type.rs diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/wrong-type.stderr b/precompiles/utils/macro/tests/compile-fail/precompile/handle/wrong-type.stderr similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/handle/wrong-type.stderr rename to precompiles/utils/macro/tests/compile-fail/precompile/handle/wrong-type.stderr diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/no-parameter.rs b/precompiles/utils/macro/tests/compile-fail/precompile/pre-check/no-parameter.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/no-parameter.rs rename to precompiles/utils/macro/tests/compile-fail/precompile/pre-check/no-parameter.rs diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/no-parameter.stderr b/precompiles/utils/macro/tests/compile-fail/precompile/pre-check/no-parameter.stderr similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/no-parameter.stderr rename to precompiles/utils/macro/tests/compile-fail/precompile/pre-check/no-parameter.stderr diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/too-many-parameters.rs b/precompiles/utils/macro/tests/compile-fail/precompile/pre-check/too-many-parameters.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/too-many-parameters.rs rename to precompiles/utils/macro/tests/compile-fail/precompile/pre-check/too-many-parameters.rs diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/too-many-parameters.stderr b/precompiles/utils/macro/tests/compile-fail/precompile/pre-check/too-many-parameters.stderr similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/too-many-parameters.stderr rename to precompiles/utils/macro/tests/compile-fail/precompile/pre-check/too-many-parameters.stderr diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/wrong-parameter.rs b/precompiles/utils/macro/tests/compile-fail/precompile/pre-check/wrong-parameter.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/wrong-parameter.rs rename to precompiles/utils/macro/tests/compile-fail/precompile/pre-check/wrong-parameter.rs diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/wrong-parameter.stderr b/precompiles/utils/macro/tests/compile-fail/precompile/pre-check/wrong-parameter.stderr similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/pre-check/wrong-parameter.stderr rename to precompiles/utils/macro/tests/compile-fail/precompile/pre-check/wrong-parameter.stderr diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/test-gen/generic-arg.rs b/precompiles/utils/macro/tests/compile-fail/precompile/test-gen/generic-arg.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/test-gen/generic-arg.rs rename to precompiles/utils/macro/tests/compile-fail/precompile/test-gen/generic-arg.rs diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile/test-gen/generic-arg.stderr b/precompiles/utils/macro/tests/compile-fail/precompile/test-gen/generic-arg.stderr similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile/test-gen/generic-arg.stderr rename to precompiles/utils/macro/tests/compile-fail/precompile/test-gen/generic-arg.stderr diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile_name/not_tuple.rs b/precompiles/utils/macro/tests/compile-fail/precompile_name/not_tuple.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile_name/not_tuple.rs rename to precompiles/utils/macro/tests/compile-fail/precompile_name/not_tuple.rs diff --git a/precompiles/utils_v2/macro/tests/compile-fail/precompile_name/not_tuple.stderr b/precompiles/utils/macro/tests/compile-fail/precompile_name/not_tuple.stderr similarity index 100% rename from precompiles/utils_v2/macro/tests/compile-fail/precompile_name/not_tuple.stderr rename to precompiles/utils/macro/tests/compile-fail/precompile_name/not_tuple.stderr diff --git a/precompiles/utils_v2/macro/tests/expand/precompile.expanded.rs b/precompiles/utils/macro/tests/expand/precompile.expanded.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/expand/precompile.expanded.rs rename to precompiles/utils/macro/tests/expand/precompile.expanded.rs diff --git a/precompiles/utils_v2/macro/tests/expand/precompile.rs b/precompiles/utils/macro/tests/expand/precompile.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/expand/precompile.rs rename to precompiles/utils/macro/tests/expand/precompile.rs diff --git a/precompiles/utils_v2/macro/tests/expand/precompile_name.expanded.rs b/precompiles/utils/macro/tests/expand/precompile_name.expanded.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/expand/precompile_name.expanded.rs rename to precompiles/utils/macro/tests/expand/precompile_name.expanded.rs diff --git a/precompiles/utils_v2/macro/tests/expand/precompile_name.rs b/precompiles/utils/macro/tests/expand/precompile_name.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/expand/precompile_name.rs rename to precompiles/utils/macro/tests/expand/precompile_name.rs diff --git a/precompiles/utils_v2/macro/tests/expand/precompileset.expanded.rs b/precompiles/utils/macro/tests/expand/precompileset.expanded.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/expand/precompileset.expanded.rs rename to precompiles/utils/macro/tests/expand/precompileset.expanded.rs diff --git a/precompiles/utils_v2/macro/tests/expand/precompileset.rs b/precompiles/utils/macro/tests/expand/precompileset.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/expand/precompileset.rs rename to precompiles/utils/macro/tests/expand/precompileset.rs diff --git a/precompiles/utils_v2/macro/tests/expand/returns_tuple.expanded.rs b/precompiles/utils/macro/tests/expand/returns_tuple.expanded.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/expand/returns_tuple.expanded.rs rename to precompiles/utils/macro/tests/expand/returns_tuple.expanded.rs diff --git a/precompiles/utils_v2/macro/tests/expand/returns_tuple.rs b/precompiles/utils/macro/tests/expand/returns_tuple.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/expand/returns_tuple.rs rename to precompiles/utils/macro/tests/expand/returns_tuple.rs diff --git a/precompiles/utils_v2/macro/tests/pass/derive_codec.rs b/precompiles/utils/macro/tests/pass/derive_codec.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/pass/derive_codec.rs rename to precompiles/utils/macro/tests/pass/derive_codec.rs diff --git a/precompiles/utils_v2/macro/tests/pass/precompile_fn_modifiers.rs b/precompiles/utils/macro/tests/pass/precompile_fn_modifiers.rs similarity index 100% rename from precompiles/utils_v2/macro/tests/pass/precompile_fn_modifiers.rs rename to precompiles/utils/macro/tests/pass/precompile_fn_modifiers.rs diff --git a/precompiles/utils/macro/tests/tests.rs b/precompiles/utils/macro/tests/tests.rs index 95ce768513..dd456be727 100644 --- a/precompiles/utils/macro/tests/tests.rs +++ b/precompiles/utils/macro/tests/tests.rs @@ -1,58 +1,56 @@ -// 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 +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. // -// This file is part of Utils package, originally developed by Purestake Inc. -// Utils package used in Astar Network in terms of GPLv3. +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. // -// Utils is free software: you can redistribute it and/or modify +// This program 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, +// +// This program 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 +// 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 sha3::{Digest, Keccak256}; +// along with this program. If not, see . -#[precompile_utils_macro::generate_function_selector] -pub enum Action { - Toto = "toto()", - Tata = "tata()", -} +use sp_core_hashing::keccak_256; #[test] fn test_keccak256() { assert_eq!( - &precompile_utils_macro::keccak256!(""), - Keccak256::digest(b"").as_slice(), + &precompile_utils_macro_v2::keccak256!(""), + keccak_256(b"").as_slice(), ); assert_eq!( - &precompile_utils_macro::keccak256!("toto()"), - Keccak256::digest(b"toto()").as_slice(), + &precompile_utils_macro_v2::keccak256!("toto()"), + keccak_256(b"toto()").as_slice(), ); assert_ne!( - &precompile_utils_macro::keccak256!("toto()"), - Keccak256::digest(b"tata()").as_slice(), + &precompile_utils_macro_v2::keccak256!("toto()"), + keccak_256(b"tata()").as_slice(), ); } #[test] -fn test_generate_function_selector() { - assert_eq!( - &(Action::Toto as u32).to_be_bytes()[..], - &Keccak256::digest(b"toto()")[0..4], - ); - assert_eq!( - &(Action::Tata as u32).to_be_bytes()[..], - &Keccak256::digest(b"tata()")[0..4], - ); - assert_ne!(Action::Toto as u32, Action::Tata as u32); +#[ignore] +fn ui() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/compile-fail/**/*.rs"); + t.pass("tests/pass/**/*.rs"); +} + +// Cargo expand is not supported on stable rust +#[test] +#[ignore] +fn expand() { + // Use `expand` to update the expansions + // Replace it with `expand_without_refresh` afterward so that + // CI checks the expension don't change + + // macrotest::expand("tests/expand/**/*.rs"); + macrotest::expand_without_refresh("tests/expand/**/*.rs"); } diff --git a/precompiles/utils/src/bytes.rs b/precompiles/utils/src/bytes.rs deleted file mode 100644 index f1e6994b96..0000000000 --- a/precompiles/utils/src/bytes.rs +++ /dev/null @@ -1,218 +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 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()); - } - - let data = inner_reader.read_raw_bytes(array_size)?; - - 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 deleted file mode 100644 index 3d4c1242cd..0000000000 --- a/precompiles/utils/src/data.rs +++ /dev/null @@ -1,731 +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::{revert, EvmResult}; - -use alloc::borrow::ToOwned; -use core::{any::type_name, marker::PhantomData, ops::Range}; -use impl_trait_for_tuples::impl_for_tuples; -use sp_core::{Get, H160, H256, U256}; -use sp_std::{convert::TryInto, vec, vec::Vec}; - -/// The `address` type of Solidity. -/// H160 could represent 2 types of data (bytes20 and address) that are not encoded the same way. -/// To avoid issues writing H160 is thus not supported. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct Address(pub H160); - -impl From for Address { - fn from(a: H160) -> Address { - Address(a) - } -} - -impl From
for H160 { - fn from(a: Address) -> H160 { - a.0 - } -} - -/// 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(Clone, Debug, Eq, PartialEq)] -pub struct Bytes(pub Vec); - -impl Bytes { - /// Interpret as `bytes`. - pub fn as_bytes(&self) -> &[u8] { - &self.0 - } - - /// Interpret as `string`. - /// Can fail if the string is not valid UTF8. - pub fn as_str(&self) -> Result<&str, sp_std::str::Utf8Error> { - sp_std::str::from_utf8(&self.0) - } -} - -impl From<&[u8]> for Bytes { - fn from(a: &[u8]) -> Self { - Self(a.to_owned()) - } -} - -impl From<&str> for Bytes { - fn from(a: &str) -> Self { - a.as_bytes().into() - } -} - -impl From for Vec { - fn from(b: Bytes) -> Vec { - b.0 - } -} - -/// Wrapper around an EVM input slice, helping to parse it. -/// Provide functions to parse common types. -#[derive(Clone, Copy, Debug)] -pub struct EvmDataReader<'a> { - input: &'a [u8], - cursor: usize, -} - -impl<'a> EvmDataReader<'a> { - /// Create a new input parser. - pub fn new(input: &'a [u8]) -> Self { - Self { input, cursor: 0 } - } - - /// Create a new input parser from a selector-initial input. - pub fn read_selector(input: &'a [u8]) -> EvmResult - where - T: num_enum::TryFromPrimitive, - { - if input.len() < 4 { - return Err(revert("tried to parse selector out of bounds")); - } - - let mut buffer = [0u8; 4]; - buffer.copy_from_slice(&input[0..4]); - let selector = T::try_from_primitive(u32::from_be_bytes(buffer)).map_err(|_| { - log::trace!( - target: "precompile-utils", - "Failed to match function selector for {}", - type_name::() - ); - revert("unknown selector") - })?; - - Ok(selector) - } - - /// Create a new input parser from a selector-initial input. - pub fn new_skip_selector(input: &'a [u8]) -> EvmResult { - if input.len() < 4 { - return Err(revert("input is too short")); - } - - Ok(Self::new(&input[4..])) - } - - /// Check the input has at least the correct amount of arguments before the end (32 bytes values). - pub fn expect_arguments(&self, args: usize) -> EvmResult { - if self.input.len() >= self.cursor + args * 32 { - Ok(()) - } else { - Err(revert("input doesn't match expected length")) - } - } - - /// Read data from the input. - pub fn read(&mut self) -> EvmResult { - T::read(self) - } - - /// Read raw bytes from the input. - /// Doesn't handle any alignment checks, prefer using `read` instead of possible. - /// Returns an error if trying to parse out of bounds. - pub fn read_raw_bytes(&mut self, len: usize) -> EvmResult<&[u8]> { - let range = self.move_cursor(len)?; - - let data = self - .input - .get(range) - .ok_or_else(|| revert("tried to parse raw bytes out of bounds"))?; - - Ok(data) - } - - /// Reads a pointer, returning a reader targetting the pointed location. - pub fn read_pointer(&mut self) -> EvmResult { - let offset: usize = self - .read::() - .map_err(|_| revert("tried to parse array offset out of bounds"))? - .try_into() - .map_err(|_| revert("array offset is too large"))?; - - if offset >= self.input.len() { - return Err(revert("pointer points out of bounds")); - } - - Ok(Self { - input: &self.input[offset..], - cursor: 0, - }) - } - - /// Read remaining bytes - pub fn read_till_end(&mut self) -> EvmResult<&[u8]> { - let range = self.move_cursor(self.input.len() - self.cursor)?; - - let data = self - .input - .get(range) - .ok_or_else(|| revert("tried to parse raw bytes out of bounds"))?; - - 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. - fn move_cursor(&mut self, len: usize) -> EvmResult> { - let start = self.cursor; - let end = self - .cursor - .checked_add(len) - .ok_or_else(|| revert("data reading cursor overflow"))?; - - self.cursor = end; - - Ok(start..end) - } -} - -/// Help build an EVM input/output data. -/// -/// Functions takes `self` to allow chaining all calls like -/// `EvmDataWriter::new().write(...).write(...).build()`. -/// While it could be more ergonomic to take &mut self, this would -/// prevent to have a `build` function that don't clone the output. -#[derive(Clone, Debug)] -pub struct EvmDataWriter { - pub(crate) data: Vec, - offset_data: Vec, - selector: Option, -} - -#[derive(Clone, Debug)] -struct OffsetDatum { - // Offset location in the container data. - offset_position: usize, - // Data pointed by the offset that must be inserted at the end of container data. - data: Vec, - // Inside of arrays, the offset is not from the start of array data (length), but from the start - // of the item. This shift allow to correct this. - offset_shift: usize, -} - -impl EvmDataWriter { - /// Creates a new empty output builder (without selector). - pub fn new() -> Self { - Self { - data: vec![], - offset_data: vec![], - selector: None, - } - } - - /// Creates a new empty output builder with provided selector. - /// Selector will only be appended before the data when calling - /// `build` to not mess with the offsets. - pub fn new_with_selector(selector: impl Into) -> Self { - Self { - data: vec![], - offset_data: vec![], - selector: Some(selector.into()), - } - } - - /// Return the built data. - pub fn build(mut self) -> Vec { - Self::bake_offsets(&mut self.data, self.offset_data); - - if let Some(selector) = self.selector { - let mut output = selector.to_be_bytes().to_vec(); - output.append(&mut self.data); - output - } else { - self.data - } - } - - /// Add offseted data at the end of this writer's data, updating the offsets. - fn bake_offsets(output: &mut Vec, offsets: Vec) { - for mut offset_datum in offsets { - let offset_position = offset_datum.offset_position; - let offset_position_end = offset_position + 32; - - // The offset is the distance between the start of the data and the - // start of the pointed data (start of a struct, length of an array). - // Offsets in inner data are relative to the start of their respective "container". - // However in arrays the "container" is actually the item itself instead of the whole - // array, which is corrected by `offset_shift`. - let free_space_offset = output.len() - offset_datum.offset_shift; - - // Override dummy offset to the offset it will be in the final output. - U256::from(free_space_offset) - .to_big_endian(&mut output[offset_position..offset_position_end]); - - // Append this data at the end of the current output. - output.append(&mut offset_datum.data); - } - } - - /// 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 { - self.data.extend_from_slice(value); - self - } - - /// Write data of requested type. - pub fn write(mut self, value: T) -> Self { - T::write(&mut self, value); - self - } - - /// Writes a pointer to given data. - /// The data will be appended when calling `build`. - /// Initially write a dummy value as offset in this writer's data, which will be replaced by - /// the correct offset once the pointed data is appended. - /// - /// Takes `&mut self` since its goal is to be used inside `EvmData` impl and not in chains. - pub fn write_pointer(&mut self, data: Vec) { - let offset_position = self.data.len(); - H256::write(self, H256::repeat_byte(0xff)); - - self.offset_data.push(OffsetDatum { - offset_position, - data, - offset_shift: 0, - }); - } -} - -impl Default for EvmDataWriter { - fn default() -> Self { - Self::new() - } -} - -/// Data that can be converted from and to EVM data types. -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 { - for_tuples!(#( Tuple::has_static_size() )&*) - } - - fn read(reader: &mut EvmDataReader) -> EvmResult { - if !Self::has_static_size() { - let reader = &mut reader.read_pointer()?; - Ok(for_tuples!( ( #( reader.read::()? ),* ) )) - } else { - Ok(for_tuples!( ( #( reader.read::()? ),* ) )) - } - } - - fn write(writer: &mut EvmDataWriter, value: Self) { - if !Self::has_static_size() { - let mut inner_writer = EvmDataWriter::new(); - for_tuples!( #( Tuple::write(&mut inner_writer, value.Tuple); )* ); - writer.write_pointer(inner_writer.build()); - } else { - for_tuples!( #( Tuple::write(writer, value.Tuple); )* ); - } - } -} - -impl EvmData for H256 { - fn read(reader: &mut EvmDataReader) -> EvmResult { - let range = reader.move_cursor(32)?; - - let data = reader - .input - .get(range) - .ok_or_else(|| revert("tried to parse H256 out of bounds"))?; - - Ok(H256::from_slice(data)) - } - - fn write(writer: &mut EvmDataWriter, value: Self) { - writer.data.extend_from_slice(value.as_bytes()); - } - - fn has_static_size() -> bool { - true - } -} - -impl EvmData for Address { - fn read(reader: &mut EvmDataReader) -> EvmResult { - let range = reader.move_cursor(32)?; - - let data = reader - .input - .get(range) - .ok_or_else(|| revert("tried to parse H160 out of bounds"))?; - - Ok(H160::from_slice(&data[12..32]).into()) - } - - fn write(writer: &mut EvmDataWriter, value: Self) { - H256::write(writer, value.0.into()); - } - - fn has_static_size() -> bool { - true - } -} - -impl EvmData for U256 { - fn read(reader: &mut EvmDataReader) -> EvmResult { - let range = reader.move_cursor(32)?; - - let data = reader - .input - .get(range) - .ok_or_else(|| revert("tried to parse U256 out of bounds"))?; - - Ok(U256::from_big_endian(data)) - } - - fn write(writer: &mut EvmDataWriter, value: Self) { - let mut buffer = [0u8; 32]; - value.to_big_endian(&mut buffer); - writer.data.extend_from_slice(&buffer); - } - - fn has_static_size() -> bool { - true - } -} - -macro_rules! impl_evmdata_for_uints { - ($($uint:ty, )*) => { - $( - impl EvmData for $uint { - fn read(reader: &mut EvmDataReader) -> EvmResult { - let range = reader.move_cursor(32)?; - - let data = reader - .input - .get(range) - .ok_or_else(|| revert(alloc::format!( - "tried to parse {} out of bounds", core::any::type_name::() - )))?; - - let mut buffer = [0u8; core::mem::size_of::()]; - buffer.copy_from_slice(&data[32 - core::mem::size_of::()..]); - Ok(Self::from_be_bytes(buffer)) - } - - fn write(writer: &mut EvmDataWriter, value: Self) { - let mut buffer = [0u8; 32]; - buffer[32 - core::mem::size_of::()..].copy_from_slice(&value.to_be_bytes()); - writer.data.extend_from_slice(&buffer); - } - - fn has_static_size() -> bool { - true - } - } - )* - }; -} - -impl_evmdata_for_uints!(u16, u32, u64, u128,); - -// The implementation for u8 is specific, for performance reasons. -impl EvmData for u8 { - fn read(reader: &mut EvmDataReader) -> EvmResult { - let range = reader.move_cursor(32)?; - - let data = reader - .input - .get(range) - .ok_or_else(|| revert("tried to parse u64 out of bounds"))?; - - Ok(data[31]) - } - - fn write(writer: &mut EvmDataWriter, value: Self) { - let mut buffer = [0u8; 32]; - buffer[31] = value; - - writer.data.extend_from_slice(&buffer); - } - - fn has_static_size() -> bool { - true - } -} - -impl EvmData for bool { - fn read(reader: &mut EvmDataReader) -> EvmResult { - let h256 = H256::read(reader).map_err(|_| revert("tried to parse bool out of bounds"))?; - - Ok(!h256.is_zero()) - } - - fn write(writer: &mut EvmDataWriter, value: Self) { - let mut buffer = [0u8; 32]; - if value { - buffer[31] = 1; - } - - writer.data.extend_from_slice(&buffer); - } - - fn has_static_size() -> bool { - true - } -} - -impl EvmData for Vec { - fn read(reader: &mut EvmDataReader) -> EvmResult { - let mut inner_reader = reader.read_pointer()?; - - let array_size: usize = inner_reader - .read::() - .map_err(|_| revert("tried to parse array length out of bounds"))? - .try_into() - .map_err(|_| revert("array length is too large"))?; - - let mut array = vec![]; - - let mut item_reader = EvmDataReader { - input: inner_reader - .input - .get(32..) - .ok_or_else(|| revert("try to read array items out of bound"))?, - cursor: 0, - }; - - for _ in 0..array_size { - array.push(item_reader.read()?); - } - - Ok(array) - } - - fn write(writer: &mut EvmDataWriter, value: Self) { - 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 EvmData for Bytes { - 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("tried to parse bytes/string length out of bounds"))? - .try_into() - .map_err(|_| revert("bytes/string length is too large"))?; - - // Get valid range over the bytes data. - let range = inner_reader.move_cursor(array_size)?; - - let data = inner_reader - .input - .get(range) - .ok_or_else(|| revert("tried to parse bytes/string out of bounds"))?; - - let bytes = Self(data.to_owned()); - - Ok(bytes) - } - - fn write(writer: &mut EvmDataWriter, value: Self) { - let length = value.0.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.0.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 - } -} - -/// 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_v2/src/evm/costs.rs b/precompiles/utils/src/evm/costs.rs similarity index 100% rename from precompiles/utils_v2/src/evm/costs.rs rename to precompiles/utils/src/evm/costs.rs diff --git a/precompiles/utils_v2/src/evm/handle.rs b/precompiles/utils/src/evm/handle.rs similarity index 100% rename from precompiles/utils_v2/src/evm/handle.rs rename to precompiles/utils/src/evm/handle.rs diff --git a/precompiles/utils_v2/src/evm/logs.rs b/precompiles/utils/src/evm/logs.rs similarity index 100% rename from precompiles/utils_v2/src/evm/logs.rs rename to precompiles/utils/src/evm/logs.rs diff --git a/precompiles/utils_v2/src/evm/mod.rs b/precompiles/utils/src/evm/mod.rs similarity index 100% rename from precompiles/utils_v2/src/evm/mod.rs rename to precompiles/utils/src/evm/mod.rs diff --git a/precompiles/utils/src/lib.rs b/precompiles/utils/src/lib.rs index a58db7f77a..10fd3cad5f 100644 --- a/precompiles/utils/src/lib.rs +++ b/precompiles/utils/src/lib.rs @@ -1,491 +1,83 @@ -// 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 +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. // -// This file is part of Utils package, originally developed by Purestake Inc. -// Utils package used in Astar Network in terms of GPLv3. +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. // -// Utils is free software: you can redistribute it and/or modify +// This program 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, +// +// This program 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 +// 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 . +// along with this program. If not, see . #![cfg_attr(not(feature = "std"), no_std)] extern crate alloc; -use crate::alloc::borrow::ToOwned; -pub use alloc::string::String; -use fp_evm::{ - Context, ExitError, ExitRevert, ExitSucceed, PrecompileFailure, PrecompileHandle, - PrecompileOutput, -}; -use frame_support::{ - dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, - pallet_prelude::Weight, - traits::Get, -}; -use pallet_evm::{GasWeightMapping, Log}; -use sp_core::{H160, H256, U256}; -use sp_std::{marker::PhantomData, vec, vec::Vec}; +// Allows to use inside this crate `solidity::Codec` derive macro,which depends on +// `precompile_utils` being in the list of imported crates. +extern crate self as precompile_utils; -pub mod bytes; -pub mod data; -pub mod xcm; +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}; +pub mod solidity; #[cfg(feature = "testing")] pub mod testing; -#[cfg(test)] -mod tests; -/// Alias for Result returning an EVM precompile error. -pub type EvmResult = Result; +use fp_evm::PrecompileFailure; -/// Return an error with provided (static) text. -/// Using the `revert` function of `Gasometer` is preferred as erroring -/// consumed all the gas limit and the error message is not easily -/// retrievable. -pub fn error>>(text: T) -> PrecompileFailure { - PrecompileFailure::Error { - exit_status: ExitError::Other(text.into()), - } -} - -/// Builder for PrecompileOutput. -#[derive(Clone, Debug)] -pub struct LogsBuilder { - address: H160, -} +// pub mod data; -impl LogsBuilder { - /// Create a new builder with no logs. - /// Takes the address of the precompile (usually `context.address`). - pub fn new(address: H160) -> Self { - Self { address } - } - - /// Create a 0-topic log. - #[must_use] - pub fn log0(&self, data: impl Into>) -> Log { - Log { - address: self.address, - topics: vec![], - data: data.into(), - } - } - - /// Create a 1-topic log. - #[must_use] - pub fn log1(&self, topic0: impl Into, data: impl Into>) -> Log { - Log { - address: self.address, - topics: vec![topic0.into()], - data: data.into(), - } - } - - /// Create a 2-topics log. - #[must_use] - pub fn log2( - &self, - topic0: impl Into, - topic1: impl Into, - data: impl Into>, - ) -> Log { - Log { - address: self.address, - topics: vec![topic0.into(), topic1.into()], - data: data.into(), - } - } - - /// Create a 3-topics log. - #[must_use] - pub fn log3( - &self, - topic0: impl Into, - topic1: impl Into, - topic2: impl Into, - data: impl Into>, - ) -> Log { - Log { - address: self.address, - topics: vec![topic0.into(), topic1.into(), topic2.into()], - data: data.into(), - } - } - - /// Create a 4-topics log. - #[must_use] - pub fn log4( - &self, - topic0: impl Into, - topic1: impl Into, - topic2: impl Into, - topic3: impl Into, - data: impl Into>, - ) -> Log { - Log { - address: self.address, - 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 { - log_costs(self.topics.len(), self.data.len()) - } -} - -/// Helper functions requiring a 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, -{ - #[inline(always)] - pub fn record_weight_v2_cost( - handle: &mut impl PrecompileHandle, - weight: Weight, - ) -> Result<(), ExitError> { - // Make sure there is enough gas. - let remaining_gas = handle.remaining_gas(); - let required_gas = Runtime::GasWeightMapping::weight_to_gas(weight); - if required_gas > remaining_gas { - return Err(ExitError::OutOfGas); - } - - // Make sure there is enough remaining weight - handle.record_external_cost(None, Some(weight.proof_size())) - } - - #[inline(always)] - pub fn refund_weight_v2_cost( - handle: &mut impl PrecompileHandle, - weight: Weight, - maybe_actual_weight: Option, - ) -> Result { - // Refund weights and compute used weight them record used gas - let used_weight = if let Some(actual_weight) = maybe_actual_weight { - let refund_weight = weight - actual_weight; - handle.refund_external_cost(None, Some(refund_weight.proof_size())); - actual_weight - } else { - weight - }; - let used_gas = Runtime::GasWeightMapping::weight_to_gas(used_weight); - handle.record_cost(used_gas)?; - Ok(used_gas) - } - - /// 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 PrecompileHandleExt, - origin: ::RuntimeOrigin, - call: Call, - ) -> EvmResult<()> - where - Runtime::RuntimeCall: From, - { - let call = Runtime::RuntimeCall::from(call); - let dispatch_info = call.get_dispatch_info(); - - // Make sure there is enough gas. - Self::record_weight_v2_cost(handle, dispatch_info.weight)?; - - // 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 = call - .dispatch(origin) - .map_err(|e| revert(alloc::format!("Dispatched call failed with error: {:?}", e)))?; - - Self::refund_weight_v2_cost( - handle, - dispatch_info.weight, - post_dispatch_info.actual_weight, - )?; - - Ok(()) - } -} - -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), - ) - } -} - -/// Represents modifiers a Solidity function can be annotated with. -#[derive(Copy, Clone, PartialEq, Eq)] -pub enum FunctionModifier { - /// Function that doesn't modify the state. - View, - /// Function that modifies the state but refuse receiving funds. - /// Correspond to a Solidity function with no modifiers. - NonPayable, - /// Function that modifies the state and accept funds. - Payable, -} +// pub use data::{solidity::Codec, Reader, Writer}; +pub use fp_evm::Precompile; +pub use precompile_utils_macro_v2::{keccak256, precompile, precompile_name_from_address}; -pub trait PrecompileHandleExt: PrecompileHandle { - #[must_use] - /// Record cost of a log manually. - /// This can be useful to record log costs early when their content have static size. - fn record_log_costs_manual(&mut self, topics: usize, data_len: usize) -> EvmResult; - - #[must_use] - /// Record cost of logs. - 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] - /// Read the selector from the input data. - fn read_selector(&self) -> EvmResult - where - T: num_enum::TryFromPrimitive; - - #[must_use] - /// Returns a reader of the input, skipping the selector. - fn read_input(&self) -> EvmResult; - - /// Record cost of one DB read manually. - /// The expected key & value data length should be provided. - #[must_use] - fn record_db_read( - &mut self, - data_length: usize, - ) -> Result<(), ExitError>; -} - -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) -} - -impl PrecompileHandleExt for T { - #[must_use] - /// Record cost of a log manualy. - /// This can be useful to record log costs early when their content have static size. - fn record_log_costs_manual(&mut self, topics: usize, data_len: usize) -> EvmResult { - self.record_cost(log_costs(topics, data_len)?)?; - - Ok(()) - } - - #[must_use] - /// Record cost of logs. - 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] - /// Read the selector from the input data. - fn read_selector(&self) -> EvmResult - where - S: num_enum::TryFromPrimitive, - { - EvmDataReader::read_selector(self.input()) - } - - #[must_use] - /// Returns a reader of the input, skipping the selector. - fn read_input(&self) -> EvmResult { - EvmDataReader::new_skip_selector(self.input()) - } - - #[must_use] - fn record_db_read( - &mut self, - data_length: usize, - ) -> Result<(), ExitError> { - self.record_cost(RuntimeHelper::::db_read_gas_cost())?; - self.record_external_cost(None, Some(data_length as u64)) - } -} - -#[must_use] -pub fn revert(output: impl AsRef<[u8]>) -> PrecompileFailure { - PrecompileFailure::Revert { - exit_status: ExitRevert::Reverted, - output: output.as_ref().to_owned(), - } -} - -#[must_use] -pub fn succeed(output: impl AsRef<[u8]>) -> PrecompileOutput { - PrecompileOutput { - exit_status: ExitSucceed::Returned, - output: output.as_ref().to_owned(), - } -} - -#[must_use] -/// Check that a function call is compatible with the context it is -/// called into. -pub fn check_function_modifier( - context: &Context, - is_static: bool, - modifier: FunctionModifier, -) -> EvmResult { - if is_static && modifier != FunctionModifier::View { - return Err(revert("can't call non-static function in static context")); - } - - if modifier != FunctionModifier::Payable && context.apparent_value > U256::zero() { - return Err(revert("function is not payable")); - } +/// Alias for Result returning an EVM precompile error. +pub type EvmResult = Result; - Ok(()) +pub mod prelude { + pub use { + crate::{ + evm::{ + handle::PrecompileHandleExt, + logs::{log0, log1, log2, log3, log4, LogExt}, + }, + precompile_set::DiscriminantResult, + solidity::{ + // We export solidity itself to encourage using `solidity::Codec` to avoid confusion + // with parity_scale_codec, + self, + codec::{ + Address, + BoundedBytes, + BoundedString, + BoundedVec, + // Allow usage of Codec methods while not exporting the name directly. + Codec as _, + Convert, + UnboundedBytes, + UnboundedString, + }, + revert::{ + revert, BacktraceExt, InjectBacktrace, MayRevert, Revert, RevertExt, + RevertReason, + }, + }, + substrate::{RuntimeHelper, TryDispatchError}, + EvmResult, + }, + alloc::string::String, + pallet_evm::{PrecompileHandle, PrecompileOutput}, + precompile_utils_macro_v2::{keccak256, precompile}, + }; } diff --git a/precompiles/utils_v2/src/precompile_set.rs b/precompiles/utils/src/precompile_set.rs similarity index 99% rename from precompiles/utils_v2/src/precompile_set.rs rename to precompiles/utils/src/precompile_set.rs index 8625906c8f..836eeb9815 100644 --- a/precompiles/utils_v2/src/precompile_set.rs +++ b/precompiles/utils/src/precompile_set.rs @@ -32,7 +32,6 @@ use fp_evm::{ }; 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, @@ -1086,12 +1085,8 @@ impl PrecompileSetBuilder } /// Return the list of addresses contained in this PrecompileSet. - pub fn used_addresses() -> impl Iterator { - Self::new() - .inner - .used_addresses() - .into_iter() - .map(R::AddressMapping::into_account_id) + pub fn used_addresses() -> impl Iterator { + Self::new().inner.used_addresses().into_iter() } pub fn summarize_checks(&self) -> Vec { diff --git a/precompiles/utils_v2/src/solidity/codec/bytes.rs b/precompiles/utils/src/solidity/codec/bytes.rs similarity index 100% rename from precompiles/utils_v2/src/solidity/codec/bytes.rs rename to precompiles/utils/src/solidity/codec/bytes.rs diff --git a/precompiles/utils_v2/src/solidity/codec/mod.rs b/precompiles/utils/src/solidity/codec/mod.rs similarity index 99% rename from precompiles/utils_v2/src/solidity/codec/mod.rs rename to precompiles/utils/src/solidity/codec/mod.rs index 223213799b..1ebf6a32d1 100644 --- a/precompiles/utils_v2/src/solidity/codec/mod.rs +++ b/precompiles/utils/src/solidity/codec/mod.rs @@ -23,6 +23,9 @@ pub mod bytes; pub mod native; +#[cfg(any(feature = "codec-xcm", test))] +pub mod xcm; + use crate::solidity::revert::{MayRevert, RevertReason}; use core::{marker::PhantomData, ops::Range}; use sp_core::{H256, U256}; diff --git a/precompiles/utils_v2/src/solidity/codec/native.rs b/precompiles/utils/src/solidity/codec/native.rs similarity index 100% rename from precompiles/utils_v2/src/solidity/codec/native.rs rename to precompiles/utils/src/solidity/codec/native.rs diff --git a/precompiles/utils/src/xcm.rs b/precompiles/utils/src/solidity/codec/xcm.rs similarity index 68% rename from precompiles/utils/src/xcm.rs rename to precompiles/utils/src/solidity/codec/xcm.rs index 16897c774a..4928b3def1 100644 --- a/precompiles/utils/src/xcm.rs +++ b/precompiles/utils/src/solidity/codec/xcm.rs @@ -1,40 +1,38 @@ -// 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 +// 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. -// Utils is distributed in the hope that it will be useful, +// 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 Utils. If not, see . +// along with Moonbeam. If not, see . //! Encoding of XCM types for solidity -use crate::Address; -use sp_core::U256; -use sp_runtime::traits::Zero; use { - crate::{bytes::*, revert, EvmData, EvmDataReader, EvmDataWriter, EvmResult}, - frame_support::{ensure, pallet_prelude::Weight, traits::ConstU32}, + crate::solidity::{ + codec::{bytes::*, Codec, Reader, Writer}, + revert::{BacktraceExt, InjectBacktrace, MayRevert, RevertReason}, + }, + alloc::string::String, + frame_support::{ensure, traits::ConstU32}, sp_core::H256, sp_std::vec::Vec, xcm::latest::{Junction, Junctions, MultiLocation, NetworkId}, }; + pub const JUNCTION_SIZE_LIMIT: u32 = 2u32.pow(16); // Function to convert network id to bytes +// We don't implement solidity::Codec here as these bytes will be appended only +// to certain Junction variants // Each NetworkId variant is represented as bytes // The first byte represents the enum variant to be used. // - Indexes 0,2,3 represent XCM V2 variants @@ -111,23 +109,28 @@ pub(crate) fn network_id_to_bytes(network_id: Option) -> Vec { } // Function to convert bytes to networkId -pub(crate) fn network_id_from_bytes(encoded_bytes: Vec) -> EvmResult> { - ensure!(encoded_bytes.len() > 0, revert("Junctions cannot be empty")); - let mut encoded_network_id = EvmDataReader::new(&encoded_bytes); +pub(crate) fn network_id_from_bytes(encoded_bytes: Vec) -> MayRevert> { + ensure!( + encoded_bytes.len() > 0, + RevertReason::custom("Junctions cannot be empty") + ); + let mut encoded_network_id = Reader::new(&encoded_bytes); let network_selector = encoded_network_id .read_raw_bytes(1) - .map_err(|_| revert("network selector (1 byte)"))?; + .map_err(|_| RevertReason::read_out_of_bounds("network selector (1 byte)"))?; match network_selector[0] { 0 => Ok(None), 1 => Ok(Some(NetworkId::ByGenesis( encoded_network_id .read_till_end() - .map_err(|_| revert("can't read till end"))? + .in_field("genesis")? .to_vec() .try_into() - .map_err(|_| revert("network by genesis"))?, + .map_err(|_| { + RevertReason::value_is_too_large("network by genesis").in_field("genesis") + })?, ))), 2 => Ok(Some(NetworkId::Polkadot)), 3 => Ok(Some(NetworkId::Kusama)), @@ -154,27 +157,27 @@ pub(crate) fn network_id_from_bytes(encoded_bytes: Vec) -> EvmResult Ok(Some(NetworkId::BitcoinCore)), 10 => Ok(Some(NetworkId::BitcoinCash)), - _ => Err(revert("Non-valid Network Id").into()), + _ => Err(RevertReason::custom("Non-valid Network Id").into()), } } -impl EvmData for Junction { - fn read(reader: &mut EvmDataReader) -> EvmResult { +impl Codec for Junction { + fn read(reader: &mut Reader) -> MayRevert { let junction = reader.read::>>()?; let junction_bytes: Vec<_> = junction.into(); ensure!( junction_bytes.len() > 0, - revert("Junctions cannot be empty") + RevertReason::custom("Junctions cannot be empty") ); // For simplicity we use an EvmReader here - let mut encoded_junction = EvmDataReader::new(&junction_bytes); + let mut encoded_junction = Reader::new(&junction_bytes); // We take the first byte let enum_selector = encoded_junction .read_raw_bytes(1) - .map_err(|_| revert("junction variant"))?; + .map_err(|_| RevertReason::read_out_of_bounds("junction variant"))?; // The firs byte selects the enum variant match enum_selector[0] { @@ -230,30 +233,27 @@ impl EvmData for Junction { 6 => { let length = encoded_junction .read_raw_bytes(1) - .map_err(|_| revert("General Key length"))?[0]; + .map_err(|_| RevertReason::read_out_of_bounds("General Key length"))?[0]; - let data = encoded_junction - .read::() - .map_err(|_| revert("can't read"))? - .into(); + let data = encoded_junction.read::().in_field("data")?.into(); Ok(Junction::GeneralKey { length, data }) } 7 => Ok(Junction::OnlyChild), - 8 => Err(revert("Junction::Plurality not supported yet").into()), + 8 => Err(RevertReason::custom("Junction::Plurality not supported yet").into()), 9 => { let network = encoded_junction.read_till_end()?.to_vec(); if let Some(network_id) = network_id_from_bytes(network)? { Ok(Junction::GlobalConsensus(network_id)) } else { - Err(revert("Unknown NetworkId").into()) + Err(RevertReason::custom("Unknown NetworkId").into()) } } - _ => Err(revert("Unknown Junction variant").into()), + _ => Err(RevertReason::custom("Unknown Junction variant").into()), } } - fn write(writer: &mut EvmDataWriter, value: Self) { + fn write(writer: &mut Writer, value: Self) { let mut encoded: Vec = Vec::new(); let encoded_bytes: UnboundedBytes = match value { Junction::Parachain(para_id) => { @@ -308,159 +308,63 @@ impl EvmData for Junction { // type that we need to evaluate how to support _ => unreachable!("Junction::Plurality not supported yet"), }; - EvmData::write(writer, encoded_bytes); + Codec::write(writer, encoded_bytes); } fn has_static_size() -> bool { false } + + fn signature() -> String { + UnboundedBytes::signature() + } } -impl EvmData for Junctions { - fn read(reader: &mut EvmDataReader) -> EvmResult { +impl Codec for Junctions { + fn read(reader: &mut Reader) -> MayRevert { let junctions_bytes: Vec = reader.read()?; let mut junctions = Junctions::Here; for item in junctions_bytes { junctions .push(item) - .map_err(|_| revert("overflow when reading junctions"))?; + .map_err(|_| RevertReason::custom("overflow when reading junctions"))?; } Ok(junctions) } - fn write(writer: &mut EvmDataWriter, value: Self) { + fn write(writer: &mut Writer, value: Self) { let encoded: Vec = value.iter().map(|junction| junction.clone()).collect(); - EvmData::write(writer, encoded); + Codec::write(writer, encoded); } fn has_static_size() -> bool { false } + + fn signature() -> String { + Vec::::signature() + } } // Cannot used derive macro since it is a foreign struct. -impl EvmData for MultiLocation { - fn read(reader: &mut EvmDataReader) -> EvmResult { - let (parents, interior) = reader.read()?; +impl Codec for MultiLocation { + fn read(reader: &mut Reader) -> MayRevert { + let (parents, interior) = reader + .read() + .map_in_tuple_to_field(&["parents", "interior"])?; Ok(MultiLocation { parents, interior }) } - fn write(writer: &mut EvmDataWriter, value: Self) { - EvmData::write(writer, (value.parents, value.interior)); + fn write(writer: &mut Writer, value: Self) { + Codec::write(writer, (value.parents, value.interior)); } fn has_static_size() -> bool { <(u8, Junctions)>::has_static_size() } -} - -#[derive(Debug, Clone)] -pub struct WeightV2 { - ref_time: u64, - proof_size: u64, -} -impl WeightV2 { - pub fn from(ref_time: u64, proof_size: u64) -> Self { - WeightV2 { - ref_time, - proof_size, - } - } - pub fn get_weight(&self) -> Weight { - Weight::from_parts(self.ref_time, self.proof_size) - } - pub fn is_zero(&self) -> bool { - self.ref_time.is_zero() - } -} -impl EvmData for WeightV2 { - fn read(reader: &mut EvmDataReader) -> EvmResult { - let (ref_time, proof_size) = reader.read()?; - Ok(WeightV2 { - ref_time, - proof_size, - }) - } - fn write(writer: &mut EvmDataWriter, value: Self) { - EvmData::write(writer, (value.ref_time, value.proof_size)); - } - - fn has_static_size() -> bool { - <(U256, U256)>::has_static_size() - } -} -#[derive(Debug)] -pub struct EvmMultiAsset { - location: MultiLocation, - amount: U256, -} - -impl EvmMultiAsset { - pub fn get_location(&self) -> MultiLocation { - self.location - } - pub fn get_amount(&self) -> U256 { - self.amount - } -} -impl From<(MultiLocation, U256)> for EvmMultiAsset { - fn from(tuple: (MultiLocation, U256)) -> Self { - EvmMultiAsset { - location: tuple.0, - amount: tuple.1, - } - } -} -impl EvmData for EvmMultiAsset { - fn read(reader: &mut EvmDataReader) -> EvmResult { - let (location, amount) = reader.read()?; - Ok(EvmMultiAsset { location, amount }) - } - - fn write(writer: &mut EvmDataWriter, value: Self) { - EvmData::write(writer, (value.location, value.amount)); - } - - fn has_static_size() -> bool { - <(MultiLocation, U256)>::has_static_size() - } -} - -pub struct Currency { - address: Address, - amount: U256, -} - -impl Currency { - pub fn get_address(&self) -> Address { - self.address - } - pub fn get_amount(&self) -> U256 { - self.amount - } -} -impl From<(Address, U256)> for Currency { - fn from(tuple: (Address, U256)) -> Self { - Currency { - address: tuple.0, - amount: tuple.1, - } - } -} - -impl EvmData for Currency { - fn read(reader: &mut EvmDataReader) -> EvmResult { - let (address, amount) = reader.read()?; - Ok(Currency { address, amount }) - } - - fn write(writer: &mut EvmDataWriter, value: Self) { - EvmData::write(writer, (value.address, value.amount)); - } - - fn has_static_size() -> bool { - <(Address, U256)>::has_static_size() + fn signature() -> String { + <(u8, Junctions)>::signature() } } diff --git a/precompiles/utils_v2/src/solidity/mod.rs b/precompiles/utils/src/solidity/mod.rs similarity index 100% rename from precompiles/utils_v2/src/solidity/mod.rs rename to precompiles/utils/src/solidity/mod.rs diff --git a/precompiles/utils_v2/src/solidity/modifier.rs b/precompiles/utils/src/solidity/modifier.rs similarity index 100% rename from precompiles/utils_v2/src/solidity/modifier.rs rename to precompiles/utils/src/solidity/modifier.rs diff --git a/precompiles/utils_v2/src/solidity/revert.rs b/precompiles/utils/src/solidity/revert.rs similarity index 100% rename from precompiles/utils_v2/src/solidity/revert.rs rename to precompiles/utils/src/solidity/revert.rs diff --git a/precompiles/utils_v2/src/substrate.rs b/precompiles/utils/src/substrate.rs similarity index 100% rename from precompiles/utils_v2/src/substrate.rs rename to precompiles/utils/src/substrate.rs diff --git a/precompiles/utils/src/testing/account.rs b/precompiles/utils/src/testing/account.rs index 66c5a13714..ec9bcf964a 100644 --- a/precompiles/utils/src/testing/account.rs +++ b/precompiles/utils/src/testing/account.rs @@ -1,30 +1,26 @@ -// 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 +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. // -// This file is part of Utils package, originally developed by Purestake Inc. -// Utils package used in Astar Network in terms of GPLv3. +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. // -// Utils is free software: you can redistribute it and/or modify +// This program 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, +// +// This program 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 +// 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 { - pallet_evm::AddressMapping, - scale_info::TypeInfo, - serde::{Deserialize, Serialize}, - sp_core::{Decode, Encode, MaxEncodedLen, H160, H256}, -}; +// along with this program. If not, see . + +use pallet_evm::AddressMapping; +use scale_info::TypeInfo; +use serde::{Deserialize, Serialize}; +use sp_core::{Decode, Encode, MaxEncodedLen, H160, H256}; #[derive( Eq, @@ -104,6 +100,12 @@ impl AddressMapping for MockAccount { } } +impl sp_runtime::traits::Convert for MockAccount { + fn convert(address: H160) -> MockAccount { + address.into() + } +} + #[macro_export] macro_rules! mock_account { ($name:ident, $convert:expr) => { diff --git a/precompiles/utils/src/testing/execution.rs b/precompiles/utils/src/testing/execution.rs index c7a52a9261..e37c207557 100644 --- a/precompiles/utils/src/testing/execution.rs +++ b/precompiles/utils/src/testing/execution.rs @@ -1,56 +1,32 @@ -// 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 +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. // -// This file is part of Utils package, originally developed by Purestake Inc. -// Utils package used in Astar Network in terms of GPLv3. +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. // -// Utils is free software: you can redistribute it and/or modify +// This program 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, +// This program 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 +// 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, - fp_evm::{ - Context, ExitError, ExitSucceed, Log, PrecompileFailure, PrecompileOutput, - PrecompileResult, PrecompileSet, - }, - sp_core::{H160, U256}, - sp_std::boxed::Box, +// along with this program. If not, see . + +use crate::{ + solidity::codec::Codec, + testing::{decode_revert_message, MockHandle, PrettyLog, SubcallHandle, SubcallTrait}, }; +use fp_evm::{ + Context, ExitError, ExitSucceed, Log, PrecompileFailure, PrecompileOutput, PrecompileResult, + PrecompileSet, +}; +use sp_core::{H160, U256}; +use sp_std::boxed::Box; #[must_use] pub struct PrecompilesTester<'p, P> { @@ -62,6 +38,7 @@ pub struct PrecompilesTester<'p, P> { expected_cost: Option, expected_logs: Option>, + static_call: bool, } impl<'p, P: PrecompileSet> PrecompilesTester<'p, P> { @@ -73,7 +50,7 @@ impl<'p, P: PrecompileSet> PrecompilesTester<'p, P> { ) -> Self { let to = to.into(); let mut handle = MockHandle::new( - to.clone(), + to, Context { address: to, caller: from.into(), @@ -92,6 +69,7 @@ impl<'p, P: PrecompileSet> PrecompilesTester<'p, P> { expected_cost: None, expected_logs: None, + static_call: false, } } @@ -110,8 +88,8 @@ impl<'p, P: PrecompileSet> PrecompilesTester<'p, P> { self } - pub fn with_gas_limit(mut self, gas_limit: u64) -> Self { - self.handle.gas_limit = gas_limit; + pub fn with_static_call(mut self, static_call: bool) -> Self { + self.static_call = static_call; self } @@ -127,7 +105,7 @@ impl<'p, P: PrecompileSet> PrecompilesTester<'p, P> { pub fn expect_log(mut self, log: Log) -> Self { self.expected_logs = Some({ - let mut logs = self.expected_logs.unwrap_or_else(Vec::new); + let mut logs = self.expected_logs.unwrap_or_default(); logs.push(PrettyLog(log)); logs }); @@ -147,6 +125,7 @@ impl<'p, P: PrecompileSet> PrecompilesTester<'p, P> { 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; @@ -214,19 +193,33 @@ impl<'p, P: PrecompileSet> PrecompilesTester<'p, P> { } /// Execute the precompile set and check it returns provided Solidity encoded output. - pub fn execute_returns(self, output: Vec) { - self.execute_returns_raw(output) + pub fn execute_returns(self, output: impl Codec) { + self.execute_returns_raw(crate::solidity::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(); - assert_matches!( - res, - Some(Err(PrecompileFailure::Revert { output, ..})) - if check(&output) - ); + + 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(); } diff --git a/precompiles/utils/src/testing/handle.rs b/precompiles/utils/src/testing/handle.rs index b88714e7eb..cf34dda88c 100644 --- a/precompiles/utils/src/testing/handle.rs +++ b/precompiles/utils/src/testing/handle.rs @@ -1,32 +1,27 @@ -// 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 +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. // -// This file is part of Utils package, originally developed by Purestake Inc. -// Utils package used in Astar Network in terms of GPLv3. +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. // -// Utils is free software: you can redistribute it and/or modify +// This program 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, +// +// This program 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 +// 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 . +// along with this program. 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, -}; +use crate::testing::PrettyLog; +use evm::{ExitRevert, ExitSucceed}; +use fp_evm::{Context, ExitError, ExitReason, Log, PrecompileHandle, Transfer}; +use sp_core::{H160, H256}; +use sp_std::boxed::Box; #[derive(Debug, Clone)] pub struct Subcall { @@ -121,7 +116,7 @@ impl PrecompileHandle for MockHandle { context: &Context, ) -> (ExitReason, Vec) { if self - .record_cost(crate::call_cost( + .record_cost(crate::evm::costs::call_cost( context.apparent_value, &evm::Config::london(), )) @@ -171,15 +166,6 @@ impl PrecompileHandle for MockHandle { } } - fn record_external_cost( - &mut self, - _ref_time: Option, - _proof_size: Option, - ) -> Result<(), ExitError> { - Ok(()) - } - fn refund_external_cost(&mut self, _ref_time: Option, _proof_size: Option) {} - fn remaining_gas(&self) -> u64 { self.gas_limit - self.gas_used } @@ -217,4 +203,14 @@ impl PrecompileHandle for MockHandle { fn gas_limit(&self) -> Option { Some(self.gas_limit) } + + fn record_external_cost( + &mut self, + _ref_time: Option, + _proof_size: Option, + ) -> Result<(), ExitError> { + Ok(()) + } + + fn refund_external_cost(&mut self, _ref_time: Option, _proof_size: Option) {} } diff --git a/precompiles/utils/src/testing/mod.rs b/precompiles/utils/src/testing/mod.rs index be6c5a881c..89e826a4fb 100644 --- a/precompiles/utils/src/testing/mod.rs +++ b/precompiles/utils/src/testing/mod.rs @@ -1,30 +1,33 @@ -// 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 +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. // -// This file is part of Utils package, originally developed by Purestake Inc. -// Utils package used in Astar Network in terms of GPLv3. +// Copyright (c) 2019-2022 Moonsong Labs. +// Copyright (c) 2023 Parity Technologies (UK) Ltd. // -// Utils is free software: you can redistribute it and/or modify +// This program 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, +// +// This program 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 +// 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 . +// along with this program. If not, see . pub mod account; pub mod execution; pub mod handle; +pub mod modifier; +mod solidity; -pub use {account::*, execution::*, handle::*}; +pub use account::*; +pub use execution::*; +pub use handle::*; +pub use modifier::*; +pub use solidity::{check_precompile_implements_solidity_interfaces, compute_selector}; use fp_evm::Log; @@ -63,6 +66,7 @@ impl core::fmt::Debug for PrettyLog { .finish() } } + /// Panics if an event is not found in the system log of events #[macro_export] macro_rules! assert_event_emitted { @@ -70,10 +74,10 @@ macro_rules! assert_event_emitted { match &$event { e => { assert!( - crate::mock::events().iter().find(|x| *x == e).is_some(), + $crate::mock::events().iter().find(|x| *x == e).is_some(), "Event {:?} was not found in events: \n {:?}", e, - crate::mock::events() + $crate::mock::events() ); } } @@ -87,10 +91,10 @@ macro_rules! assert_event_not_emitted { match &$event { e => { assert!( - crate::mock::events().iter().find(|x| *x == e).is_none(), + $crate::mock::events().iter().find(|x| *x == e).is_none(), "Event {:?} was found in events: \n {:?}", e, - crate::mock::events() + $crate::mock::events() ); } } diff --git a/precompiles/utils_v2/src/testing/modifier.rs b/precompiles/utils/src/testing/modifier.rs similarity index 100% rename from precompiles/utils_v2/src/testing/modifier.rs rename to precompiles/utils/src/testing/modifier.rs diff --git a/precompiles/utils_v2/src/testing/solidity.rs b/precompiles/utils/src/testing/solidity.rs similarity index 100% rename from precompiles/utils_v2/src/testing/solidity.rs rename to precompiles/utils/src/testing/solidity.rs diff --git a/precompiles/utils/src/tests.rs b/precompiles/utils/src/tests.rs deleted file mode 100644 index 6756687bbe..0000000000 --- a/precompiles/utils/src/tests.rs +++ /dev/null @@ -1,746 +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 hex_literal::hex; -use sp_core::{H256, U256}; - -fn u256_repeat_byte(byte: u8) -> U256 { - let value = H256::repeat_byte(byte); - - U256::from_big_endian(value.as_bytes()) -} - -// When debugging it is useful to display data in chunks of 32 bytes. -#[allow(dead_code)] -fn display_bytes(bytes: &[u8]) { - bytes - .chunks_exact(32) - .map(|chunk| H256::from_slice(chunk)) - .for_each(|hash| println!("{:?}", hash)); -} - -#[test] -fn write_bool() { - let value = true; - - let writer_output = EvmDataWriter::new().write(value).build(); - - let mut expected_output = [0u8; 32]; - expected_output[31] = 1; - - assert_eq!(writer_output, expected_output); -} - -#[test] -fn read_bool() { - let value = true; - - let writer_output = EvmDataWriter::new().write(value).build(); - - let mut reader = EvmDataReader::new(&writer_output); - let parsed: bool = reader.read().expect("to correctly parse bool"); - - assert_eq!(value, parsed); -} - -#[test] -fn write_u64() { - let value = 42u64; - - let writer_output = EvmDataWriter::new().write(value).build(); - - let mut expected_output = [0u8; 32]; - expected_output[24..].copy_from_slice(&value.to_be_bytes()); - - assert_eq!(writer_output, expected_output); -} - -#[test] -fn read_u64() { - let value = 42u64; - let writer_output = EvmDataWriter::new().write(value).build(); - - let mut reader = EvmDataReader::new(&writer_output); - let parsed: u64 = reader.read().expect("to correctly parse u64"); - - assert_eq!(value, parsed); -} - -#[test] -fn write_u128() { - let value = 42u128; - - let writer_output = EvmDataWriter::new().write(value).build(); - - let mut expected_output = [0u8; 32]; - expected_output[16..].copy_from_slice(&value.to_be_bytes()); - - assert_eq!(writer_output, expected_output); -} - -#[test] -fn read_u128() { - let value = 42u128; - let writer_output = EvmDataWriter::new().write(value).build(); - - let mut reader = EvmDataReader::new(&writer_output); - let parsed: u128 = reader.read().expect("to correctly parse u128"); - - assert_eq!(value, parsed); -} - -#[test] -fn write_u256() { - let value = U256::from(42); - - let writer_output = EvmDataWriter::new().write(value).build(); - - let mut expected_output = [0u8; 32]; - value.to_big_endian(&mut expected_output); - - assert_eq!(writer_output, expected_output); -} - -#[test] -fn read_u256() { - let value = U256::from(42); - let writer_output = EvmDataWriter::new().write(value).build(); - - let mut reader = EvmDataReader::new(&writer_output); - let parsed: U256 = reader.read().expect("to correctly parse U256"); - - assert_eq!(value, parsed); -} - -#[test] -fn read_selector() { - use sha3::{Digest, Keccak256}; - - #[precompile_utils_macro::generate_function_selector] - #[derive(Debug, PartialEq)] - enum FakeAction { - Action1 = "action1()", - } - - let selector = &Keccak256::digest(b"action1()")[0..4]; - - let parsed_selector = - EvmDataReader::read_selector::(selector).expect("there is a selector"); - EvmDataReader::new_skip_selector(selector).expect("there is a selector"); - - assert_eq!(parsed_selector, FakeAction::Action1) -} - -#[test] -#[should_panic(expected = "to correctly parse U256")] -fn read_u256_too_short() { - let value = U256::from(42); - let writer_output = EvmDataWriter::new().write(value).build(); - - let mut reader = EvmDataReader::new(&writer_output[0..31]); - let _: U256 = reader.read().expect("to correctly parse U256"); -} - -#[test] -fn write_h256() { - let mut raw = [0u8; 32]; - raw[0] = 42; - raw[12] = 43; - raw[31] = 44; - - let value = H256::from(raw); - - let output = EvmDataWriter::new().write(value).build(); - - assert_eq!(&output, &raw); -} - -#[test] -fn tmp() { - let u = U256::from(1_000_000_000); - println!("U256={:?}", u.0); -} - -#[test] -fn read_h256() { - let mut raw = [0u8; 32]; - raw[0] = 42; - raw[12] = 43; - raw[31] = 44; - let value = H256::from(raw); - let writer_output = EvmDataWriter::new().write(value).build(); - - let mut reader = EvmDataReader::new(&writer_output); - let parsed: H256 = reader.read().expect("to correctly parse H256"); - - assert_eq!(value, parsed); -} - -#[test] -#[should_panic(expected = "to correctly parse H256")] -fn read_h256_too_short() { - let mut raw = [0u8; 32]; - raw[0] = 42; - raw[12] = 43; - raw[31] = 44; - let value = H256::from(raw); - let writer_output = EvmDataWriter::new().write(value).build(); - - let mut reader = EvmDataReader::new(&writer_output[0..31]); - let _: H256 = reader.read().expect("to correctly parse H256"); -} - -#[test] -fn write_address() { - let value = H160::repeat_byte(0xAA); - - let output = EvmDataWriter::new().write(Address(value)).build(); - - assert_eq!(output.len(), 32); - assert_eq!(&output[12..32], value.as_bytes()); -} - -#[test] -fn read_address() { - let value = H160::repeat_byte(0xAA); - let writer_output = EvmDataWriter::new().write(Address(value)).build(); - - let mut reader = EvmDataReader::new(&writer_output); - let parsed: Address = reader.read().expect("to correctly parse Address"); - - assert_eq!(value, parsed.0); -} - -#[test] -fn write_h256_array() { - let array = vec![ - H256::repeat_byte(0x11), - H256::repeat_byte(0x22), - H256::repeat_byte(0x33), - H256::repeat_byte(0x44), - H256::repeat_byte(0x55), - ]; - let writer_output = EvmDataWriter::new().write(array.clone()).build(); - assert_eq!(writer_output.len(), 0xE0); - - // We can read this "manualy" using simpler functions since arrays are 32-byte aligned. - let mut reader = EvmDataReader::new(&writer_output); - - assert_eq!(reader.read::().expect("read offset"), 32.into()); - assert_eq!(reader.read::().expect("read size"), 5.into()); - assert_eq!(reader.read::().expect("read 1st"), array[0]); - assert_eq!(reader.read::().expect("read 2nd"), array[1]); - assert_eq!(reader.read::().expect("read 3rd"), array[2]); - assert_eq!(reader.read::().expect("read 4th"), array[3]); - assert_eq!(reader.read::().expect("read 5th"), array[4]); -} - -#[test] -fn read_h256_array() { - let array = vec![ - H256::repeat_byte(0x11), - H256::repeat_byte(0x22), - H256::repeat_byte(0x33), - H256::repeat_byte(0x44), - H256::repeat_byte(0x55), - ]; - let writer_output = EvmDataWriter::new().write(array.clone()).build(); - - let mut reader = EvmDataReader::new(&writer_output); - let parsed: Vec = reader.read().expect("to correctly parse Vec"); - - assert_eq!(array, parsed); -} - -#[test] -fn write_u256_array() { - let array = vec![ - u256_repeat_byte(0x11), - u256_repeat_byte(0x22), - u256_repeat_byte(0x33), - u256_repeat_byte(0x44), - u256_repeat_byte(0x55), - ]; - let writer_output = EvmDataWriter::new().write(array.clone()).build(); - assert_eq!(writer_output.len(), 0xE0); - - // We can read this "manualy" using simpler functions since arrays are 32-byte aligned. - let mut reader = EvmDataReader::new(&writer_output); - - assert_eq!(reader.read::().expect("read offset"), 32.into()); - assert_eq!(reader.read::().expect("read size"), 5.into()); - assert_eq!(reader.read::().expect("read 1st"), array[0]); - assert_eq!(reader.read::().expect("read 2nd"), array[1]); - assert_eq!(reader.read::().expect("read 3rd"), array[2]); - assert_eq!(reader.read::().expect("read 4th"), array[3]); - assert_eq!(reader.read::().expect("read 5th"), array[4]); -} - -#[test] -fn read_u256_array() { - let array = vec![ - u256_repeat_byte(0x11), - u256_repeat_byte(0x22), - u256_repeat_byte(0x33), - u256_repeat_byte(0x44), - u256_repeat_byte(0x55), - ]; - let writer_output = EvmDataWriter::new().write(array.clone()).build(); - - let mut reader = EvmDataReader::new(&writer_output); - let parsed: Vec = reader.read().expect("to correctly parse Vec"); - - assert_eq!(array, parsed); -} - -#[test] -fn write_address_array() { - let array = vec![ - Address(H160::repeat_byte(0x11)), - Address(H160::repeat_byte(0x22)), - Address(H160::repeat_byte(0x33)), - Address(H160::repeat_byte(0x44)), - Address(H160::repeat_byte(0x55)), - ]; - let writer_output = EvmDataWriter::new().write(array.clone()).build(); - - // We can read this "manualy" using simpler functions since arrays are 32-byte aligned. - let mut reader = EvmDataReader::new(&writer_output); - - assert_eq!(reader.read::().expect("read offset"), 32.into()); - assert_eq!(reader.read::().expect("read size"), 5.into()); - assert_eq!(reader.read::
().expect("read 1st"), array[0]); - assert_eq!(reader.read::
().expect("read 2nd"), array[1]); - assert_eq!(reader.read::
().expect("read 3rd"), array[2]); - assert_eq!(reader.read::
().expect("read 4th"), array[3]); - assert_eq!(reader.read::
().expect("read 5th"), array[4]); -} - -#[test] -fn read_address_array() { - let array = vec![ - Address(H160::repeat_byte(0x11)), - Address(H160::repeat_byte(0x22)), - Address(H160::repeat_byte(0x33)), - Address(H160::repeat_byte(0x44)), - Address(H160::repeat_byte(0x55)), - ]; - let writer_output = EvmDataWriter::new().write(array.clone()).build(); - - let mut reader = EvmDataReader::new(&writer_output); - let parsed: Vec
= reader.read().expect("to correctly parse Vec"); - - assert_eq!(array, parsed); -} - -#[test] -fn read_address_array_size_too_big() { - let array = vec![ - Address(H160::repeat_byte(0x11)), - Address(H160::repeat_byte(0x22)), - Address(H160::repeat_byte(0x33)), - Address(H160::repeat_byte(0x44)), - Address(H160::repeat_byte(0x55)), - ]; - let mut writer_output = EvmDataWriter::new().write(array).build(); - - U256::from(6u32).to_big_endian(&mut writer_output[0x20..0x40]); - - let mut reader = EvmDataReader::new(&writer_output); - - match reader.read::>() { - Ok(_) => panic!("should not parse correctly"), - Err(PrecompileFailure::Revert { output: err, .. }) => { - assert_eq!(err, b"tried to parse H160 out of bounds") - } - Err(_) => panic!("unexpected error"), - } -} - -#[test] -fn write_address_nested_array() { - let array = vec![ - vec![ - Address(H160::repeat_byte(0x11)), - Address(H160::repeat_byte(0x22)), - Address(H160::repeat_byte(0x33)), - ], - vec![ - Address(H160::repeat_byte(0x44)), - Address(H160::repeat_byte(0x55)), - ], - ]; - let writer_output = EvmDataWriter::new().write(array.clone()).build(); - assert_eq!(writer_output.len(), 0x160); - - // We can read this "manualy" using simpler functions since arrays are 32-byte aligned. - let mut reader = EvmDataReader::new(&writer_output); - - assert_eq!(reader.read::().expect("read offset"), 0x20.into()); // 0x00 - assert_eq!(reader.read::().expect("read size"), 2.into()); // 0x20 - assert_eq!(reader.read::().expect("read 1st offset"), 0x40.into()); // 0x40 - assert_eq!(reader.read::().expect("read 2st offset"), 0xc0.into()); // 0x60 - assert_eq!(reader.read::().expect("read 1st size"), 3.into()); // 0x80 - assert_eq!(reader.read::
().expect("read 1-1"), array[0][0]); // 0xA0 - assert_eq!(reader.read::
().expect("read 1-2"), array[0][1]); // 0xC0 - assert_eq!(reader.read::
().expect("read 1-3"), array[0][2]); // 0xE0 - assert_eq!(reader.read::().expect("read 2nd size"), 2.into()); // 0x100 - assert_eq!(reader.read::
().expect("read 2-1"), array[1][0]); // 0x120 - assert_eq!(reader.read::
().expect("read 2-2"), array[1][1]); // 0x140 -} - -#[test] -fn read_address_nested_array() { - let array = vec![ - vec![ - Address(H160::repeat_byte(0x11)), - Address(H160::repeat_byte(0x22)), - Address(H160::repeat_byte(0x33)), - ], - vec![ - Address(H160::repeat_byte(0x44)), - Address(H160::repeat_byte(0x55)), - ], - ]; - let writer_output = EvmDataWriter::new().write(array.clone()).build(); - - let mut reader = EvmDataReader::new(&writer_output); - let parsed: Vec> = reader.read().expect("to correctly parse Vec>"); - - assert_eq!(array, parsed); -} - -#[test] - -fn write_multiple_arrays() { - let array1 = vec![ - Address(H160::repeat_byte(0x11)), - Address(H160::repeat_byte(0x22)), - Address(H160::repeat_byte(0x33)), - ]; - - let array2 = vec![H256::repeat_byte(0x44), H256::repeat_byte(0x55)]; - - let writer_output = EvmDataWriter::new() - .write(array1.clone()) - .write(array2.clone()) - .build(); - - assert_eq!(writer_output.len(), 0x120); - - // We can read this "manualy" using simpler functions since arrays are 32-byte aligned. - let mut reader = EvmDataReader::new(&writer_output); - - assert_eq!(reader.read::().expect("read 1st offset"), 0x40.into()); // 0x00 - assert_eq!(reader.read::().expect("read 2nd offset"), 0xc0.into()); // 0x20 - assert_eq!(reader.read::().expect("read 1st size"), 3.into()); // 0x40 - assert_eq!(reader.read::
().expect("read 1-1"), array1[0]); // 0x60 - assert_eq!(reader.read::
().expect("read 1-2"), array1[1]); // 0x80 - assert_eq!(reader.read::
().expect("read 1-3"), array1[2]); // 0xA0 - assert_eq!(reader.read::().expect("read 2nd size"), 2.into()); // 0xC0 - assert_eq!(reader.read::().expect("read 2-1"), array2[0]); // 0xE0 - assert_eq!(reader.read::().expect("read 2-2"), array2[1]); // 0x100 -} - -#[test] -fn read_multiple_arrays() { - let array1 = vec![ - Address(H160::repeat_byte(0x11)), - Address(H160::repeat_byte(0x22)), - Address(H160::repeat_byte(0x33)), - ]; - - let array2 = vec![H256::repeat_byte(0x44), H256::repeat_byte(0x55)]; - - let writer_output = EvmDataWriter::new() - .write(array1.clone()) - .write(array2.clone()) - .build(); - - // offset 0x20 - // offset 0x40 - // size 0x60 - // 3 addresses 0xC0 - // size 0xE0 - // 2 H256 0x120 - assert_eq!(writer_output.len(), 0x120); - - let mut reader = EvmDataReader::new(&writer_output); - - let parsed: Vec
= reader.read().expect("to correctly parse Vec
"); - assert_eq!(array1, parsed); - - let parsed: Vec = reader.read().expect("to correctly parse Vec"); - assert_eq!(array2, parsed); -} - -#[test] -fn read_bytes() { - let data = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod\ - tempor incididunt ut labore et dolore magna aliqua."; - let writer_output = EvmDataWriter::new().write(Bytes::from(&data[..])).build(); - - let mut reader = EvmDataReader::new(&writer_output); - let parsed: Bytes = reader.read().expect("to correctly parse Bytes"); - - assert_eq!(data, parsed.as_bytes()); -} - -#[test] -fn write_bytes() { - let data = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod\ - tempor incididunt ut labore et dolore magna aliqua."; - - let writer_output = EvmDataWriter::new().write(Bytes::from(&data[..])).build(); - - // We can read this "manualy" using simpler functions. - let mut reader = EvmDataReader::new(&writer_output); - - // We pad data to a multiple of 32 bytes. - let mut padded = data.to_vec(); - assert!(data.len() < 0x80); - padded.resize(0x80, 0); - - assert_eq!(reader.read::().expect("read offset"), 32.into()); - assert_eq!(reader.read::().expect("read size"), data.len().into()); - let mut read = |e| reader.read::().expect(e); // shorthand - assert_eq!(read("read part 1"), H256::from_slice(&padded[0x00..0x20])); - assert_eq!(read("read part 2"), H256::from_slice(&padded[0x20..0x40])); - assert_eq!(read("read part 3"), H256::from_slice(&padded[0x40..0x60])); - assert_eq!(read("read part 4"), H256::from_slice(&padded[0x60..0x80])); -} - -#[test] -fn read_string() { - let data = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod\ - tempor incididunt ut labore et dolore magna aliqua."; - let writer_output = EvmDataWriter::new().write(Bytes::from(data)).build(); - - let mut reader = EvmDataReader::new(&writer_output); - let parsed: Bytes = reader.read().expect("to correctly parse Bytes"); - - assert_eq!(data, parsed.as_str().expect("valid utf8")); -} - -#[test] -fn write_string() { - let data = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod\ - tempor incididunt ut labore et dolore magna aliqua."; - - let writer_output = EvmDataWriter::new().write(Bytes::from(data)).build(); - - // We can read this "manualy" using simpler functions. - let mut reader = EvmDataReader::new(&writer_output); - - // We pad data to next multiple of 32 bytes. - let mut padded = data.as_bytes().to_vec(); - assert!(data.len() < 0x80); - padded.resize(0x80, 0); - - assert_eq!(reader.read::().expect("read offset"), 32.into()); - assert_eq!(reader.read::().expect("read size"), data.len().into()); - let mut read = |e| reader.read::().expect(e); // shorthand - assert_eq!(read("read part 1"), H256::from_slice(&padded[0x00..0x20])); - assert_eq!(read("read part 2"), H256::from_slice(&padded[0x20..0x40])); - assert_eq!(read("read part 3"), H256::from_slice(&padded[0x40..0x60])); - assert_eq!(read("read part 4"), H256::from_slice(&padded[0x60..0x80])); -} - -#[test] -fn write_vec_bytes() { - let data = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod\ - tempor incididunt ut labore et dolore magna aliqua."; - - let writer_output = EvmDataWriter::new() - .write(vec![Bytes::from(&data[..]), Bytes::from(&data[..])]) - .build(); - - writer_output - .chunks_exact(32) - .map(|chunk| H256::from_slice(chunk)) - .for_each(|hash| println!("{:?}", hash)); - - // We pad data to a multiple of 32 bytes. - let mut padded = data.to_vec(); - assert!(data.len() < 0x80); - padded.resize(0x80, 0); - - let mut reader = EvmDataReader::new(&writer_output); - - // Offset of vec - assert_eq!(reader.read::().expect("read offset"), 32.into()); - - // Length of vec - assert_eq!(reader.read::().expect("read offset"), 2.into()); - - // Relative offset of first bytgmes object - assert_eq!(reader.read::().expect("read offset"), 0x40.into()); - // Relative offset of second bytes object - assert_eq!(reader.read::().expect("read offset"), 0xe0.into()); - - // Length of first bytes object - assert_eq!(reader.read::().expect("read size"), data.len().into()); - - // First byte objects data - let mut read = |e| reader.read::().expect(e); // shorthand - assert_eq!(read("read part 1"), H256::from_slice(&padded[0x00..0x20])); - assert_eq!(read("read part 2"), H256::from_slice(&padded[0x20..0x40])); - assert_eq!(read("read part 3"), H256::from_slice(&padded[0x40..0x60])); - assert_eq!(read("read part 4"), H256::from_slice(&padded[0x60..0x80])); - - // Length of second bytes object - assert_eq!(reader.read::().expect("read size"), data.len().into()); - - // Second byte objects data - let mut read = |e| reader.read::().expect(e); // shorthand - assert_eq!(read("read part 1"), H256::from_slice(&padded[0x00..0x20])); - assert_eq!(read("read part 2"), H256::from_slice(&padded[0x20..0x40])); - assert_eq!(read("read part 3"), H256::from_slice(&padded[0x40..0x60])); - assert_eq!(read("read part 4"), H256::from_slice(&padded[0x60..0x80])); -} - -#[test] -fn read_vec_of_bytes() { - let data = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod\ - tempor incididunt ut labore et dolore magna aliqua."; - - let writer_output = EvmDataWriter::new() - .write(vec![Bytes::from(&data[..]), Bytes::from(&data[..])]) - .build(); - - writer_output - .chunks_exact(32) - .map(|chunk| H256::from_slice(chunk)) - .for_each(|hash| println!("{:?}", hash)); - - let mut reader = EvmDataReader::new(&writer_output); - let parsed: Vec = reader.read().expect("to correctly parse Vec"); - - assert_eq!(vec![Bytes::from(&data[..]), Bytes::from(&data[..])], parsed); -} - -// The following test parses input data generated by web3 from a Solidity contract. -// This is important to test on external data since all the above tests can only test consistency -// between `EvmDataReader` and `EvmDataWriter`. -// -// It also provides an example on how to impl `EvmData` for Solidity structs. -// -// struct MultiLocation { -// uint8 parents; -// bytes [] interior; -// } -// -// function transfer( -// address currency_address, -// uint256 amount, -// MultiLocation memory destination, -// uint64 weight -// ) external; - -#[derive(Clone, Debug, Eq, PartialEq)] -struct MultiLocation { - parents: u8, - interior: Vec, -} - -impl EvmData for MultiLocation { - fn read(reader: &mut EvmDataReader) -> EvmResult { - let (parents, interior) = reader.read()?; - Ok(MultiLocation { parents, interior }) - } - - fn write(writer: &mut EvmDataWriter, value: Self) { - EvmData::write(writer, (value.parents, value.interior)); - } - - fn has_static_size() -> bool { - <(u8, Vec)>::has_static_size() - } -} - -#[crate::generate_function_selector] -#[derive(Debug, PartialEq)] -pub enum Action { - TransferMultiAsset = "transfer_multiasset((uint8,bytes[]),uint256,(uint8,bytes[]),uint64)", -} - -#[test] -fn read_complex_solidity_function() { - // Function call data generated by web3. - let data = hex!( - "b38c60fa - 0000000000000000000000000000000000000000000000000000000000000080 - 0000000000000000000000000000000000000000000000000000000000000064 - 00000000000000000000000000000000000000000000000000000000000001a0 - 0000000000000000000000000000000000000000000000000000000000000064 - 0000000000000000000000000000000000000000000000000000000000000001 - 0000000000000000000000000000000000000000000000000000000000000040 - 0000000000000000000000000000000000000000000000000000000000000002 - 0000000000000000000000000000000000000000000000000000000000000040 - 0000000000000000000000000000000000000000000000000000000000000080 - 0000000000000000000000000000000000000000000000000000000000000005 - 00000003e8000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000002 - 0403000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000001 - 0000000000000000000000000000000000000000000000000000000000000040 - 0000000000000000000000000000000000000000000000000000000000000001 - 0000000000000000000000000000000000000000000000000000000000000020 - 0000000000000000000000000000000000000000000000000000000000000022 - 0101010101010101010101010101010101010101010101010101010101010101 - 0100000000000000000000000000000000000000000000000000000000000000" - ); - - let selector = EvmDataReader::read_selector::(&data).expect("to read selector"); - let mut reader = EvmDataReader::new_skip_selector(&data).expect("to read selector"); - - assert_eq!(selector, Action::TransferMultiAsset); - // asset - assert_eq!( - reader.read::().unwrap(), - MultiLocation { - parents: 1, - interior: vec![ - Bytes::from(&hex!("00000003e8")[..]), - Bytes::from(&hex!("0403")[..]), - ], - } - ); - - // amount - assert_eq!(reader.read::().unwrap(), 100u32.into()); - - // destination - assert_eq!( - reader.read::().unwrap(), - MultiLocation { - parents: 1, - interior: vec![Bytes::from( - &hex!("01010101010101010101010101010101010101010101010101010101010101010100")[..] - )], - } - ); - - // weight - assert_eq!(reader.read::().unwrap(), 100u32.into()); -} diff --git a/precompiles/utils_v2/tests-external/Cargo.toml b/precompiles/utils/tests-external/Cargo.toml similarity index 100% rename from precompiles/utils_v2/tests-external/Cargo.toml rename to precompiles/utils/tests-external/Cargo.toml diff --git a/precompiles/utils_v2/tests-external/lib.rs b/precompiles/utils/tests-external/lib.rs similarity index 95% rename from precompiles/utils_v2/tests-external/lib.rs rename to precompiles/utils/tests-external/lib.rs index 75199c1cee..342e3ed067 100644 --- a/precompiles/utils_v2/tests-external/lib.rs +++ b/precompiles/utils/tests-external/lib.rs @@ -207,7 +207,7 @@ pub type Precompiles = PrecompileSetBuilder< ), >; -pub type PCall = MockPrecompileCall; +pub type PrecompileCall = MockPrecompileCall; const MAX_POV_SIZE: u64 = 5 * 1024 * 1024; @@ -281,7 +281,7 @@ fn precompiles() -> Precompiles { fn default_checks_succeed_when_called_by_eoa() { ExtBuilder::default().build().execute_with(|| { precompiles() - .prepare_test(Alice, H160::from_low_u64_be(1), PCall::success {}) + .prepare_test(Alice, H160::from_low_u64_be(1), PrecompileCall::success {}) .with_subcall_handle(|Subcall { .. }| panic!("there should be no subcall")) .execute_returns(()) }) @@ -294,7 +294,7 @@ fn default_checks_revert_when_called_by_precompile() { .prepare_test( H160::from_low_u64_be(1), H160::from_low_u64_be(1), - PCall::success {}, + PrecompileCall::success {}, ) .with_subcall_handle(|Subcall { .. }| panic!("there should be no subcall")) .execute_reverts(|r| r == b"Function not callable by precompiles") @@ -310,7 +310,7 @@ fn default_checks_revert_when_called_by_contract() { ); precompiles() - .prepare_test(Alice, H160::from_low_u64_be(1), PCall::success {}) + .prepare_test(Alice, H160::from_low_u64_be(1), PrecompileCall::success {}) .with_subcall_handle(|Subcall { .. }| panic!("there should be no subcall")) .execute_reverts(|r| r == b"Function not callable by smart contracts") }) @@ -320,7 +320,7 @@ fn default_checks_revert_when_called_by_contract() { fn default_checks_revert_when_doing_subcall() { ExtBuilder::default().build().execute_with(|| { precompiles() - .prepare_test(Alice, H160::from_low_u64_be(1), PCall::subcall {}) + .prepare_test(Alice, H160::from_low_u64_be(1), PrecompileCall::subcall {}) .with_subcall_handle(|Subcall { .. }| panic!("there should be no subcall")) .execute_reverts(|r| r == b"subcalls disabled for this precompile") }) @@ -335,7 +335,7 @@ fn callable_by_contract_works() { ); precompiles() - .prepare_test(Alice, H160::from_low_u64_be(2), PCall::success {}) + .prepare_test(Alice, H160::from_low_u64_be(2), PrecompileCall::success {}) .with_subcall_handle(|Subcall { .. }| panic!("there should be no subcall")) .execute_returns(()) }) @@ -348,7 +348,7 @@ fn callable_by_precompile_works() { .prepare_test( H160::from_low_u64_be(3), H160::from_low_u64_be(3), - PCall::success {}, + PrecompileCall::success {}, ) .with_subcall_handle(|Subcall { .. }| panic!("there should be no subcall")) .execute_returns(()) @@ -362,7 +362,7 @@ fn subcalls_works_when_allowed() { { let subcall_occured = Rc::clone(&subcall_occured); precompiles() - .prepare_test(Alice, H160::from_low_u64_be(4), PCall::subcall {}) + .prepare_test(Alice, H160::from_low_u64_be(4), PrecompileCall::subcall {}) .with_subcall_handle(move |Subcall { .. }| { *subcall_occured.borrow_mut() = true; SubcallOutput::succeed() diff --git a/precompiles/utils_v2/tests/solidity_test.sol b/precompiles/utils/tests/solidity_test.sol similarity index 100% rename from precompiles/utils_v2/tests/solidity_test.sol rename to precompiles/utils/tests/solidity_test.sol diff --git a/precompiles/utils_v2/Cargo.toml b/precompiles/utils_v2/Cargo.toml deleted file mode 100644 index 936fd6194e..0000000000 --- a/precompiles/utils_v2/Cargo.toml +++ /dev/null @@ -1,53 +0,0 @@ -[package] -name = "precompile-utils-v2" -authors = { workspace = true } -description = "Utils to write EVM precompiles." -edition = "2021" -version = "0.1.0" - -[dependencies] -derive_more = { workspace = true, optional = true } -environmental = { workspace = true } -hex = { workspace = true } -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 } -similar-asserts = { workspace = true, optional = true } - -# Moonbeam -precompile-utils-macro-v2 = { path = "macro" } - -# Substrate -frame-support = { workspace = true } -frame-system = { workspace = true } -parity-scale-codec = { workspace = true } -sp-core = { workspace = true } -sp-io = { workspace = true } -sp-runtime = { 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] -hex-literal = { workspace = true } - -[features] -default = ["std"] -std = [ - "environmental/std", - "fp-evm/std", - "frame-support/std", - "frame-system/std", - "pallet-evm/std", - "parity-scale-codec/std", - "sp-core/std", - "sp-io/std", - "sp-std/std", -] -testing = ["derive_more", "hex-literal", "scale-info", "serde", "similar-asserts", "std"] diff --git a/precompiles/utils_v2/macro/Cargo.toml b/precompiles/utils_v2/macro/Cargo.toml deleted file mode 100644 index cc6e820931..0000000000 --- a/precompiles/utils_v2/macro/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -name = "precompile-utils-macro-v2" -authors = { workspace = true } -description = "" -edition = "2021" -version = "0.1.0" - -[lib] -proc-macro = true - -[[test]] -name = "tests" -path = "tests/tests.rs" - -[dependencies] -case = "1.0" -num_enum = { workspace = true } -prettyplease = "0.2.12" -proc-macro2 = "1.0" -quote = "1.0" -sp-core-hashing = { workspace = true } -syn = { version = "1.0", features = ["extra-traits", "fold", "full", "visit"] } - -[dev-dependencies] -macrotest = "1.0.9" -trybuild = "1.0" - -precompile-utils-v2 = { path = "../", features = ["testing"] } - -fp-evm = { workspace = true } -frame-support = { workspace = true } -sp-core-hashing = { workspace = true } -sp-std = { workspace = true } diff --git a/precompiles/utils_v2/macro/src/lib.rs b/precompiles/utils_v2/macro/src/lib.rs deleted file mode 100644 index 0b595ca042..0000000000 --- a/precompiles/utils_v2/macro/src/lib.rs +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 -// This file is part of Frontier. -// -// Copyright (c) 2019-2022 Moonsong Labs. -// Copyright (c) 2023 Parity Technologies (UK) Ltd. -// -// This program 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. -// -// This program 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 this program. If not, see . - -#![crate_type = "proc-macro"] -extern crate proc_macro; - -use proc_macro::TokenStream; -use quote::{quote, quote_spanned}; -use sp_core_hashing::keccak_256; -use syn::{parse_macro_input, spanned::Spanned, Expr, Ident, ItemType, Lit, LitStr}; - -mod derive_codec; -mod precompile; -mod precompile_name_from_address; - -struct Bytes(Vec); - -impl ::std::fmt::Debug for Bytes { - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter) -> ::std::fmt::Result { - let data = &self.0; - write!(f, "[")?; - if !data.is_empty() { - write!(f, "{:#04x}u8", data[0])?; - for unit in data.iter().skip(1) { - write!(f, ", {:#04x}", unit)?; - } - } - write!(f, "]") - } -} - -#[proc_macro] -pub fn keccak256(input: TokenStream) -> TokenStream { - let lit_str = parse_macro_input!(input as LitStr); - - let hash = keccak_256(lit_str.value().as_bytes()); - - let bytes = Bytes(hash.to_vec()); - let eval_str = format!("{:?}", bytes); - let eval_ts: proc_macro2::TokenStream = eval_str.parse().unwrap_or_else(|_| { - panic!( - "Failed to parse the string \"{}\" to TokenStream.", - eval_str - ); - }); - quote!(#eval_ts).into() -} - -#[proc_macro_attribute] -pub fn precompile(attr: TokenStream, input: TokenStream) -> TokenStream { - precompile::main(attr, input) -} - -#[proc_macro_attribute] -pub fn precompile_name_from_address(attr: TokenStream, input: TokenStream) -> TokenStream { - precompile_name_from_address::main(attr, input) -} - -#[proc_macro_derive(Codec)] -pub fn derive_codec(input: TokenStream) -> TokenStream { - derive_codec::main(input) -} diff --git a/precompiles/utils_v2/macro/tests/tests.rs b/precompiles/utils_v2/macro/tests/tests.rs deleted file mode 100644 index dd456be727..0000000000 --- a/precompiles/utils_v2/macro/tests/tests.rs +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 -// This file is part of Frontier. -// -// Copyright (c) 2019-2022 Moonsong Labs. -// Copyright (c) 2023 Parity Technologies (UK) Ltd. -// -// This program 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. -// -// This program 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 this program. If not, see . - -use sp_core_hashing::keccak_256; - -#[test] -fn test_keccak256() { - assert_eq!( - &precompile_utils_macro_v2::keccak256!(""), - keccak_256(b"").as_slice(), - ); - assert_eq!( - &precompile_utils_macro_v2::keccak256!("toto()"), - keccak_256(b"toto()").as_slice(), - ); - assert_ne!( - &precompile_utils_macro_v2::keccak256!("toto()"), - keccak_256(b"tata()").as_slice(), - ); -} - -#[test] -#[ignore] -fn ui() { - let t = trybuild::TestCases::new(); - t.compile_fail("tests/compile-fail/**/*.rs"); - t.pass("tests/pass/**/*.rs"); -} - -// Cargo expand is not supported on stable rust -#[test] -#[ignore] -fn expand() { - // Use `expand` to update the expansions - // Replace it with `expand_without_refresh` afterward so that - // CI checks the expension don't change - - // macrotest::expand("tests/expand/**/*.rs"); - macrotest::expand_without_refresh("tests/expand/**/*.rs"); -} diff --git a/precompiles/utils_v2/src/lib.rs b/precompiles/utils_v2/src/lib.rs deleted file mode 100644 index 10fd3cad5f..0000000000 --- a/precompiles/utils_v2/src/lib.rs +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 -// This file is part of Frontier. -// -// Copyright (c) 2019-2022 Moonsong Labs. -// Copyright (c) 2023 Parity Technologies (UK) Ltd. -// -// This program 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. -// -// This program 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 this program. If not, see . - -#![cfg_attr(not(feature = "std"), no_std)] - -extern crate alloc; - -// Allows to use inside this crate `solidity::Codec` derive macro,which depends on -// `precompile_utils` being in the list of imported crates. -extern crate self as precompile_utils; - -pub mod evm; -pub mod precompile_set; -pub mod substrate; - -pub mod solidity; - -#[cfg(feature = "testing")] -pub mod testing; - -use fp_evm::PrecompileFailure; - -// pub mod data; - -// pub use data::{solidity::Codec, Reader, Writer}; -pub use fp_evm::Precompile; -pub use precompile_utils_macro_v2::{keccak256, precompile, precompile_name_from_address}; - -/// Alias for Result returning an EVM precompile error. -pub type EvmResult = Result; - -pub mod prelude { - pub use { - crate::{ - evm::{ - handle::PrecompileHandleExt, - logs::{log0, log1, log2, log3, log4, LogExt}, - }, - precompile_set::DiscriminantResult, - solidity::{ - // We export solidity itself to encourage using `solidity::Codec` to avoid confusion - // with parity_scale_codec, - self, - codec::{ - Address, - BoundedBytes, - BoundedString, - BoundedVec, - // Allow usage of Codec methods while not exporting the name directly. - Codec as _, - Convert, - UnboundedBytes, - UnboundedString, - }, - revert::{ - revert, BacktraceExt, InjectBacktrace, MayRevert, Revert, RevertExt, - RevertReason, - }, - }, - substrate::{RuntimeHelper, TryDispatchError}, - EvmResult, - }, - alloc::string::String, - pallet_evm::{PrecompileHandle, PrecompileOutput}, - precompile_utils_macro_v2::{keccak256, precompile}, - }; -} diff --git a/precompiles/utils_v2/src/testing/account.rs b/precompiles/utils_v2/src/testing/account.rs deleted file mode 100644 index ec9bcf964a..0000000000 --- a/precompiles/utils_v2/src/testing/account.rs +++ /dev/null @@ -1,188 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 -// This file is part of Frontier. -// -// Copyright (c) 2019-2022 Moonsong Labs. -// Copyright (c) 2023 Parity Technologies (UK) Ltd. -// -// This program 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. -// -// This program 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 this program. If not, see . - -use pallet_evm::AddressMapping; -use scale_info::TypeInfo; -use serde::{Deserialize, Serialize}; -use 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() - } -} - -impl sp_runtime::traits::Convert for MockAccount { - fn convert(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_v2/src/testing/execution.rs b/precompiles/utils_v2/src/testing/execution.rs deleted file mode 100644 index e37c207557..0000000000 --- a/precompiles/utils_v2/src/testing/execution.rs +++ /dev/null @@ -1,255 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 -// This file is part of Frontier. -// -// Copyright (c) 2019-2022 Moonsong Labs. -// Copyright (c) 2023 Parity Technologies (UK) Ltd. -// -// This program 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. -// -// This program 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 this program. If not, see . - -use crate::{ - solidity::codec::Codec, - testing::{decode_revert_message, MockHandle, PrettyLog, SubcallHandle, SubcallTrait}, -}; -use fp_evm::{ - Context, ExitError, ExitSucceed, Log, PrecompileFailure, PrecompileOutput, PrecompileResult, - PrecompileSet, -}; -use sp_core::{H160, U256}; -use 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, - 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_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(); - 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 Codec) { - self.execute_returns_raw(crate::solidity::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_v2/src/testing/handle.rs b/precompiles/utils_v2/src/testing/handle.rs deleted file mode 100644 index cf34dda88c..0000000000 --- a/precompiles/utils_v2/src/testing/handle.rs +++ /dev/null @@ -1,216 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 -// This file is part of Frontier. -// -// Copyright (c) 2019-2022 Moonsong Labs. -// Copyright (c) 2023 Parity Technologies (UK) Ltd. -// -// This program 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. -// -// This program 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 this program. If not, see . - -use crate::testing::PrettyLog; -use evm::{ExitRevert, ExitSucceed}; -use fp_evm::{Context, ExitError, ExitReason, Log, PrecompileHandle, Transfer}; -use sp_core::{H160, H256}; -use 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) - } - - fn record_external_cost( - &mut self, - _ref_time: Option, - _proof_size: Option, - ) -> Result<(), ExitError> { - Ok(()) - } - - fn refund_external_cost(&mut self, _ref_time: Option, _proof_size: Option) {} -} diff --git a/precompiles/utils_v2/src/testing/mod.rs b/precompiles/utils_v2/src/testing/mod.rs deleted file mode 100644 index 89e826a4fb..0000000000 --- a/precompiles/utils_v2/src/testing/mod.rs +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 -// This file is part of Frontier. -// -// Copyright (c) 2019-2022 Moonsong Labs. -// Copyright (c) 2023 Parity Technologies (UK) Ltd. -// -// This program 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. -// -// This program 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 this program. If not, see . - -pub mod account; -pub mod execution; -pub mod handle; -pub mod modifier; -mod solidity; - -pub use account::*; -pub use execution::*; -pub use handle::*; -pub use modifier::*; -pub use solidity::{check_precompile_implements_solidity_interfaces, compute_selector}; - -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() - ); - } - } - }; -} diff --git a/precompiles/xcm/Cargo.toml b/precompiles/xcm/Cargo.toml index 49bb2fdbbc..e6b547697f 100644 --- a/precompiles/xcm/Cargo.toml +++ b/precompiles/xcm/Cargo.toml @@ -13,7 +13,7 @@ log = { workspace = true } num_enum = { workspace = true } pallet-evm-precompile-assets-erc20 = { workspace = true } pallet-xcm = { workspace = true } -precompile-utils = { workspace = true } +precompile-utils = { workspace = true, features = ["codec-xcm"] } # Substrate frame-support = { workspace = true } @@ -42,7 +42,7 @@ hex-literal = { workspace = true } scale-info = { workspace = true } serde = { workspace = true } -precompile-utils = { workspace = true, features = ["testing"] } +precompile-utils = { workspace = true, features = ["testing", "codec-xcm"] } pallet-balances = { workspace = true } pallet-timestamp = { workspace = true } diff --git a/precompiles/xcm/src/lib.rs b/precompiles/xcm/src/lib.rs index 5d6c886cdd..9e4914cd75 100644 --- a/precompiles/xcm/src/lib.rs +++ b/precompiles/xcm/src/lib.rs @@ -19,7 +19,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use astar_primitives::xcm::XCM_SIZE_LIMIT; -use fp_evm::{PrecompileHandle, PrecompileOutput}; +use fp_evm::PrecompileHandle; use frame_support::{ dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, pallet_prelude::Weight, @@ -27,7 +27,7 @@ use frame_support::{ }; type GetXcmSizeLimit = ConstU32; -use pallet_evm::{AddressMapping, Precompile}; +use pallet_evm::AddressMapping; use parity_scale_codec::DecodeLimit; use sp_core::{H160, H256, U256}; @@ -38,41 +38,12 @@ use xcm::{latest::prelude::*, VersionedMultiAsset, VersionedMultiAssets, Version use xcm_executor::traits::Convert; use pallet_evm_precompile_assets_erc20::AddressToAssetId; -use precompile_utils::{ - bytes::BoundedBytes, - data::BoundedVec, - revert, succeed, - xcm::{Currency, EvmMultiAsset, WeightV2}, - Address, Bytes, EvmDataWriter, EvmResult, FunctionModifier, PrecompileHandleExt, RuntimeHelper, -}; +use precompile_utils::prelude::*; #[cfg(test)] mod mock; #[cfg(test)] mod tests; -#[precompile_utils::generate_function_selector] -#[derive(Debug, PartialEq)] -pub enum Action { - AssetsWithdrawNative = "assets_withdraw(address[],uint256[],bytes32,bool,uint256,uint256)", - AssetsWithdrawEvm = "assets_withdraw(address[],uint256[],address,bool,uint256,uint256)", - RemoteTransact = "remote_transact(uint256,bool,address,uint256,bytes,uint64)", - AssetsReserveTransferNative = - "assets_reserve_transfer(address[],uint256[],bytes32,bool,uint256,uint256)", - AssetsReserveTransferEvm = - "assets_reserve_transfer(address[],uint256[],address,bool,uint256,uint256)", - SendXCM = "send_xcm((uint8,bytes[]),bytes)", - XtokensTransfer = "transfer(address,uint256,(uint8,bytes[]),(uint64,uint64))", - XtokensTransferWithFee = - "transfer_with_fee(address,uint256,uint256,(uint8,bytes[]),(uint64,uint64))", - XtokensTransferMultiasset = - "transfer_multiasset((uint8,bytes[]),uint256,(uint8,bytes[]),(uint64,uint64))", - XtokensTransferMultiassetWithFee = "transfer_multiasset_with_fee((uint8,bytes[]),uint256,uint256,(uint8,bytes[]),(uint64,uint64))", - XtokensTransferMulticurrencies = - "transfer_multi_currencies((address,uint256)[],uint32,(uint8,bytes[]),(uint64,uint64))", - XtokensTransferMultiassets = - "transfet_multi_assets(((uint8,bytes[]),uint256)[],uint32,(uint8,bytes[]),(uint64,uint64))", -} - /// Dummy H160 address representing native currency (e.g. ASTR or SDN) const NATIVE_ADDRESS: H160 = H160::zero(); @@ -95,12 +66,14 @@ where /// A precompile that expose XCM related functions. pub struct XcmPrecompile(PhantomData<(Runtime, C)>); -impl Precompile for XcmPrecompile +#[precompile_utils::precompile] +#[precompile::test_concrete_types(mock::Runtime, mock::AssetIdConverter)] +impl XcmPrecompile where Runtime: pallet_evm::Config + pallet_xcm::Config - + pallet_assets::Config + orml_xtokens::Config + + pallet_assets::Config + AddressToAssetId<::AssetId>, <::RuntimeCall as Dispatchable>::RuntimeOrigin: From>, @@ -114,79 +87,69 @@ where From<::AssetId>, C: Convert::AssetId>, { - fn execute(handle: &mut impl PrecompileHandle) -> EvmResult { - log::trace!(target: "xcm-precompile", "In XCM precompile"); - - let selector = handle.read_selector()?; - - handle.check_function_modifier(FunctionModifier::NonPayable)?; - - // Dispatch the call - match selector { - Action::AssetsWithdrawNative => { - Self::assets_withdraw_v1(handle, BeneficiaryType::Account32) - } - Action::AssetsWithdrawEvm => { - Self::assets_withdraw_v1(handle, BeneficiaryType::Account20) - } - Action::RemoteTransact => Self::remote_transact_v1(handle), - Action::AssetsReserveTransferNative => { - Self::assets_reserve_transfer_v1(handle, BeneficiaryType::Account32) - } - Action::AssetsReserveTransferEvm => { - Self::assets_reserve_transfer_v1(handle, BeneficiaryType::Account20) - } - Action::SendXCM => Self::send_xcm(handle), - Action::XtokensTransfer => Self::transfer(handle), - Action::XtokensTransferWithFee => Self::transfer_with_fee(handle), - Action::XtokensTransferMultiasset => Self::transfer_multiasset(handle), - Action::XtokensTransferMultiassetWithFee => Self::transfer_multiasset_with_fee(handle), - Action::XtokensTransferMulticurrencies => Self::transfer_multi_currencies(handle), - Action::XtokensTransferMultiassets => Self::transfer_multi_assets(handle), + #[precompile::public("assets_withdraw(address[],uint256[],bytes32,bool,uint256,uint256)")] + fn assets_withdraw_native_v1( + handle: &mut impl PrecompileHandle, + assets: BoundedVec>, + amounts: BoundedVec>, + recipient_account_id: H256, + is_relay: bool, + parachain_id: U256, + fee_index: U256, + ) -> EvmResult { + let beneficiary: Junction = Junction::AccountId32 { + network: None, + id: recipient_account_id.into(), } + .into(); + Self::assets_withdraw_v1_internal( + handle, + assets.into(), + amounts.into(), + beneficiary, + is_relay, + parachain_id, + fee_index, + ) } -} - -/// The supported beneficiary account types -enum BeneficiaryType { - /// 256 bit (32 byte) public key - Account32, - /// 160 bit (20 byte) address is expected - Account20, -} -impl XcmPrecompile -where - Runtime: pallet_evm::Config - + pallet_xcm::Config - + orml_xtokens::Config - + pallet_assets::Config - + AddressToAssetId<::AssetId>, - <::RuntimeCall as Dispatchable>::RuntimeOrigin: - From>, - ::AccountId: Into<[u8; 32]>, - ::RuntimeCall: From> - + From> - + Dispatchable - + GetDispatchInfo, - XBalanceOf: TryFrom + Into + From, - ::CurrencyId: - From<::AssetId>, - C: Convert::AssetId>, -{ - fn assets_withdraw_v1( + #[precompile::public("assets_withdraw(address[],uint256[],address,bool,uint256,uint256)")] + fn assets_withdraw_evm_v1( handle: &mut impl PrecompileHandle, - beneficiary_type: BeneficiaryType, - ) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(6)?; + assets: BoundedVec>, + amounts: BoundedVec>, + recipient_account_id: Address, + is_relay: bool, + parachain_id: U256, + fee_index: U256, + ) -> EvmResult { + let beneficiary: Junction = Junction::AccountKey20 { + network: None, + key: recipient_account_id.0.to_fixed_bytes(), + } + .into(); + Self::assets_withdraw_v1_internal( + handle, + assets.into(), + amounts.into(), + beneficiary, + is_relay, + parachain_id, + fee_index, + ) + } + fn assets_withdraw_v1_internal( + handle: &mut impl PrecompileHandle, + assets: Vec
, + amounts: Vec, + beneficiary: Junction, + is_relay: bool, + parachain_id: U256, + fee_index: U256, + ) -> EvmResult { // Read arguments and check it - let assets_raw: Vec<_> = input - .read::>>()? - .into(); - - let assets = assets_raw + let assets = assets .iter() .cloned() .filter_map(|address| { @@ -194,11 +157,7 @@ where }) .collect::>(); - let amounts_raw: Vec = input - .read::>>()? - .into(); - - let amounts = amounts_raw + let amounts = amounts .into_iter() .map(|x| x.try_into()) .collect::, _>>() @@ -211,33 +170,11 @@ where return Err(revert("Assets resolution failure.")); } - let beneficiary: Junction = match beneficiary_type { - BeneficiaryType::Account32 => { - let recipient: [u8; 32] = input.read::()?.into(); - Junction::AccountId32 { - network: None, - id: recipient, - } - } - BeneficiaryType::Account20 => { - let recipient: H160 = input.read::
()?.into(); - Junction::AccountKey20 { - network: None, - key: recipient.to_fixed_bytes(), - } - } - } - .into(); - - let is_relay = input.read::()?; - - let parachain_id: u32 = input - .read::()? + let parachain_id: u32 = parachain_id .try_into() .map_err(|_| revert("error converting parachain_id, maybe value too large"))?; - let fee_item: u32 = input - .read::()? + let fee_item: u32 = fee_index .try_into() .map_err(|_| revert("error converting fee_index, maybe value too large"))?; @@ -275,31 +212,29 @@ where // Dispatch a call. RuntimeHelper::::try_dispatch(handle, origin, call)?; - - Ok(succeed(EvmDataWriter::new().write(true).build())) + Ok(true) } - fn remote_transact_v1(handle: &mut impl PrecompileHandle) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(6)?; - + #[precompile::public("remote_transact(uint256,bool,address,uint256,bytes,uint64)")] + fn remote_transact_v1( + handle: &mut impl PrecompileHandle, + para_id: U256, + is_relay: bool, + fee_asset_addr: Address, + fee_amount: U256, + remote_call: UnboundedBytes, + transact_weight: u64, + ) -> EvmResult { // Raw call arguments - let para_id: u32 = input - .read::()? + let para_id: u32 = para_id .try_into() .map_err(|_| revert("error converting para_id, maybe value too large"))?; - let is_relay = input.read::()?; - - let fee_asset_addr = input.read::
()?; - - let fee_amount: u128 = input - .read::()? + let fee_amount: u128 = fee_amount .try_into() .map_err(|_| revert("error converting fee_amount, maybe value too large"))?; - let remote_call: Vec = input.read::()?.into(); - let transact_weight = input.read::()?; + let remote_call: Vec = remote_call.into(); log::trace!(target: "xcm-precompile:remote_transact", "Raw arguments: para_id: {}, is_relay: {}, fee_asset_addr: {:?}, \ fee_amount: {:?}, remote_call: {:?}, transact_weight: {}", @@ -362,27 +297,23 @@ where // Dispatch a call. RuntimeHelper::::try_dispatch(handle, origin, call)?; - Ok(succeed(EvmDataWriter::new().write(true).build())) + Ok(true) } - fn assets_reserve_transfer_v1( + fn assets_reserve_transfer_v1_internal( handle: &mut impl PrecompileHandle, - beneficiary_type: BeneficiaryType, - ) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(6)?; - - // Read arguments and check it - let assets_raw: Vec<_> = input - .read::>>()? - .into(); - - let assets: Vec = assets_raw + assets: Vec
, + amounts: Vec, + beneficiary: Junction, + is_relay: bool, + parachain_id: U256, + fee_item: U256, + ) -> EvmResult { + let assets: Vec = assets .iter() .cloned() .filter_map(|address| { let address: H160 = address.into(); - // Special case where zero address maps to native token by convention. if address == NATIVE_ADDRESS { Some(Here.into()) @@ -391,11 +322,8 @@ where } }) .collect(); - let amounts_raw: Vec = input - .read::>>()? - .into(); - let amounts = amounts_raw + let amounts: Vec = amounts .into_iter() .map(|x| x.try_into()) .collect::, _>>() @@ -408,32 +336,11 @@ where return Err(revert("Assets resolution failure.")); } - let beneficiary: Junction = match beneficiary_type { - BeneficiaryType::Account32 => { - let recipient: [u8; 32] = input.read::()?.into(); - Junction::AccountId32 { - network: None, - id: recipient, - } - } - BeneficiaryType::Account20 => { - let recipient: H160 = input.read::
()?.into(); - Junction::AccountKey20 { - network: None, - key: recipient.to_fixed_bytes(), - } - } - } - .into(); - - let is_relay = input.read::()?; - let parachain_id: u32 = input - .read::()? + let parachain_id: u32 = parachain_id .try_into() .map_err(|_| revert("error converting parachain_id, maybe value too large"))?; - let fee_item: u32 = input - .read::()? + let fee_item: u32 = fee_item .try_into() .map_err(|_| revert("error converting fee_index, maybe value too large"))?; @@ -473,16 +380,74 @@ where // Dispatch a call. RuntimeHelper::::try_dispatch(handle, origin, call)?; - Ok(succeed(EvmDataWriter::new().write(true).build())) + Ok(true) + } + + #[precompile::public( + "assets_reserve_transfer(address[],uint256[],bytes32,bool,uint256,uint256)" + )] + fn assets_reserve_transfer_native_v1( + handle: &mut impl PrecompileHandle, + assets: BoundedVec>, + amounts: BoundedVec>, + recipient_account_id: H256, + is_relay: bool, + parachain_id: U256, + fee_index: U256, + ) -> EvmResult { + let beneficiary: Junction = Junction::AccountId32 { + network: None, + id: recipient_account_id.into(), + } + .into(); + Self::assets_reserve_transfer_v1_internal( + handle, + assets.into(), + amounts.into(), + beneficiary, + is_relay, + parachain_id, + fee_index, + ) } - fn send_xcm(handle: &mut impl PrecompileHandle) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(2)?; + #[precompile::public( + "assets_reserve_transfer(address[],uint256[],address,bool,uint256,uint256)" + )] + fn assets_reserve_transfer_evm_v1( + handle: &mut impl PrecompileHandle, + assets: BoundedVec>, + amounts: BoundedVec>, + recipient_account_id: Address, + is_relay: bool, + parachain_id: U256, + fee_index: U256, + ) -> EvmResult { + let beneficiary: Junction = Junction::AccountKey20 { + network: None, + key: recipient_account_id.0.to_fixed_bytes(), + } + .into(); + Self::assets_reserve_transfer_v1_internal( + handle, + assets.into(), + amounts.into(), + beneficiary, + is_relay, + parachain_id, + fee_index, + ) + } + #[precompile::public("send_xcm((uint8,bytes[]),bytes)")] + fn send_xcm( + handle: &mut impl PrecompileHandle, + dest: MultiLocation, + xcm_call: BoundedBytes, + ) -> EvmResult { // Raw call arguments - let dest: MultiLocation = input.read::()?; - let xcm_call: Vec = input.read::>()?.into(); + let dest: MultiLocation = dest.into(); + let xcm_call: Vec = xcm_call.into(); log::trace!(target:"xcm-precompile::send_xcm", "Raw arguments: dest: {:?}, xcm_call: {:?}", dest, xcm_call); @@ -505,21 +470,21 @@ where // Dispatch a call. RuntimeHelper::::try_dispatch(handle, origin, call)?; - Ok(succeed(EvmDataWriter::new().write(true).build())) + Ok(true) } - fn transfer(handle: &mut impl PrecompileHandle) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(4)?; - + #[precompile::public("transfer(address,uint256,(uint8,bytes[]),(uint64,uint64))")] + fn transfer( + handle: &mut impl PrecompileHandle, + currency_address: Address, + amount_of_tokens: U256, + destination: MultiLocation, + weight: WeightV2, + ) -> EvmResult { // Read call arguments - let currency_address = input.read::
()?; - let amount_of_tokens: u128 = input - .read::()? + let amount_of_tokens: u128 = amount_of_tokens .try_into() .map_err(|_| revert("error converting amount_of_tokens, maybe value too large"))?; - let destination = input.read::()?; - let weight = input.read::()?; let dest_weight_limit = if weight.is_zero() { WeightLimit::Unlimited @@ -565,26 +530,25 @@ where // Dispatch a call. RuntimeHelper::::try_dispatch(handle, origin, call)?; - Ok(succeed(EvmDataWriter::new().write(true).build())) + Ok(true) } - fn transfer_with_fee(handle: &mut impl PrecompileHandle) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(5)?; - + #[precompile::public( + "transfer_with_fee(address,uint256,uint256,(uint8,bytes[]),(uint64,uint64))" + )] + fn transfer_with_fee( + handle: &mut impl PrecompileHandle, + currency_address: Address, + amount_of_tokens: U256, + fee: U256, + destination: MultiLocation, + weight: WeightV2, + ) -> EvmResult { // Read call arguments - let currency_address = input.read::
()?; - let amount_of_tokens: u128 = input - .read::()? + let amount_of_tokens: u128 = amount_of_tokens .try_into() .map_err(|_| revert("error converting amount_of_tokens, maybe value too large"))?; - let fee: u128 = input - .read::()? - .try_into() - .map_err(|_| revert("can't convert fee"))?; - - let destination = input.read::()?; - let weight = input.read::()?; + let fee: u128 = fee.try_into().map_err(|_| revert("can't convert fee"))?; let dest_weight_limit = if weight.is_zero() { WeightLimit::Unlimited @@ -632,21 +596,23 @@ where // Dispatch a call. RuntimeHelper::::try_dispatch(handle, origin, call)?; - Ok(succeed(EvmDataWriter::new().write(true).build())) + Ok(true) } - fn transfer_multiasset(handle: &mut impl PrecompileHandle) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(4)?; - + #[precompile::public( + "transfer_multiasset((uint8,bytes[]),uint256,(uint8,bytes[]),(uint64,uint64))" + )] + fn transfer_multiasset( + handle: &mut impl PrecompileHandle, + asset_location: MultiLocation, + amount_of_tokens: U256, + destination: MultiLocation, + weight: WeightV2, + ) -> EvmResult { // Read call arguments - let asset_location = input.read::()?; - let amount_of_tokens: u128 = input - .read::()? + let amount_of_tokens: u128 = amount_of_tokens .try_into() .map_err(|_| revert("error converting amount_of_tokens, maybe value too large"))?; - let destination = input.read::()?; - let weight = input.read::()?; let dest_weight_limit = if weight.is_zero() { WeightLimit::Unlimited @@ -674,27 +640,25 @@ where // Dispatch a call. RuntimeHelper::::try_dispatch(handle, origin, call)?; - Ok(succeed(EvmDataWriter::new().write(true).build())) + Ok(true) } + #[precompile::public( + "transfer_multiasset_with_fee((uint8,bytes[]),uint256,uint256,(uint8,bytes[]),(uint64,uint64))" + )] fn transfer_multiasset_with_fee( handle: &mut impl PrecompileHandle, - ) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(5)?; - + asset_location: MultiLocation, + amount_of_tokens: U256, + fee: U256, + destination: MultiLocation, + weight: WeightV2, + ) -> EvmResult { // Read call arguments - let asset_location = input.read::()?; - let amount_of_tokens: u128 = input - .read::()? + let amount_of_tokens: u128 = amount_of_tokens .try_into() .map_err(|_| revert("error converting amount_of_tokens, maybe value too large"))?; - let fee: u128 = input - .read::()? - .try_into() - .map_err(|_| revert("can't convert fee"))?; - let destination = input.read::()?; - let weight = input.read::()?; + let fee: u128 = fee.try_into().map_err(|_| revert("can't convert fee"))?; let dest_weight_limit = if weight.is_zero() { WeightLimit::Unlimited @@ -723,22 +687,20 @@ where // Dispatch a call. RuntimeHelper::::try_dispatch(handle, origin, call)?; - Ok(succeed(EvmDataWriter::new().write(true).build())) + Ok(true) } + #[precompile::public( + "transfer_multi_currencies((address,uint256)[],uint32,(uint8,bytes[]),(uint64,uint64))" + )] fn transfer_multi_currencies( handle: &mut impl PrecompileHandle, - ) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(4)?; - - let currencies: Vec<_> = input - .read::>>()? - .into(); - let fee_item = input.read::()?; - let destination = input.read::()?; - let weight = input.read::()?; - + currencies: BoundedVec>, + fee_item: u32, + destination: MultiLocation, + weight: WeightV2, + ) -> EvmResult { + let currencies: Vec<_> = currencies.into(); let currencies = currencies .into_iter() .map(|currency| { @@ -781,19 +743,20 @@ where // Dispatch a call. RuntimeHelper::::try_dispatch(handle, origin, call)?; - Ok(succeed(EvmDataWriter::new().write(true).build())) + Ok(true) } - fn transfer_multi_assets(handle: &mut impl PrecompileHandle) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(4)?; - - let assets: Vec<_> = input - .read::>>()? - .into(); - let fee_item = input.read::()?; - let destination = input.read::()?; - let weight = input.read::()?; + #[precompile::public( + "transfet_multi_assets(((uint8,bytes[]),uint256)[],uint32,(uint8,bytes[]),(uint64,uint64))" + )] + fn transfer_multi_assets( + handle: &mut impl PrecompileHandle, + assets: BoundedVec>, + fee_item: u32, + destination: MultiLocation, + weight: WeightV2, + ) -> EvmResult { + let assets: Vec<_> = assets.into(); let dest_weight_limit = if weight.is_zero() { WeightLimit::Unlimited @@ -838,6 +801,79 @@ where // Dispatch a call. RuntimeHelper::::try_dispatch(handle, origin, call)?; - Ok(succeed(EvmDataWriter::new().write(true).build())) + Ok(true) + } +} + +#[derive(Debug, Clone, solidity::Codec)] +pub struct WeightV2 { + ref_time: u64, + proof_size: u64, +} + +impl WeightV2 { + pub fn from(ref_time: u64, proof_size: u64) -> Self { + WeightV2 { + ref_time, + proof_size, + } + } + + pub fn get_weight(&self) -> Weight { + Weight::from_parts(self.ref_time, self.proof_size) + } + + pub fn is_zero(&self) -> bool { + self.ref_time == 0u64 + } +} + +#[derive(Debug, Clone, solidity::Codec)] +pub struct Currency { + address: Address, + amount: U256, +} + +impl Currency { + pub fn get_address(&self) -> Address { + self.address + } + + pub fn get_amount(&self) -> U256 { + self.amount + } +} + +impl From<(Address, U256)> for Currency { + fn from(tuple: (Address, U256)) -> Self { + Currency { + address: tuple.0, + amount: tuple.1, + } + } +} + +#[derive(Debug, Clone, solidity::Codec)] +pub struct EvmMultiAsset { + location: MultiLocation, + amount: U256, +} + +impl From<(MultiLocation, U256)> for EvmMultiAsset { + fn from(tuple: (MultiLocation, U256)) -> Self { + EvmMultiAsset { + location: tuple.0, + amount: tuple.1, + } + } +} + +impl EvmMultiAsset { + pub fn get_location(&self) -> MultiLocation { + self.location + } + + pub fn get_amount(&self) -> U256 { + self.amount } } diff --git a/precompiles/xcm/src/mock.rs b/precompiles/xcm/src/mock.rs index fdb3ddebb2..4321c7fd2b 100644 --- a/precompiles/xcm/src/mock.rs +++ b/precompiles/xcm/src/mock.rs @@ -20,7 +20,7 @@ use super::*; -use fp_evm::IsPrecompileResult; +use fp_evm::{IsPrecompileResult, Precompile}; use frame_support::{ construct_runtime, parameter_types, traits::{AsEnsureOriginWithArg, ConstU64, Everything, Nothing}, @@ -362,6 +362,8 @@ parameter_types! { pub WeightPerGas: Weight = Weight::from_parts(1,0); } +pub type PrecompileCall = XcmPrecompileCall>; + impl pallet_evm::Config for Runtime { type FeeCalculator = (); type GasWeightMapping = pallet_evm::FixedGasWeightMapping; diff --git a/precompiles/xcm/src/tests.rs b/precompiles/xcm/src/tests.rs index f6671b8333..5fc0baf17d 100644 --- a/precompiles/xcm/src/tests.rs +++ b/precompiles/xcm/src/tests.rs @@ -25,7 +25,6 @@ use xcm::latest::{ use orml_xtokens::Event as XtokensEvent; use parity_scale_codec::Encode; use precompile_utils::testing::*; -use precompile_utils::EvmDataWriter; use sp_core::{H160, H256}; use sp_runtime::traits::Convert; use xcm::VersionedXcm; @@ -43,14 +42,14 @@ mod xcm_old_interface_test { .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsWithdrawNative) - .write(vec![Address::from(H160::repeat_byte(0xF1))]) - .write(Vec::::new()) - .write(H256::repeat_byte(0xF1)) - .write(true) - .write(U256::from(0_u64)) - .write(U256::from(0_u64)) - .build(), + PrecompileCall::assets_withdraw_native_v1 { + assets: vec![Address::from(H160::repeat_byte(0xF1))].into(), + amounts: vec![].into(), + recipient_account_id: H256::repeat_byte(0xF1), + is_relay: true, + parachain_id: 0.into(), + fee_index: 0.into(), + }, ) .expect_no_logs() .execute_reverts(|output| output == b"Assets resolution failure."); @@ -59,14 +58,14 @@ mod xcm_old_interface_test { .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsWithdrawNative) - .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) - .write(vec![U256::from(42000u64)]) - .write(H256::repeat_byte(0xF1)) - .write(true) - .write(U256::from(0_u64)) - .write(U256::from(2_u64)) - .build(), + PrecompileCall::assets_withdraw_native_v1 { + assets: vec![Address::from(Runtime::asset_id_to_address(1u128))].into(), + amounts: vec![42000u64.into()].into(), + recipient_account_id: H256::repeat_byte(0xF1), + is_relay: true, + parachain_id: 0.into(), + fee_index: 2.into(), + }, ) .expect_no_logs() .execute_reverts(|output| { @@ -84,14 +83,14 @@ mod xcm_old_interface_test { .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsWithdrawNative) - .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) - .write(vec![U256::from(42000u64)]) - .write(H256::repeat_byte(0xF1)) - .write(false) - .write(U256::from(u64::MAX)) // parachain id should be u32 - .write(U256::from(0_u64)) - .build(), + PrecompileCall::assets_withdraw_native_v1 { + assets: vec![Address::from(Runtime::asset_id_to_address(1u128))].into(), + amounts: vec![42000u64.into()].into(), + recipient_account_id: H256::repeat_byte(0xF1), + is_relay: false, + parachain_id: u64::MAX.into(), + fee_index: 0.into(), + }, ) .expect_no_logs() .execute_reverts(|output| { @@ -103,27 +102,29 @@ mod xcm_old_interface_test { .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsWithdrawNative) - .write(vec![ + PrecompileCall::assets_withdraw_native_v1 { + assets: vec![ Address::from(H160::repeat_byte(0xF1)), Address::from(H160::repeat_byte(0xF2)), Address::from(H160::repeat_byte(0xF3)), - ]) - .write(vec![ + ] + .into(), + amounts: vec![ U256::from(42000u64), U256::from(42000u64), U256::from(42000u64), - ]) - .write(H256::repeat_byte(0xF1)) - .write(false) - .write(U256::from(1_u64)) - .write(U256::from(0_u64)) - .build(), + ] + .into(), + recipient_account_id: H256::repeat_byte(0xF1), + is_relay: false, + parachain_id: 1.into(), + fee_index: 0.into(), + }, ) .expect_no_logs() .execute_reverts(|output| { let error_string = String::from_utf8_lossy(output); - error_string.contains("Array has more than max items allowed") + error_string.contains("assets: Value is too large for length") }); }); } @@ -136,51 +137,51 @@ mod xcm_old_interface_test { .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsWithdrawNative) - .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) - .write(vec![U256::from(42000u64)]) - .write(H256::repeat_byte(0xF1)) - .write(true) - .write(U256::from(0_u64)) - .write(U256::from(0_u64)) - .build(), + PrecompileCall::assets_withdraw_native_v1 { + assets: vec![Address::from(Runtime::asset_id_to_address(1u128))].into(), + amounts: vec![42000u64.into()].into(), + recipient_account_id: H256::repeat_byte(0xF1), + is_relay: true, + parachain_id: 0.into(), + fee_index: 0.into(), + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); // H160 precompiles() .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsWithdrawEvm) - .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) - .write(vec![U256::from(42000u64)]) - .write(Address::from(H160::repeat_byte(0xDE))) - .write(true) - .write(U256::from(0_u64)) - .write(U256::from(0_u64)) - .build(), + PrecompileCall::assets_withdraw_evm_v1 { + assets: vec![Address::from(Runtime::asset_id_to_address(1u128))].into(), + amounts: vec![42000u64.into()].into(), + recipient_account_id: Address(H160::repeat_byte(0xDE)), + is_relay: false, + parachain_id: 0.into(), + fee_index: 0.into(), + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); // Checking for non-relay destination case precompiles() .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsWithdrawEvm) - .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) - .write(vec![U256::from(42000u64)]) - .write(Address::from(H160::repeat_byte(0xDE))) - .write(false) - .write(U256::from(123_u64)) - .write(U256::from(0_u64)) - .build(), + PrecompileCall::assets_withdraw_evm_v1 { + assets: vec![Address::from(Runtime::asset_id_to_address(1u128))].into(), + amounts: vec![42000u64.into()].into(), + recipient_account_id: Address(H160::repeat_byte(0xDE)), + is_relay: false, + parachain_id: 123.into(), + fee_index: 0.into(), + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); }); } @@ -192,17 +193,17 @@ mod xcm_old_interface_test { .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::RemoteTransact) - .write(U256::from(0_u64)) - .write(true) - .write(Address::from(Runtime::asset_id_to_address(1_u128))) - .write(U256::from(367)) - .write(vec![0xff_u8, 0xaa, 0x77, 0x00]) - .write(U256::from(3_000_000_000u64)) - .build(), + PrecompileCall::remote_transact_v1 { + para_id: 0.into(), + is_relay: true, + fee_asset_addr: Address::from(Runtime::asset_id_to_address(1_u128)), + fee_amount: 367.into(), + remote_call: vec![0xff_u8, 0xaa, 0x77, 0x00].into(), + transact_weight: 3_000_000_000u64.into(), + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); }); } @@ -214,34 +215,34 @@ mod xcm_old_interface_test { .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsReserveTransferNative) - .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) - .write(vec![U256::from(42000u64)]) - .write(H256::repeat_byte(0xF1)) - .write(true) - .write(U256::from(0_u64)) - .write(U256::from(0_u64)) - .build(), + PrecompileCall::assets_reserve_transfer_native_v1 { + assets: vec![Address::from(Runtime::asset_id_to_address(1u128))].into(), + amounts: vec![U256::from(42000u64)].into(), + recipient_account_id: H256::repeat_byte(0xF1), + is_relay: true, + parachain_id: 0.into(), + fee_index: 0.into(), + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); // H160 precompiles() .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsReserveTransferEvm) - .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) - .write(vec![U256::from(42000u64)]) - .write(Address::from(H160::repeat_byte(0xDE))) - .write(true) - .write(U256::from(0_u64)) - .write(U256::from(0_u64)) - .build(), + PrecompileCall::assets_reserve_transfer_evm_v1 { + assets: vec![Address::from(Runtime::asset_id_to_address(1u128))].into(), + amounts: vec![U256::from(42000u64)].into(), + recipient_account_id: Address::from(H160::repeat_byte(0xDE)), + is_relay: true, + parachain_id: 0.into(), + fee_index: 0.into(), + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); for (location, Xcm(instructions)) in take_sent_xcm() { assert_eq!( @@ -290,33 +291,33 @@ mod xcm_old_interface_test { .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsReserveTransferNative) - .write(vec![Address::from(H160::zero())]) // zero address by convention - .write(vec![U256::from(42000u64)]) - .write(H256::repeat_byte(0xF1)) - .write(true) - .write(U256::from(0_u64)) - .write(U256::from(0_u64)) - .build(), + PrecompileCall::assets_reserve_transfer_native_v1 { + assets: vec![Address::from(H160::zero())].into(), + amounts: vec![U256::from(42000u64)].into(), + recipient_account_id: H256::repeat_byte(0xF1), + is_relay: true, + parachain_id: 0.into(), + fee_index: 0.into(), + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); precompiles() .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsReserveTransferEvm) - .write(vec![Address::from(H160::zero())]) // zero address by convention - .write(vec![U256::from(42000u64)]) - .write(Address::from(H160::repeat_byte(0xDE))) - .write(true) - .write(U256::from(0_u64)) - .write(U256::from(0_u64)) - .build(), + PrecompileCall::assets_reserve_transfer_evm_v1 { + assets: vec![Address::from(H160::zero())].into(), + amounts: vec![U256::from(42000u64)].into(), + recipient_account_id: Address::from(H160::repeat_byte(0xDE)), + is_relay: true, + parachain_id: 0.into(), + fee_index: 0.into(), + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); for (location, Xcm(instructions)) in take_sent_xcm() { assert_eq!( @@ -373,15 +374,15 @@ mod xcm_old_interface_test { .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::SendXCM) - .write(dest) - .write(Bytes::from(xcm_to_send.as_slice())) - .build(), + PrecompileCall::send_xcm { + dest, + xcm_call: xcm_to_send.as_slice().into(), + }, ) // Fixed: TestWeightInfo .expect_cost(100000000) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); let sent_messages = take_sent_xcm(); let (_, sent_message) = sent_messages.first().unwrap(); @@ -422,15 +423,15 @@ mod xcm_new_interface_test { .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::XtokensTransfer) - .write(Address::from(Runtime::asset_id_to_address(1u128))) // zero address by convention - .write(U256::from(42000u64)) - .write(parent_destination) - .write(weight.clone()) - .build(), + PrecompileCall::transfer { + currency_address: Address::from(Runtime::asset_id_to_address(1u128)), + amount_of_tokens: 42000u64.into(), + destination: parent_destination, + weight: weight.clone(), + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); let expected_asset: MultiAsset = MultiAsset { id: AssetId::Concrete(CurrencyIdToMultiLocation::convert(1).unwrap()), @@ -452,15 +453,15 @@ mod xcm_new_interface_test { .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::XtokensTransfer) - .write(Address::from(Runtime::asset_id_to_address(2u128))) // zero address by convention - .write(U256::from(42000u64)) - .write(sibling_parachain_location) - .write(weight) - .build(), + PrecompileCall::transfer { + currency_address: Address::from(Runtime::asset_id_to_address(2u128)), + amount_of_tokens: 42000u64.into(), + destination: sibling_parachain_location, + weight, + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); let expected_asset: MultiAsset = MultiAsset { id: AssetId::Concrete(CurrencyIdToMultiLocation::convert(2).unwrap()), @@ -496,15 +497,15 @@ mod xcm_new_interface_test { .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::XtokensTransfer) - .write(Address::from(NATIVE_ADDRESS)) // zero address by convention - .write(U256::from(42000u64)) - .write(parent_destination) - .write(weight.clone()) - .build(), + PrecompileCall::transfer { + currency_address: Address::from(NATIVE_ADDRESS), + amount_of_tokens: 42000u64.into(), + destination: parent_destination, + weight: weight.clone(), + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); let expected_asset: MultiAsset = MultiAsset { id: AssetId::Concrete(Here.into()), @@ -540,16 +541,16 @@ mod xcm_new_interface_test { .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::XtokensTransferWithFee) - .write(Address::from(Runtime::asset_id_to_address(1u128))) // zero address by convention - .write(U256::from(42000u64)) - .write(U256::from(50)) - .write(parent_destination) - .write(weight) - .build(), + PrecompileCall::transfer_with_fee { + currency_address: Address::from(Runtime::asset_id_to_address(1u128)), + amount_of_tokens: 42000u64.into(), + fee: 50.into(), + destination: parent_destination, + weight, + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); let expected_asset: MultiAsset = MultiAsset { id: AssetId::Concrete(CurrencyIdToMultiLocation::convert(1).unwrap()), @@ -589,16 +590,16 @@ mod xcm_new_interface_test { .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::XtokensTransferWithFee) - .write(Address::from(NATIVE_ADDRESS)) // zero address by convention - .write(U256::from(42000u64)) - .write(U256::from(50)) - .write(parent_destination) - .write(weight.clone()) - .build(), + PrecompileCall::transfer_with_fee { + currency_address: Address::from(NATIVE_ADDRESS), + amount_of_tokens: 42000u64.into(), + fee: 50.into(), + destination: parent_destination, + weight, + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); let expected_asset: MultiAsset = MultiAsset { id: AssetId::Concrete(Here.into()), @@ -653,15 +654,15 @@ mod xcm_new_interface_test { .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::XtokensTransferMultiasset) - .write(relay_token_location) // zero address by convention - .write(U256::from(amount)) - .write(relay_destination) - .write(weight.clone()) - .build(), + PrecompileCall::transfer_multiasset { + asset_location: relay_token_location, + amount_of_tokens: amount.into(), + destination: relay_destination, + weight: weight.clone(), + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); let expected_asset: MultiAsset = MultiAsset { id: AssetId::Concrete(relay_token_location), @@ -684,15 +685,15 @@ mod xcm_new_interface_test { .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::XtokensTransferMultiasset) - .write(relay_token_location) // zero address by convention - .write(U256::from(amount)) - .write(para_destination) - .write(weight.clone()) - .build(), + PrecompileCall::transfer_multiasset { + asset_location: relay_token_location, + amount_of_tokens: amount.into(), + destination: para_destination, + weight: weight.clone(), + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); let expected_asset: MultiAsset = MultiAsset { id: AssetId::Concrete(relay_token_location), @@ -716,15 +717,15 @@ mod xcm_new_interface_test { .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::XtokensTransferMultiasset) - .write(native_token_location) // zero address by convention - .write(U256::from(amount)) - .write(para_destination) - .write(weight.clone()) - .build(), + PrecompileCall::transfer_multiasset { + asset_location: native_token_location, // zero address by convention + amount_of_tokens: amount.into(), + destination: para_destination, + weight: weight.clone(), + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); let expected_asset: MultiAsset = MultiAsset { id: AssetId::Concrete(native_token_location), @@ -758,7 +759,7 @@ mod xcm_new_interface_test { // NOTE: Currently only support `ToReserve` with relay-chain asset as fee. other case // like `NonReserve` or `SelfReserve` with relay-chain fee is not support. - let currencies: Vec = vec![ + let currencies = vec![ ( Address::from(Runtime::asset_id_to_address(2u128)), U256::from(500), @@ -769,22 +770,23 @@ mod xcm_new_interface_test { U256::from(500), ) .into(), - ]; + ] + .into(); ExtBuilder::default().build().execute_with(|| { precompiles() .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::XtokensTransferMulticurrencies) - .write(currencies) // zero address by convention - .write(U256::from(0)) - .write(destination) - .write(weight) - .build(), + PrecompileCall::transfer_multi_currencies { + currencies, + fee_item: 0u32, + destination, + weight, + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); let expected_asset_1: MultiAsset = MultiAsset { id: AssetId::Concrete(CurrencyIdToMultiLocation::convert(2u128).unwrap()), @@ -818,7 +820,7 @@ mod xcm_new_interface_test { ); let weight = WeightV2::from(3_000_000_000u64, 1024); // we only allow upto 2 currencies to be transfered - let currencies: Vec = vec![ + let currencies = vec![ ( Address::from(Runtime::asset_id_to_address(2u128)), U256::from(500), @@ -834,24 +836,23 @@ mod xcm_new_interface_test { U256::from(500), ) .into(), - ]; + ] + .into(); ExtBuilder::default().build().execute_with(|| { precompiles() .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::XtokensTransferMulticurrencies) - .write(currencies) // zero address by convention - .write(U256::from(0)) - .write(destination) - .write(weight) - .build(), + PrecompileCall::transfer_multi_currencies { + currencies, + fee_item: 0u32, + destination, + weight, + }, ) .expect_no_logs() - .execute_reverts(|output| { - output == b"value too large : Array has more than max items allowed" - }); + .execute_reverts(|output| output == b"currencies: Value is too large for length"); }); } @@ -878,10 +879,11 @@ mod xcm_new_interface_test { Junctions::X2(Junction::Parachain(2), Junction::GeneralIndex(1u128)), ); - let assets: Vec = vec![ + let assets = vec![ (asset_1_location.clone(), U256::from(500)).into(), (asset_2_location.clone(), U256::from(500)).into(), - ]; + ] + .into(); let multiassets = MultiAssets::from_sorted_and_deduplicated(vec![ (asset_1_location.clone(), 500).into(), @@ -894,15 +896,15 @@ mod xcm_new_interface_test { .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::XtokensTransferMultiassets) - .write(assets) // zero address by convention - .write(U256::from(0)) - .write(destination) - .write(weight) - .build(), + PrecompileCall::transfer_multi_assets { + assets, + fee_item: 0u32, + destination, + weight, + }, ) .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); + .execute_returns(true); let expected: crate::mock::RuntimeEvent = mock::RuntimeEvent::Xtokens(XtokensEvent::TransferredMultiAssets { @@ -945,28 +947,27 @@ mod xcm_new_interface_test { Junctions::X2(Junction::Parachain(2), Junction::GeneralIndex(3u128)), ); - let assets: Vec = vec![ + let assets = vec![ (asset_1_location.clone(), U256::from(500)).into(), (asset_2_location.clone(), U256::from(500)).into(), (asset_3_location.clone(), U256::from(500)).into(), - ]; + ] + .into(); ExtBuilder::default().build().execute_with(|| { precompiles() .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::XtokensTransferMultiassets) - .write(assets) // zero address by convention - .write(U256::from(0)) - .write(destination) - .write(weight) - .build(), + PrecompileCall::transfer_multi_assets { + assets, + fee_item: 0u32, + destination, + weight, + }, ) .expect_no_logs() - .execute_reverts(|output| { - output == b"value too large : Array has more than max items allowed" - }); + .execute_reverts(|output| output == b"assets: Value is too large for length"); }); } } diff --git a/precompiles/xvm/src/lib.rs b/precompiles/xvm/src/lib.rs index adc004f700..db838453dd 100644 --- a/precompiles/xvm/src/lib.rs +++ b/precompiles/xvm/src/lib.rs @@ -21,19 +21,14 @@ extern crate alloc; use alloc::format; -use astar_primitives::{ - xvm::{Context, FailureReason, VmId, XvmCall}, - Balance, -}; -use fp_evm::{PrecompileHandle, PrecompileOutput}; +use astar_primitives::xvm::{Context, FailureReason, VmId, XvmCall}; +use fp_evm::{ExitRevert, PrecompileFailure, PrecompileHandle}; use frame_support::dispatch::Dispatchable; -use pallet_evm::{AddressMapping, GasWeightMapping, Precompile}; -use sp_std::{marker::PhantomData, prelude::*}; - -use precompile_utils::{ - revert, succeed, Bytes, EvmDataWriter, EvmResult, FunctionModifier, PrecompileHandleExt, -}; +use pallet_evm::{AddressMapping, GasWeightMapping}; +use sp_core::U256; +use sp_std::marker::PhantomData; +use precompile_utils::prelude::*; #[cfg(test)] mod mock; #[cfg(test)] @@ -42,36 +37,10 @@ mod tests; // The selector on EVM revert, calculated by: `Keccak256::digest(b"Error(string)")[..4]` const EVM_ERROR_MSG_SELECTOR: [u8; 4] = [8, 195, 121, 160]; -#[precompile_utils::generate_function_selector] -#[derive(Debug, PartialEq)] -pub enum Action { - XvmCall = "xvm_call(uint8,bytes,bytes,uint256,uint256)", -} - /// A precompile that expose XVM related functions. pub struct XvmPrecompile(PhantomData<(T, XC)>); -impl Precompile for XvmPrecompile -where - R: pallet_evm::Config, - <::RuntimeCall as Dispatchable>::RuntimeOrigin: - From>, - XC: XvmCall, -{ - fn execute(handle: &mut impl PrecompileHandle) -> EvmResult { - log::trace!(target: "xvm-precompile", "In XVM precompile"); - - let selector = handle.read_selector()?; - - handle.check_function_modifier(FunctionModifier::NonPayable)?; - - match selector { - // Dispatchables - Action::XvmCall => Self::xvm_call(handle), - } - } -} - +#[precompile_utils::precompile] impl XvmPrecompile where R: pallet_evm::Config, @@ -79,14 +48,16 @@ where From>, XC: XvmCall, { - fn xvm_call(handle: &mut impl PrecompileHandle) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(4)?; - - let vm_id = { - let id = input.read::()?; - id.try_into().map_err(|_| revert("invalid vm id")) - }?; + #[precompile::public("xvm_call(uint8,bytes,bytes,uint256,uint256)")] + fn xvm_call( + handle: &mut impl PrecompileHandle, + vm_id: u8, + call_to: UnboundedBytes, + call_input: UnboundedBytes, + value: U256, + storage_deposit_limit: U256, + ) -> EvmResult<(bool, UnboundedBytes)> { + let vm_id = vm_id.try_into().map_err(|_| revert("invalid vm id"))?; let mut gas_limit = handle.remaining_gas(); // If user specified a gas limit, make sure it's not exceeded. @@ -99,28 +70,28 @@ where weight_limit, }; - let call_to = input.read::()?.0; - let call_input = input.read::()?.0; - let value = input.read::()?; - let storage_deposit_limit = { - let limit = input.read::()?; - if limit == 0 { + let call_to = call_to.into(); + let call_input = call_input.into(); + let value = value.try_into().map_err(|_| revert("value overflow"))?; + let storage_deposit_limit: u128 = storage_deposit_limit + .try_into() + .map_err(|_| revert("value overflow"))?; + + let limit = { + if storage_deposit_limit == 0 { None } else { - Some(limit) + Some(storage_deposit_limit) } }; + let from = R::AddressMapping::into_account_id(handle.context().caller); - let call_result = XC::call( - xvm_context, - vm_id, - from, - call_to, - call_input, - value, - storage_deposit_limit, + log::trace!( + target: "xvm-precompile::xvm_call", + "vm_id: {:?}, from: {:?}, call_to: {:?}, call_input: {:?}, value: {:?}, limit: {:?}", vm_id, from, call_to, call_input, value, limit ); + let call_result = XC::call(xvm_context, vm_id, from, call_to, call_input, value, limit); let used_weight = match &call_result { Ok(s) => s.used_weight, @@ -137,12 +108,7 @@ where "success: {:?}", success ); - Ok(succeed( - EvmDataWriter::new() - .write(true) - .write(Bytes(success.output)) - .build(), - )) + Ok((true, success.output.into())) } Err(failure) => { @@ -162,11 +128,14 @@ where format!("{:?}", failure_error) } }; - let data = - EvmDataWriter::new_with_selector(u32::from_be_bytes(EVM_ERROR_MSG_SELECTOR)) - .write(Bytes(message.into_bytes())) - .build(); - Err(revert(data)) + let data = solidity::encode_with_selector( + u32::from_be_bytes(EVM_ERROR_MSG_SELECTOR), + UnboundedBytes::from(message.into_bytes()), + ); + Err(PrecompileFailure::Revert { + exit_status: ExitRevert::Reverted, + output: data, + }) } } } diff --git a/precompiles/xvm/src/mock.rs b/precompiles/xvm/src/mock.rs index 19cbfed110..647d58e692 100644 --- a/precompiles/xvm/src/mock.rs +++ b/precompiles/xvm/src/mock.rs @@ -20,7 +20,7 @@ use super::*; -use fp_evm::IsPrecompileResult; +use fp_evm::{IsPrecompileResult, Precompile}; use frame_support::{ construct_runtime, ensure, parameter_types, traits::{ConstU32, ConstU64, Everything}, @@ -216,6 +216,8 @@ parameter_types! { pub WeightPerGas: Weight = Weight::from_parts(1, 0); } +pub type PrecompileCall = XvmPrecompileCall; + impl pallet_evm::Config for Runtime { type FeeCalculator = (); type GasWeightMapping = pallet_evm::FixedGasWeightMapping; @@ -258,7 +260,7 @@ impl WeightLimitCalledWith { } } -struct MockXvmWithArgsCheck; +pub struct MockXvmWithArgsCheck; impl XvmCall for MockXvmWithArgsCheck { fn call( context: Context, diff --git a/precompiles/xvm/src/tests.rs b/precompiles/xvm/src/tests.rs index c51f80de3d..3f4e12af02 100644 --- a/precompiles/xvm/src/tests.rs +++ b/precompiles/xvm/src/tests.rs @@ -20,8 +20,6 @@ use crate::mock::*; use crate::*; use precompile_utils::testing::*; -use precompile_utils::EvmDataWriter; -use sp_core::U256; fn precompiles() -> TestPrecompileSet { PrecompilesValue::get() @@ -34,23 +32,22 @@ fn wrong_argument_reverts() { .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::XvmCall) - .write(42u64) - .build(), + solidity::encode_with_selector(PrecompileCall::xvm_call_selectors()[0], 42u32), ) .expect_no_logs() - .execute_reverts(|output| output == b"input doesn't match expected length"); + .execute_reverts(|output| output == b"Expected at least 5 arguments"); precompiles() .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::XvmCall) - .write(0u8) - .write(Bytes(b"".to_vec())) - .write(Bytes(b"".to_vec())) - .write(U256::one()) - .build(), + PrecompileCall::xvm_call { + vm_id: 0.into(), + call_to: b"".into(), + call_input: b"".into(), + value: 1.into(), + storage_deposit_limit: 0.into(), + }, ) .expect_no_logs() .execute_reverts(|output| output == b"invalid vm id"); @@ -64,15 +61,15 @@ fn correct_arguments_works() { .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::XvmCall) - .write(0x1Fu8) - .write(Bytes(b"".to_vec())) - .write( - hex::decode("0000000000000000000000000000000000000000") - .expect("invalid hex"), - ) - .write(U256::one()) - .build(), + PrecompileCall::xvm_call { + vm_id: 0x1Fu8.into(), + call_to: b"".into(), + call_input: hex::decode("0000000000000000000000000000000000000000") + .expect("invalid hex") + .into(), + value: 1.into(), + storage_deposit_limit: 0.into(), + }, ) .expect_no_logs() .execute_some(); @@ -87,15 +84,15 @@ fn weight_limit_is_min_of_remaining_and_user_limit() { .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::XvmCall) - .write(0x1Fu8) - .write(Bytes( - hex::decode("0000000000000000000000000000000000000000") - .expect("invalid hex"), - )) - .write(Bytes(b"".to_vec())) - .write(U256::one()) - .build(), + PrecompileCall::xvm_call { + vm_id: 0x1Fu8.into(), + call_to: hex::decode("0000000000000000000000000000000000000000") + .expect("invalid hex") + .into(), + call_input: b"".into(), + value: 1.into(), + storage_deposit_limit: 0.into(), + }, ) .expect_no_logs() .execute_some(); @@ -110,17 +107,17 @@ fn weight_limit_is_min_of_remaining_and_user_limit() { .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::XvmCall) - .write(0x1Fu8) - .write(Bytes( - hex::decode("0000000000000000000000000000000000000000") - .expect("invalid hex"), - )) - .write(Bytes(b"".to_vec())) - .write(U256::one()) - .build(), + PrecompileCall::xvm_call { + vm_id: 0x1Fu8.into(), + call_to: hex::decode("0000000000000000000000000000000000000000") + .expect("invalid hex") + .into(), + call_input: b"".into(), + value: 1.into(), + storage_deposit_limit: 0.into(), + }, ) - .with_gas_limit(gas_limit) + .with_target_gas(gas_limit.into()) .expect_no_logs() .execute_some(); assert_eq!( diff --git a/primitives/src/precompiles.rs b/primitives/src/precompiles.rs index 26e5abef52..18ebf8c1c6 100644 --- a/primitives/src/precompiles.rs +++ b/primitives/src/precompiles.rs @@ -16,8 +16,6 @@ // You should have received a copy of the GNU General Public License // along with Astar. If not, see . -#![cfg_attr(not(feature = "std"), no_std)] - use core::marker::PhantomData; use fp_evm::{ExitError, PrecompileFailure}; diff --git a/runtime/astar/Cargo.toml b/runtime/astar/Cargo.toml index 0c597e2ff4..4948ec59de 100644 --- a/runtime/astar/Cargo.toml +++ b/runtime/astar/Cargo.toml @@ -11,6 +11,7 @@ repository.workspace = true # third-party dependencies hex-literal = { workspace = true } log = { workspace = true, optional = true } +num_enum = { workspace = true } parity-scale-codec = { workspace = true } scale-info = { workspace = true } smallvec = { workspace = true } @@ -113,6 +114,7 @@ pallet-xcm = { workspace = true } moonbeam-evm-tracer = { workspace = true, optional = true } moonbeam-rpc-primitives-debug = { workspace = true, optional = true } moonbeam-rpc-primitives-txpool = { workspace = true, optional = true } +precompile-utils = { workspace = true } [build-dependencies] substrate-wasm-builder = { workspace = true, optional = true } diff --git a/runtime/astar/src/lib.rs b/runtime/astar/src/lib.rs index 0a6a46baef..c03b403732 100644 --- a/runtime/astar/src/lib.rs +++ b/runtime/astar/src/lib.rs @@ -88,8 +88,8 @@ mod xcm_config; pub type AstarAssetLocationIdConverter = AssetLocationIdConverter; -pub use precompiles::{AstarNetworkPrecompiles, ASSET_PRECOMPILE_ADDRESS_PREFIX}; -pub type Precompiles = AstarNetworkPrecompiles; +pub use precompiles::{AstarPrecompiles, ASSET_PRECOMPILE_ADDRESS_PREFIX}; +pub type Precompiles = AstarPrecompiles; /// Constant values used within the runtime. pub const MICROASTR: Balance = 1_000_000_000_000; @@ -777,7 +777,7 @@ parameter_types! { pub BlockGasLimit: U256 = U256::from( NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT.ref_time() / WEIGHT_PER_GAS ); - pub PrecompilesValue: Precompiles = AstarNetworkPrecompiles::<_, _>::new(); + pub PrecompilesValue: Precompiles = AstarPrecompiles::<_, _>::new(); pub WeightPerGas: Weight = Weight::from_parts(WEIGHT_PER_GAS, 0); /// The amount of gas per PoV size. Value is calculated as: /// diff --git a/runtime/astar/src/precompiles.rs b/runtime/astar/src/precompiles.rs index 3a0d442f09..938f4fec06 100644 --- a/runtime/astar/src/precompiles.rs +++ b/runtime/astar/src/precompiles.rs @@ -20,12 +20,8 @@ use crate::RuntimeCall; use astar_primitives::precompiles::DispatchFilterValidate; -use frame_support::traits::Contains; -use pallet_evm::{ - ExitRevert, IsPrecompileResult, Precompile, PrecompileFailure, PrecompileHandle, - PrecompileResult, PrecompileSet, -}; -use pallet_evm_precompile_assets_erc20::{AddressToAssetId, Erc20AssetsPrecompileSet}; +use frame_support::{parameter_types, traits::Contains}; +use pallet_evm_precompile_assets_erc20::Erc20AssetsPrecompileSet; use pallet_evm_precompile_blake2::Blake2F; use pallet_evm_precompile_bn128::{Bn128Add, Bn128Mul, Bn128Pairing}; use pallet_evm_precompile_dapps_staking::DappsStakingWrapper; @@ -37,18 +33,21 @@ use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripe use pallet_evm_precompile_sr25519::Sr25519Precompile; use pallet_evm_precompile_substrate_ecdsa::SubstrateEcdsaPrecompile; use pallet_evm_precompile_xcm::XcmPrecompile; -use sp_core::H160; +use precompile_utils::precompile_set::*; use sp_std::fmt::Debug; -use sp_std::marker::PhantomData; - -use xcm::latest::prelude::MultiLocation; /// The asset precompile address prefix. Addresses that match against this prefix will be routed /// to Erc20AssetsPrecompileSet pub const ASSET_PRECOMPILE_ADDRESS_PREFIX: &[u8] = &[255u8; 4]; +parameter_types! { + pub AssetPrefix: &'static [u8] = ASSET_PRECOMPILE_ADDRESS_PREFIX; +} -/// Filter that only allows whitelisted runtime call to pass through dispatch precompile +/// Precompile checks for ethereum spec precompiles +/// We allow DELEGATECALL to stay compliant with Ethereum behavior. +type EthereumPrecompilesChecks = (AcceptDelegateCall, CallableByContract, CallableByPrecompile); +/// Filter that only allows whitelisted runtime call to pass through dispatch precompile pub struct WhitelistedCalls; impl Contains for WhitelistedCalls { @@ -65,108 +64,67 @@ impl Contains for WhitelistedCalls { } } /// The PrecompileSet installed in the Astar runtime. -#[derive(Debug, Default, Clone, Copy)] -pub struct AstarNetworkPrecompiles(PhantomData<(R, C)>); - -impl AstarNetworkPrecompiles { - pub fn new() -> Self { - Self(Default::default()) - } - - /// 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, 1024, 1025, 1026, 1027, 20481, 20482, 20483, 20484,] - .into_iter() - .map(hash) - } - - /// Returns all addresses which are blacklisted for dummy code deployment. - /// This is in order to keep the local testnets consistent with the live network. - pub fn is_blacklisted(address: &H160) -> bool { - // `dispatch` precompile is not allowed to be called by smart contracts, hence the ommision of this address. - hash(1025) == *address - } -} - -/// The following distribution has been decided for the precompiles -/// 0-1023: Ethereum Mainnet Precompiles -/// 1024-2047 Precompiles that are not in Ethereum Mainnet -impl PrecompileSet for AstarNetworkPrecompiles -where - Erc20AssetsPrecompileSet: PrecompileSet, - DappsStakingWrapper: Precompile, - XcmPrecompile: Precompile, - Dispatch>: Precompile, - R: pallet_evm::Config - + pallet_assets::Config - + pallet_xcm::Config - + AddressToAssetId<::AssetId>, - C: xcm_executor::traits::Convert::AssetId>, -{ - fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { - let address = handle.code_address(); - if let IsPrecompileResult::Answer { is_precompile, .. } = - self.is_precompile(address, u64::MAX) - { - if is_precompile && address > hash(9) && handle.context().address != address { - return Some(Err(PrecompileFailure::Revert { - exit_status: ExitRevert::Reverted, - output: b"cannot be called with DELEGATECALL or CALLCODE".to_vec(), - })); - } - } - match address { - // Ethereum precompiles : - a if a == hash(1) => Some(ECRecover::execute(handle)), - a if a == hash(2) => Some(Sha256::execute(handle)), - a if a == hash(3) => Some(Ripemd160::execute(handle)), - a if a == hash(4) => Some(Identity::execute(handle)), - a if a == hash(5) => Some(Modexp::execute(handle)), - a if a == hash(6) => Some(Bn128Add::execute(handle)), - a if a == hash(7) => Some(Bn128Mul::execute(handle)), - a if a == hash(8) => Some(Bn128Pairing::execute(handle)), - a if a == hash(9) => Some(Blake2F::execute(handle)), - // nor Ethereum precompiles : - a if a == hash(1024) => Some(Sha3FIPS256::execute(handle)), - a if a == hash(1025) => Some(Dispatch::< - R, - DispatchFilterValidate, - >::execute(handle)), - a if a == hash(1026) => Some(ECRecoverPublicKey::execute(handle)), - a if a == hash(1027) => Some(Ed25519Verify::execute(handle)), - // Astar precompiles (starts from 0x5000): - // DappStaking 0x5001 - a if a == hash(20481) => Some(DappsStakingWrapper::::execute(handle)), - // Sr25519 0x5002 - a if a == hash(20482) => Some(Sr25519Precompile::::execute(handle)), - // SubstrateEcdsa 0x5003 - a if a == hash(20483) => Some(SubstrateEcdsaPrecompile::::execute(handle)), - // Xcm 0x5004 - a if a == hash(20484) => Some(XcmPrecompile::::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) - } - // Default - _ => None, - } - } - - fn is_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult { - let assets_precompile = - match Erc20AssetsPrecompileSet::::new().is_precompile(address, gas) { - IsPrecompileResult::Answer { is_precompile, .. } => is_precompile, - _ => false, - }; - - IsPrecompileResult::Answer { - is_precompile: assets_precompile || Self::used_addresses().any(|x| x == address), - extra_cost: 0, - } - } -} - -fn hash(a: u64) -> H160 { - H160::from_low_u64_be(a) -} +#[precompile_utils::precompile_name_from_address] +pub type AstarPrecompilesSetAt = ( + // Ethereum precompiles: + // We allow DELEGATECALL to stay compliant with Ethereum behavior. + PrecompileAt, ECRecover, EthereumPrecompilesChecks>, + PrecompileAt, Sha256, EthereumPrecompilesChecks>, + PrecompileAt, Ripemd160, EthereumPrecompilesChecks>, + PrecompileAt, Identity, EthereumPrecompilesChecks>, + PrecompileAt, Modexp, EthereumPrecompilesChecks>, + PrecompileAt, Bn128Add, EthereumPrecompilesChecks>, + PrecompileAt, Bn128Mul, EthereumPrecompilesChecks>, + PrecompileAt, Bn128Pairing, EthereumPrecompilesChecks>, + PrecompileAt, Blake2F, EthereumPrecompilesChecks>, + // Non-Astar specific nor Ethereum precompiles : + PrecompileAt, Sha3FIPS256, (CallableByContract, CallableByPrecompile)>, + PrecompileAt< + AddressU64<1025>, + Dispatch>, + // Not callable from smart contract nor precompiles, only EOA accounts + // TODO: test this without the gensis hack for blacklisted + (), + >, + PrecompileAt, ECRecoverPublicKey, (CallableByContract, CallableByPrecompile)>, + PrecompileAt, Ed25519Verify, (CallableByContract, CallableByPrecompile)>, + // Astar specific precompiles: + PrecompileAt< + AddressU64<20481>, + DappsStakingWrapper, + (CallableByContract, CallableByPrecompile), + >, + PrecompileAt< + AddressU64<20482>, + Sr25519Precompile, + (CallableByContract, CallableByPrecompile), + >, + PrecompileAt< + AddressU64<20483>, + SubstrateEcdsaPrecompile, + (CallableByContract, CallableByPrecompile), + >, + PrecompileAt< + AddressU64<20484>, + XcmPrecompile, + ( + SubcallWithMaxNesting<1>, + CallableByContract, + CallableByPrecompile, + ), + >, +); + +pub type AstarPrecompiles = PrecompileSetBuilder< + R, + ( + // Skip precompiles if out of range. + PrecompilesInRangeInclusive< + // We take range as last precompile index, UPDATE this once new prcompile is added + (AddressU64<1>, AddressU64<20484>), + AstarPrecompilesSetAt, + >, + // Prefixed precompile sets (XC20) + PrecompileSetStartingWith, CallableByContract>, + ), +>; diff --git a/runtime/local/Cargo.toml b/runtime/local/Cargo.toml index a41d39144c..0f31ebec72 100644 --- a/runtime/local/Cargo.toml +++ b/runtime/local/Cargo.toml @@ -9,6 +9,7 @@ repository.workspace = true [dependencies] log = { workspace = true, optional = true } +num_enum = { workspace = true } parity-scale-codec = { workspace = true } scale-info = { workspace = true } smallvec = { workspace = true } @@ -86,6 +87,8 @@ pallet-xvm = { workspace = true } dapp-staking-v3-runtime-api = { workspace = true } +precompile-utils = { workspace = true } + # Moonbeam tracing moonbeam-evm-tracer = { workspace = true, optional = true } moonbeam-rpc-primitives-debug = { workspace = true, optional = true } diff --git a/runtime/local/src/lib.rs b/runtime/local/src/lib.rs index ac63f7d50e..71a002fef6 100644 --- a/runtime/local/src/lib.rs +++ b/runtime/local/src/lib.rs @@ -111,8 +111,8 @@ impl_opaque_keys! { } mod precompiles; -pub use precompiles::{LocalNetworkPrecompiles, ASSET_PRECOMPILE_ADDRESS_PREFIX}; -pub type Precompiles = LocalNetworkPrecompiles; +pub use precompiles::{LocalPrecompiles, ASSET_PRECOMPILE_ADDRESS_PREFIX}; +pub type Precompiles = LocalPrecompiles; mod chain_extensions; pub use chain_extensions::*; @@ -675,7 +675,7 @@ parameter_types! { pub BlockGasLimit: U256 = U256::from( NORMAL_DISPATCH_RATIO * WEIGHT_REF_TIME_PER_SECOND / WEIGHT_PER_GAS ); - pub PrecompilesValue: Precompiles = LocalNetworkPrecompiles::<_>::new(); + pub PrecompilesValue: Precompiles = LocalPrecompiles::<_>::new(); pub WeightPerGas: Weight = Weight::from_parts(WEIGHT_PER_GAS, 0); /// The amount of gas per PoV size. Value is calculated as: /// diff --git a/runtime/local/src/precompiles.rs b/runtime/local/src/precompiles.rs index 4eca364f73..151c7ca577 100644 --- a/runtime/local/src/precompiles.rs +++ b/runtime/local/src/precompiles.rs @@ -16,16 +16,12 @@ // You should have received a copy of the GNU General Public License // along with Astar. If not, see . -//! The Local EVM precompiles. This can be compiled with ``#[no_std]`, ready for Wasm. +//! The Local Network EVM precompiles. This can be compiled with ``#[no_std]`, ready for Wasm. -use crate::RuntimeCall; +use crate::{RuntimeCall, UnifiedAccounts}; use astar_primitives::precompiles::DispatchFilterValidate; -use frame_support::traits::Contains; -use pallet_evm::{ - ExitRevert, IsPrecompileResult, Precompile, PrecompileFailure, PrecompileHandle, - PrecompileResult, PrecompileSet, -}; -use pallet_evm_precompile_assets_erc20::{AddressToAssetId, Erc20AssetsPrecompileSet}; +use frame_support::{parameter_types, traits::Contains}; +use pallet_evm_precompile_assets_erc20::Erc20AssetsPrecompileSet; use pallet_evm_precompile_blake2::Blake2F; use pallet_evm_precompile_bn128::{Bn128Add, Bn128Mul, Bn128Pairing}; use pallet_evm_precompile_dapps_staking::DappsStakingWrapper; @@ -38,13 +34,19 @@ use pallet_evm_precompile_sr25519::Sr25519Precompile; use pallet_evm_precompile_substrate_ecdsa::SubstrateEcdsaPrecompile; use pallet_evm_precompile_unified_accounts::UnifiedAccountsPrecompile; use pallet_evm_precompile_xvm::XvmPrecompile; -use sp_core::H160; +use precompile_utils::precompile_set::*; use sp_std::fmt::Debug; -use sp_std::marker::PhantomData; /// The asset precompile address prefix. Addresses that match against this prefix will be routed /// to Erc20AssetsPrecompileSet pub const ASSET_PRECOMPILE_ADDRESS_PREFIX: &[u8] = &[255u8; 4]; +parameter_types! { + pub AssetPrefix: &'static [u8] = ASSET_PRECOMPILE_ADDRESS_PREFIX; +} + +/// Precompile checks for ethereum spec precompiles +/// We allow DELEGATECALL to stay compliant with Ethereum behavior. +type EthereumPrecompilesChecks = (AcceptDelegateCall, CallableByContract, CallableByPrecompile); /// Filter that only allows whitelisted runtime call to pass through dispatch precompile pub struct WhitelistedCalls; @@ -56,112 +58,76 @@ impl Contains for WhitelistedCalls { | RuntimeCall::Utility(pallet_utility::Call::batch_all { calls }) => { calls.iter().all(|call| WhitelistedCalls::contains(call)) } - RuntimeCall::Assets(pallet_assets::Call::transfer { .. }) => true, RuntimeCall::DappsStaking(_) => true, + RuntimeCall::Assets(pallet_assets::Call::transfer { .. }) => true, _ => false, } } } - /// The PrecompileSet installed in the Local runtime. -#[derive(Debug, Default, Clone, Copy)] -pub struct LocalNetworkPrecompiles(PhantomData); - -impl LocalNetworkPrecompiles { - pub fn new() -> Self { - Self(Default::default()) - } - - /// 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, 20486 - ] - .into_iter() - .map(hash) - } -} - -/// The following distribution has been decided for the precompiles -/// 0-1023: Ethereum Mainnet Precompiles -/// 1024-2047 Precompiles that are not in Ethereum Mainnet -impl PrecompileSet for LocalNetworkPrecompiles -where - Erc20AssetsPrecompileSet: PrecompileSet, - DappsStakingWrapper: Precompile, - XvmPrecompile>: Precompile, - Dispatch>: Precompile, - UnifiedAccountsPrecompile>: Precompile, - R: pallet_evm::Config - + pallet_xvm::Config - + pallet_assets::Config - + pallet_unified_accounts::Config - + AddressToAssetId<::AssetId>, -{ - fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { - let address = handle.code_address(); - if let IsPrecompileResult::Answer { is_precompile, .. } = - self.is_precompile(address, u64::MAX) - { - if is_precompile && address > hash(9) && handle.context().address != address { - return Some(Err(PrecompileFailure::Revert { - exit_status: ExitRevert::Reverted, - output: b"cannot be called with DELEGATECALL or CALLCODE".to_vec(), - })); - } - } - match address { - // Ethereum precompiles : - a if a == hash(1) => Some(ECRecover::execute(handle)), - a if a == hash(2) => Some(Sha256::execute(handle)), - a if a == hash(3) => Some(Ripemd160::execute(handle)), - a if a == hash(4) => Some(Identity::execute(handle)), - a if a == hash(5) => Some(Modexp::execute(handle)), - a if a == hash(6) => Some(Bn128Add::execute(handle)), - a if a == hash(7) => Some(Bn128Mul::execute(handle)), - a if a == hash(8) => Some(Bn128Pairing::execute(handle)), - a if a == hash(9) => Some(Blake2F::execute(handle)), - // nor Ethereum precompiles : - a if a == hash(1024) => Some(Sha3FIPS256::execute(handle)), - a if a == hash(1025) => Some(Dispatch::< - R, - DispatchFilterValidate, - >::execute(handle)), - a if a == hash(1026) => Some(ECRecoverPublicKey::execute(handle)), - a if a == hash(1027) => Some(Ed25519Verify::execute(handle)), - // Astar precompiles (starts from 0x5000): - // DappStaking 0x5001 - a if a == hash(20481) => Some(DappsStakingWrapper::::execute(handle)), - // Sr25519 0x5002 - a if a == hash(20482) => Some(Sr25519Precompile::::execute(handle)), - // SubstrateEcdsa 0x5003 - a if a == hash(20483) => Some(SubstrateEcdsaPrecompile::::execute(handle)), - // Xvm 0x5005 - a if a == hash(20485) => { - Some(XvmPrecompile::>::execute(handle)) - } - a if a == hash(20486) => Some(UnifiedAccountsPrecompile::< - R, - pallet_unified_accounts::Pallet, - >::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) - } - // Default - _ => None, - } - } +#[precompile_utils::precompile_name_from_address] +pub type LocalPrecompilesSetAt = ( + // Ethereum precompiles: + // We allow DELEGATECALL to stay compliant with Ethereum behavior. + PrecompileAt, ECRecover, EthereumPrecompilesChecks>, + PrecompileAt, Sha256, EthereumPrecompilesChecks>, + PrecompileAt, Ripemd160, EthereumPrecompilesChecks>, + PrecompileAt, Identity, EthereumPrecompilesChecks>, + PrecompileAt, Modexp, EthereumPrecompilesChecks>, + PrecompileAt, Bn128Add, EthereumPrecompilesChecks>, + PrecompileAt, Bn128Mul, EthereumPrecompilesChecks>, + PrecompileAt, Bn128Pairing, EthereumPrecompilesChecks>, + PrecompileAt, Blake2F, EthereumPrecompilesChecks>, + // Non-Local specific nor Ethereum precompiles : + PrecompileAt, Sha3FIPS256, (CallableByContract, CallableByPrecompile)>, + PrecompileAt< + AddressU64<1025>, + Dispatch>, + // Not callable from smart contract nor precompiles, only EOA accounts + // TODO: test this without the gensis hack for blacklisted + (), + >, + PrecompileAt, ECRecoverPublicKey, (CallableByContract, CallableByPrecompile)>, + PrecompileAt, Ed25519Verify, (CallableByContract, CallableByPrecompile)>, + // Local specific precompiles: + PrecompileAt< + AddressU64<20481>, + DappsStakingWrapper, + (CallableByContract, CallableByPrecompile), + >, + PrecompileAt< + AddressU64<20482>, + Sr25519Precompile, + (CallableByContract, CallableByPrecompile), + >, + PrecompileAt< + AddressU64<20483>, + SubstrateEcdsaPrecompile, + (CallableByContract, CallableByPrecompile), + >, + // skip 20484 for xcm precompile + PrecompileAt< + AddressU64<20485>, + XvmPrecompile>, + (CallableByContract, CallableByPrecompile), + >, + PrecompileAt< + AddressU64<20486>, + UnifiedAccountsPrecompile, + (CallableByContract, CallableByPrecompile), + >, +); - fn is_precompile(&self, address: H160, _gas: u64) -> IsPrecompileResult { - IsPrecompileResult::Answer { - is_precompile: Self::used_addresses().any(|x| x == address), - extra_cost: 0, - } - } -} - -fn hash(a: u64) -> H160 { - H160::from_low_u64_be(a) -} +pub type LocalPrecompiles = PrecompileSetBuilder< + R, + ( + // Skip precompiles if out of range. + PrecompilesInRangeInclusive< + // We take range as last precompile index, UPDATE this once new prcompile is added + (AddressU64<1>, AddressU64<20486>), + LocalPrecompilesSetAt, + >, + // Prefixed precompile sets (XC20) + PrecompileSetStartingWith, CallableByContract>, + ), +>; diff --git a/runtime/shibuya/Cargo.toml b/runtime/shibuya/Cargo.toml index 23e0c7c322..49eb2d6c01 100644 --- a/runtime/shibuya/Cargo.toml +++ b/runtime/shibuya/Cargo.toml @@ -11,6 +11,7 @@ repository.workspace = true # third-party dependencies hex-literal = { workspace = true } log = { workspace = true, optional = true } +num_enum = { workspace = true } parity-scale-codec = { workspace = true } scale-info = { workspace = true } smallvec = { workspace = true } @@ -115,6 +116,8 @@ pallet-xc-asset-config = { workspace = true } pallet-xcm = { workspace = true } pallet-xvm = { workspace = true } +precompile-utils = { workspace = true } + # Moonbeam tracing moonbeam-evm-tracer = { workspace = true, optional = true } moonbeam-rpc-primitives-debug = { workspace = true, optional = true } diff --git a/runtime/shibuya/src/lib.rs b/runtime/shibuya/src/lib.rs index 6f86563f02..38a59fe5ed 100644 --- a/runtime/shibuya/src/lib.rs +++ b/runtime/shibuya/src/lib.rs @@ -95,8 +95,8 @@ mod xcm_config; pub type ShibuyaAssetLocationIdConverter = AssetLocationIdConverter; -pub use precompiles::{ShibuyaNetworkPrecompiles, ASSET_PRECOMPILE_ADDRESS_PREFIX}; -pub type Precompiles = ShibuyaNetworkPrecompiles; +pub use precompiles::{ShibuyaPrecompiles, ASSET_PRECOMPILE_ADDRESS_PREFIX}; +pub type Precompiles = ShibuyaPrecompiles; use chain_extensions::*; @@ -840,7 +840,7 @@ parameter_types! { pub BlockGasLimit: U256 = U256::from( NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT.ref_time() / WEIGHT_PER_GAS ); - pub PrecompilesValue: Precompiles = ShibuyaNetworkPrecompiles::<_, _>::new(); + pub PrecompilesValue: Precompiles = ShibuyaPrecompiles::<_, _>::new(); pub WeightPerGas: Weight = Weight::from_parts(WEIGHT_PER_GAS, 0); /// The amount of gas per PoV size. Value is calculated as: /// diff --git a/runtime/shibuya/src/precompiles.rs b/runtime/shibuya/src/precompiles.rs index 520a6dabb2..93f3e4ec16 100644 --- a/runtime/shibuya/src/precompiles.rs +++ b/runtime/shibuya/src/precompiles.rs @@ -16,16 +16,12 @@ // You should have received a copy of the GNU General Public License // along with Astar. If not, see . -//! The Shibuya Network EVM precompiles. This can be compiled with ``#[no_std]`, ready for Wasm. +//! The Astar Network EVM precompiles. This can be compiled with ``#[no_std]`, ready for Wasm. -use crate::RuntimeCall; +use crate::{RuntimeCall, UnifiedAccounts, Xvm}; use astar_primitives::precompiles::DispatchFilterValidate; -use frame_support::traits::Contains; -use pallet_evm::{ - ExitRevert, IsPrecompileResult, Precompile, PrecompileFailure, PrecompileHandle, - PrecompileResult, PrecompileSet, -}; -use pallet_evm_precompile_assets_erc20::{AddressToAssetId, Erc20AssetsPrecompileSet}; +use frame_support::{parameter_types, traits::Contains}; +use pallet_evm_precompile_assets_erc20::Erc20AssetsPrecompileSet; use pallet_evm_precompile_blake2::Blake2F; use pallet_evm_precompile_bn128::{Bn128Add, Bn128Mul, Bn128Pairing}; use pallet_evm_precompile_dapps_staking::DappsStakingWrapper; @@ -39,15 +35,19 @@ use pallet_evm_precompile_substrate_ecdsa::SubstrateEcdsaPrecompile; use pallet_evm_precompile_unified_accounts::UnifiedAccountsPrecompile; use pallet_evm_precompile_xcm::XcmPrecompile; use pallet_evm_precompile_xvm::XvmPrecompile; -use sp_core::H160; +use precompile_utils::precompile_set::*; use sp_std::fmt::Debug; -use sp_std::marker::PhantomData; - -use xcm::latest::prelude::MultiLocation; /// The asset precompile address prefix. Addresses that match against this prefix will be routed /// to Erc20AssetsPrecompileSet pub const ASSET_PRECOMPILE_ADDRESS_PREFIX: &[u8] = &[255u8; 4]; +parameter_types! { + pub AssetPrefix: &'static [u8] = ASSET_PRECOMPILE_ADDRESS_PREFIX; +} + +/// Precompile checks for ethereum spec precompiles +/// We allow DELEGATECALL to stay compliant with Ethereum behavior. +type EthereumPrecompilesChecks = (AcceptDelegateCall, CallableByContract, CallableByPrecompile); /// Filter that only allows whitelisted runtime call to pass through dispatch precompile pub struct WhitelistedCalls; @@ -59,125 +59,84 @@ impl Contains for WhitelistedCalls { | RuntimeCall::Utility(pallet_utility::Call::batch_all { calls }) => { calls.iter().all(|call| WhitelistedCalls::contains(call)) } - RuntimeCall::Assets(pallet_assets::Call::transfer { .. }) => true, RuntimeCall::DappsStaking(_) => true, + RuntimeCall::Assets(pallet_assets::Call::transfer { .. }) => true, _ => false, } } } - -/// The PrecompileSet installed in the Shiden runtime. -#[derive(Debug, Default, Clone, Copy)] -pub struct ShibuyaNetworkPrecompiles(PhantomData<(R, C)>); - -impl ShibuyaNetworkPrecompiles { - pub fn new() -> Self { - Self(Default::default()) - } - - /// 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, 20484, 20485, - 20486 - ] - .into_iter() - .map(hash) - } -} - -/// The following distribution has been decided for the precompiles -/// 0-1023: Ethereum Mainnet Precompiles -/// 1024-2047 Precompiles that are not in Ethereum Mainnet -impl PrecompileSet for ShibuyaNetworkPrecompiles -where - Erc20AssetsPrecompileSet: PrecompileSet, - DappsStakingWrapper: Precompile, - XcmPrecompile: Precompile, - XvmPrecompile>: Precompile, - Dispatch>: Precompile, - UnifiedAccountsPrecompile>: Precompile, - R: pallet_evm::Config - + pallet_assets::Config - + pallet_xcm::Config - + pallet_xvm::Config - + pallet_unified_accounts::Config - + AddressToAssetId<::AssetId>, - C: xcm_executor::traits::Convert::AssetId>, -{ - fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { - let address = handle.code_address(); - if let IsPrecompileResult::Answer { is_precompile, .. } = - self.is_precompile(address, u64::MAX) - { - if is_precompile && address > hash(9) && handle.context().address != address { - return Some(Err(PrecompileFailure::Revert { - exit_status: ExitRevert::Reverted, - output: b"cannot be called with DELEGATECALL or CALLCODE".to_vec(), - })); - } - } - match address { - // Ethereum precompiles : - a if a == hash(1) => Some(ECRecover::execute(handle)), - a if a == hash(2) => Some(Sha256::execute(handle)), - a if a == hash(3) => Some(Ripemd160::execute(handle)), - a if a == hash(4) => Some(Identity::execute(handle)), - a if a == hash(5) => Some(Modexp::execute(handle)), - a if a == hash(6) => Some(Bn128Add::execute(handle)), - a if a == hash(7) => Some(Bn128Mul::execute(handle)), - a if a == hash(8) => Some(Bn128Pairing::execute(handle)), - a if a == hash(9) => Some(Blake2F::execute(handle)), - // nor Ethereum precompiles : - a if a == hash(1024) => Some(Sha3FIPS256::execute(handle)), - a if a == hash(1025) => Some(Dispatch::< - R, - DispatchFilterValidate, - >::execute(handle)), - a if a == hash(1026) => Some(ECRecoverPublicKey::execute(handle)), - a if a == hash(1027) => Some(Ed25519Verify::execute(handle)), - // Astar precompiles (starts from 0x5000): - // DappStaking 0x5001 - a if a == hash(20481) => Some(DappsStakingWrapper::::execute(handle)), - // Sr25519 0x5002 - a if a == hash(20482) => Some(Sr25519Precompile::::execute(handle)), - // SubstrateEcdsa 0x5003 - a if a == hash(20483) => Some(SubstrateEcdsaPrecompile::::execute(handle)), - // Xcm 0x5004 - a if a == hash(20484) => Some(XcmPrecompile::::execute(handle)), - // Xvm 0x5005 - a if a == hash(20485) => { - Some(XvmPrecompile::>::execute(handle)) - } - // UnifiedAccounts 0x5006 - a if a == hash(20486) => Some(UnifiedAccountsPrecompile::< - R, - pallet_unified_accounts::Pallet, - >::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) - } - // Default - _ => None, - } - } - - fn is_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult { - let assets_precompile = - match Erc20AssetsPrecompileSet::::new().is_precompile(address, gas) { - IsPrecompileResult::Answer { is_precompile, .. } => is_precompile, - _ => false, - }; - - IsPrecompileResult::Answer { - is_precompile: assets_precompile || Self::used_addresses().any(|x| x == address), - extra_cost: 0, - } - } -} - -fn hash(a: u64) -> H160 { - H160::from_low_u64_be(a) -} +/// The PrecompileSet installed in the Shibuya runtime. +#[precompile_utils::precompile_name_from_address] +pub type ShibuyaPrecompilesSetAt = ( + // Ethereum precompiles: + // We allow DELEGATECALL to stay compliant with Ethereum behavior. + PrecompileAt, ECRecover, EthereumPrecompilesChecks>, + PrecompileAt, Sha256, EthereumPrecompilesChecks>, + PrecompileAt, Ripemd160, EthereumPrecompilesChecks>, + PrecompileAt, Identity, EthereumPrecompilesChecks>, + PrecompileAt, Modexp, EthereumPrecompilesChecks>, + PrecompileAt, Bn128Add, EthereumPrecompilesChecks>, + PrecompileAt, Bn128Mul, EthereumPrecompilesChecks>, + PrecompileAt, Bn128Pairing, EthereumPrecompilesChecks>, + PrecompileAt, Blake2F, EthereumPrecompilesChecks>, + // Non-Astar specific nor Ethereum precompiles : + PrecompileAt, Sha3FIPS256, (CallableByContract, CallableByPrecompile)>, + PrecompileAt< + AddressU64<1025>, + Dispatch>, + // Not callable from smart contract nor precompiles, only EOA accounts + // TODO: test this without the gensis hack for blacklisted + (), + >, + PrecompileAt, ECRecoverPublicKey, (CallableByContract, CallableByPrecompile)>, + PrecompileAt, Ed25519Verify, (CallableByContract, CallableByPrecompile)>, + // Astar specific precompiles: + PrecompileAt< + AddressU64<20481>, + DappsStakingWrapper, + (CallableByContract, CallableByPrecompile), + >, + PrecompileAt< + AddressU64<20482>, + Sr25519Precompile, + (CallableByContract, CallableByPrecompile), + >, + PrecompileAt< + AddressU64<20483>, + SubstrateEcdsaPrecompile, + (CallableByContract, CallableByPrecompile), + >, + PrecompileAt< + AddressU64<20484>, + XcmPrecompile, + ( + SubcallWithMaxNesting<1>, + CallableByContract, + CallableByPrecompile, + ), + >, + PrecompileAt< + AddressU64<20485>, + XvmPrecompile, + (CallableByContract, CallableByPrecompile), + >, + PrecompileAt< + AddressU64<20486>, + UnifiedAccountsPrecompile, + (CallableByContract, CallableByPrecompile), + >, +); + +pub type ShibuyaPrecompiles = PrecompileSetBuilder< + R, + ( + // Skip precompiles if out of range. + PrecompilesInRangeInclusive< + // TODO: what is the range for precompiles sets 1 - ? + (AddressU64<1>, AddressU64<40951>), + ShibuyaPrecompilesSetAt, + >, + // Prefixed precompile sets (XC20) + PrecompileSetStartingWith, CallableByContract>, + ), +>; diff --git a/runtime/shiden/Cargo.toml b/runtime/shiden/Cargo.toml index 4ee039fea1..b95fad217d 100644 --- a/runtime/shiden/Cargo.toml +++ b/runtime/shiden/Cargo.toml @@ -11,6 +11,7 @@ repository.workspace = true # third-party dependencies hex-literal = { workspace = true } log = { workspace = true, optional = true } +num_enum = { workspace = true } parity-scale-codec = { workspace = true } scale-info = { workspace = true } smallvec = { workspace = true } @@ -111,6 +112,8 @@ pallet-evm-precompile-xcm = { workspace = true } pallet-xc-asset-config = { workspace = true } pallet-xcm = { workspace = true } +precompile-utils = { workspace = true } + # Moonbeam tracing moonbeam-evm-tracer = { workspace = true, optional = true } moonbeam-rpc-primitives-debug = { workspace = true, optional = true } @@ -145,6 +148,7 @@ std = [ "pallet-assets/std", "pallet-balances/std", "pallet-contracts/std", + "pallet-collective/std", "pallet-contracts-primitives/std", "pallet-block-rewards-hybrid/std", "pallet-dynamic-evm-base-fee/std", diff --git a/runtime/shiden/src/lib.rs b/runtime/shiden/src/lib.rs index f1253b967f..50fe7657a0 100644 --- a/runtime/shiden/src/lib.rs +++ b/runtime/shiden/src/lib.rs @@ -89,8 +89,8 @@ mod xcm_config; pub type ShidenAssetLocationIdConverter = AssetLocationIdConverter; -pub use precompiles::{ShidenNetworkPrecompiles, ASSET_PRECOMPILE_ADDRESS_PREFIX}; -pub type Precompiles = ShidenNetworkPrecompiles; +pub use precompiles::{ShidenPrecompiles, ASSET_PRECOMPILE_ADDRESS_PREFIX}; +pub type Precompiles = ShidenPrecompiles; /// Constant values used within the runtime. pub const NANOSDN: Balance = 1_000_000_000; @@ -769,7 +769,7 @@ parameter_types! { pub BlockGasLimit: U256 = U256::from( NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT.ref_time() / WEIGHT_PER_GAS ); - pub PrecompilesValue: Precompiles = ShidenNetworkPrecompiles::<_, _>::new(); + pub PrecompilesValue: Precompiles = ShidenPrecompiles::<_, _>::new(); pub WeightPerGas: Weight = Weight::from_parts(WEIGHT_PER_GAS, 0); /// The amount of gas per PoV size. Value is calculated as: /// diff --git a/runtime/shiden/src/precompiles.rs b/runtime/shiden/src/precompiles.rs index 89a29be023..6a8cb319ca 100644 --- a/runtime/shiden/src/precompiles.rs +++ b/runtime/shiden/src/precompiles.rs @@ -20,12 +20,8 @@ use crate::RuntimeCall; use astar_primitives::precompiles::DispatchFilterValidate; -use frame_support::traits::Contains; -use pallet_evm::{ - ExitRevert, IsPrecompileResult, Precompile, PrecompileFailure, PrecompileHandle, - PrecompileResult, PrecompileSet, -}; -use pallet_evm_precompile_assets_erc20::{AddressToAssetId, Erc20AssetsPrecompileSet}; +use frame_support::{parameter_types, traits::Contains}; +use pallet_evm_precompile_assets_erc20::Erc20AssetsPrecompileSet; use pallet_evm_precompile_blake2::Blake2F; use pallet_evm_precompile_bn128::{Bn128Add, Bn128Mul, Bn128Pairing}; use pallet_evm_precompile_dapps_staking::DappsStakingWrapper; @@ -37,15 +33,19 @@ use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripe use pallet_evm_precompile_sr25519::Sr25519Precompile; use pallet_evm_precompile_substrate_ecdsa::SubstrateEcdsaPrecompile; use pallet_evm_precompile_xcm::XcmPrecompile; -use sp_core::H160; +use precompile_utils::precompile_set::*; use sp_std::fmt::Debug; -use sp_std::marker::PhantomData; - -use xcm::latest::prelude::MultiLocation; /// The asset precompile address prefix. Addresses that match against this prefix will be routed /// to Erc20AssetsPrecompileSet pub const ASSET_PRECOMPILE_ADDRESS_PREFIX: &[u8] = &[255u8; 4]; +parameter_types! { + pub AssetPrefix: &'static [u8] = ASSET_PRECOMPILE_ADDRESS_PREFIX; +} + +/// Precompile checks for ethereum spec precompiles +/// We allow DELEGATECALL to stay compliant with Ethereum behavior. +type EthereumPrecompilesChecks = (AcceptDelegateCall, CallableByContract, CallableByPrecompile); /// Filter that only allows whitelisted runtime call to pass through dispatch precompile pub struct WhitelistedCalls; @@ -63,110 +63,68 @@ impl Contains for WhitelistedCalls { } } } - /// The PrecompileSet installed in the Shiden runtime. -#[derive(Debug, Default, Clone, Copy)] -pub struct ShidenNetworkPrecompiles(PhantomData<(R, C)>); - -impl ShidenNetworkPrecompiles { - pub fn new() -> Self { - Self(Default::default()) - } - - /// 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, 20484,] - .into_iter() - .map(hash) - } - - /// Returns all addresses which are blacklisted for dummy code deployment. - /// This is in order to keep the local testnets consistent with the live network. - pub fn is_blacklisted(address: &H160) -> bool { - // `dispatch` precompile is not allowed to be called by smart contracts, hence the ommision of this address. - hash(1025) == *address - } -} - -/// The following distribution has been decided for the precompiles -/// 0-1023: Ethereum Mainnet Precompiles -/// 1024-2047 Precompiles that are not in Ethereum Mainnet -impl PrecompileSet for ShidenNetworkPrecompiles -where - Erc20AssetsPrecompileSet: PrecompileSet, - DappsStakingWrapper: Precompile, - XcmPrecompile: Precompile, - Dispatch>: Precompile, - R: pallet_evm::Config - + pallet_assets::Config - + pallet_xcm::Config - + AddressToAssetId<::AssetId>, - C: xcm_executor::traits::Convert::AssetId>, -{ - fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { - let address = handle.code_address(); - if let IsPrecompileResult::Answer { is_precompile, .. } = - self.is_precompile(address, u64::MAX) - { - if is_precompile && address > hash(9) && handle.context().address != address { - return Some(Err(PrecompileFailure::Revert { - exit_status: ExitRevert::Reverted, - output: b"cannot be called with DELEGATECALL or CALLCODE".to_vec(), - })); - } - } - match address { - // Ethereum precompiles : - a if a == hash(1) => Some(ECRecover::execute(handle)), - a if a == hash(2) => Some(Sha256::execute(handle)), - a if a == hash(3) => Some(Ripemd160::execute(handle)), - a if a == hash(4) => Some(Identity::execute(handle)), - a if a == hash(5) => Some(Modexp::execute(handle)), - a if a == hash(6) => Some(Bn128Add::execute(handle)), - a if a == hash(7) => Some(Bn128Mul::execute(handle)), - a if a == hash(8) => Some(Bn128Pairing::execute(handle)), - a if a == hash(9) => Some(Blake2F::execute(handle)), - // nor Ethereum precompiles : - a if a == hash(1024) => Some(Sha3FIPS256::execute(handle)), - a if a == hash(1025) => Some(Dispatch::< - R, - DispatchFilterValidate, - >::execute(handle)), - a if a == hash(1026) => Some(ECRecoverPublicKey::execute(handle)), - a if a == hash(1027) => Some(Ed25519Verify::execute(handle)), - // Astar precompiles (starts from 0x5000): - // DappStaking 0x5001 - a if a == hash(20481) => Some(DappsStakingWrapper::::execute(handle)), - // Sr25519 0x5002 - a if a == hash(20482) => Some(Sr25519Precompile::::execute(handle)), - // SubstrateEcdsa 0x5003 - a if a == hash(20483) => Some(SubstrateEcdsaPrecompile::::execute(handle)), - // Xcm 0x5004 - a if a == hash(20484) => Some(XcmPrecompile::::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) - } - // Default - _ => None, - } - } - - fn is_precompile(&self, address: H160, gas: u64) -> IsPrecompileResult { - let assets_precompile = - match Erc20AssetsPrecompileSet::::new().is_precompile(address, gas) { - IsPrecompileResult::Answer { is_precompile, .. } => is_precompile, - _ => false, - }; - - IsPrecompileResult::Answer { - is_precompile: assets_precompile || Self::used_addresses().any(|x| x == address), - extra_cost: 0, - } - } -} - -fn hash(a: u64) -> H160 { - H160::from_low_u64_be(a) -} +#[precompile_utils::precompile_name_from_address] +pub type ShidenPrecompilesSetAt = ( + // Ethereum precompiles: + // We allow DELEGATECALL to stay compliant with Ethereum behavior. + PrecompileAt, ECRecover, EthereumPrecompilesChecks>, + PrecompileAt, Sha256, EthereumPrecompilesChecks>, + PrecompileAt, Ripemd160, EthereumPrecompilesChecks>, + PrecompileAt, Identity, EthereumPrecompilesChecks>, + PrecompileAt, Modexp, EthereumPrecompilesChecks>, + PrecompileAt, Bn128Add, EthereumPrecompilesChecks>, + PrecompileAt, Bn128Mul, EthereumPrecompilesChecks>, + PrecompileAt, Bn128Pairing, EthereumPrecompilesChecks>, + PrecompileAt, Blake2F, EthereumPrecompilesChecks>, + // Non-Astar specific nor Ethereum precompiles : + PrecompileAt, Sha3FIPS256, (CallableByContract, CallableByPrecompile)>, + PrecompileAt< + AddressU64<1025>, + Dispatch>, + // Not callable from smart contract nor precompiles, only EOA accounts + // TODO: test this without the gensis hack for blacklisted + (), + >, + PrecompileAt, ECRecoverPublicKey, (CallableByContract, CallableByPrecompile)>, + PrecompileAt, Ed25519Verify, (CallableByContract, CallableByPrecompile)>, + // Astar specific precompiles: + PrecompileAt< + AddressU64<20481>, + DappsStakingWrapper, + (CallableByContract, CallableByPrecompile), + >, + PrecompileAt< + AddressU64<20482>, + Sr25519Precompile, + (CallableByContract, CallableByPrecompile), + >, + PrecompileAt< + AddressU64<20483>, + SubstrateEcdsaPrecompile, + (CallableByContract, CallableByPrecompile), + >, + PrecompileAt< + AddressU64<20484>, + XcmPrecompile, + ( + SubcallWithMaxNesting<1>, + CallableByContract, + CallableByPrecompile, + ), + >, +); + +pub type ShidenPrecompiles = PrecompileSetBuilder< + R, + ( + // Skip precompiles if out of range. + PrecompilesInRangeInclusive< + // We take range as last precompile index, UPDATE this once new prcompile is added + (AddressU64<1>, AddressU64<20484>), + ShidenPrecompilesSetAt, + >, + // Prefixed precompile sets (XC20) + PrecompileSetStartingWith, CallableByContract>, + ), +>; diff --git a/tests/integration/src/xvm.rs b/tests/integration/src/xvm.rs index cefff28776..79a5f921d2 100644 --- a/tests/integration/src/xvm.rs +++ b/tests/integration/src/xvm.rs @@ -29,7 +29,7 @@ use frame_support::{dispatch::PostDispatchInfo, traits::Currency, weights::Weigh use pallet_contracts::{CollectEvents, DebugInfo, Determinism}; use pallet_contracts_primitives::{ExecReturnValue, ReturnFlags}; use parity_scale_codec::Encode; -use precompile_utils::{Bytes, EvmDataWriter}; +use precompile_utils::{prelude::*, solidity}; use sp_runtime::MultiAddress; // Build EVM revert message error data. @@ -37,9 +37,7 @@ fn evm_revert_message_error(msg: &str) -> Vec { let hash = &Keccak256::digest(b"Error(string)")[..4]; let selector = u32::from_be_bytes([hash[0], hash[1], hash[2], hash[3]]); - EvmDataWriter::new_with_selector(selector) - .write(Bytes(msg.to_owned().into_bytes())) - .build() + solidity::encode_with_selector(selector, UnboundedBytes::from(msg.to_owned().into_bytes())) } /*