diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fa65561c46..ed3aad9f26 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -467,7 +467,8 @@ jobs: chain=${{ matrix.chain }} runtime_version=$(cat $chain-compressed-info.json | jq '.core_version' | tr -d '"' | cut -d ' ' -f 1) echo $runtime_version - echo "::set-output name=runtime_version::$runtime_version" + echo "runtime=$(echo $runtime_version)" >> $GITHUB_ENV + echo "${{ matrix.chain }}=$(echo $runtime_version)" >> $GITHUB_OUTPUT - name: Upload ${{ matrix.chain }} Wasm uses: actions/upload-release-asset@v1 @@ -476,7 +477,7 @@ jobs: with: upload_url: ${{ needs.publish-release-draft.outputs.upload_url }} asset_path: ${{ matrix.chain }}_runtime.compact.compressed.wasm - asset_name: ${{ steps.get-runtime-version.outputs.runtime_version }}.wasm + asset_name: ${{ env.runtime }}.wasm asset_content_type: application/wasm - name: Upload ${{ matrix.chain }} Metadata @@ -509,8 +510,13 @@ jobs: asset_name: ${{ matrix.chain }}-srtool-digest.json asset_content_type: application/json + outputs: + astar_runtime_version: ${{ steps.get-runtime-version.outputs.astar }} + shiden_runtime_version: ${{ steps.get-runtime-version.outputs.shiden }} + shibuya_runtime_version: ${{ steps.get-runtime-version.outputs.shibuya }} + upload-evm-tracing-artifacts: - needs: publish-release-draft + needs: [publish-release-draft,upload-runtimes] runs-on: ubuntu-latest steps: - name: Download pre-built collator binary for evm tracing @@ -518,11 +524,6 @@ jobs: with: name: astar-evm-tracing-ubuntu-latest-x86_64-unknown-linux-gnu path: evm-tracing-artifacts - - - name: Make evm tracing binary executable and tar gzip - run: | - cd evm-tracing-artifacts - chmod +x astar-collator - name: Download evm tracing runtime uses: actions/download-artifact@v3 @@ -542,6 +543,14 @@ jobs: name: shibuya-evm-tracing-runtime path: evm-tracing-artifacts + - name: Make evm tracing binary executable and rename + run: | + cd evm-tracing-artifacts + chmod +x astar-collator + mv astar_evm_tracing_runtime.compact.compressed.wasm ${{needs.upload-runtimes.outputs.astar_runtime_version}}_evm_tracing_runtime.compact.compressed.wasm + mv shiden_evm_tracing_runtime.compact.compressed.wasm ${{needs.upload-runtimes.outputs.shiden_runtime_version}}_evm_tracing_runtime.compact.compressed.wasm + mv shibuya_evm_tracing_runtime.compact.compressed.wasm ${{needs.upload-runtimes.outputs.shibuya_runtime_version}}_evm_tracing_runtime.compact.compressed.wasm + - name: Compress folder run: | tar zcvf evm-tracing-artifacts.tar.gz evm-tracing-artifacts diff --git a/Cargo.lock b/Cargo.lock index 155d1231eb..94e894bc5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -404,7 +404,7 @@ dependencies = [ [[package]] name = "astar-collator" -version = "5.16.1" +version = "5.17.0" dependencies = [ "astar-primitives", "astar-runtime", @@ -515,6 +515,9 @@ dependencies = [ "frame-support", "impl-trait-for-tuples", "log", + "pallet-assets", + "pallet-evm", + "pallet-evm-precompile-assets-erc20", "pallet-xc-asset-config", "parity-scale-codec", "scale-info", @@ -529,7 +532,7 @@ dependencies = [ [[package]] name = "astar-runtime" -version = "5.16.1" +version = "5.17.0" dependencies = [ "array-bytes 6.1.0", "astar-primitives", @@ -4456,13 +4459,22 @@ dependencies = [ name = "integration-tests" version = "0.1.0" dependencies = [ + "astar-primitives", "astar-runtime", "frame-support", "frame-system", + "hex", + "pallet-assets", "pallet-balances", + "pallet-contracts", + "pallet-contracts-primitives", "pallet-dapps-staking", + "pallet-ethereum-checked", + "pallet-evm", + "pallet-evm-precompile-assets-erc20", "pallet-proxy", "pallet-utility", + "parity-scale-codec", "shibuya-runtime", "shiden-runtime", "sp-core", @@ -5475,7 +5487,7 @@ checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" [[package]] name = "local-runtime" -version = "5.16.1" +version = "5.17.0" dependencies = [ "array-bytes 6.1.0", "astar-primitives", @@ -5510,6 +5522,7 @@ dependencies = [ "pallet-ethereum-checked", "pallet-evm", "pallet-evm-precompile-assets-erc20", + "pallet-evm-precompile-batch", "pallet-evm-precompile-blake2", "pallet-evm-precompile-bn128", "pallet-evm-precompile-dapps-staking", @@ -7288,6 +7301,34 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-evm-precompile-batch" +version = "0.1.0" +dependencies = [ + "derive_more", + "evm", + "fp-evm", + "frame-support", + "frame-system", + "hex-literal", + "log", + "num_enum 0.5.11", + "pallet-balances", + "pallet-evm", + "pallet-timestamp", + "parity-scale-codec", + "paste", + "precompile-utils", + "scale-info", + "serde", + "sha3", + "slices", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-evm-precompile-blake2" version = "2.0.0-dev" @@ -7467,6 +7508,7 @@ dependencies = [ "fp-evm", "frame-support", "frame-system", + "hex", "hex-literal", "log", "num_enum 0.5.11", @@ -8234,10 +8276,12 @@ name = "pallet-xvm" version = "0.2.2" dependencies = [ "astar-primitives", + "environmental", "fp-evm", "frame-benchmarking", "frame-support", "frame-system", + "hex", "log", "pallet-balances", "pallet-contracts", @@ -9805,9 +9849,11 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "precompile-utils" -version = "0.4.3" +version = "0.5.0" dependencies = [ "assert_matches", + "derive_more", + "environmental", "evm", "fp-evm", "frame-support", @@ -9819,6 +9865,8 @@ dependencies = [ "pallet-evm", "parity-scale-codec", "precompile-utils-macro", + "scale-info", + "serde", "sha3", "similar-asserts", "sp-core", @@ -12299,7 +12347,7 @@ dependencies = [ [[package]] name = "shibuya-runtime" -version = "5.16.1" +version = "5.17.0" dependencies = [ "array-bytes 6.1.0", "astar-primitives", @@ -12349,6 +12397,7 @@ dependencies = [ "pallet-evm", "pallet-evm-chain-id", "pallet-evm-precompile-assets-erc20", + "pallet-evm-precompile-batch", "pallet-evm-precompile-blake2", "pallet-evm-precompile-bn128", "pallet-evm-precompile-dapps-staking", @@ -12407,7 +12456,7 @@ dependencies = [ [[package]] name = "shiden-runtime" -version = "5.16.1" +version = "5.17.0" dependencies = [ "array-bytes 6.1.0", "astar-primitives", diff --git a/Cargo.toml b/Cargo.toml index 566fadbe41..6e3948a47f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,7 @@ num-traits = { version = "0.2", default-features = false } rand = { version = "0.8.5", default-features = false } bounded-collections = { version = "0.1.5", default-features = false } hex = { version = "0.4.3", default-features = false } +paste = "1.0.6" # (native) array-bytes = "6.0.0" @@ -285,6 +286,7 @@ pallet-evm-precompile-substrate-ecdsa = { path = "./precompiles/substrate-ecdsa" pallet-evm-precompile-xcm = { path = "./precompiles/xcm", default-features = false } pallet-evm-precompile-xvm = { path = "./precompiles/xvm", default-features = false } pallet-evm-precompile-dapps-staking = { path = "./precompiles/dapps-staking", default-features = false } +pallet-evm-precompile-batch = { path = "./precompiles/batch", default-features = false } pallet-chain-extension-dapps-staking = { path = "./chain-extensions/dapps-staking", default-features = false } pallet-chain-extension-xvm = { path = "./chain-extensions/xvm", default-features = false } diff --git a/bin/collator/Cargo.toml b/bin/collator/Cargo.toml index b6c7a10b58..46e4e590af 100644 --- a/bin/collator/Cargo.toml +++ b/bin/collator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astar-collator" -version = "5.16.1" +version = "5.17.0" description = "Astar collator implementation in Rust." build = "build.rs" default-run = "astar-collator" diff --git a/chain-extensions/types/xvm/src/lib.rs b/chain-extensions/types/xvm/src/lib.rs index d23ccd9102..c42b71c4d5 100644 --- a/chain-extensions/types/xvm/src/lib.rs +++ b/chain-extensions/types/xvm/src/lib.rs @@ -18,7 +18,7 @@ #![cfg_attr(not(feature = "std"), no_std)] -use astar_primitives::xvm::CallError; +use astar_primitives::{xvm::CallError, Balance}; use parity_scale_codec::{Decode, Encode}; use sp_std::vec::Vec; @@ -38,10 +38,10 @@ impl From for XvmExecutionResult { // `0` is reserved for `Ok` let error_code = match input { InvalidVmId => 1, - SameVmCallNotAllowed => 2, + SameVmCallDenied => 2, InvalidTarget => 3, InputTooLarge => 4, - BadOrigin => 5, + ReentranceDenied => 5, ExecutionFailed(_) => 6, }; Self::Err(error_code) @@ -65,4 +65,6 @@ pub struct XvmCallArgs { pub to: Vec, /// Encoded call params pub input: Vec, + /// Value to transfer + pub value: Balance, } diff --git a/chain-extensions/xvm/src/lib.rs b/chain-extensions/xvm/src/lib.rs index 9696fc6d7a..8b6c48b8f6 100644 --- a/chain-extensions/xvm/src/lib.rs +++ b/chain-extensions/xvm/src/lib.rs @@ -18,19 +18,15 @@ #![cfg_attr(not(feature = "std"), no_std)] -use astar_primitives::xvm::{CallError, Context, VmId, XvmCall}; +use astar_primitives::xvm::{Context, VmId, XvmCall}; use frame_support::dispatch::Encode; -use pallet_contracts::{ - chain_extension::{ChainExtension, Environment, Ext, InitState, RetVal}, - Origin, -}; +use pallet_contracts::chain_extension::{ChainExtension, Environment, Ext, InitState, RetVal}; use sp_runtime::DispatchError; use sp_std::marker::PhantomData; use xvm_chain_extension_types::{XvmCallArgs, XvmExecutionResult}; enum XvmFuncId { Call, - // TODO: expand with other calls too } impl TryFrom for XvmFuncId { @@ -76,28 +72,22 @@ where // So we will charge a 32KB dummy value as a temporary replacement. let charged_weight = env.charge_weight(weight_limit.set_proof_size(32 * 1024))?; - let caller = match env.ext().caller().clone() { - Origin::Signed(address) => address, - Origin::Root => { - log::trace!( - target: "xvm-extension::xvm_call", - "root origin not supported" - ); - return Ok(RetVal::Converging( - XvmExecutionResult::from(CallError::BadOrigin).into(), - )); - } - }; + let XvmCallArgs { + vm_id, + to, + input, + value, + } = env.read_as_unbounded(env.in_len())?; - let XvmCallArgs { vm_id, to, input } = env.read_as_unbounded(env.in_len())?; + // Similar to EVM behavior, the `source` should be (limited to) the + // contract address. Otherwise contracts would be able to do arbitrary + // things on behalf of the caller via XVM. + let source = env.ext().address(); - let _origin_address = env.ext().address().clone(); - let _value = env.ext().value_transferred(); let xvm_context = Context { source_vm_id: VmId::Wasm, weight_limit, }; - let vm_id = { match TryInto::::try_into(vm_id) { Ok(id) => id, @@ -108,7 +98,7 @@ where } } }; - let call_result = XC::call(xvm_context, vm_id, caller, to, input); + let call_result = XC::call(xvm_context, vm_id, source.clone(), to, input, value); let actual_weight = match call_result { Ok(ref info) => info.used_weight, diff --git a/pallets/ethereum-checked/src/benchmarking.rs b/pallets/ethereum-checked/src/benchmarking.rs index b45458afe6..6abf9f8075 100644 --- a/pallets/ethereum-checked/src/benchmarking.rs +++ b/pallets/ethereum-checked/src/benchmarking.rs @@ -18,7 +18,7 @@ use super::*; -use astar_primitives::ethereum_checked::MAX_ETHEREUM_TX_INPUT_SIZE; +use astar_primitives::ethereum_checked::EthereumTxInput; use frame_benchmarking::v2::*; #[benchmarks] @@ -31,7 +31,7 @@ mod benchmarks { let target = H160::from_slice(&hex::decode("dfb975d018f03994a3b943808e3aa0964bd78463").unwrap()); // Calling `store(3)` - let input = BoundedVec::>::try_from( + let input = EthereumTxInput::try_from( hex::decode("6057361d0000000000000000000000000000000000000000000000000000000000000003") .unwrap(), ) diff --git a/pallets/ethereum-checked/src/mock.rs b/pallets/ethereum-checked/src/mock.rs index b63a34c23d..f4575e956d 100644 --- a/pallets/ethereum-checked/src/mock.rs +++ b/pallets/ethereum-checked/src/mock.rs @@ -294,7 +294,7 @@ impl ExtBuilder { assert_ok!(Evm::create2( RuntimeOrigin::root(), ALICE_H160, - hex::decode(STORAGE_CONTRACT).unwrap(), + hex::decode(STORAGE_CONTRACT).expect("invalid code hex"), H256::zero(), U256::zero(), 1_000_000, diff --git a/pallets/ethereum-checked/src/tests.rs b/pallets/ethereum-checked/src/tests.rs index 7d77601fd9..e5c9b8f41c 100644 --- a/pallets/ethereum-checked/src/tests.rs +++ b/pallets/ethereum-checked/src/tests.rs @@ -21,14 +21,14 @@ use super::*; use mock::*; -use astar_primitives::ethereum_checked::MAX_ETHEREUM_TX_INPUT_SIZE; +use astar_primitives::ethereum_checked::EthereumTxInput; use ethereum::{ReceiptV3, TransactionV2 as Transaction}; -use frame_support::{assert_noop, assert_ok, traits::ConstU32}; +use frame_support::{assert_noop, assert_ok}; use sp_runtime::DispatchError; -fn bounded_input(data: &'static str) -> BoundedVec> { - BoundedVec::>::try_from(hex::decode(data).unwrap()) - .unwrap() +fn bounded_input(data: &'static str) -> EthereumTxInput { + EthereumTxInput::try_from(hex::decode(data).expect("invalid input hex")) + .expect("input too large") } #[test] diff --git a/pallets/xc-asset-config/src/lib.rs b/pallets/xc-asset-config/src/lib.rs index 4310198def..193cdf961f 100644 --- a/pallets/xc-asset-config/src/lib.rs +++ b/pallets/xc-asset-config/src/lib.rs @@ -85,21 +85,6 @@ pub mod pallet { #[pallet::without_storage_info] pub struct Pallet(PhantomData); - /// Callback definition trait for cross-chain asset registration/deregistration notifications. - pub trait XcAssetChanged { - /// Will be called by pallet when new asset Id has been registered - fn xc_asset_registered(asset_id: T::AssetId); - - /// Will be called by pallet when asset Id has been unregistered - fn xc_asset_unregistered(asset_id: T::AssetId); - } - - /// Implementation that does nothing - impl XcAssetChanged for () { - fn xc_asset_registered(_: T::AssetId) {} - fn xc_asset_unregistered(_: T::AssetId) {} - } - /// Defines conversion between asset Id and cross-chain asset location pub trait XcAssetLocation { /// Get asset type from assetId @@ -139,9 +124,6 @@ pub mod pallet { /// a AssetLocation type AssetId: Member + Parameter + Default + Copy + HasCompact + MaxEncodedLen; - /// Callback handling for cross-chain asset registration or unregistration. - type XcAssetChanged: XcAssetChanged; - /// The required origin for managing cross-chain asset configuration /// /// Should most likely be root. @@ -242,8 +224,6 @@ pub mod pallet { AssetIdToLocation::::insert(&asset_id, asset_location.clone()); AssetLocationToId::::insert(&asset_location, asset_id); - T::XcAssetChanged::xc_asset_registered(asset_id); - Self::deposit_event(Event::AssetRegistered { asset_location, asset_id, @@ -354,7 +334,6 @@ pub mod pallet { AssetIdToLocation::::remove(&asset_id); AssetLocationToId::::remove(&asset_location); AssetLocationUnitsPerSecond::::remove(&asset_location); - T::XcAssetChanged::xc_asset_unregistered(asset_id); Self::deposit_event(Event::AssetRemoved { asset_id, diff --git a/pallets/xc-asset-config/src/mock.rs b/pallets/xc-asset-config/src/mock.rs index aa00246b1f..f15736b8b8 100644 --- a/pallets/xc-asset-config/src/mock.rs +++ b/pallets/xc-asset-config/src/mock.rs @@ -108,7 +108,6 @@ type AssetId = u128; impl pallet_xc_asset_config::Config for Test { type RuntimeEvent = RuntimeEvent; type AssetId = AssetId; - type XcAssetChanged = (); type ManagerOrigin = frame_system::EnsureRoot; type WeightInfo = (); } diff --git a/pallets/xvm/Cargo.toml b/pallets/xvm/Cargo.toml index 6f1022bf24..d9f9bec944 100644 --- a/pallets/xvm/Cargo.toml +++ b/pallets/xvm/Cargo.toml @@ -7,6 +7,7 @@ homepage.workspace = true repository.workspace = true [dependencies] +environmental = { workspace = true } log = { workspace = true } serde = { workspace = true, optional = true } @@ -33,6 +34,7 @@ astar-primitives = { workspace = true } [dev-dependencies] fp-evm = { workspace = true } +hex = { workspace = true } pallet-balances = { workspace = true, features = ["std"] } pallet-insecure-randomness-collective-flip = { workspace = true, features = ["std"] } pallet-timestamp = { workspace = true, features = ["std"] } @@ -41,6 +43,7 @@ sp-io = { workspace = true } [features] default = ["std"] std = [ + "environmental/std", "log/std", "parity-scale-codec/std", "frame-support/std", diff --git a/pallets/xvm/src/benchmarking.rs b/pallets/xvm/src/benchmarking.rs index 97edd7d0d5..38c41a1d76 100644 --- a/pallets/xvm/src/benchmarking.rs +++ b/pallets/xvm/src/benchmarking.rs @@ -24,7 +24,11 @@ use parity_scale_codec::Encode; use sp_core::H160; use sp_runtime::MultiAddress; -#[benchmarks] +use astar_primitives::Balance; + +#[benchmarks( + where ::Currency: Currency, +)] mod benchmarks { use super::*; @@ -38,10 +42,12 @@ mod benchmarks { let source = whitelisted_caller(); let target = H160::repeat_byte(1).encode(); let input = vec![1, 2, 3]; + let value = 1_000_000u128; #[block] { - Pallet::::call_without_execution(context, vm_id, source, target, input).unwrap(); + Pallet::::call_without_execution(context, vm_id, source, target, input, value) + .unwrap(); } } @@ -55,10 +61,12 @@ mod benchmarks { let source = whitelisted_caller(); let target = MultiAddress::::Id(whitelisted_caller()).encode(); let input = vec![1, 2, 3]; + let value = 1_000_000u128; #[block] { - Pallet::::call_without_execution(context, vm_id, source, target, input).unwrap(); + Pallet::::call_without_execution(context, vm_id, source, target, input, value) + .unwrap(); } } diff --git a/pallets/xvm/src/lib.rs b/pallets/xvm/src/lib.rs index 5da16856fc..0f15c8b7d8 100644 --- a/pallets/xvm/src/lib.rs +++ b/pallets/xvm/src/lib.rs @@ -37,19 +37,20 @@ #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::{ensure, traits::ConstU32, BoundedVec}; +use frame_support::{ensure, traits::Currency, weights::Weight}; use pallet_contracts::{CollectEvents, DebugInfo, Determinism}; use pallet_evm::GasWeightMapping; use parity_scale_codec::Decode; -use sp_core::U256; +use sp_core::{H160, U256}; use sp_runtime::traits::StaticLookup; use sp_std::{marker::PhantomData, prelude::*}; use astar_primitives::{ ethereum_checked::{ - AccountMapping, CheckedEthereumTransact, CheckedEthereumTx, MAX_ETHEREUM_TX_INPUT_SIZE, + AccountMapping, CheckedEthereumTransact, CheckedEthereumTx, EthereumTxInput, }, xvm::{CallError, CallErrorWithWeight, CallInfo, CallResult, Context, VmId, XvmCall}, + Balance, }; #[cfg(feature = "runtime-benchmarks")] @@ -58,13 +59,15 @@ mod benchmarking; pub mod weights; pub use weights::WeightInfo; -#[cfg(test)] mod mock; +mod tests; pub use pallet::*; pub type WeightInfoOf = ::WeightInfo; +environmental::thread_local_impl!(static IN_XVM: environmental::RefCell = environmental::RefCell::new(false)); + #[frame_support::pallet] pub mod pallet { use super::*; @@ -88,42 +91,84 @@ pub mod pallet { } } -impl XvmCall for Pallet { +impl XvmCall for Pallet +where + T: Config, + T::Currency: Currency, +{ fn call( context: Context, vm_id: VmId, source: T::AccountId, target: Vec, input: Vec, + value: Balance, ) -> CallResult { - Pallet::::do_call(context, vm_id, source, target, input, false) + Pallet::::do_call(context, vm_id, source, target, input, value, false) } } -impl Pallet { +impl Pallet +where + T: Config, + T::Currency: Currency, +{ fn do_call( context: Context, vm_id: VmId, source: T::AccountId, target: Vec, input: Vec, + value: Balance, skip_execution: bool, ) -> CallResult { + let overheads = match vm_id { + VmId::Evm => WeightInfoOf::::evm_call_overheads(), + VmId::Wasm => WeightInfoOf::::wasm_call_overheads(), + }; + ensure!( context.source_vm_id != vm_id, CallErrorWithWeight { - error: CallError::SameVmCallNotAllowed, - used_weight: match vm_id { - VmId::Evm => WeightInfoOf::::evm_call_overheads(), - VmId::Wasm => WeightInfoOf::::wasm_call_overheads(), - }, + error: CallError::SameVmCallDenied, + used_weight: overheads, } ); - match vm_id { - VmId::Evm => Pallet::::evm_call(context, source, target, input, skip_execution), - VmId::Wasm => Pallet::::wasm_call(context, source, target, input, skip_execution), + // Set `IN_XVM` to true & check reentrance. + if IN_XVM.with(|in_xvm| in_xvm.replace(true)) { + return Err(CallErrorWithWeight { + error: CallError::ReentranceDenied, + used_weight: overheads, + }); } + + let res = match vm_id { + VmId::Evm => Pallet::::evm_call( + context, + source, + target, + input, + value, + overheads, + skip_execution, + ), + VmId::Wasm => Pallet::::wasm_call( + context, + source, + target, + input, + value, + overheads, + skip_execution, + ), + }; + + // Set `IN_XVM` to false. + // We should make sure that this line is executed whatever the execution path. + let _ = IN_XVM.with(|in_xvm| in_xvm.take()); + + res } fn evm_call( @@ -131,37 +176,43 @@ impl Pallet { source: T::AccountId, target: Vec, input: Vec, + value: Balance, + overheads: Weight, skip_execution: bool, ) -> CallResult { log::trace!( target: "xvm::evm_call", - "Calling EVM: {:?} {:?}, {:?}, {:?}", - context, source, target, input, + "Calling EVM: {:?} {:?}, {:?}, {:?}, {:?}", + context, source, target, input, value, ); + ensure!( + target.len() == H160::len_bytes(), + CallErrorWithWeight { + error: CallError::InvalidTarget, + used_weight: overheads, + } + ); let target_decoded = Decode::decode(&mut target.as_ref()).map_err(|_| CallErrorWithWeight { error: CallError::InvalidTarget, - used_weight: WeightInfoOf::::evm_call_overheads(), - })?; - let bounded_input = BoundedVec::>::try_from(input) - .map_err(|_| CallErrorWithWeight { - error: CallError::InputTooLarge, - used_weight: WeightInfoOf::::evm_call_overheads(), + used_weight: overheads, })?; + let bounded_input = EthereumTxInput::try_from(input).map_err(|_| CallErrorWithWeight { + error: CallError::InputTooLarge, + used_weight: overheads, + })?; - let value = U256::zero(); + let value_u256 = U256::from(value); // With overheads, less weight is available. - let weight_limit = context - .weight_limit - .saturating_sub(WeightInfoOf::::evm_call_overheads()); + let weight_limit = context.weight_limit.saturating_sub(overheads); let gas_limit = U256::from(T::GasWeightMapping::weight_to_gas(weight_limit)); let source = T::AccountMapping::into_h160(source); let tx = CheckedEthereumTx { gas_limit, target: target_decoded, - value, + value: value_u256, input: bounded_input, maybe_access_list: None, }; @@ -171,7 +222,7 @@ impl Pallet { if skip_execution { return Ok(CallInfo { output: vec![], - used_weight: WeightInfoOf::::evm_call_overheads(), + used_weight: overheads, }); } @@ -186,7 +237,7 @@ impl Pallet { let used_weight = post_dispatch_info .actual_weight .unwrap_or_default() - .saturating_add(WeightInfoOf::::evm_call_overheads()); + .saturating_add(overheads); CallInfo { output: call_info.value, used_weight, @@ -197,7 +248,7 @@ impl Pallet { .post_info .actual_weight .unwrap_or_default() - .saturating_add(WeightInfoOf::::evm_call_overheads()); + .saturating_add(overheads); CallErrorWithWeight { error: CallError::ExecutionFailed(Into::<&str>::into(e.error).into()), used_weight, @@ -210,35 +261,34 @@ impl Pallet { source: T::AccountId, target: Vec, input: Vec, + value: Balance, + overheads: Weight, skip_execution: bool, ) -> CallResult { log::trace!( target: "xvm::wasm_call", - "Calling WASM: {:?} {:?}, {:?}, {:?}", - context, source, target, input, + "Calling WASM: {:?} {:?}, {:?}, {:?}, {:?}", + context, source, target, input, value, ); let dest = { let error = CallErrorWithWeight { error: CallError::InvalidTarget, - used_weight: WeightInfoOf::::wasm_call_overheads(), + used_weight: overheads, }; let decoded = Decode::decode(&mut target.as_ref()).map_err(|_| error.clone())?; T::Lookup::lookup(decoded).map_err(|_| error) }?; // With overheads, less weight is available. - let weight_limit = context - .weight_limit - .saturating_sub(WeightInfoOf::::wasm_call_overheads()); - let value = Default::default(); + let weight_limit = context.weight_limit.saturating_sub(overheads); // Note the skip execution check should be exactly before `pallet_contracts::bare_call` // to benchmark the correct overheads. if skip_execution { return Ok(CallInfo { output: vec![], - used_weight: WeightInfoOf::::wasm_call_overheads(), + used_weight: overheads, }); } @@ -255,9 +305,7 @@ impl Pallet { ); log::trace!(target: "xvm::wasm_call", "WASM call result: {:?}", call_result); - let used_weight = call_result - .gas_consumed - .saturating_add(WeightInfoOf::::wasm_call_overheads()); + let used_weight = call_result.gas_consumed.saturating_add(overheads); match call_result.result { Ok(success) => Ok(CallInfo { output: success.data, @@ -277,7 +325,8 @@ impl Pallet { source: T::AccountId, target: Vec, input: Vec, + value: Balance, ) -> CallResult { - Self::do_call(context, vm_id, source, target, input, true) + Self::do_call(context, vm_id, source, target, input, value, true) } } diff --git a/pallets/xvm/src/mock.rs b/pallets/xvm/src/mock.rs index a02472c3b7..7f134b4c3c 100644 --- a/pallets/xvm/src/mock.rs +++ b/pallets/xvm/src/mock.rs @@ -36,6 +36,7 @@ use sp_runtime::{ traits::{AccountIdLookup, BlakeTwo256}, AccountId32, }; +use sp_std::cell::RefCell; parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = @@ -131,12 +132,23 @@ impl astar_primitives::ethereum_checked::AccountMapping for HashedAcc } } +thread_local! { + static TRANSACTED: RefCell> = RefCell::new(None); +} + pub struct MockEthereumTransact; +impl MockEthereumTransact { + pub(crate) fn assert_transacted(source: H160, checked_tx: CheckedEthereumTx) { + let transacted = TRANSACTED.with(|v| v.borrow().clone()); + assert_eq!(transacted, Some((source, checked_tx))); + } +} impl CheckedEthereumTransact for MockEthereumTransact { fn xvm_transact( - _source: H160, - _checked_tx: CheckedEthereumTx, + source: H160, + checked_tx: CheckedEthereumTx, ) -> Result<(PostDispatchInfo, EvmCallInfo), DispatchErrorWithPostInfo> { + TRANSACTED.with(|v| *v.borrow_mut() = Some((source, checked_tx))); Ok(( PostDispatchInfo { actual_weight: Default::default(), @@ -170,12 +182,11 @@ impl pallet_xvm::Config for TestRuntime { type GasWeightMapping = MockGasWeightMapping; type AccountMapping = HashedAccountMapping; type EthereumTransact = MockEthereumTransact; - type WeightInfo = (); + type WeightInfo = weights::SubstrateWeight; } pub(crate) type AccountId = AccountId32; pub(crate) type BlockNumber = u64; -pub(crate) type Balance = u128; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -196,12 +207,16 @@ construct_runtime!( } ); +pub(crate) const ALICE: AccountId = AccountId32::new([0u8; 32]); + #[derive(Default)] pub struct ExtBuilder; impl ExtBuilder { #[allow(dead_code)] pub fn build(self) -> TestExternalities { + TRANSACTED.with(|v| *v.borrow_mut() = None); + let t = frame_system::GenesisConfig::default() .build_storage::() .unwrap(); diff --git a/pallets/xvm/src/tests.rs b/pallets/xvm/src/tests.rs new file mode 100644 index 0000000000..ba7b32b585 --- /dev/null +++ b/pallets/xvm/src/tests.rs @@ -0,0 +1,202 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +#![cfg(test)] + +use super::*; +use mock::*; + +use frame_support::{assert_noop, assert_ok, weights::Weight}; +use parity_scale_codec::Encode; +use sp_core::H160; +use sp_runtime::MultiAddress; + +#[test] +fn calling_into_same_vm_is_not_allowed() { + ExtBuilder::default().build().execute_with(|| { + // Calling EVM from EVM + let evm_context = Context { + source_vm_id: VmId::Evm, + weight_limit: Weight::from_parts(1_000_000, 1_000_000), + }; + let evm_vm_id = VmId::Evm; + let evm_target = H160::repeat_byte(1).encode(); + let input = vec![1, 2, 3]; + let value = 1_000_000u128; + let evm_used_weight: Weight = weights::SubstrateWeight::::evm_call_overheads(); + assert_noop!( + Xvm::call( + evm_context, + evm_vm_id, + ALICE, + evm_target, + input.clone(), + value + ), + CallErrorWithWeight { + error: CallError::SameVmCallDenied, + used_weight: evm_used_weight + }, + ); + + // Calling WASM from WASM + let wasm_context = Context { + source_vm_id: VmId::Wasm, + weight_limit: Weight::from_parts(1_000_000, 1_000_000), + }; + let wasm_vm_id = VmId::Wasm; + let wasm_target = MultiAddress::::Id(ALICE).encode(); + let wasm_used_weight: Weight = + weights::SubstrateWeight::::wasm_call_overheads(); + assert_noop!( + Xvm::call(wasm_context, wasm_vm_id, ALICE, wasm_target, input, value), + CallErrorWithWeight { + error: CallError::SameVmCallDenied, + used_weight: wasm_used_weight + }, + ); + }); +} + +#[test] +fn evm_call_fails_if_target_not_h160() { + ExtBuilder::default().build().execute_with(|| { + let context = Context { + source_vm_id: VmId::Wasm, + weight_limit: Weight::from_parts(1_000_000, 1_000_000), + }; + let vm_id = VmId::Evm; + let input = vec![1; 65_536]; + let value = 1_000_000u128; + let used_weight: Weight = weights::SubstrateWeight::::evm_call_overheads(); + + assert_noop!( + Xvm::call( + context.clone(), + vm_id, + ALICE, + ALICE.encode(), + input.clone(), + value + ), + CallErrorWithWeight { + error: CallError::InvalidTarget, + used_weight + }, + ); + + assert_noop!( + Xvm::call(context, vm_id, ALICE, vec![1, 2, 3], input, value), + CallErrorWithWeight { + error: CallError::InvalidTarget, + used_weight + }, + ); + }); +} + +#[test] +fn evm_call_fails_if_input_too_large() { + ExtBuilder::default().build().execute_with(|| { + let context = Context { + source_vm_id: VmId::Wasm, + weight_limit: Weight::from_parts(1_000_000, 1_000_000), + }; + let vm_id = VmId::Evm; + let target = H160::repeat_byte(0xFF); + let value = 1_000_000u128; + let used_weight: Weight = weights::SubstrateWeight::::evm_call_overheads(); + + assert_noop!( + Xvm::call( + context, + vm_id, + ALICE, + target.encode(), + vec![1; 65_537], + value + ), + CallErrorWithWeight { + error: CallError::InputTooLarge, + used_weight + }, + ); + }); +} + +#[test] +fn evm_call_works() { + ExtBuilder::default().build().execute_with(|| { + let context = Context { + source_vm_id: VmId::Wasm, + weight_limit: Weight::from_parts(1_000_000, 1_000_000), + }; + let vm_id = VmId::Evm; + let target = H160::repeat_byte(0xFF); + let input = vec![1; 65_536]; + let value = 1_000_000u128; + + assert_ok!(Xvm::call( + context, + vm_id, + ALICE, + target.encode(), + input.clone(), + value + )); + let source = Decode::decode( + &mut hex::decode("f0bd9ffde7f9f4394d8cc1d86bf24d87e5d5a9a9") + .expect("invalid source hex") + .as_ref(), + ) + .expect("invalid source"); + MockEthereumTransact::assert_transacted( + source, + CheckedEthereumTx { + gas_limit: U256::from(246000), + target: H160::repeat_byte(0xFF), + value: U256::from(value), + input: EthereumTxInput::try_from(input).expect("input too large"), + maybe_access_list: None, + }, + ); + }); +} + +#[test] +fn wasm_call_fails_if_invalid_target() { + ExtBuilder::default().build().execute_with(|| { + let context = Context { + source_vm_id: VmId::Evm, + weight_limit: Weight::from_parts(1_000_000, 1_000_000), + }; + let vm_id = VmId::Wasm; + let target = vec![1, 2, 3]; + let input = vec![1, 2, 3]; + let value = 1_000_000u128; + let used_weight: Weight = weights::SubstrateWeight::::wasm_call_overheads(); + + assert_noop!( + Xvm::call(context, vm_id, ALICE, target.encode(), input, value), + CallErrorWithWeight { + error: CallError::InvalidTarget, + used_weight + }, + ); + }); +} diff --git a/pallets/xvm/src/weights.rs b/pallets/xvm/src/weights.rs index 0d458675c2..c25a3b20d5 100644 --- a/pallets/xvm/src/weights.rs +++ b/pallets/xvm/src/weights.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for pallet_xvm //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-27, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-08-04, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `devserver-01`, CPU: `Intel(R) Xeon(R) E-2236 CPU @ 3.40GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("shibuya-dev"), DB CACHE: 1024 @@ -59,15 +59,15 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 771_000 picoseconds. - Weight::from_parts(818_000, 0) + // Minimum execution time: 730_000 picoseconds. + Weight::from_parts(754_000, 0) } fn wasm_call_overheads() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 304_000 picoseconds. - Weight::from_parts(337_000, 0) + // Minimum execution time: 309_000 picoseconds. + Weight::from_parts(347_000, 0) } } @@ -77,14 +77,14 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 771_000 picoseconds. - Weight::from_parts(818_000, 0) + // Minimum execution time: 730_000 picoseconds. + Weight::from_parts(754_000, 0) } fn wasm_call_overheads() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 304_000 picoseconds. - Weight::from_parts(337_000, 0) + // Minimum execution time: 309_000 picoseconds. + Weight::from_parts(347_000, 0) } } diff --git a/precompiles/assets-erc20/Cargo.toml b/precompiles/assets-erc20/Cargo.toml index cafa20e6b9..f70e155468 100644 --- a/precompiles/assets-erc20/Cargo.toml +++ b/precompiles/assets-erc20/Cargo.toml @@ -55,3 +55,4 @@ std = [ "sp-runtime/std", "sp-std/std", ] +runtime-benchmarks = [] diff --git a/precompiles/assets-erc20/src/mock.rs b/precompiles/assets-erc20/src/mock.rs index 3d7ec86fca..47c4ba498b 100644 --- a/precompiles/assets-erc20/src/mock.rs +++ b/precompiles/assets-erc20/src/mock.rs @@ -283,6 +283,8 @@ impl pallet_assets::Config for Runtime { type RemoveItemsLimit = ConstU32<0>; type AssetIdParameter = AssetId; type CallbackHandle = (); + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); } // Configure a mock runtime to test the pallet. diff --git a/precompiles/batch/Batch.sol b/precompiles/batch/Batch.sol new file mode 100644 index 0000000000..b2884de6d3 --- /dev/null +++ b/precompiles/batch/Batch.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity >=0.8.3; + +/// Interface to the precompiled contract on Shibuya/Shiden/Astar +/// Predeployed at the address 0x0000000000000000000000000000000000005006 +/// For better understanding check the source code: +/// repo: https://github.com/AstarNetwork/astar + +/// @title Batch precompile +/// @dev Allows to perform multiple calls through one call to the precompile. +/// Can be used by EOA to do multiple calls in a single transaction. +interface Batch { + /// @dev Batch multiple calls into a single transaction. + /// All calls are performed from the address calling this precompile. + /// + /// In case of one subcall reverting following subcalls will still be attempted. + /// + /// @param to List of addresses to call. + /// @param value List of values for each subcall. If array is shorter than `to` then additional + /// calls will be performed with a value of 0. + /// @param callData Call data for each `to` address. If array is shorter than `to` then + /// additional calls will be performed with an empty call data. + /// @param gasLimit Gas limit for each `to` address. Use 0 to forward all the remaining gas. + /// If array is shorter than `to` then the remaining gas available will be used. + function batchSome( + address[] memory to, + uint256[] memory value, + bytes[] memory callData, + uint64[] memory gasLimit + ) external; + + /// @dev Batch multiple calls into a single transaction. + /// All calls are performed from the address calling this precompile. + /// + /// In case of one subcall reverting, no more subcalls will be executed but + /// the batch transaction will succeed. Use "batchAll" to revert on any subcall revert. + /// + /// @param to List of addresses to call. + /// @param value List of values for each subcall. If array is shorter than `to` then additional + /// calls will be performed with a value of 0. + /// @param callData Call data for each `to` address. If array is shorter than `to` then + /// additional calls will be performed with an empty call data. + /// @param gasLimit Gas limit for each `to` address. Use 0 to forward all the remaining gas. + /// If array is shorter than `to` then the remaining gas available will be used. + function batchSomeUntilFailure( + address[] memory to, + uint256[] memory value, + bytes[] memory callData, + uint64[] memory gasLimit + ) external; + + /// @dev Batch multiple calls into a single transaction. + /// All calls are performed from the address calling this precompile. + /// + /// In case of one subcall reverting, the entire batch will revert. + /// + /// @param to List of addresses to call. + /// @param value List of values for each subcall. If array is shorter than `to` then additional + /// calls will be performed with a value of 0. + /// @param callData Call data for each `to` address. If array is shorter than `to` then + /// additional calls will be performed with an empty call data. + /// @param gasLimit Gas limit for each `to` address. Use 0 to forward all the remaining gas. + /// If array is shorter than `to` then the remaining gas available will be used. + function batchAll( + address[] memory to, + uint256[] memory value, + bytes[] memory callData, + uint64[] memory gasLimit + ) external; + + /// Emitted when a subcall succeeds. + event SubcallSucceeded(uint256 index); + + /// Emitted when a subcall fails. + event SubcallFailed(uint256 index); + +} diff --git a/precompiles/batch/Cargo.toml b/precompiles/batch/Cargo.toml new file mode 100644 index 0000000000..56a366b43e --- /dev/null +++ b/precompiles/batch/Cargo.toml @@ -0,0 +1,57 @@ +[package] +name = "pallet-evm-precompile-batch" +description = "A Precompile to batch multiple calls." +version = "0.1.0" +authors = ["StakeTechnologies", "PureStake"] +edition.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +log = { workspace = true } +num_enum = { workspace = true } +paste = { workspace = true } +slices = { workspace = true } + +# Moonbeam +precompile-utils = { workspace = true } + +# Substrate +frame-support = { workspace = true } +frame-system = { workspace = true } +parity-scale-codec = { workspace = true, features = ["max-encoded-len"] } +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-std = { workspace = true } + +# Frontier +evm = { workspace = true, features = ["with-codec"] } +fp-evm = { workspace = true } +pallet-evm = { workspace = true } + +[dev-dependencies] +derive_more = { workspace = true } +hex-literal = { workspace = true } +serde = { workspace = true } +sha3 = { workspace = true } + +pallet-balances = { workspace = true, features = ["std"] } +pallet-timestamp = { workspace = true, features = ["std"] } +parity-scale-codec = { workspace = true, features = ["max-encoded-len", "std"] } +precompile-utils = { workspace = true, features = ["std", "testing"] } +scale-info = { workspace = true, features = ["derive", "std"] } +sp-runtime = { workspace = true, features = ["std"] } + +[features] +default = ["std"] +std = [ + "fp-evm/std", + "frame-support/std", + "frame-system/std", + "pallet-evm/std", + "parity-scale-codec/std", + "precompile-utils/std", + "sp-core/std", + "sp-io/std", + "sp-std/std", +] diff --git a/precompiles/batch/src/lib.rs b/precompiles/batch/src/lib.rs new file mode 100644 index 0000000000..029c2e09a1 --- /dev/null +++ b/precompiles/batch/src/lib.rs @@ -0,0 +1,337 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +// Copyright 2019-2022 PureStake Inc. +// Copyright 2022 Stake Technologies +// This file is part of pallet-evm-precompile-batch package, originally developed by Purestake Inc. +// pallet-evm-precompile-batch package used in Astar Network in terms of GPLv3. +// +// pallet-evm-precompile-batch is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// pallet-evm-precompile-batch is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with pallet-evm-precompile-batch. If not, see . +#![cfg_attr(not(feature = "std"), no_std)] + +use ::evm::{ExitError, ExitReason}; +use fp_evm::{Context, Log, PrecompileFailure, PrecompileHandle, Transfer}; +use frame_support::{ + dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, + traits::ConstU32, +}; +use pallet_evm::{Precompile, PrecompileOutput}; +use precompile_utils::{bytes::BoundedBytes, data::BoundedVec, *}; +use sp_core::{H160, U256}; +use sp_std::{iter::repeat, marker::PhantomData, vec, vec::Vec}; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Mode { + BatchSome, // = "batchSome(address[],uint256[],bytes[],uint64[])", + BatchSomeUntilFailure, // = "batchSomeUntilFailure(address[],uint256[],bytes[],uint64[])", + BatchAll, // = "batchAll(address[],uint256[],bytes[],uint64[])", +} + +pub const LOG_SUBCALL_SUCCEEDED: [u8; 32] = keccak256!("SubcallSucceeded(uint256)"); +pub const LOG_SUBCALL_FAILED: [u8; 32] = keccak256!("SubcallFailed(uint256)"); +pub const CALL_DATA_LIMIT: u32 = 2u32.pow(16); +pub const ARRAY_LIMIT: u32 = 2u32.pow(9); + +type GetCallDataLimit = ConstU32; +type GetArrayLimit = ConstU32; + +fn log_subcall_succeeded(address: impl Into, index: usize) -> Log { + LogsBuilder::new(address.into()).log1( + LOG_SUBCALL_SUCCEEDED, + data::encode_event_data(U256::from(index)), + ) +} + +fn log_subcall_failed(address: impl Into, index: usize) -> Log { + LogsBuilder::new(address.into()).log1( + LOG_SUBCALL_FAILED, + data::encode_event_data(U256::from(index)), + ) +} + +#[precompile_utils::generate_function_selector] +#[derive(Debug, PartialEq)] +pub enum Action { + BatchSome = "batchSome(address[],uint256[],bytes[],uint64[])", + BatchSomeUntilFailure = "batchSomeUntilFailure(address[],uint256[],bytes[],uint64[])", + BatchAll = "batchAll(address[],uint256[],bytes[],uint64[])", +} + +/// Batch precompile. +#[derive(Debug, Clone)] +pub struct BatchPrecompile(PhantomData); + +impl Precompile for BatchPrecompile +where + Runtime: pallet_evm::Config, + Runtime::RuntimeCall: Dispatchable + GetDispatchInfo, +{ + fn execute(handle: &mut impl PrecompileHandle) -> EvmResult { + let selector = handle.read_selector()?; + + handle.check_function_modifier(FunctionModifier::NonPayable)?; + match selector { + Action::BatchSome => Self::batch_some(handle), + Action::BatchAll => Self::batch_all(handle), + Action::BatchSomeUntilFailure => Self::batch_some_until_failure(handle), + } + } +} +// No funds are transfered to the precompile address. +// Transfers will directly be made on the behalf of the user by the precompile. +// #[precompile_utils::precompile] +impl BatchPrecompile +where + Runtime: pallet_evm::Config, + Runtime::RuntimeCall: Dispatchable + GetDispatchInfo, +{ + fn batch_some(handle: &mut impl PrecompileHandle) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(4)?; + let to = input.read::>()?; + let value = input.read::>()?; + let call_data = + input.read::, GetArrayLimit>>()?; + let gas_limit = input.read::>()?; + log::trace!(target: "batch-precompile", "batch_some\n to address(s) {:?}, value(s) {:?} call_data(s) {:?}, gas_limit(s) {:?}", to, value,call_data, gas_limit); + Self::inner_batch(Mode::BatchSome, handle, to, value, call_data, gas_limit) + } + + fn batch_some_until_failure(handle: &mut impl PrecompileHandle) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(4)?; + let to = input.read::>()?; + let value = input.read::>()?; + let call_data = + input.read::, GetArrayLimit>>()?; + let gas_limit = input.read::>()?; + log::trace!(target: "batch-precompile", "batch_some_until_failure\n to address(s) {:?}, value(s) {:?} call_data(s) {:?}, gas_limit(s) {:?}", to, value,call_data, gas_limit); + Self::inner_batch( + Mode::BatchSomeUntilFailure, + handle, + to, + value, + call_data, + gas_limit, + ) + } + + fn batch_all(handle: &mut impl PrecompileHandle) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(4)?; + let to = input.read::>()?; + let value = input.read::>()?; + let call_data = + input.read::, GetArrayLimit>>()?; + let gas_limit = input.read::>()?; + log::trace!(target: "batch-precompile", "batch_all\n to address(s) {:?}, value(s) {:?} call_data(s) {:?}, gas_limit(s) {:?}", to, value,call_data, gas_limit); + Self::inner_batch(Mode::BatchAll, handle, to, value, call_data, gas_limit) + } + + fn inner_batch( + mode: Mode, + handle: &mut impl PrecompileHandle, + to: BoundedVec, + value: BoundedVec, + call_data: BoundedVec, GetArrayLimit>, + gas_limit: BoundedVec, + ) -> EvmResult { + let addresses = Vec::from(to).into_iter().enumerate(); + let values = Vec::from(value) + .into_iter() + .map(|x| Some(x)) + .chain(repeat(None)); + let calls_data = Vec::from(call_data) + .into_iter() + .map(|x| Some(x.into())) + .chain(repeat(None)); + let gas_limits = Vec::from(gas_limit).into_iter().map(|x| + // x = 0 => forward all remaining gas + if x == 0 { + None + } else { + Some(x) + } + ).chain(repeat(None)); + + // Cost of batch log. (doesn't change when index changes) + let log_cost = log_subcall_failed(handle.code_address(), 0) + .compute_cost() + .map_err(|_| revert("Failed to compute log cost"))?; + + for ((i, address), (value, (call_data, gas_limit))) in + addresses.zip(values.zip(calls_data.zip(gas_limits))) + { + let address = address.0; + let value = value.unwrap_or(U256::zero()); + let call_data = call_data.unwrap_or(vec![]); + + let sub_context = Context { + caller: handle.context().caller, + address: address.clone(), + apparent_value: value, + }; + + let transfer = if value.is_zero() { + None + } else { + Some(Transfer { + source: handle.context().caller, + target: address.clone(), + value, + }) + }; + + // We reserve enough gas to emit a final log and perform the subcall itself. + // If not enough gas we stop there according to Mode strategy. + let remaining_gas = handle.remaining_gas(); + + let forwarded_gas = match (remaining_gas.checked_sub(log_cost), mode) { + (Some(remaining), _) => remaining, + (None, Mode::BatchAll) => { + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }) + } + (None, _) => { + return Ok(succeed(EvmDataWriter::new().write(true).build())); + } + }; + + // Cost of the call itself that the batch precompile must pay. + let call_cost = call_cost(value, ::config()); + + let forwarded_gas = match forwarded_gas.checked_sub(call_cost) { + Some(remaining) => remaining, + None => { + let log = log_subcall_failed(handle.code_address(), i); + handle.record_log_costs(&[&log])?; + log.record(handle)?; + + match mode { + Mode::BatchAll => { + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }) + } + Mode::BatchSomeUntilFailure => { + return Ok(succeed(EvmDataWriter::new().write(true).build())) + } + Mode::BatchSome => continue, + } + } + }; + + // If there is a provided gas limit we ensure there is enough gas remaining. + let forwarded_gas = match gas_limit { + None => forwarded_gas, // provide all gas if no gas limit, + Some(limit) => { + if limit > forwarded_gas { + let log = log_subcall_failed(handle.code_address(), i); + handle.record_log_costs(&[&log])?; + log.record(handle)?; + + match mode { + Mode::BatchAll => { + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }) + } + Mode::BatchSomeUntilFailure => { + return Ok(succeed(EvmDataWriter::new().write(true).build())) + } + Mode::BatchSome => continue, + } + } + limit + } + }; + + let (reason, output) = handle.call( + address, + transfer, + call_data, + Some(forwarded_gas), + false, + &sub_context, + ); + + // Logs + // We reserved enough gas so this should not OOG. + match reason { + ExitReason::Revert(_) | ExitReason::Error(_) => { + let log = log_subcall_failed(handle.code_address(), i); + handle.record_log_costs(&[&log])?; + log.record(handle)? + } + ExitReason::Succeed(_) => { + let log = log_subcall_succeeded(handle.code_address(), i); + handle.record_log_costs(&[&log])?; + log.record(handle)? + } + _ => (), + } + + // How to proceed + match (mode, reason) { + // _: Fatal is always fatal + (_, ExitReason::Fatal(exit_status)) => { + return Err(PrecompileFailure::Fatal { exit_status }) + } + + // BatchAll : Reverts and errors are immediatly forwarded. + (Mode::BatchAll, ExitReason::Revert(exit_status)) => { + return Err(PrecompileFailure::Revert { + exit_status, + output, + }) + } + (Mode::BatchAll, ExitReason::Error(exit_status)) => { + return Err(PrecompileFailure::Error { exit_status }) + } + + // BatchSomeUntilFailure : Reverts and errors prevent subsequent subcalls to + // be executed but the precompile still succeed. + (Mode::BatchSomeUntilFailure, ExitReason::Revert(_) | ExitReason::Error(_)) => { + return Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + // Success or ignored revert/error. + (_, _) => (), + } + } + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } +} diff --git a/precompiles/batch/src/mock.rs b/precompiles/batch/src/mock.rs new file mode 100644 index 0000000000..4ac61de881 --- /dev/null +++ b/precompiles/batch/src/mock.rs @@ -0,0 +1,225 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +// Copyright 2019-2022 PureStake Inc. +// Copyright 2022 Stake Technologies +// This file is part of pallet-evm-precompile-batch package, originally developed by Purestake Inc. +// pallet-evm-precompile-batch package used in Astar Network in terms of GPLv3. +// +// pallet-evm-precompile-batch is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// pallet-evm-precompile-batch is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with pallet-evm-precompile-batch. If not, see . + +//! Test utilities +use super::*; + +use fp_evm::IsPrecompileResult; +use frame_support::traits::{ConstU64, Everything}; +use frame_support::{construct_runtime, parameter_types, weights::Weight}; +use pallet_evm::{EnsureAddressNever, EnsureAddressRoot, PrecompileResult, PrecompileSet}; +use precompile_utils::{mock_account, testing::MockAccount}; +use sp_core::H256; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + Perbill, +}; + +pub type AccountId = MockAccount; +pub type Balance = u128; +pub type BlockNumber = u32; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Evm: pallet_evm::{Pallet, Call, Storage, Event}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + } +); + +parameter_types! { + pub const BlockHashCount: u32 = 250; + pub const MaximumBlockWeight: Weight = Weight::from_parts(1024, 1); + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); + pub const SS58Prefix: u8 = 42; +} + +impl frame_system::Config for Runtime { + type BaseCallFilter = Everything; + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = BlockNumber; + type RuntimeCall = RuntimeCall; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = sp_runtime::generic::Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type BlockWeights = (); + type BlockLength = (); + type SS58Prefix = SS58Prefix; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} +parameter_types! { + pub const ExistentialDeposit: u128 = 1; +} +impl pallet_balances::Config for Runtime { + type MaxReserves = (); + type ReserveIdentifier = [u8; 4]; + type MaxLocks = (); + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type HoldIdentifier = (); + type FreezeIdentifier = (); + type MaxHolds = (); + type MaxFreezes = (); +} + +pub fn precompile_address() -> H160 { + H160::from_low_u64_be(0x5002) +} + +#[derive(Debug, Clone, Copy)] +pub struct BatchPrecompileMock(PhantomData); + +impl PrecompileSet for BatchPrecompileMock +where + R: pallet_evm::Config, + BatchPrecompile: Precompile, +{ + fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { + match handle.code_address() { + a if a == precompile_address() => Some(BatchPrecompile::::execute(handle)), + _ => None, + } + } + + fn is_precompile(&self, address: sp_core::H160, _gas: u64) -> IsPrecompileResult { + IsPrecompileResult::Answer { + is_precompile: address == precompile_address(), + extra_cost: 0, + } + } +} + +mock_account!(Revert, |_| MockAccount::from_u64(2)); + +parameter_types! { + pub BlockGasLimit: U256 = U256::max_value(); + pub PrecompilesValue: BatchPrecompileMock = BatchPrecompileMock(Default::default()); + pub const WeightPerGas: Weight = Weight::from_parts(1, 0); +} + +impl pallet_evm::Config for Runtime { + type FeeCalculator = (); + type GasWeightMapping = pallet_evm::FixedGasWeightMapping; + type WeightPerGas = WeightPerGas; + type CallOrigin = EnsureAddressRoot; + type WithdrawOrigin = EnsureAddressNever; + type AddressMapping = AccountId; + type Currency = Balances; + type RuntimeEvent = RuntimeEvent; + type Runner = pallet_evm::runner::stack::Runner; + type PrecompilesType = BatchPrecompileMock; + type PrecompilesValue = PrecompilesValue; + type Timestamp = Timestamp; + type ChainId = (); + type OnChargeTransaction = (); + type BlockGasLimit = BlockGasLimit; + type BlockHashMapping = pallet_evm::SubstrateBlockHashMapping; + type FindAuthor = (); + type OnCreate = (); + type WeightInfo = (); + type GasLimitPovSizeRatio = ConstU64<4>; +} + +parameter_types! { + pub const MinimumPeriod: u64 = 5; +} +impl pallet_timestamp::Config for Runtime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +pub(crate) struct ExtBuilder { + // endowed accounts with balances + balances: Vec<(AccountId, Balance)>, +} + +impl Default for ExtBuilder { + fn default() -> ExtBuilder { + ExtBuilder { balances: vec![] } + } +} + +impl ExtBuilder { + pub(crate) fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .expect("Frame system builds valid default genesis config"); + + pallet_balances::GenesisConfig:: { + balances: self.balances, + } + .assimilate_storage(&mut t) + .expect("Pallet balances storage can be assimilated"); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + pallet_evm::Pallet::::create_account( + Revert.into(), + hex_literal::hex!("1460006000fd").to_vec(), + ); + }); + ext + } +} diff --git a/precompiles/batch/src/tests.rs b/precompiles/batch/src/tests.rs new file mode 100644 index 0000000000..55241fe08a --- /dev/null +++ b/precompiles/batch/src/tests.rs @@ -0,0 +1,638 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +// Copyright 2019-2022 PureStake Inc. +// Copyright 2022 Stake Technologies +// This file is part of pallet-evm-precompile-batch package, originally developed by Purestake Inc. +// pallet-evm-precompile-batch package used in Astar Network in terms of GPLv3. +// +// pallet-evm-precompile-batch is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// pallet-evm-precompile-batch is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with pallet-evm-precompile-batch. If not, see . + +use crate::mock::{precompile_address, BatchPrecompileMock, ExtBuilder, PrecompilesValue, Runtime}; +use crate::{log_subcall_failed, log_subcall_succeeded, Mode, *}; +use fp_evm::ExitError; +use precompile_utils::{call_cost, testing::*, LogsBuilder}; +use sp_core::{H256, U256}; + +fn precompiles() -> BatchPrecompileMock { + PrecompilesValue::get() +} + +fn costs() -> (u64, u64) { + let return_log_cost = log_subcall_failed(precompile_address(), 0) + .compute_cost() + .unwrap(); + let call_cost = + return_log_cost + call_cost(U256::one(), ::config()); + (return_log_cost, call_cost) +} + +#[test] +fn batch_some_empty() { + ExtBuilder::default().build().execute_with(|| { + precompiles() + .prepare_test( + Alice, + precompile_address(), + EvmDataWriter::new_with_selector(Action::BatchSome) + .write::>(vec![]) + .write::>(vec![]) + .write::>(vec![]) + .write::>(vec![]) + .build(), + ) + .execute_returns(EvmDataWriter::new().write(true).build()); + }) +} + +#[test] +fn batch_some_until_failure_empty() { + ExtBuilder::default().build().execute_with(|| { + precompiles() + .prepare_test( + Alice, + precompile_address(), + EvmDataWriter::new_with_selector(Action::BatchSomeUntilFailure) + .write::>(vec![]) + .write::>(vec![]) + .write::>(vec![]) + .write::>(vec![]) + .build(), + ) + .execute_returns(EvmDataWriter::new().write(true).build()); + }) +} + +#[test] +fn batch_all_empty() { + ExtBuilder::default().build().execute_with(|| { + precompiles() + .prepare_test( + Alice, + precompile_address(), + EvmDataWriter::new_with_selector(Action::BatchAll) + .write::>(vec![]) + .write::>(vec![]) + .write::>(vec![]) + .write::>(vec![]) + .build(), + ) + .execute_returns(EvmDataWriter::new().write(true).build()); + }) +} + +fn check_mode(mode: Mode) -> Action { + match mode { + Mode::BatchAll => Action::BatchAll, + Mode::BatchSome => Action::BatchSome, + Mode::BatchSomeUntilFailure => Action::BatchSomeUntilFailure, + } +} + +fn batch_returns( + precompiles: &BatchPrecompileMock, + mode: Mode, +) -> PrecompilesTester> { + let mut counter = 0; + let one = b"one"; + let two = b"two"; + let (_, total_call_cost) = costs(); + + precompiles + .prepare_test( + Alice, + precompile_address(), + EvmDataWriter::new_with_selector(check_mode(mode)) + .write(vec![Address(Bob.into()), Address(Charlie.into())]) + .write(vec![U256::from(1u8), U256::from(2u8)]) + .write(vec![Bytes::from(&one[..]), Bytes::from(&two[..])]) + .write::>(vec![]) + .build(), + ) + .with_target_gas(Some(100_000)) + .with_subcall_handle(move |subcall| { + let Subcall { + address, + transfer, + input, + target_gas, + is_static, + context, + } = subcall; + + // Called from the precompile caller. + assert_eq!(context.caller, Alice.into()); + assert_eq!(is_static, false); + + match address { + a if a == Bob.into() => { + assert_eq!(counter, 0, "this is the first call"); + counter += 1; + + assert_eq!( + target_gas, + Some(100_000 - total_call_cost), + "batch forward all gas" + ); + let transfer = transfer.expect("there is a transfer"); + assert_eq!(transfer.source, Alice.into()); + assert_eq!(transfer.target, Bob.into()); + assert_eq!(transfer.value, 1u8.into()); + + assert_eq!(context.address, Bob.into()); + assert_eq!(context.apparent_value, 1u8.into()); + + assert_eq!(&input, b"one"); + + SubcallOutput { + cost: 13, + logs: vec![ + LogsBuilder::new(Bob.into()).log1(H256::repeat_byte(0x11), vec![]) + ], + ..SubcallOutput::succeed() + } + } + a if a == Charlie.into() => { + assert_eq!(counter, 1, "this is the second call"); + counter += 1; + + assert_eq!( + target_gas, + Some(100_000 - 13 - total_call_cost * 2), + "batch forward all gas" + ); + let transfer = transfer.expect("there is a transfer"); + assert_eq!(transfer.source, Alice.into()); + assert_eq!(transfer.target, Charlie.into()); + assert_eq!(transfer.value, 2u8.into()); + + assert_eq!(context.address, Charlie.into()); + assert_eq!(context.apparent_value, 2u8.into()); + + assert_eq!(&input, b"two"); + + SubcallOutput { + cost: 17, + logs: vec![ + LogsBuilder::new(Charlie.into()).log1(H256::repeat_byte(0x22), vec![]) + ], + ..SubcallOutput::succeed() + } + } + _ => panic!("unexpected subcall"), + } + }) + .expect_cost(13 + 17 + total_call_cost * 2) +} + +#[test] +fn batch_some_returns() { + ExtBuilder::default().build().execute_with(|| { + batch_returns(&precompiles(), Mode::BatchSome) + .expect_log(LogsBuilder::new(Bob.into()).log1(H256::repeat_byte(0x11), vec![])) + .expect_log(log_subcall_succeeded(precompile_address(), 0)) + .expect_log(LogsBuilder::new(Charlie.into()).log1(H256::repeat_byte(0x22), vec![])) + .expect_log(log_subcall_succeeded(precompile_address(), 1)) + .execute_returns(EvmDataWriter::new().write(true).build()) + }) +} + +#[test] +fn batch_some_until_failure_returns() { + ExtBuilder::default().build().execute_with(|| { + batch_returns(&precompiles(), Mode::BatchSomeUntilFailure) + .expect_log(LogsBuilder::new(Bob.into()).log1(H256::repeat_byte(0x11), vec![])) + .expect_log(log_subcall_succeeded(precompile_address(), 0)) + .expect_log(LogsBuilder::new(Charlie.into()).log1(H256::repeat_byte(0x22), vec![])) + .expect_log(log_subcall_succeeded(precompile_address(), 1)) + .execute_returns(EvmDataWriter::new().write(true).build()) + }) +} + +#[test] +fn batch_all_returns() { + ExtBuilder::default().build().execute_with(|| { + batch_returns(&precompiles(), Mode::BatchAll) + .expect_log(LogsBuilder::new(Bob.into()).log1(H256::repeat_byte(0x11), vec![])) + .expect_log(log_subcall_succeeded(precompile_address(), 0)) + .expect_log(LogsBuilder::new(Charlie.into()).log1(H256::repeat_byte(0x22), vec![])) + .expect_log(log_subcall_succeeded(precompile_address(), 1)) + .execute_returns(EvmDataWriter::new().write(true).build()) + }) +} + +fn batch_out_of_gas( + precompiles: &BatchPrecompileMock, + mode: Mode, +) -> PrecompilesTester> { + let one = b"one"; + let (_, total_call_cost) = costs(); + precompiles + .prepare_test( + Alice, + precompile_address(), + EvmDataWriter::new_with_selector(check_mode(mode)) + .write(vec![Address(Bob.into())]) + .write(vec![U256::from(1u8)]) + .write(vec![Bytes::from(&one[..])]) + .write::>(vec![]) + .build(), + ) + .with_target_gas(Some(50_000)) + .with_subcall_handle(move |subcall| { + let Subcall { + address, + transfer, + input, + target_gas, + is_static, + context, + } = subcall; + + // Called from the precompile caller. + assert_eq!(context.caller, Alice.into()); + assert_eq!(is_static, false); + + match address { + a if a == Bob.into() => { + assert_eq!( + target_gas, + Some(50_000 - total_call_cost), + "batch forward all gas" + ); + let transfer = transfer.expect("there is a transfer"); + assert_eq!(transfer.source, Alice.into()); + assert_eq!(transfer.target, Bob.into()); + assert_eq!(transfer.value, 1u8.into()); + + assert_eq!(context.address, Bob.into()); + assert_eq!(context.apparent_value, 1u8.into()); + + assert_eq!(&input, b"one"); + + SubcallOutput { + cost: 11_000, + ..SubcallOutput::out_of_gas() + } + } + _ => panic!("unexpected subcall"), + } + }) +} + +#[test] +fn batch_some_out_of_gas() { + ExtBuilder::default().build().execute_with(|| { + batch_out_of_gas(&precompiles(), Mode::BatchSome) + .expect_log(log_subcall_failed(precompile_address(), 0)) + .execute_returns(EvmDataWriter::new().write(true).build()) + }) +} + +#[test] +fn batch_some_until_failure_out_of_gas() { + ExtBuilder::default().build().execute_with(|| { + batch_out_of_gas(&precompiles(), Mode::BatchSomeUntilFailure) + .expect_log(log_subcall_failed(precompile_address(), 0)) + .execute_returns(EvmDataWriter::new().write(true).build()) + }) +} + +#[test] +fn batch_all_out_of_gas() { + ExtBuilder::default().build().execute_with(|| { + batch_out_of_gas(&precompiles(), Mode::BatchAll).execute_error(ExitError::OutOfGas) + }) +} + +fn batch_incomplete( + precompiles: &BatchPrecompileMock, + mode: Mode, +) -> PrecompilesTester> { + let mut counter = 0; + let one = b"one"; + + let (_, total_call_cost) = costs(); + + precompiles + .prepare_test( + Alice, + precompile_address(), + EvmDataWriter::new_with_selector(check_mode(mode)) + .write(vec![ + Address(Bob.into()), + Address(Charlie.into()), + Address(Alice.into()), + ]) + .write(vec![U256::from(1u8), U256::from(2u8), U256::from(3u8)]) + .write(vec![Bytes::from(&one[..])]) + .write::>(vec![]) + .build(), + ) + .with_target_gas(Some(300_000)) + .with_subcall_handle(move |subcall| { + let Subcall { + address, + transfer, + input, + target_gas, + is_static, + context, + } = subcall; + + // Called from the precompile caller. + assert_eq!(context.caller, Alice.into()); + assert_eq!(is_static, false); + + match address { + a if a == Bob.into() => { + assert_eq!(counter, 0, "this is the first call"); + counter += 1; + + assert_eq!( + target_gas, + Some(300_000 - total_call_cost), + "batch forward all gas" + ); + let transfer = transfer.expect("there is a transfer"); + assert_eq!(transfer.source, Alice.into()); + assert_eq!(transfer.target, Bob.into()); + assert_eq!(transfer.value, 1u8.into()); + + assert_eq!(context.address, Bob.into()); + assert_eq!(context.apparent_value, 1u8.into()); + + assert_eq!(&input, b"one"); + + SubcallOutput { + cost: 13, + logs: vec![ + LogsBuilder::new(Bob.into()).log1(H256::repeat_byte(0x11), vec![]) + ], + ..SubcallOutput::succeed() + } + } + a if a == Charlie.into() => { + assert_eq!(counter, 1, "this is the second call"); + counter += 1; + + assert_eq!( + target_gas, + Some(300_000 - 13 - total_call_cost * 2), + "batch forward all gas" + ); + let transfer = transfer.expect("there is a transfer"); + assert_eq!(transfer.source, Alice.into()); + assert_eq!(transfer.target, Charlie.into()); + assert_eq!(transfer.value, 2u8.into()); + + assert_eq!(context.address, Charlie.into()); + assert_eq!(context.apparent_value, 2u8.into()); + + assert_eq!(&input, b""); + + SubcallOutput { + output: String::from("Revert message").as_bytes().to_vec(), + cost: 17, + ..SubcallOutput::revert() + } + } + a if a == Alice.into() => { + assert_eq!(counter, 2, "this is the third call"); + counter += 1; + + assert_eq!( + target_gas, + Some(300_000 - 13 - 17 - total_call_cost * 3), + "batch forward all gas" + ); + let transfer = transfer.expect("there is a transfer"); + assert_eq!(transfer.source, Alice.into()); + assert_eq!(transfer.target, Alice.into()); + assert_eq!(transfer.value, 3u8.into()); + + assert_eq!(context.address, Alice.into()); + assert_eq!(context.apparent_value, 3u8.into()); + + assert_eq!(&input, b""); + + SubcallOutput { + cost: 19, + logs: vec![ + LogsBuilder::new(Alice.into()).log1(H256::repeat_byte(0x33), vec![]) + ], + ..SubcallOutput::succeed() + } + } + _ => panic!("unexpected subcall"), + } + }) +} + +#[test] +fn batch_some_incomplete() { + ExtBuilder::default().build().execute_with(|| { + let (_, total_call_cost) = costs(); + + batch_incomplete(&precompiles(), Mode::BatchSome) + .expect_log(LogsBuilder::new(Bob.into()).log1(H256::repeat_byte(0x11), vec![])) + .expect_log(log_subcall_succeeded(precompile_address(), 0)) + .expect_log(log_subcall_failed(precompile_address(), 1)) + .expect_log(LogsBuilder::new(Alice.into()).log1(H256::repeat_byte(0x33), vec![])) + .expect_log(log_subcall_succeeded(precompile_address(), 2)) + .expect_cost(13 + 17 + 19 + total_call_cost * 3) + .execute_returns(EvmDataWriter::new().write(true).build()) + }) +} + +#[test] +fn batch_some_until_failure_incomplete() { + ExtBuilder::default().build().execute_with(|| { + let (_, total_call_cost) = costs(); + + batch_incomplete(&precompiles(), Mode::BatchSomeUntilFailure) + .expect_log(LogsBuilder::new(Bob.into()).log1(H256::repeat_byte(0x11), vec![])) + .expect_log(log_subcall_succeeded(precompile_address(), 0)) + .expect_log(log_subcall_failed(precompile_address(), 1)) + .expect_cost(13 + 17 + total_call_cost * 2) + .execute_returns(EvmDataWriter::new().write(true).build()) + }) +} + +#[test] +fn batch_all_incomplete() { + ExtBuilder::default().build().execute_with(|| { + batch_incomplete(&precompiles(), Mode::BatchAll) + .execute_reverts(|output| output == b"Revert message") + }) +} + +fn batch_log_out_of_gas( + precompiles: &BatchPrecompileMock, + mode: Mode, +) -> PrecompilesTester> { + let (log_cost, _) = costs(); + let one = b"one"; + + precompiles + .prepare_test( + Alice, + precompile_address(), + EvmDataWriter::new_with_selector(check_mode(mode)) + .write(vec![Address(Bob.into())]) + .write(vec![U256::from(1u8)]) + .write(vec![Bytes::from(&one[..])]) + .write::>(vec![]) + .build(), + ) + .with_target_gas(Some(log_cost - 1)) + .with_subcall_handle(move |_subcall| panic!("there shouldn't be any subcalls")) +} + +#[test] +fn batch_all_log_out_of_gas() { + ExtBuilder::default().build().execute_with(|| { + batch_log_out_of_gas(&precompiles(), Mode::BatchAll).execute_error(ExitError::OutOfGas); + }) +} + +#[test] +fn batch_some_log_out_of_gas() { + ExtBuilder::default().build().execute_with(|| { + batch_log_out_of_gas(&precompiles(), Mode::BatchSome) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + }) +} + +#[test] +fn batch_some_until_failure_log_out_of_gas() { + ExtBuilder::default().build().execute_with(|| { + batch_log_out_of_gas(&precompiles(), Mode::BatchSomeUntilFailure) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + }) +} + +fn batch_call_out_of_gas( + precompiles: &BatchPrecompileMock, + mode: Mode, +) -> PrecompilesTester> { + let (_, total_call_cost) = costs(); + let one = b"one"; + + precompiles + .prepare_test( + Alice, + precompile_address(), + EvmDataWriter::new_with_selector(check_mode(mode)) + .write(vec![Address(Bob.into())]) + .write(vec![U256::from(1u8)]) + .write(vec![Bytes::from(&one[..])]) + .write::>(vec![]) + .build(), + ) + .with_target_gas(Some(total_call_cost - 1)) + .with_subcall_handle(move |_subcall| panic!("there shouldn't be any subcalls")) +} + +#[test] +fn batch_all_call_out_of_gas() { + ExtBuilder::default().build().execute_with(|| { + batch_call_out_of_gas(&precompiles(), Mode::BatchAll).execute_error(ExitError::OutOfGas); + }) +} + +#[test] +fn batch_some_call_out_of_gas() { + ExtBuilder::default().build().execute_with(|| { + batch_call_out_of_gas(&precompiles(), Mode::BatchSome) + .expect_log(log_subcall_failed(precompile_address(), 0)) + .execute_returns(EvmDataWriter::new().write(true).build()); + }) +} + +#[test] +fn batch_some_until_failure_call_out_of_gas() { + ExtBuilder::default().build().execute_with(|| { + batch_call_out_of_gas(&precompiles(), Mode::BatchSomeUntilFailure) + .expect_log(log_subcall_failed(precompile_address(), 0)) + .execute_returns(EvmDataWriter::new().write(true).build()); + }) +} + +fn batch_gas_limit( + precompiles: &BatchPrecompileMock, + mode: Mode, +) -> PrecompilesTester> { + let (_, total_call_cost) = costs(); + let one = b"one"; + + precompiles + .prepare_test( + Alice, + precompile_address(), + EvmDataWriter::new_with_selector(check_mode(mode)) + .write(vec![Address(Bob.into())]) + .write(vec![U256::from(1u8)]) + .write(vec![Bytes::from(&one[..])]) + .write::>(vec![U256::from(50_000 - total_call_cost + 1)]) + .build(), + ) + .with_target_gas(Some(50_000)) + .with_subcall_handle(move |_subcall| panic!("there shouldn't be any subcalls")) +} + +#[test] +fn batch_all_gas_limit() { + ExtBuilder::default().build().execute_with(|| { + batch_gas_limit(&precompiles(), Mode::BatchAll).execute_error(ExitError::OutOfGas); + }) +} + +#[test] +fn batch_some_gas_limit() { + ExtBuilder::default().build().execute_with(|| { + let (return_log_cost, _) = costs(); + + batch_gas_limit(&precompiles(), Mode::BatchSome) + .expect_log(log_subcall_failed(precompile_address(), 0)) + .expect_cost(return_log_cost) + .execute_returns(EvmDataWriter::new().write(true).build()); + }) +} + +#[test] +fn batch_some_until_failure_gas_limit() { + ExtBuilder::default().build().execute_with(|| { + batch_gas_limit(&precompiles(), Mode::BatchSomeUntilFailure) + .expect_log(log_subcall_failed(precompile_address(), 0)) + .execute_returns(EvmDataWriter::new().write(true).build()); + }) +} diff --git a/precompiles/dapps-staking/Cargo.toml b/precompiles/dapps-staking/Cargo.toml index 232780acf6..aa5584170c 100644 --- a/precompiles/dapps-staking/Cargo.toml +++ b/precompiles/dapps-staking/Cargo.toml @@ -31,7 +31,7 @@ pallet-evm = { workspace = true } [dev-dependencies] derive_more = { workspace = true } -pallet-balances = { workspace = true } +pallet-balances = { workspace = true, features = ["std"] } pallet-timestamp = { workspace = true } precompile-utils = { workspace = true, features = ["testing"] } serde = { workspace = true } diff --git a/precompiles/utils/Cargo.toml b/precompiles/utils/Cargo.toml index 6be1724ce6..a97a1a81ff 100644 --- a/precompiles/utils/Cargo.toml +++ b/precompiles/utils/Cargo.toml @@ -2,17 +2,22 @@ name = "precompile-utils" authors = ["StakeTechnologies", "PureStake"] description = "Utils to write EVM precompiles." -version = "0.4.3" +version = "0.5.0" edition.workspace = true homepage.workspace = true repository.workspace = true [dependencies] # There's a problem with --all-features when this is moved under dev-deps -evm = { workspace = true, features = ["std"], optional = true } +derive_more = { workspace = true, optional = true } +environmental = { workspace = true } +evm = { workspace = true, features = ["with-codec"] } +hex-literal = { workspace = true, optional = true } impl-trait-for-tuples = { workspace = true } log = { workspace = true } num_enum = { workspace = true } +scale-info = { workspace = true, optional = true, features = ["derive"] } +serde = { workspace = true, optional = true } sha3 = { workspace = true } similar-asserts = { workspace = true, optional = true } @@ -53,5 +58,6 @@ std = [ "sp-std/std", "sp-runtime/std", "xcm/std", + "environmental/std", ] -testing = ["similar-asserts", "std"] +testing = ["similar-asserts", "std", "scale-info", "serde", "derive_more", "hex-literal"] diff --git a/precompiles/utils/src/bytes.rs b/precompiles/utils/src/bytes.rs new file mode 100644 index 0000000000..ba86df9297 --- /dev/null +++ b/precompiles/utils/src/bytes.rs @@ -0,0 +1,218 @@ +// 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(value) + .build(), + ); + } + + fn has_static_size() -> bool { + false + } +} + +// BytesString <=> Vec/&[u8] + +impl From> for Vec { + fn from(value: BoundedBytesString) -> Self { + value.data + } +} + +impl From> for BoundedBytesString { + fn from(value: Vec) -> Self { + Self { + data: value, + _phantom: PhantomData, + } + } +} + +impl From<&[u8]> for BoundedBytesString { + fn from(value: &[u8]) -> Self { + Self { + data: value.to_vec(), + _phantom: PhantomData, + } + } +} + +impl From<[u8; N]> for BoundedBytesString { + fn from(value: [u8; N]) -> Self { + Self { + data: value.to_vec(), + _phantom: PhantomData, + } + } +} + +impl From<&[u8; N]> for BoundedBytesString { + fn from(value: &[u8; N]) -> Self { + Self { + data: value.to_vec(), + _phantom: PhantomData, + } + } +} + +// BytesString <=> String/str + +impl TryFrom> for String { + type Error = alloc::string::FromUtf8Error; + + fn try_from(value: BoundedBytesString) -> Result { + alloc::string::String::from_utf8(value.data) + } +} + +impl From<&str> for BoundedBytesString { + fn from(value: &str) -> Self { + Self { + data: value.as_bytes().into(), + _phantom: PhantomData, + } + } +} + +impl From for BoundedBytesString { + fn from(value: String) -> Self { + Self { + data: value.as_bytes().into(), + _phantom: PhantomData, + } + } +} diff --git a/precompiles/utils/src/data.rs b/precompiles/utils/src/data.rs index 2359bcefd4..2504171b33 100644 --- a/precompiles/utils/src/data.rs +++ b/precompiles/utils/src/data.rs @@ -23,9 +23,9 @@ use crate::{revert, EvmResult}; use alloc::borrow::ToOwned; -use core::{any::type_name, ops::Range}; +use core::{any::type_name, marker::PhantomData, ops::Range}; use impl_trait_for_tuples::impl_for_tuples; -use sp_core::{H160, H256, U256}; +use sp_core::{Get, H160, H256, U256}; use sp_std::{convert::TryInto, vec, vec::Vec}; /// The `address` type of Solidity. @@ -325,8 +325,31 @@ pub trait EvmData: Sized { fn read(reader: &mut EvmDataReader) -> EvmResult; fn write(writer: &mut EvmDataWriter, value: Self); fn has_static_size() -> bool; + fn is_explicit_tuple() -> bool { + false + } +} +/// Encode the value into its Solidity ABI format. +/// If `T` is a tuple it is encoded as a Solidity tuple with dynamic-size offset. +fn encode(value: T) -> Vec { + EvmDataWriter::new().write(value).build() +} + +/// Encode the value into its Solidity ABI format. +/// If `T` is a tuple every element is encoded without a prefixed offset. +/// It matches the encoding of Solidity function arguments and return value, or event data. +pub fn encode_arguments(value: T) -> Vec { + let output = encode(value); + if T::is_explicit_tuple() && !T::has_static_size() { + output[32..].to_vec() + } else { + output + } } +pub use self::encode_arguments as encode_return_value; +pub use self::encode_arguments as encode_event_data; + #[impl_for_tuples(1, 18)] impl EvmData for Tuple { fn has_static_size() -> bool { @@ -604,3 +627,105 @@ impl EvmData for Bytes { false } } + +/// Wrapper around a Vec that provides a max length bound on read. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct BoundedVec { + inner: Vec, + _phantom: PhantomData, +} + +impl> EvmData for BoundedVec { + fn read(reader: &mut EvmDataReader) -> EvmResult { + let mut inner_reader = reader.read_pointer()?; + + let array_size: usize = inner_reader + .read::() + .map_err(|_| revert("out of bounds: length of array"))? + .try_into() + .map_err(|_| revert("value too large : Array has more than max items allowed"))?; + + if array_size > S::get() as usize { + return Err(revert("value too large : Array has more than max items allowed").into()); + } + + let mut array = vec![]; + + let mut item_reader = EvmDataReader { + input: inner_reader + .input + .get(32..) + .ok_or_else(|| revert("read out of bounds: array content"))?, + cursor: 0, + }; + + for _ in 0..array_size { + array.push(item_reader.read()?); + } + + Ok(BoundedVec { + inner: array, + _phantom: PhantomData, + }) + } + + fn write(writer: &mut EvmDataWriter, value: Self) { + let value: Vec<_> = value.into(); + let mut inner_writer = EvmDataWriter::new().write(U256::from(value.len())); + + for inner in value { + // Any offset in items are relative to the start of the item instead of the + // start of the array. However if there is offseted data it must but appended after + // all items (offsets) are written. We thus need to rely on `compute_offsets` to do + // that, and must store a "shift" to correct the offsets. + let shift = inner_writer.data.len(); + let item_writer = EvmDataWriter::new().write(inner); + + inner_writer = inner_writer.write_raw_bytes(&item_writer.data); + for mut offset_datum in item_writer.offset_data { + offset_datum.offset_shift += 32; + offset_datum.offset_position += shift; + inner_writer.offset_data.push(offset_datum); + } + } + + writer.write_pointer(inner_writer.build()); + } + + fn has_static_size() -> bool { + false + } +} + +impl From> for BoundedVec { + fn from(value: Vec) -> Self { + BoundedVec { + inner: value, + _phantom: PhantomData, + } + } +} + +impl From<&[T]> for BoundedVec { + fn from(value: &[T]) -> Self { + BoundedVec { + inner: value.to_vec(), + _phantom: PhantomData, + } + } +} + +impl From<[T; N]> for BoundedVec { + fn from(value: [T; N]) -> Self { + BoundedVec { + inner: value.to_vec(), + _phantom: PhantomData, + } + } +} + +impl From> for Vec { + fn from(value: BoundedVec) -> Self { + value.inner + } +} diff --git a/precompiles/utils/src/lib.rs b/precompiles/utils/src/lib.rs index 1b67c7ea24..ac7cfce9e3 100644 --- a/precompiles/utils/src/lib.rs +++ b/precompiles/utils/src/lib.rs @@ -25,6 +25,7 @@ extern crate alloc; use crate::alloc::borrow::ToOwned; +pub use alloc::string::String; use fp_evm::{ Context, ExitError, ExitRevert, ExitSucceed, PrecompileFailure, PrecompileHandle, PrecompileOutput, @@ -38,7 +39,8 @@ use pallet_evm::{GasWeightMapping, Log}; use sp_core::{H160, H256, U256}; use sp_std::{marker::PhantomData, vec, vec::Vec}; -mod data; +pub mod bytes; +pub mod data; pub use data::{Address, Bytes, EvmData, EvmDataReader, EvmDataWriter}; pub use precompile_utils_macro::{generate_function_selector, keccak256}; @@ -338,6 +340,68 @@ pub fn log_costs(topics: usize, data_len: usize) -> EvmResult { }) } +// Compute the cost of doing a subcall. +// Some parameters cannot be known in advance, so we estimate the worst possible cost. +pub fn call_cost(value: U256, config: &evm::Config) -> u64 { + // Copied from EVM code since not public. + pub const G_CALLVALUE: u64 = 9000; + pub const G_NEWACCOUNT: u64 = 25000; + + fn address_access_cost(is_cold: bool, regular_value: u64, config: &evm::Config) -> u64 { + if config.increase_state_access_gas { + if is_cold { + config.gas_account_access_cold + } else { + config.gas_storage_read_warm + } + } else { + regular_value + } + } + + fn xfer_cost(is_call_or_callcode: bool, transfers_value: bool) -> u64 { + if is_call_or_callcode && transfers_value { + G_CALLVALUE + } else { + 0 + } + } + + fn new_cost( + is_call_or_staticcall: bool, + new_account: bool, + transfers_value: bool, + config: &evm::Config, + ) -> u64 { + let eip161 = !config.empty_considered_exists; + if is_call_or_staticcall { + if eip161 { + if transfers_value && new_account { + G_NEWACCOUNT + } else { + 0 + } + } else if new_account { + G_NEWACCOUNT + } else { + 0 + } + } else { + 0 + } + } + + let transfers_value = value != U256::default(); + let is_cold = true; + let is_call_or_callcode = true; + let is_call_or_staticcall = true; + let new_account = true; + + address_access_cost(is_cold, config.gas_call, config) + + xfer_cost(is_call_or_callcode, transfers_value) + + new_cost(is_call_or_staticcall, new_account, transfers_value, config) +} + impl PrecompileHandleExt for T { #[must_use] /// Record cost of a log manualy. @@ -409,7 +473,7 @@ pub fn succeed(output: impl AsRef<[u8]>) -> PrecompileOutput { #[must_use] /// Check that a function call is compatible with the context it is /// called into. -fn check_function_modifier( +pub fn check_function_modifier( context: &Context, is_static: bool, modifier: FunctionModifier, diff --git a/precompiles/utils/src/testing.rs b/precompiles/utils/src/testing.rs deleted file mode 100644 index 9eb1023a20..0000000000 --- a/precompiles/utils/src/testing.rs +++ /dev/null @@ -1,443 +0,0 @@ -// This file is part of Astar. - -// Copyright 2019-2022 PureStake Inc. -// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later -// -// This file is part of Utils package, originally developed by Purestake Inc. -// Utils package used in Astar Network in terms of GPLv3. -// -// Utils is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Utils is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Utils. If not, see . -use super::*; -use assert_matches::assert_matches; -use fp_evm::{ - ExitReason, ExitSucceed, PrecompileOutput, PrecompileResult, PrecompileSet, Transfer, -}; -use sp_std::boxed::Box; - -pub struct Subcall { - pub address: H160, - pub transfer: Option, - pub input: Vec, - pub target_gas: Option, - pub is_static: bool, - pub context: Context, -} - -pub struct SubcallOutput { - pub reason: ExitReason, - pub output: Vec, - pub cost: u64, - pub logs: Vec, -} - -pub trait SubcallTrait: FnMut(Subcall) -> SubcallOutput + 'static {} - -impl SubcallOutput + 'static> SubcallTrait for T {} - -pub type SubcallHandle = Box; - -/// Mock handle to write tests for precompiles. -pub struct MockHandle { - pub gas_limit: u64, - pub gas_used: u64, - pub logs: Vec, - pub subcall_handle: Option, - pub code_address: H160, - pub input: Vec, - pub context: Context, - pub is_static: bool, -} - -impl MockHandle { - pub fn new(code_address: H160, context: Context) -> Self { - Self { - gas_limit: u64::MAX, - gas_used: 0, - logs: vec![], - subcall_handle: None, - code_address, - input: Vec::new(), - context, - is_static: false, - } - } -} - -// Compute the cost of doing a subcall. -// Some parameters cannot be known in advance, so we estimate the worst possible cost. -pub fn call_cost(value: U256, config: &evm::Config) -> u64 { - // Copied from EVM code since not public. - pub const G_CALLVALUE: u64 = 9000; - pub const G_NEWACCOUNT: u64 = 25000; - - fn address_access_cost(is_cold: bool, regular_value: u64, config: &evm::Config) -> u64 { - if config.increase_state_access_gas { - if is_cold { - config.gas_account_access_cold - } else { - config.gas_storage_read_warm - } - } else { - regular_value - } - } - - fn xfer_cost(is_call_or_callcode: bool, transfers_value: bool) -> u64 { - if is_call_or_callcode && transfers_value { - G_CALLVALUE - } else { - 0 - } - } - - fn new_cost( - is_call_or_staticcall: bool, - new_account: bool, - transfers_value: bool, - config: &evm::Config, - ) -> u64 { - let eip161 = !config.empty_considered_exists; - if is_call_or_staticcall { - if eip161 { - if transfers_value && new_account { - G_NEWACCOUNT - } else { - 0 - } - } else if new_account { - G_NEWACCOUNT - } else { - 0 - } - } else { - 0 - } - } - - let transfers_value = value != U256::default(); - let is_cold = true; - let is_call_or_callcode = true; - let is_call_or_staticcall = true; - let new_account = true; - - address_access_cost(is_cold, config.gas_call, config) - + xfer_cost(is_call_or_callcode, transfers_value) - + new_cost(is_call_or_staticcall, new_account, transfers_value, config) -} - -impl PrecompileHandle for MockHandle { - /// Perform subcall in provided context. - /// Precompile specifies in which context the subcall is executed. - fn call( - &mut self, - address: H160, - transfer: Option, - input: Vec, - target_gas: Option, - is_static: bool, - context: &Context, - ) -> (ExitReason, Vec) { - if self - .record_cost(call_cost(context.apparent_value, &evm::Config::london())) - .is_err() - { - return (ExitReason::Error(ExitError::OutOfGas), vec![]); - } - - match &mut self.subcall_handle { - Some(handle) => { - let SubcallOutput { - reason, - output, - cost, - logs, - } = handle(Subcall { - address, - transfer, - input, - target_gas, - is_static, - context: context.clone(), - }); - - if self.record_cost(cost).is_err() { - return (ExitReason::Error(ExitError::OutOfGas), vec![]); - } - - for log in logs { - self.log(log.address, log.topics, log.data) - .expect("cannot fail"); - } - - (reason, output) - } - None => panic!("no subcall handle registered"), - } - } - - fn record_cost(&mut self, cost: u64) -> Result<(), ExitError> { - self.gas_used += cost; - - if self.gas_used > self.gas_limit { - Err(ExitError::OutOfGas) - } else { - Ok(()) - } - } - - fn remaining_gas(&self) -> u64 { - self.gas_limit - self.gas_used - } - - fn log(&mut self, address: H160, topics: Vec, data: Vec) -> Result<(), ExitError> { - self.logs.push(PrettyLog(Log { - address, - topics, - data, - })); - Ok(()) - } - - /// Retreive the code address (what is the address of the precompile being called). - fn code_address(&self) -> H160 { - self.code_address - } - - /// Retreive the input data the precompile is called with. - fn input(&self) -> &[u8] { - &self.input - } - - /// Retreive the context in which the precompile is executed. - fn context(&self) -> &Context { - &self.context - } - - /// Is the precompile call is done statically. - fn is_static(&self) -> bool { - self.is_static - } - - /// Retreive the gas limit of this call. - fn gas_limit(&self) -> Option { - Some(self.gas_limit) - } - - 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) {} -} - -pub struct PrecompilesTester<'p, P> { - precompiles: &'p P, - handle: MockHandle, - - target_gas: Option, - subcall_handle: Option, - - expected_cost: Option, - expected_logs: Option>, -} - -impl<'p, P: PrecompileSet> PrecompilesTester<'p, P> { - pub fn new( - precompiles: &'p P, - from: impl Into, - to: impl Into, - data: Vec, - ) -> Self { - let to = to.into(); - let mut handle = MockHandle::new( - to, - Context { - address: to, - caller: from.into(), - apparent_value: U256::zero(), - }, - ); - - handle.input = data; - - Self { - precompiles, - handle, - - target_gas: None, - subcall_handle: None, - - expected_cost: None, - expected_logs: None, - } - } - - pub fn with_value(mut self, value: impl Into) -> Self { - self.handle.context.apparent_value = value.into(); - self - } - - pub fn with_subcall_handle(mut self, subcall_handle: impl SubcallTrait) -> Self { - self.subcall_handle = Some(Box::new(subcall_handle)); - self - } - - pub fn with_target_gas(mut self, target_gas: Option) -> Self { - self.target_gas = target_gas; - self - } - - pub fn expect_cost(mut self, cost: u64) -> Self { - self.expected_cost = Some(cost); - self - } - - pub fn expect_no_logs(mut self) -> Self { - self.expected_logs = Some(vec![]); - self - } - - pub fn expect_log(mut self, log: Log) -> Self { - self.expected_logs = Some({ - let mut logs = self.expected_logs.unwrap_or_default(); - logs.push(PrettyLog(log)); - logs - }); - self - } - - fn assert_optionals(&self) { - if let Some(cost) = &self.expected_cost { - assert_eq!(&self.handle.gas_used, cost); - } - - if let Some(logs) = &self.expected_logs { - similar_asserts::assert_eq!(&self.handle.logs, logs); - } - } - - fn execute(&mut self) -> Option { - let handle = &mut self.handle; - handle.subcall_handle = self.subcall_handle.take(); - - if let Some(gas_limit) = self.target_gas { - handle.gas_limit = gas_limit; - } - - let res = self.precompiles.execute(handle); - - self.subcall_handle = handle.subcall_handle.take(); - - res - } - - /// Execute the precompile set and expect some precompile to have been executed, regardless of the - /// result. - pub fn execute_some(mut self) { - let res = self.execute(); - assert!(res.is_some()); - self.assert_optionals(); - } - - /// Execute the precompile set and expect no precompile to have been executed. - pub fn execute_none(mut self) { - let res = self.execute(); - assert!(res.is_none()); - self.assert_optionals(); - } - - /// Execute the precompile set and check it returns provided output. - pub fn execute_returns(mut self, output: Vec) { - let res = self.execute(); - assert_eq!( - res, - Some(Ok(PrecompileOutput { - exit_status: ExitSucceed::Returned, - output - })) - ); - self.assert_optionals(); - } - - /// Execute the precompile set and check if it reverts. - /// Take a closure allowing to perform custom matching on the output. - pub fn execute_reverts(mut self, check: impl Fn(&[u8]) -> bool) { - let res = self.execute(); - assert_matches!( - res, - Some(Err(PrecompileFailure::Revert { output, ..})) - if check(&output) - ); - self.assert_optionals(); - } - - /// Execute the precompile set and check it returns provided output. - pub fn execute_error(mut self, error: ExitError) { - let res = self.execute(); - assert_eq!( - res, - Some(Err(PrecompileFailure::Error { exit_status: error })) - ); - self.assert_optionals(); - } -} - -pub trait PrecompileTesterExt: PrecompileSet + Sized { - fn prepare_test( - &self, - from: impl Into, - to: impl Into, - data: Vec, - ) -> PrecompilesTester; -} - -impl PrecompileTesterExt for T { - fn prepare_test( - &self, - from: impl Into, - to: impl Into, - data: Vec, - ) -> PrecompilesTester { - PrecompilesTester::new(self, from, to, data) - } -} - -#[derive(Clone, PartialEq, Eq)] -pub struct PrettyLog(Log); - -impl core::fmt::Debug for PrettyLog { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { - let bytes = self - .0 - .data - .iter() - .map(|b| format!("{:02X}", b)) - .collect::>() - .join(""); - - let message = String::from_utf8(self.0.data.clone()).ok(); - - f.debug_struct("Log") - .field("address", &self.0.address) - .field("topics", &self.0.topics) - .field("data", &bytes) - .field("data_utf8", &message) - .finish() - } -} diff --git a/precompiles/utils/src/testing/account.rs b/precompiles/utils/src/testing/account.rs new file mode 100644 index 0000000000..66c5a13714 --- /dev/null +++ b/precompiles/utils/src/testing/account.rs @@ -0,0 +1,186 @@ +// 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 { + pallet_evm::AddressMapping, + scale_info::TypeInfo, + serde::{Deserialize, Serialize}, + sp_core::{Decode, Encode, MaxEncodedLen, H160, H256}, +}; + +#[derive( + Eq, + PartialEq, + Ord, + PartialOrd, + Clone, + Encode, + Decode, + Debug, + MaxEncodedLen, + TypeInfo, + Serialize, + Deserialize, + derive_more::Display, +)] +pub struct MockAccount(pub H160); + +impl MockAccount { + pub fn from_u64(v: u64) -> Self { + H160::from_low_u64_be(v).into() + } + + pub fn zero() -> Self { + H160::zero().into() + } + + pub fn has_prefix(&self, prefix: &[u8]) -> bool { + &self.0[0..4] == prefix + } + + pub fn has_prefix_u32(&self, prefix: u32) -> bool { + self.0[0..4] == prefix.to_be_bytes() + } + + pub fn without_prefix(&self) -> u128 { + u128::from_be_bytes(<[u8; 16]>::try_from(&self.0[4..20]).expect("slice have len 16")) + } +} + +impl From for H160 { + fn from(account: MockAccount) -> H160 { + account.0 + } +} + +impl From for [u8; 20] { + fn from(account: MockAccount) -> [u8; 20] { + let x: H160 = account.into(); + x.into() + } +} + +impl From for H256 { + fn from(x: MockAccount) -> H256 { + let x: H160 = x.into(); + x.into() + } +} + +impl From for MockAccount { + fn from(address: H160) -> MockAccount { + MockAccount(address) + } +} + +impl From<[u8; 20]> for MockAccount { + fn from(address: [u8; 20]) -> MockAccount { + let x: H160 = address.into(); + MockAccount(x) + } +} + +impl AddressMapping for MockAccount { + fn into_account_id(address: H160) -> MockAccount { + address.into() + } +} + +#[macro_export] +macro_rules! mock_account { + ($name:ident, $convert:expr) => { + pub struct $name; + mock_account!(# $name, $convert); + }; + ($name:ident ( $($field:ty),* ), $convert:expr) => { + pub struct $name($(pub $field),*); + mock_account!(# $name, $convert); + }; + (# $name:ident, $convert:expr) => { + impl From<$name> for MockAccount { + fn from(value: $name) -> MockAccount { + $convert(value) + } + } + + impl From<$name> for sp_core::H160 { + fn from(value: $name) -> sp_core::H160 { + MockAccount::from(value).into() + } + } + + impl From<$name> for sp_core::H256 { + fn from(value: $name) -> sp_core::H256 { + MockAccount::from(value).into() + } + } + }; +} + +mock_account!(Zero, |_| MockAccount::zero()); +mock_account!(Alice, |_| H160::repeat_byte(0xAA).into()); +mock_account!(Bob, |_| H160::repeat_byte(0xBB).into()); +mock_account!(Charlie, |_| H160::repeat_byte(0xCC).into()); +mock_account!(David, |_| H160::repeat_byte(0xDD).into()); + +mock_account!(Precompile1, |_| MockAccount::from_u64(1)); + +mock_account!(CryptoAlith, |_| H160::from(hex_literal::hex!( + "f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac" +)) +.into()); +mock_account!(CryptoBaltathar, |_| H160::from(hex_literal::hex!( + "3Cd0A705a2DC65e5b1E1205896BaA2be8A07c6e0" +)) +.into()); +mock_account!(CryptoCarleth, |_| H160::from(hex_literal::hex!( + "798d4Ba9baf0064Ec19eB4F0a1a45785ae9D6DFc" +)) +.into()); + +mock_account!( + AddressInPrefixedSet(u32, u128), + |value: AddressInPrefixedSet| { + let prefix: u32 = value.0; + let index: u128 = value.1; + + let mut buffer = Vec::with_capacity(20); // 160 bits + + buffer.extend_from_slice(&prefix.to_be_bytes()); + buffer.extend_from_slice(&index.to_be_bytes()); + + assert_eq!(buffer.len(), 20, "address buffer should have len of 20"); + + H160::from_slice(&buffer).into() + } +); + +pub fn alith_secret_key() -> [u8; 32] { + hex_literal::hex!("5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133") +} + +pub fn baltathar_secret_key() -> [u8; 32] { + hex_literal::hex!("8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b") +} + +pub fn charleth_secret_key() -> [u8; 32] { + hex_literal::hex!("0b6e18cafb6ed99687ec547bd28139cafdd2bffe70e6b688025de6b445aa5c5b") +} diff --git a/precompiles/utils/src/testing/execution.rs b/precompiles/utils/src/testing/execution.rs new file mode 100644 index 0000000000..c7a52a9261 --- /dev/null +++ b/precompiles/utils/src/testing/execution.rs @@ -0,0 +1,262 @@ +// This file is part of Astar. + +// Copyright 2019-2022 PureStake Inc. +// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Utils package, originally developed by Purestake Inc. +// Utils package used in Astar Network in terms of GPLv3. +// +// Utils is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Utils is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Utils. If not, see . +// This file is part of Astar. + +// Copyright 2019-2022 PureStake Inc. +// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Utils package, originally developed by Purestake Inc. +// Utils package used in Astar Network in terms of GPLv3. +// +// Utils is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Utils is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Utils. If not, see . + +use { + crate::testing::{decode_revert_message, MockHandle, PrettyLog, SubcallHandle, SubcallTrait}, + assert_matches::assert_matches, + fp_evm::{ + Context, ExitError, ExitSucceed, Log, PrecompileFailure, PrecompileOutput, + PrecompileResult, PrecompileSet, + }, + sp_core::{H160, U256}, + sp_std::boxed::Box, +}; + +#[must_use] +pub struct PrecompilesTester<'p, P> { + precompiles: &'p P, + handle: MockHandle, + + target_gas: Option, + subcall_handle: Option, + + expected_cost: Option, + expected_logs: Option>, +} + +impl<'p, P: PrecompileSet> PrecompilesTester<'p, P> { + pub fn new( + precompiles: &'p P, + from: impl Into, + to: impl Into, + data: Vec, + ) -> Self { + let to = to.into(); + let mut handle = MockHandle::new( + to.clone(), + Context { + address: to, + caller: from.into(), + apparent_value: U256::zero(), + }, + ); + + handle.input = data; + + Self { + precompiles, + handle, + + target_gas: None, + subcall_handle: None, + + expected_cost: None, + expected_logs: None, + } + } + + 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_gas_limit(mut self, gas_limit: u64) -> Self { + self.handle.gas_limit = gas_limit; + self + } + + pub fn expect_cost(mut self, cost: u64) -> Self { + self.expected_cost = Some(cost); + self + } + + pub fn expect_no_logs(mut self) -> Self { + self.expected_logs = Some(vec![]); + self + } + + pub fn expect_log(mut self, log: Log) -> Self { + self.expected_logs = Some({ + let mut logs = self.expected_logs.unwrap_or_else(Vec::new); + logs.push(PrettyLog(log)); + logs + }); + self + } + + fn assert_optionals(&self) { + if let Some(cost) = &self.expected_cost { + assert_eq!(&self.handle.gas_used, cost); + } + + if let Some(logs) = &self.expected_logs { + similar_asserts::assert_eq!(&self.handle.logs, logs); + } + } + + fn execute(&mut self) -> Option { + let handle = &mut self.handle; + handle.subcall_handle = self.subcall_handle.take(); + + if let Some(gas_limit) = self.target_gas { + handle.gas_limit = gas_limit; + } + + let res = self.precompiles.execute(handle); + + self.subcall_handle = handle.subcall_handle.take(); + + res + } + + /// Execute the precompile set and expect some precompile to have been executed, regardless of the + /// result. + pub fn execute_some(mut self) { + let res = self.execute(); + assert!(res.is_some()); + self.assert_optionals(); + } + + /// Execute the precompile set and expect no precompile to have been executed. + pub fn execute_none(mut self) { + let res = self.execute(); + assert!(res.is_some()); + self.assert_optionals(); + } + + /// Execute the precompile set and check it returns provided output. + pub fn execute_returns_raw(mut self, output: Vec) { + let res = self.execute(); + + match res { + Some(Err(PrecompileFailure::Revert { output, .. })) => { + let decoded = decode_revert_message(&output); + eprintln!( + "Revert message (bytes): {:?}", + sp_core::hexdisplay::HexDisplay::from(&decoded) + ); + eprintln!( + "Revert message (string): {:?}", + core::str::from_utf8(decoded).ok() + ); + panic!("Shouldn't have reverted"); + } + Some(Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output: execution_output, + })) => { + if execution_output != output { + eprintln!( + "Output (bytes): {:?}", + sp_core::hexdisplay::HexDisplay::from(&execution_output) + ); + eprintln!( + "Output (string): {:?}", + core::str::from_utf8(&execution_output).ok() + ); + panic!("Output doesn't match"); + } + } + other => panic!("Unexpected result: {:?}", other), + } + + self.assert_optionals(); + } + + /// Execute the precompile set and check it returns provided Solidity encoded output. + pub fn execute_returns(self, output: Vec) { + self.execute_returns_raw(output) + } + + /// Execute the precompile set and check if it reverts. + /// Take a closure allowing to perform custom matching on the output. + pub fn execute_reverts(mut self, check: impl Fn(&[u8]) -> bool) { + let res = self.execute(); + assert_matches!( + res, + Some(Err(PrecompileFailure::Revert { output, ..})) + if check(&output) + ); + self.assert_optionals(); + } + + /// Execute the precompile set and check it returns provided output. + pub fn execute_error(mut self, error: ExitError) { + let res = self.execute(); + assert_eq!( + res, + Some(Err(PrecompileFailure::Error { exit_status: error })) + ); + self.assert_optionals(); + } +} + +pub trait PrecompileTesterExt: PrecompileSet + Sized { + fn prepare_test( + &self, + from: impl Into, + to: impl Into, + data: impl Into>, + ) -> PrecompilesTester; +} + +impl PrecompileTesterExt for T { + fn prepare_test( + &self, + from: impl Into, + to: impl Into, + data: impl Into>, + ) -> PrecompilesTester { + PrecompilesTester::new(self, from, to, data.into()) + } +} diff --git a/precompiles/utils/src/testing/handle.rs b/precompiles/utils/src/testing/handle.rs new file mode 100644 index 0000000000..b88714e7eb --- /dev/null +++ b/precompiles/utils/src/testing/handle.rs @@ -0,0 +1,220 @@ +// This file is part of Astar. + +// Copyright 2019-2022 PureStake Inc. +// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Utils package, originally developed by Purestake Inc. +// Utils package used in Astar Network in terms of GPLv3. +// +// Utils is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Utils is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Utils. If not, see . + +use { + crate::testing::PrettyLog, + evm::{ExitRevert, ExitSucceed}, + fp_evm::{Context, ExitError, ExitReason, Log, PrecompileHandle, Transfer}, + sp_core::{H160, H256}, + sp_std::boxed::Box, +}; + +#[derive(Debug, Clone)] +pub struct Subcall { + pub address: H160, + pub transfer: Option, + pub input: Vec, + pub target_gas: Option, + pub is_static: bool, + pub context: Context, +} + +#[derive(Debug, Clone)] +pub struct SubcallOutput { + pub reason: ExitReason, + pub output: Vec, + pub cost: u64, + pub logs: Vec, +} + +impl SubcallOutput { + pub fn revert() -> Self { + Self { + reason: ExitReason::Revert(ExitRevert::Reverted), + output: Vec::new(), + cost: 0, + logs: Vec::new(), + } + } + + pub fn succeed() -> Self { + Self { + reason: ExitReason::Succeed(ExitSucceed::Returned), + output: Vec::new(), + cost: 0, + logs: Vec::new(), + } + } + + pub fn out_of_gas() -> Self { + Self { + reason: ExitReason::Error(ExitError::OutOfGas), + output: Vec::new(), + cost: 0, + logs: Vec::new(), + } + } +} + +pub trait SubcallTrait: FnMut(Subcall) -> SubcallOutput + 'static {} + +impl SubcallOutput + 'static> SubcallTrait for T {} + +pub type SubcallHandle = Box; + +/// Mock handle to write tests for precompiles. +pub struct MockHandle { + pub gas_limit: u64, + pub gas_used: u64, + pub logs: Vec, + pub subcall_handle: Option, + pub code_address: H160, + pub input: Vec, + pub context: Context, + pub is_static: bool, +} + +impl MockHandle { + pub fn new(code_address: H160, context: Context) -> Self { + Self { + gas_limit: u64::MAX, + gas_used: 0, + logs: vec![], + subcall_handle: None, + code_address, + input: Vec::new(), + context, + is_static: false, + } + } +} + +impl PrecompileHandle for MockHandle { + /// Perform subcall in provided context. + /// Precompile specifies in which context the subcall is executed. + fn call( + &mut self, + address: H160, + transfer: Option, + input: Vec, + target_gas: Option, + is_static: bool, + context: &Context, + ) -> (ExitReason, Vec) { + if self + .record_cost(crate::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 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 + } + + fn log(&mut self, address: H160, topics: Vec, data: Vec) -> Result<(), ExitError> { + self.logs.push(PrettyLog(Log { + address, + topics, + data, + })); + Ok(()) + } + + /// Retreive the code address (what is the address of the precompile being called). + fn code_address(&self) -> H160 { + self.code_address + } + + /// Retreive the input data the precompile is called with. + fn input(&self) -> &[u8] { + &self.input + } + + /// Retreive the context in which the precompile is executed. + fn context(&self) -> &Context { + &self.context + } + + /// Is the precompile call is done statically. + fn is_static(&self) -> bool { + self.is_static + } + + /// Retreive the gas limit of this call. + fn gas_limit(&self) -> Option { + Some(self.gas_limit) + } +} diff --git a/precompiles/utils/src/testing/mod.rs b/precompiles/utils/src/testing/mod.rs new file mode 100644 index 0000000000..be6c5a881c --- /dev/null +++ b/precompiles/utils/src/testing/mod.rs @@ -0,0 +1,98 @@ +// This file is part of Astar. + +// Copyright 2019-2022 PureStake Inc. +// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Utils package, originally developed by Purestake Inc. +// Utils package used in Astar Network in terms of GPLv3. +// +// Utils is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Utils is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Utils. If not, see . + +pub mod account; +pub mod execution; +pub mod handle; + +pub use {account::*, execution::*, handle::*}; + +use fp_evm::Log; + +pub fn decode_revert_message(encoded: &[u8]) -> &[u8] { + let encoded_len = encoded.len(); + // selector 4 + offset 32 + string length 32 + if encoded_len > 68 { + let message_len = encoded[36..68].iter().sum::(); + if encoded_len >= 68 + message_len as usize { + return &encoded[68..68 + message_len as usize]; + } + } + b"decode_revert_message: error" +} + +#[derive(Clone, PartialEq, Eq)] +pub struct PrettyLog(Log); + +impl core::fmt::Debug for PrettyLog { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { + let bytes = self + .0 + .data + .iter() + .map(|b| format!("{:02X}", b)) + .collect::>() + .join(""); + + let message = String::from_utf8(self.0.data.clone()).ok(); + + f.debug_struct("Log") + .field("address", &self.0.address) + .field("topics", &self.0.topics) + .field("data", &bytes) + .field("data_utf8", &message) + .finish() + } +} +/// Panics if an event is not found in the system log of events +#[macro_export] +macro_rules! assert_event_emitted { + ($event:expr) => { + match &$event { + e => { + assert!( + crate::mock::events().iter().find(|x| *x == e).is_some(), + "Event {:?} was not found in events: \n {:?}", + e, + crate::mock::events() + ); + } + } + }; +} + +// Panics if an event is found in the system log of events +#[macro_export] +macro_rules! assert_event_not_emitted { + ($event:expr) => { + match &$event { + e => { + assert!( + crate::mock::events().iter().find(|x| *x == e).is_none(), + "Event {:?} was found in events: \n {:?}", + e, + crate::mock::events() + ); + } + } + }; +} diff --git a/precompiles/xcm/src/mock.rs b/precompiles/xcm/src/mock.rs index 85f1ddf094..b2d2ace443 100644 --- a/precompiles/xcm/src/mock.rs +++ b/precompiles/xcm/src/mock.rs @@ -270,6 +270,8 @@ impl pallet_assets::Config for Runtime { type RemoveItemsLimit = ConstU32<0>; type AssetIdParameter = AssetId; type CallbackHandle = (); + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); } pub struct AssetIdConverter(PhantomData); diff --git a/precompiles/xvm/Cargo.toml b/precompiles/xvm/Cargo.toml index 397a412373..c64e26b3b2 100644 --- a/precompiles/xvm/Cargo.toml +++ b/precompiles/xvm/Cargo.toml @@ -8,6 +8,7 @@ homepage.workspace = true repository.workspace = true [dependencies] +hex = { workspace = true } log = { workspace = true } num_enum = { workspace = true } precompile-utils = { workspace = true } diff --git a/precompiles/xvm/evm_sdk/XVM.sol b/precompiles/xvm/evm_sdk/XVM.sol index 652582ff08..5008896afe 100644 --- a/precompiles/xvm/evm_sdk/XVM.sol +++ b/precompiles/xvm/evm_sdk/XVM.sol @@ -6,15 +6,17 @@ pragma solidity ^0.8.0; interface XVM { /** * @dev Execute external VM call - * @param context - execution context - * @param to - call recepient + * @param vm_id - target VM id + * @param to - call recipient * @param input - SCALE-encoded call arguments + * @param value - value to transfer * @return success - operation outcome * @return data - output data if successful, error data on error */ function xvm_call( - bytes calldata context, + uint8 vm_id, bytes calldata to, - bytes calldata input - ) external returns (bool success, bytes memory data); + bytes calldata input, + uint256 value + ) external payable returns (bool success, bytes memory data); } diff --git a/precompiles/xvm/evm_sdk/flipper.sol b/precompiles/xvm/evm_sdk/flipper.sol index fcbfdd3d40..058f649baa 100644 --- a/precompiles/xvm/evm_sdk/flipper.sol +++ b/precompiles/xvm/evm_sdk/flipper.sol @@ -2,10 +2,11 @@ pragma solidity ^0.8.0; interface XVM { function xvm_call( - uint8 calldata vm_id, + uint8 vm_id, bytes calldata to, bytes calldata input, - ) external; + uint256 value + ) external payable returns (bool success, bytes memory data); } library Flipper { @@ -13,6 +14,6 @@ library Flipper { function flip(bytes to) { bytes input = "0xcafecafe"; - XVM_PRECOMPILE.xvm_call(0x1F, to, input); + XVM_PRECOMPILE.xvm_call(0x1F, to, input, 1000000); } } diff --git a/precompiles/xvm/src/lib.rs b/precompiles/xvm/src/lib.rs index 78772c5244..6bb033dc1b 100644 --- a/precompiles/xvm/src/lib.rs +++ b/precompiles/xvm/src/lib.rs @@ -18,13 +18,15 @@ #![cfg_attr(not(feature = "std"), no_std)] -use astar_primitives::xvm::{Context, VmId, XvmCall}; +use astar_primitives::{ + xvm::{Context, VmId, XvmCall}, + Balance, +}; use fp_evm::{PrecompileHandle, PrecompileOutput}; use frame_support::dispatch::Dispatchable; use pallet_evm::{AddressMapping, GasWeightMapping, Precompile}; use sp_runtime::codec::Encode; -use sp_std::marker::PhantomData; -use sp_std::prelude::*; +use sp_std::{marker::PhantomData, prelude::*}; use precompile_utils::{ revert, succeed, Bytes, EvmDataWriter, EvmResult, FunctionModifier, PrecompileHandleExt, @@ -38,7 +40,7 @@ mod tests; #[precompile_utils::generate_function_selector] #[derive(Debug, PartialEq)] pub enum Action { - XvmCall = "xvm_call(uint8,bytes,bytes)", + XvmCall = "xvm_call(uint8,bytes,bytes,uint256)", } /// A precompile that expose XVM related functions. @@ -74,15 +76,19 @@ where { fn xvm_call(handle: &mut impl PrecompileHandle) -> EvmResult { let mut input = handle.read_input()?; - input.expect_arguments(3)?; + input.expect_arguments(4)?; let vm_id = { let id = input.read::()?; id.try_into().map_err(|_| revert("invalid vm id")) }?; - let remaining_gas = handle.remaining_gas(); - let weight_limit = R::GasWeightMapping::gas_to_weight(remaining_gas, true); + let mut gas_limit = handle.remaining_gas(); + // If user specified a gas limit, make sure it's not exceeded. + if let Some(user_limit) = handle.gas_limit() { + gas_limit = gas_limit.min(user_limit); + } + let weight_limit = R::GasWeightMapping::gas_to_weight(gas_limit, true); let xvm_context = Context { source_vm_id: VmId::Evm, weight_limit, @@ -90,9 +96,10 @@ where let call_to = input.read::()?.0; let call_input = input.read::()?.0; + let value = input.read::()?; let from = R::AddressMapping::into_account_id(handle.context().caller); - let call_result = XC::call(xvm_context, vm_id, from, call_to, call_input); + let call_result = XC::call(xvm_context, vm_id, from, call_to, call_input, value); let used_weight = match &call_result { Ok(s) => s.used_weight, diff --git a/precompiles/xvm/src/mock.rs b/precompiles/xvm/src/mock.rs index bf209b7480..33fd5d1a78 100644 --- a/precompiles/xvm/src/mock.rs +++ b/precompiles/xvm/src/mock.rs @@ -38,6 +38,7 @@ use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, }; +use sp_std::cell::RefCell; use astar_primitives::xvm::{CallError::*, CallErrorWithWeight, CallInfo, CallResult}; @@ -236,19 +237,39 @@ impl pallet_evm::Config for Runtime { type GasLimitPovSizeRatio = ConstU64<4>; } +thread_local! { + static WEIGHT_LIMIT: RefCell = RefCell::new(Weight::zero()); +} + +pub(crate) struct WeightLimitCalledWith; +impl WeightLimitCalledWith { + pub(crate) fn get() -> Weight { + WEIGHT_LIMIT.with(|gas_limit| *gas_limit.borrow()) + } + + pub(crate) fn set(value: Weight) { + WEIGHT_LIMIT.with(|gas_limit| *gas_limit.borrow_mut() = value) + } + + pub(crate) fn reset() { + Self::set(Weight::zero()); + } +} + struct MockXvmWithArgsCheck; impl XvmCall for MockXvmWithArgsCheck { fn call( - _context: Context, + context: Context, vm_id: VmId, _source: AccountId, target: Vec, input: Vec, + _value: Balance, ) -> CallResult { ensure!( vm_id != VmId::Evm, CallErrorWithWeight { - error: SameVmCallNotAllowed, + error: SameVmCallDenied, used_weight: Weight::zero() } ); @@ -266,6 +287,9 @@ impl XvmCall for MockXvmWithArgsCheck { used_weight: Weight::zero() } ); + + WeightLimitCalledWith::set(context.weight_limit); + Ok(CallInfo { output: vec![], used_weight: Weight::zero(), @@ -296,6 +320,8 @@ impl ExtBuilder { .build_storage::() .expect("Frame system builds valid default genesis config"); + WeightLimitCalledWith::reset(); + let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| System::set_block_number(1)); ext diff --git a/precompiles/xvm/src/tests.rs b/precompiles/xvm/src/tests.rs index 844267fc40..37bbc56cf1 100644 --- a/precompiles/xvm/src/tests.rs +++ b/precompiles/xvm/src/tests.rs @@ -23,6 +23,7 @@ use astar_primitives::xvm::CallError; use parity_scale_codec::Encode; use precompile_utils::testing::*; use precompile_utils::EvmDataWriter; +use sp_core::U256; fn precompiles() -> TestPrecompileSet { PrecompilesValue::get() @@ -50,6 +51,7 @@ fn wrong_argument_reverts() { .write(0u8) .write(Bytes(b"".to_vec())) .write(Bytes(b"".to_vec())) + .write(U256::one()) .build(), ) .expect_no_logs() @@ -68,6 +70,7 @@ fn correct_arguments_works() { .write(0x1Fu8) .write(Bytes(b"".to_vec())) .write(Bytes(b"".to_vec())) + .write(U256::one()) .build(), ) .expect_no_logs() @@ -79,3 +82,54 @@ fn correct_arguments_works() { ); }) } + +#[test] +fn weight_limit_is_min_of_remaining_and_user_limit() { + ExtBuilder::default().build().execute_with(|| { + // The caller didn't set a limit. + precompiles() + .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(), + ) + .expect_no_logs() + .execute_some(); + assert_eq!( + WeightLimitCalledWith::get(), + ::GasWeightMapping::gas_to_weight(u64::MAX, true) + ); + + // The caller set a limit. + let gas_limit = 1_000; + precompiles() + .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(), + ) + .with_gas_limit(gas_limit) + .expect_no_logs() + .execute_some(); + assert_eq!( + WeightLimitCalledWith::get(), + ::GasWeightMapping::gas_to_weight(gas_limit, true) + ); + }); +} diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 4c8eef897c..6a4306fe00 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -20,6 +20,7 @@ fp-evm = { workspace = true } # Substrate dependencies frame-support = { workspace = true } +pallet-assets = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } @@ -30,7 +31,11 @@ xcm = { workspace = true } xcm-builder = { workspace = true } xcm-executor = { workspace = true } -# Astar pallets +# Frontier dependencies +pallet-evm = { workspace = true } + +# Astar pallets & dependencies +pallet-evm-precompile-assets-erc20 = { workspace = true } pallet-xc-asset-config = { workspace = true } [features] @@ -52,5 +57,8 @@ std = [ "xcm-executor/std", "pallet-xc-asset-config/std", "fp-evm/std", + "pallet-assets/std", + "pallet-evm/std", + "pallet-evm-precompile-assets-erc20/std", ] -runtime-benchmarks = ["xcm-builder/runtime-benchmarks"] +runtime-benchmarks = ["xcm-builder/runtime-benchmarks", "pallet-assets/runtime-benchmarks"] diff --git a/primitives/src/benchmarks.rs b/primitives/src/benchmarks.rs new file mode 100644 index 0000000000..f0cfa392df --- /dev/null +++ b/primitives/src/benchmarks.rs @@ -0,0 +1,30 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +use crate::AssetId; + +#[cfg(feature = "runtime-benchmarks")] +/// Benchmark helper for `pallet-assets`. +pub struct AssetsBenchmarkHelper; +impl> pallet_assets::BenchmarkHelper + for AssetsBenchmarkHelper +{ + fn create_asset_id_parameter(id: u32) -> AssetIdParameter { + AssetId::from(id).into() + } +} diff --git a/primitives/src/ethereum_checked.rs b/primitives/src/ethereum_checked.rs index e50edc318c..29af13c229 100644 --- a/primitives/src/ethereum_checked.rs +++ b/primitives/src/ethereum_checked.rs @@ -35,6 +35,8 @@ use sp_std::{prelude::*, result::Result}; /// Max Ethereum tx input size: 65_536 bytes pub const MAX_ETHEREUM_TX_INPUT_SIZE: u32 = 2u32.pow(16); +pub type EthereumTxInput = BoundedVec>; + /// The checked Ethereum transaction. Only contracts `call` is support(no `create`). #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub struct CheckedEthereumTx { @@ -45,7 +47,7 @@ pub struct CheckedEthereumTx { /// Amount to transfer. pub value: U256, /// Input of a contract call. - pub input: BoundedVec>, + pub input: EthereumTxInput, /// Optional access list, specified in EIP-2930. pub maybe_access_list: Option)>>, } diff --git a/primitives/src/evm.rs b/primitives/src/evm.rs new file mode 100644 index 0000000000..25d1599219 --- /dev/null +++ b/primitives/src/evm.rs @@ -0,0 +1,56 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +use crate::{AccountId, AssetId}; + +use frame_support::ensure; +use sp_std::marker::PhantomData; + +use pallet_assets::AssetsCallback; +use pallet_evm_precompile_assets_erc20::AddressToAssetId; + +/// Revert opt code. It's inserted at the precompile addresses, to make them functional in EVM. +pub const EVM_REVERT_CODE: &[u8] = &[0x60, 0x00, 0x60, 0x00, 0xfd]; + +/// Handler for automatic revert code registration. +/// +/// When an asset is created, it automatically becomes available to the EVM via an `ERC20-like` interface. +/// In order for the precompile to work, dedicated asset address needs to have the revert code registered, otherwise the call will fail. +/// +/// It is important to note that if the dedicated asset EVM address is already taken, asset creation should fail. +/// After asset has been destroyed, it is also safe to remove the revert code and free the address for future usage. +pub struct EvmRevertCodeHandler(PhantomData<(A, R)>); +impl AssetsCallback for EvmRevertCodeHandler +where + A: AddressToAssetId, + R: pallet_evm::Config, +{ + fn created(id: &AssetId, _: &AccountId) -> Result<(), ()> { + let address = A::asset_id_to_address(*id); + // In case of collision, we need to cancel the asset creation. + ensure!(!pallet_evm::AccountCodes::::contains_key(&address), ()); + pallet_evm::AccountCodes::::insert(address, EVM_REVERT_CODE.to_vec()); + Ok(()) + } + + fn destroyed(id: &AssetId) -> Result<(), ()> { + let address = A::asset_id_to_address(*id); + pallet_evm::AccountCodes::::remove(address); + Ok(()) + } +} diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 777ba49fd9..9cf71cb373 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -29,10 +29,16 @@ pub mod ethereum_checked; /// XVM primitives. pub mod xvm; -use sp_runtime::traits::BlakeTwo256; +/// EVM primitives. +pub mod evm; + +/// Benchmark primitives +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmarks; + use sp_runtime::{ generic, - traits::{IdentifyAccount, Verify}, + traits::{BlakeTwo256, IdentifyAccount, Verify}, }; /// Alias to 512-bit hash when used in the context of a transaction signature on the chain. diff --git a/primitives/src/xcm/mod.rs b/primitives/src/xcm/mod.rs index f8ce3e4eb0..66210bf95f 100644 --- a/primitives/src/xcm/mod.rs +++ b/primitives/src/xcm/mod.rs @@ -33,8 +33,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use frame_support::{ - ensure, - traits::{tokens::fungibles, Contains, ContainsPair, Get, ProcessMessageError}, + traits::{tokens::fungibles, ContainsPair, Get}, weights::constants::WEIGHT_REF_TIME_PER_SECOND, }; use sp_runtime::traits::{Bounded, Zero}; @@ -43,7 +42,7 @@ use sp_std::{borrow::Borrow, marker::PhantomData, vec::Vec}; // Polkadot imports use xcm::latest::{prelude::*, Weight}; use xcm_builder::TakeRevenue; -use xcm_executor::traits::{MatchesFungibles, ShouldExecute, WeightTrader}; +use xcm_executor::traits::{MatchesFungibles, WeightTrader}; use pallet_xc_asset_config::{ExecutionPaymentRate, XcAssetLocation}; @@ -271,52 +270,6 @@ impl< } } -/// Allows execution from `origin` if it is contained in `T` (i.e. `T::Contains(origin)`) taking -/// payments into account. -/// -/// Only allows for sequence `DescendOrigin` -> `WithdrawAsset` -> `BuyExecution` -pub struct AllowPaidExecWithDescendOriginFrom(PhantomData); -impl> ShouldExecute for AllowPaidExecWithDescendOriginFrom { - fn should_execute( - origin: &MultiLocation, - message: &mut [Instruction], - max_weight: Weight, - _weight_credit: &mut Weight, - ) -> Result<(), ProcessMessageError> { - log::trace!( - target: "xcm::barriers", - "AllowPaidExecWithDescendOriginFrom origin: {:?}, message: {:?}, max_weight: {:?}, weight_credit: {:?}", - origin, message, max_weight, _weight_credit, - ); - ensure!(T::contains(origin), ProcessMessageError::Unsupported); - - match message - .iter_mut() - .take(3) - .collect::>() - .as_mut_slice() - { - [DescendOrigin(..), WithdrawAsset(..), BuyExecution { - weight_limit: Limited(ref mut limit), - .. - }] if limit.all_gte(max_weight) => { - *limit = max_weight; - Ok(()) - } - - [DescendOrigin(..), WithdrawAsset(..), BuyExecution { - weight_limit: ref mut limit @ Unlimited, - .. - }] => { - *limit = Limited(max_weight); - Ok(()) - } - - _ => return Err(ProcessMessageError::Unsupported), - } - } -} - // TODO: remove this after uplift to `polkadot-v0.9.44` or beyond, and replace it with code in XCM builder. use parity_scale_codec::{Compact, Encode}; diff --git a/primitives/src/xcm/tests.rs b/primitives/src/xcm/tests.rs index faef681aaf..21dcfbeaef 100644 --- a/primitives/src/xcm/tests.rs +++ b/primitives/src/xcm/tests.rs @@ -17,10 +17,7 @@ // along with Astar. If not, see . use super::*; -use frame_support::{ - assert_ok, - traits::{Everything, Nothing}, -}; +use frame_support::assert_ok; use sp_runtime::traits::Zero; use xcm_executor::traits::Convert; @@ -411,156 +408,6 @@ fn reserve_asset_filter_for_unsupported_asset_multi_location() { assert!(!ReserveAssetFilter::contains(&multi_asset, &origin)); } -/// Returns valid XCM sequence for bypassing `AllowPaidExecWithDescendOriginFrom` -fn desc_origin_barrier_valid_sequence() -> Vec> { - vec![ - DescendOrigin(X1(Junction::Parachain(1234))), - WithdrawAsset((Here, 100).into()), - BuyExecution { - fees: (Here, 100).into(), - weight_limit: WeightLimit::Unlimited, - }, - ] -} - -#[test] -fn allow_paid_exec_with_descend_origin_works() { - let mut valid_message = desc_origin_barrier_valid_sequence(); - - let res = AllowPaidExecWithDescendOriginFrom::::should_execute( - &Here.into(), - &mut valid_message, - Weight::from_parts(150, 0), - &mut Weight::zero(), - ); - assert_eq!(res, Ok(())); - - // Still works even if there are follow-up instructions - valid_message = desc_origin_barrier_valid_sequence(); - valid_message.push(SetErrorHandler(Default::default())); - let res = AllowPaidExecWithDescendOriginFrom::::should_execute( - &Here.into(), - &mut valid_message, - Weight::from_parts(100, 0), - &mut Weight::zero(), - ); - assert_eq!(res, Ok(())); -} - -#[test] -fn allow_paid_exec_with_descend_origin_with_weight_correction_works() { - let mut valid_message = desc_origin_barrier_valid_sequence(); - - // Ensure that `Limited` gets adjusted to the provided enforced_weight_limit - let enforced_weight_limit = Weight::from_parts(3, 0); - let res = AllowPaidExecWithDescendOriginFrom::::should_execute( - &Here.into(), - &mut valid_message, - enforced_weight_limit, - &mut Weight::zero(), - ); - assert_eq!(res, Ok(())); - - if let BuyExecution { - weight_limit, - fees: _, - } = valid_message[2].clone() - { - assert_eq!(weight_limit, WeightLimit::Limited(enforced_weight_limit)) - } else { - panic!("3rd instruction should be BuyExecution!"); - } - - // Ensure that we use `BuyExecution` with `Unlimited` weight limit - let _ = std::mem::replace( - &mut valid_message[2], - BuyExecution { - fees: (Here, 100).into(), - weight_limit: WeightLimit::Limited(enforced_weight_limit.add_ref_time(7)), - }, - ); - - // Ensure that `Unlimited` gets adjusted to the provided max weight limit - let res = AllowPaidExecWithDescendOriginFrom::::should_execute( - &Here.into(), - &mut valid_message, - enforced_weight_limit, - &mut Weight::zero(), - ); - assert_eq!(res, Ok(())); - - if let BuyExecution { - weight_limit, - fees: _, - } = valid_message[2].clone() - { - assert_eq!(weight_limit, WeightLimit::Limited(enforced_weight_limit)) - } else { - panic!("3rd instruction should be BuyExecution!"); - } -} - -#[test] -fn allow_paid_exec_with_descend_origin_with_unsupported_origin_fails() { - let mut valid_message = desc_origin_barrier_valid_sequence(); - - let res = AllowPaidExecWithDescendOriginFrom::::should_execute( - &Here.into(), - &mut valid_message, - Weight::from_parts(100, 0), - &mut Weight::zero(), - ); - assert_eq!(res, Err(ProcessMessageError::Unsupported)); -} - -#[test] -fn allow_paid_exec_with_descend_origin_with_invalid_message_fails() { - let mut invalid_message = vec![WithdrawAsset((Here, 100).into())]; - - let res = AllowPaidExecWithDescendOriginFrom::::should_execute( - &Here.into(), - &mut invalid_message, - Weight::from_parts(100, 0), - &mut Weight::zero(), - ); - assert_eq!(res, Err(ProcessMessageError::Unsupported)); - - // Should still fail, even if correct sequence follows next - invalid_message.append(&mut desc_origin_barrier_valid_sequence()); - let res = AllowPaidExecWithDescendOriginFrom::::should_execute( - &Here.into(), - &mut invalid_message, - Weight::from_parts(100, 0), - &mut Weight::zero(), - ); - assert_eq!(res, Err(ProcessMessageError::Unsupported)); -} - -#[test] -fn allow_paid_exec_with_descend_origin_too_small_weight_fails() { - let mut valid_message = desc_origin_barrier_valid_sequence(); - let enforced_weight_limit = Weight::from_parts(29, 0); - - // Ensure that we use `BuyExecution` with `Limited` weight but with insufficient weight. - // This means that not enough execution time (weight) is being bought compared to the - // weight of whole sequence. - let _ = std::mem::replace( - &mut valid_message[2], - BuyExecution { - fees: (Here, 100).into(), - weight_limit: WeightLimit::Limited(enforced_weight_limit.sub_ref_time(7)), - }, - ); - - let res = AllowPaidExecWithDescendOriginFrom::::should_execute( - &Here.into(), - &mut valid_message, - enforced_weight_limit, - &mut Weight::zero(), - ); - assert_eq!(res, Err(ProcessMessageError::Unsupported)); -} - // TODO: can be deleted after uplift to `polkadot-v0.9.44` or beyond. #[test] fn hashed_description_sanity_check() { diff --git a/primitives/src/xvm.rs b/primitives/src/xvm.rs index e3ae06c526..4b08aa6967 100644 --- a/primitives/src/xvm.rs +++ b/primitives/src/xvm.rs @@ -16,6 +16,8 @@ // You should have received a copy of the GNU General Public License // along with Astar. If not, see . +use crate::Balance; + use frame_support::weights::Weight; use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; @@ -59,13 +61,13 @@ pub enum CallError { /// Invalid VM id. InvalidVmId, /// Calling the contracts in the same VM is not allowed. - SameVmCallNotAllowed, + SameVmCallDenied, /// Target contract address is invalid. InvalidTarget, /// Input is too large. InputTooLarge, - /// Bad origin. - BadOrigin, + /// Reentrance is not allowed. + ReentranceDenied, /// The call failed on EVM or WASM execution. ExecutionFailed(Vec), } @@ -102,11 +104,13 @@ pub trait XvmCall { /// - `source`: Caller Id. /// - `target`: Target contract address. /// - `input`: call input data. + /// - `value`: value to transfer. fn call( context: Context, vm_id: VmId, source: AccountId, target: Vec, input: Vec, + value: Balance, ) -> CallResult; } diff --git a/rpc-tests/rpc-tests.toml b/rpc-tests/rpc-tests.toml index c9e55c1d58..51c3784a34 100644 --- a/rpc-tests/rpc-tests.toml +++ b/rpc-tests/rpc-tests.toml @@ -25,7 +25,7 @@ cumulus_based = true [parachains.collator] name = "astar" command = "./astar-collator" - rpc_port = 9944 + ws_port = 9944 args = [ "-l=xcm=trace", "--enable-evm-rpc" ] [[parachains]] @@ -36,7 +36,7 @@ cumulus_based = true [parachains.collator] name = "shiden" command = "./astar-collator" - rpc_port = 9945 + ws_port = 9945 args = [ "-l=xcm=trace", "--enable-evm-rpc" ] [[hrmp_channels]] diff --git a/runtime/astar/Cargo.toml b/runtime/astar/Cargo.toml index 07f8fec6f7..beff8e5c6c 100644 --- a/runtime/astar/Cargo.toml +++ b/runtime/astar/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astar-runtime" -version = "5.16.1" +version = "5.17.0" build = "build.rs" authors.workspace = true edition.workspace = true @@ -219,6 +219,7 @@ runtime-benchmarks = [ "pallet-collator-selection/runtime-benchmarks", "polkadot-runtime/runtime-benchmarks", "astar-primitives/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", ] try-runtime = [ "fp-self-contained/try-runtime", diff --git a/runtime/astar/src/lib.rs b/runtime/astar/src/lib.rs index 242a4dfd9a..d832a98a2a 100644 --- a/runtime/astar/src/lib.rs +++ b/runtime/astar/src/lib.rs @@ -23,7 +23,8 @@ #![recursion_limit = "256"] pub use astar_primitives::{ - AccountId, Address, AssetId, Balance, BlockNumber, Hash, Header, Index, Signature, + evm::EvmRevertCodeHandler, xcm::AssetLocationIdConverter, AccountId, Address, AssetId, Balance, + BlockNumber, Hash, Header, Index, Signature, }; use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; use frame_support::{ @@ -71,7 +72,6 @@ use sp_runtime::{ }; use sp_std::prelude::*; -use astar_primitives::xcm::AssetLocationIdConverter; use pallet_evm_precompile_assets_erc20::AddressToAssetId; #[cfg(any(feature = "std", test))] @@ -85,6 +85,7 @@ pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; pub use sp_runtime::BuildStorage; mod precompiles; +mod weights; mod xcm_config; pub type AstarAssetLocationIdConverter = AssetLocationIdConverter; @@ -141,7 +142,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("astar"), impl_name: create_runtime_str!("astar"), authoring_version: 1, - spec_version: 64, + spec_version: 65, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 2, @@ -580,10 +581,12 @@ impl pallet_assets::Config for Runtime { type StringLimit = AssetsStringLimit; type Freezer = (); type Extra = (); - type WeightInfo = pallet_assets::weights::SubstrateWeight; + type WeightInfo = weights::pallet_assets::SubstrateWeight; type RemoveItemsLimit = ConstU32<1000>; type AssetIdParameter = Compact; - type CallbackHandle = (); + type CallbackHandle = EvmRevertCodeHandler; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = astar_primitives::benchmarks::AssetsBenchmarkHelper; } parameter_types! { @@ -834,23 +837,9 @@ impl pallet_sudo::Config for Runtime { type WeightInfo = pallet_sudo::weights::SubstrateWeight; } -pub struct EvmRevertCodeHandler; -impl pallet_xc_asset_config::XcAssetChanged for EvmRevertCodeHandler { - fn xc_asset_registered(asset_id: AssetId) { - let address = Runtime::asset_id_to_address(asset_id); - pallet_evm::AccountCodes::::insert(address, vec![0x60, 0x00, 0x60, 0x00, 0xfd]); - } - - fn xc_asset_unregistered(asset_id: AssetId) { - let address = Runtime::asset_id_to_address(asset_id); - pallet_evm::AccountCodes::::remove(address); - } -} - impl pallet_xc_asset_config::Config for Runtime { type RuntimeEvent = RuntimeEvent; type AssetId = AssetId; - type XcAssetChanged = EvmRevertCodeHandler; type ManagerOrigin = EnsureRoot; type WeightInfo = pallet_xc_asset_config::weights::SubstrateWeight; } @@ -1163,6 +1152,7 @@ mod benches { define_benchmarks!( [frame_benchmarking, BaselineBench::] [frame_system, SystemBench::] + [pallet_assets, Assets] [pallet_balances, Balances] [pallet_timestamp, Timestamp] [pallet_dapps_staking, DappsStaking] diff --git a/runtime/astar/src/weights/mod.rs b/runtime/astar/src/weights/mod.rs new file mode 100644 index 0000000000..2db3ac12ab --- /dev/null +++ b/runtime/astar/src/weights/mod.rs @@ -0,0 +1,19 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +pub mod pallet_assets; diff --git a/runtime/astar/src/weights/pallet_assets.rs b/runtime/astar/src/weights/pallet_assets.rs new file mode 100644 index 0000000000..676729637e --- /dev/null +++ b/runtime/astar/src/weights/pallet_assets.rs @@ -0,0 +1,496 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +//! Autogenerated weights for pallet_assets +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-08-11, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `devserver-01`, CPU: `Intel(R) Xeon(R) E-2236 CPU @ 3.40GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("astar-dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/release/astar-collator +// benchmark +// pallet +// --chain=astar-dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_assets +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./benchmark-results/assets_weights.rs +// --template=./scripts/templates/weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weights for pallet_assets using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl pallet_assets::WeightInfo for SubstrateWeight { + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: EVM AccountCodes (r:1 w:1) + /// Proof Skipped: EVM AccountCodes (max_values: None, max_size: None, mode: Measured) + fn create() -> Weight { + // Proof Size summary in bytes: + // Measured: `659` + // Estimated: `4124` + // Minimum execution time: 35_155_000 picoseconds. + Weight::from_parts(35_760_000, 4124) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: EVM AccountCodes (r:1 w:1) + /// Proof Skipped: EVM AccountCodes (max_values: None, max_size: None, mode: Measured) + fn force_create() -> Weight { + // Proof Size summary in bytes: + // Measured: `519` + // Estimated: `3984` + // Minimum execution time: 20_004_000 picoseconds. + Weight::from_parts(20_348_000, 3984) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn start_destroy() -> Weight { + // Proof Size summary in bytes: + // Measured: `289` + // Estimated: `3687` + // Minimum execution time: 13_682_000 picoseconds. + Weight::from_parts(14_059_000, 3687) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1001 w:1000) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: System Account (r:1000 w:1000) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// The range of component `c` is `[0, 1000]`. + fn destroy_accounts(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `255 + c * (208 ±0)` + // Estimated: `3687 + c * (2621 ±0)` + // Minimum execution time: 17_910_000 picoseconds. + Weight::from_parts(18_110_000, 3687) + // Standard Error: 6_525 + .saturating_add(Weight::from_parts(11_623_553, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(c.into()))) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(c.into()))) + .saturating_add(Weight::from_parts(0, 2621).saturating_mul(c.into())) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1001 w:1000) + /// Proof: Assets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) + /// The range of component `a` is `[0, 1000]`. + fn destroy_approvals(a: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `438 + a * (86 ±0)` + // Estimated: `3687 + a * (2635 ±0)` + // Minimum execution time: 18_065_000 picoseconds. + Weight::from_parts(18_372_000, 3687) + // Standard Error: 4_985 + .saturating_add(Weight::from_parts(13_662_720, 0).saturating_mul(a.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(a.into()))) + .saturating_add(Weight::from_parts(0, 2635).saturating_mul(a.into())) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:0) + /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + /// Storage: EVM AccountCodes (r:0 w:1) + /// Proof Skipped: EVM AccountCodes (max_values: None, max_size: None, mode: Measured) + fn finish_destroy() -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 16_700_000 picoseconds. + Weight::from_parts(17_418_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + fn mint() -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 26_305_000 picoseconds. + Weight::from_parts(27_014_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + fn burn() -> Weight { + // Proof Size summary in bytes: + // Measured: `375` + // Estimated: `3687` + // Minimum execution time: 30_525_000 picoseconds. + Weight::from_parts(31_113_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:2 w:2) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `447` + // Estimated: `6232` + // Minimum execution time: 42_955_000 picoseconds. + Weight::from_parts(43_309_000, 6232) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:2 w:2) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn transfer_keep_alive() -> Weight { + // Proof Size summary in bytes: + // Measured: `447` + // Estimated: `6232` + // Minimum execution time: 37_951_000 picoseconds. + Weight::from_parts(38_330_000, 6232) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:2 w:2) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn force_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `447` + // Estimated: `6232` + // Minimum execution time: 43_061_000 picoseconds. + Weight::from_parts(43_561_000, 6232) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + fn freeze() -> Weight { + // Proof Size summary in bytes: + // Measured: `375` + // Estimated: `3687` + // Minimum execution time: 17_259_000 picoseconds. + Weight::from_parts(17_516_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + fn thaw() -> Weight { + // Proof Size summary in bytes: + // Measured: `375` + // Estimated: `3687` + // Minimum execution time: 17_165_000 picoseconds. + Weight::from_parts(17_547_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn freeze_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `289` + // Estimated: `3687` + // Minimum execution time: 13_634_000 picoseconds. + Weight::from_parts(14_069_000, 3687) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn thaw_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `289` + // Estimated: `3687` + // Minimum execution time: 13_456_000 picoseconds. + Weight::from_parts(13_770_000, 3687) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:0) + /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + fn transfer_ownership() -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 16_083_000 picoseconds. + Weight::from_parts(16_343_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn set_team() -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 14_443_000 picoseconds. + Weight::from_parts(14_818_000, 3687) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:1) + /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + /// The range of component `n` is `[0, 50]`. + /// The range of component `s` is `[0, 50]`. + fn set_metadata(_n: u32, _s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 28_260_000 picoseconds. + Weight::from_parts(29_181_263, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:1) + /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + fn clear_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `431` + // Estimated: `3687` + // Minimum execution time: 28_580_000 picoseconds. + Weight::from_parts(28_826_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:1) + /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + /// The range of component `n` is `[0, 50]`. + /// The range of component `s` is `[0, 50]`. + fn force_set_metadata(n: u32, s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `94` + // Estimated: `3687` + // Minimum execution time: 14_605_000 picoseconds. + Weight::from_parts(15_107_642, 3687) + // Standard Error: 450 + .saturating_add(Weight::from_parts(6_608, 0).saturating_mul(n.into())) + // Standard Error: 450 + .saturating_add(Weight::from_parts(2_783, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:1) + /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + fn force_clear_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `431` + // Estimated: `3687` + // Minimum execution time: 27_925_000 picoseconds. + Weight::from_parts(28_323_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn force_asset_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 13_904_000 picoseconds. + Weight::from_parts(14_431_000, 3687) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1 w:1) + /// Proof: Assets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) + fn approve_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `289` + // Estimated: `3687` + // Minimum execution time: 31_152_000 picoseconds. + Weight::from_parts(31_715_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1 w:1) + /// Proof: Assets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) + /// Storage: Assets Account (r:2 w:2) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn transfer_approved() -> Weight { + // Proof Size summary in bytes: + // Measured: `680` + // Estimated: `6232` + // Minimum execution time: 63_252_000 picoseconds. + Weight::from_parts(63_718_000, 6232) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1 w:1) + /// Proof: Assets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) + fn cancel_approval() -> Weight { + // Proof Size summary in bytes: + // Measured: `471` + // Estimated: `3687` + // Minimum execution time: 34_375_000 picoseconds. + Weight::from_parts(34_640_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1 w:1) + /// Proof: Assets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) + fn force_cancel_approval() -> Weight { + // Proof Size summary in bytes: + // Measured: `471` + // Estimated: `3687` + // Minimum execution time: 34_159_000 picoseconds. + Weight::from_parts(34_693_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn set_min_balance() -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 15_114_000 picoseconds. + Weight::from_parts(15_271_000, 3687) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn touch() -> Weight { + // Proof Size summary in bytes: + // Measured: `429` + // Estimated: `3687` + // Minimum execution time: 34_356_000 picoseconds. + Weight::from_parts(34_856_000, 3687) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn touch_other() -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 31_063_000 picoseconds. + Weight::from_parts(31_331_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn refund() -> Weight { + // Proof Size summary in bytes: + // Measured: `567` + // Estimated: `3687` + // Minimum execution time: 31_678_000 picoseconds. + Weight::from_parts(31_941_000, 3687) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn refund_other() -> Weight { + // Proof Size summary in bytes: + // Measured: `426` + // Estimated: `3687` + // Minimum execution time: 28_601_000 picoseconds. + Weight::from_parts(28_966_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + fn block() -> Weight { + // Proof Size summary in bytes: + // Measured: `375` + // Estimated: `3687` + // Minimum execution time: 17_545_000 picoseconds. + Weight::from_parts(17_959_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } +} diff --git a/runtime/local/Cargo.toml b/runtime/local/Cargo.toml index b49f360404..e7733bebd4 100644 --- a/runtime/local/Cargo.toml +++ b/runtime/local/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "local-runtime" -version = "5.16.1" +version = "5.17.0" build = "build.rs" authors.workspace = true edition.workspace = true @@ -28,6 +28,7 @@ pallet-democracy = { workspace = true } pallet-ethereum = { workspace = true } pallet-ethereum-checked = { workspace = true } pallet-evm = { workspace = true } +pallet-evm-precompile-batch = { workspace = true } pallet-evm-precompile-blake2 = { workspace = true } pallet-evm-precompile-bn128 = { workspace = true } pallet-evm-precompile-dispatch = { workspace = true } @@ -127,6 +128,7 @@ std = [ "pallet-evm-precompile-modexp/std", "pallet-evm-precompile-sha3fips/std", "pallet-evm-precompile-dapps-staking/std", + "pallet-evm-precompile-batch/std", "pallet-evm-precompile-sr25519/std", "pallet-evm-precompile-substrate-ecdsa/std", "pallet-evm-precompile-xvm/std", @@ -182,6 +184,7 @@ runtime-benchmarks = [ "pallet-preimage/runtime-benchmarks", "pallet-ethereum-checked/runtime-benchmarks", "astar-primitives/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", ] try-runtime = [ "fp-self-contained/try-runtime", diff --git a/runtime/local/src/lib.rs b/runtime/local/src/lib.rs index 765dc47b84..49815eddfe 100644 --- a/runtime/local/src/lib.rs +++ b/runtime/local/src/lib.rs @@ -27,8 +27,8 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); use frame_support::{ construct_runtime, parameter_types, traits::{ - AsEnsureOriginWithArg, ConstU128, ConstU32, ConstU64, Currency, EitherOfDiverse, - EqualPrivilegeOnly, FindAuthor, Get, InstanceFilter, Nothing, OnFinalize, WithdrawReasons, + AsEnsureOriginWithArg, ConstU128, ConstU32, ConstU64, Contains, Currency, EitherOfDiverse, + EqualPrivilegeOnly, FindAuthor, Get, InstanceFilter, OnFinalize, WithdrawReasons, }, weights::{ constants::{RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND}, @@ -36,6 +36,7 @@ use frame_support::{ }, ConsensusEngineId, PalletId, }; +use frame_support::traits::Nothing; use frame_system::{ limits::{BlockLength, BlockWeights}, EnsureRoot, EnsureSigned, @@ -61,7 +62,8 @@ use sp_runtime::{ use sp_std::prelude::*; pub use astar_primitives::{ - AccountId, Address, AssetId, Balance, BlockNumber, Hash, Header, Index, Signature, + evm::EvmRevertCodeHandler, AccountId, Address, AssetId, Balance, BlockNumber, Hash, Header, + Index, Signature, }; #[cfg(feature = "std")] @@ -114,6 +116,8 @@ pub type Precompiles = LocalNetworkPrecompiles; mod chain_extensions; pub use chain_extensions::*; +mod weights; + /// Constant values used within the runtime. pub const MICROAST: Balance = 1_000_000_000_000; pub const MILLIAST: Balance = 1_000 * MICROAST; @@ -321,10 +325,12 @@ impl pallet_assets::Config for Runtime { type StringLimit = AssetsStringLimit; type Freezer = (); type Extra = (); - type WeightInfo = pallet_assets::weights::SubstrateWeight; + type WeightInfo = weights::pallet_assets::SubstrateWeight; type RemoveItemsLimit = ConstU32<1000>; type AssetIdParameter = Compact; - type CallbackHandle = (); + type CallbackHandle = EvmRevertCodeHandler; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = astar_primitives::benchmarks::AssetsBenchmarkHelper; } parameter_types! { @@ -466,11 +472,6 @@ impl pallet_ethereum_checked::Config for Runtime { type WeightInfo = pallet_ethereum_checked::weights::SubstrateWeight; } -parameter_types! { - pub EvmId: u8 = 0x0F; - pub WasmId: u8 = 0x1F; -} - impl pallet_xvm::Config for Runtime { type GasWeightMapping = pallet_evm::FixedGasWeightMapping; type AccountMapping = HashedAccountMapping; @@ -807,6 +808,17 @@ parameter_types! { pub Schedule: pallet_contracts::Schedule = Default::default(); } +pub struct CallRuntimeFilter; +impl Contains for CallRuntimeFilter { + fn contains(c: &RuntimeCall) -> bool { + match *c { + RuntimeCall::Assets(pallet_assets::Call::transfer { .. }) => true, + RuntimeCall::Assets(pallet_assets::Call::transfer_approved { .. }) => true, + _ => false, + } + } +} + impl pallet_contracts::Config for Runtime { type Time = Timestamp; type Randomness = RandomnessCollectiveFlip; @@ -1130,6 +1142,7 @@ extern crate frame_benchmarking; mod benches { define_benchmarks!( [frame_benchmarking, BaselineBench::] + [pallet_assets, Assets] [frame_system, SystemBench::] [pallet_balances, Balances] [pallet_timestamp, Timestamp] diff --git a/runtime/local/src/precompiles.rs b/runtime/local/src/precompiles.rs index 9e0bf989d9..8520a7522a 100644 --- a/runtime/local/src/precompiles.rs +++ b/runtime/local/src/precompiles.rs @@ -23,6 +23,7 @@ use pallet_evm::{ PrecompileResult, PrecompileSet, }; use pallet_evm_precompile_assets_erc20::{AddressToAssetId, Erc20AssetsPrecompileSet}; +use pallet_evm_precompile_batch::BatchPrecompile; use pallet_evm_precompile_blake2::Blake2F; use pallet_evm_precompile_bn128::{Bn128Add, Bn128Mul, Bn128Pairing}; use pallet_evm_precompile_dapps_staking::DappsStakingWrapper; @@ -54,9 +55,11 @@ impl LocalNetworkPrecompiles { /// Return all addresses that contain precompiles. This can be used to populate dummy code /// under the precompile. pub fn used_addresses() -> impl Iterator { - sp_std::vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 1024, 1025, 1026, 1027, 20481, 20482, 20483, 20485] - .into_iter() - .map(hash) + sp_std::vec![ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 1024, 1025, 1026, 1027, 20481, 20482, 20483, 20485, 20846 + ] + .into_iter() + .map(hash) } } @@ -67,6 +70,7 @@ impl PrecompileSet for LocalNetworkPrecompiles where Erc20AssetsPrecompileSet: PrecompileSet, DappsStakingWrapper: Precompile, + BatchPrecompile: Precompile, XvmPrecompile>: Precompile, Dispatch: Precompile, R: pallet_evm::Config @@ -113,6 +117,9 @@ where a if a == hash(20485) => { Some(XvmPrecompile::>::execute(handle)) } + // Batch 0x5006 + a if a == hash(20486) => Some(BatchPrecompile::::execute(handle)), + // If the address matches asset prefix, the we route through the asset precompile set a if &a.to_fixed_bytes()[0..4] == ASSET_PRECOMPILE_ADDRESS_PREFIX => { Erc20AssetsPrecompileSet::::new().execute(handle) diff --git a/runtime/local/src/weights/mod.rs b/runtime/local/src/weights/mod.rs new file mode 100644 index 0000000000..2db3ac12ab --- /dev/null +++ b/runtime/local/src/weights/mod.rs @@ -0,0 +1,19 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +pub mod pallet_assets; diff --git a/runtime/local/src/weights/pallet_assets.rs b/runtime/local/src/weights/pallet_assets.rs new file mode 100644 index 0000000000..676729637e --- /dev/null +++ b/runtime/local/src/weights/pallet_assets.rs @@ -0,0 +1,496 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +//! Autogenerated weights for pallet_assets +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-08-11, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `devserver-01`, CPU: `Intel(R) Xeon(R) E-2236 CPU @ 3.40GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("astar-dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/release/astar-collator +// benchmark +// pallet +// --chain=astar-dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_assets +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./benchmark-results/assets_weights.rs +// --template=./scripts/templates/weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weights for pallet_assets using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl pallet_assets::WeightInfo for SubstrateWeight { + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: EVM AccountCodes (r:1 w:1) + /// Proof Skipped: EVM AccountCodes (max_values: None, max_size: None, mode: Measured) + fn create() -> Weight { + // Proof Size summary in bytes: + // Measured: `659` + // Estimated: `4124` + // Minimum execution time: 35_155_000 picoseconds. + Weight::from_parts(35_760_000, 4124) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: EVM AccountCodes (r:1 w:1) + /// Proof Skipped: EVM AccountCodes (max_values: None, max_size: None, mode: Measured) + fn force_create() -> Weight { + // Proof Size summary in bytes: + // Measured: `519` + // Estimated: `3984` + // Minimum execution time: 20_004_000 picoseconds. + Weight::from_parts(20_348_000, 3984) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn start_destroy() -> Weight { + // Proof Size summary in bytes: + // Measured: `289` + // Estimated: `3687` + // Minimum execution time: 13_682_000 picoseconds. + Weight::from_parts(14_059_000, 3687) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1001 w:1000) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: System Account (r:1000 w:1000) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// The range of component `c` is `[0, 1000]`. + fn destroy_accounts(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `255 + c * (208 ±0)` + // Estimated: `3687 + c * (2621 ±0)` + // Minimum execution time: 17_910_000 picoseconds. + Weight::from_parts(18_110_000, 3687) + // Standard Error: 6_525 + .saturating_add(Weight::from_parts(11_623_553, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(c.into()))) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(c.into()))) + .saturating_add(Weight::from_parts(0, 2621).saturating_mul(c.into())) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1001 w:1000) + /// Proof: Assets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) + /// The range of component `a` is `[0, 1000]`. + fn destroy_approvals(a: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `438 + a * (86 ±0)` + // Estimated: `3687 + a * (2635 ±0)` + // Minimum execution time: 18_065_000 picoseconds. + Weight::from_parts(18_372_000, 3687) + // Standard Error: 4_985 + .saturating_add(Weight::from_parts(13_662_720, 0).saturating_mul(a.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(a.into()))) + .saturating_add(Weight::from_parts(0, 2635).saturating_mul(a.into())) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:0) + /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + /// Storage: EVM AccountCodes (r:0 w:1) + /// Proof Skipped: EVM AccountCodes (max_values: None, max_size: None, mode: Measured) + fn finish_destroy() -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 16_700_000 picoseconds. + Weight::from_parts(17_418_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + fn mint() -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 26_305_000 picoseconds. + Weight::from_parts(27_014_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + fn burn() -> Weight { + // Proof Size summary in bytes: + // Measured: `375` + // Estimated: `3687` + // Minimum execution time: 30_525_000 picoseconds. + Weight::from_parts(31_113_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:2 w:2) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `447` + // Estimated: `6232` + // Minimum execution time: 42_955_000 picoseconds. + Weight::from_parts(43_309_000, 6232) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:2 w:2) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn transfer_keep_alive() -> Weight { + // Proof Size summary in bytes: + // Measured: `447` + // Estimated: `6232` + // Minimum execution time: 37_951_000 picoseconds. + Weight::from_parts(38_330_000, 6232) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:2 w:2) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn force_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `447` + // Estimated: `6232` + // Minimum execution time: 43_061_000 picoseconds. + Weight::from_parts(43_561_000, 6232) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + fn freeze() -> Weight { + // Proof Size summary in bytes: + // Measured: `375` + // Estimated: `3687` + // Minimum execution time: 17_259_000 picoseconds. + Weight::from_parts(17_516_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + fn thaw() -> Weight { + // Proof Size summary in bytes: + // Measured: `375` + // Estimated: `3687` + // Minimum execution time: 17_165_000 picoseconds. + Weight::from_parts(17_547_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn freeze_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `289` + // Estimated: `3687` + // Minimum execution time: 13_634_000 picoseconds. + Weight::from_parts(14_069_000, 3687) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn thaw_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `289` + // Estimated: `3687` + // Minimum execution time: 13_456_000 picoseconds. + Weight::from_parts(13_770_000, 3687) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:0) + /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + fn transfer_ownership() -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 16_083_000 picoseconds. + Weight::from_parts(16_343_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn set_team() -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 14_443_000 picoseconds. + Weight::from_parts(14_818_000, 3687) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:1) + /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + /// The range of component `n` is `[0, 50]`. + /// The range of component `s` is `[0, 50]`. + fn set_metadata(_n: u32, _s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 28_260_000 picoseconds. + Weight::from_parts(29_181_263, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:1) + /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + fn clear_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `431` + // Estimated: `3687` + // Minimum execution time: 28_580_000 picoseconds. + Weight::from_parts(28_826_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:1) + /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + /// The range of component `n` is `[0, 50]`. + /// The range of component `s` is `[0, 50]`. + fn force_set_metadata(n: u32, s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `94` + // Estimated: `3687` + // Minimum execution time: 14_605_000 picoseconds. + Weight::from_parts(15_107_642, 3687) + // Standard Error: 450 + .saturating_add(Weight::from_parts(6_608, 0).saturating_mul(n.into())) + // Standard Error: 450 + .saturating_add(Weight::from_parts(2_783, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:1) + /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + fn force_clear_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `431` + // Estimated: `3687` + // Minimum execution time: 27_925_000 picoseconds. + Weight::from_parts(28_323_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn force_asset_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 13_904_000 picoseconds. + Weight::from_parts(14_431_000, 3687) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1 w:1) + /// Proof: Assets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) + fn approve_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `289` + // Estimated: `3687` + // Minimum execution time: 31_152_000 picoseconds. + Weight::from_parts(31_715_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1 w:1) + /// Proof: Assets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) + /// Storage: Assets Account (r:2 w:2) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn transfer_approved() -> Weight { + // Proof Size summary in bytes: + // Measured: `680` + // Estimated: `6232` + // Minimum execution time: 63_252_000 picoseconds. + Weight::from_parts(63_718_000, 6232) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1 w:1) + /// Proof: Assets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) + fn cancel_approval() -> Weight { + // Proof Size summary in bytes: + // Measured: `471` + // Estimated: `3687` + // Minimum execution time: 34_375_000 picoseconds. + Weight::from_parts(34_640_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1 w:1) + /// Proof: Assets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) + fn force_cancel_approval() -> Weight { + // Proof Size summary in bytes: + // Measured: `471` + // Estimated: `3687` + // Minimum execution time: 34_159_000 picoseconds. + Weight::from_parts(34_693_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn set_min_balance() -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 15_114_000 picoseconds. + Weight::from_parts(15_271_000, 3687) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn touch() -> Weight { + // Proof Size summary in bytes: + // Measured: `429` + // Estimated: `3687` + // Minimum execution time: 34_356_000 picoseconds. + Weight::from_parts(34_856_000, 3687) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn touch_other() -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 31_063_000 picoseconds. + Weight::from_parts(31_331_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn refund() -> Weight { + // Proof Size summary in bytes: + // Measured: `567` + // Estimated: `3687` + // Minimum execution time: 31_678_000 picoseconds. + Weight::from_parts(31_941_000, 3687) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn refund_other() -> Weight { + // Proof Size summary in bytes: + // Measured: `426` + // Estimated: `3687` + // Minimum execution time: 28_601_000 picoseconds. + Weight::from_parts(28_966_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + fn block() -> Weight { + // Proof Size summary in bytes: + // Measured: `375` + // Estimated: `3687` + // Minimum execution time: 17_545_000 picoseconds. + Weight::from_parts(17_959_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } +} diff --git a/runtime/shibuya/Cargo.toml b/runtime/shibuya/Cargo.toml index f02da1a5fb..29e5cb658a 100644 --- a/runtime/shibuya/Cargo.toml +++ b/runtime/shibuya/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shibuya-runtime" -version = "5.16.1" +version = "5.17.0" build = "build.rs" authors.workspace = true edition.workspace = true @@ -105,6 +105,7 @@ pallet-custom-signatures = { workspace = true } pallet-dapps-staking = { workspace = true } pallet-ethereum-checked = { workspace = true } pallet-evm-precompile-assets-erc20 = { workspace = true } +pallet-evm-precompile-batch = { workspace = true } pallet-evm-precompile-dapps-staking = { workspace = true } pallet-evm-precompile-sr25519 = { workspace = true } pallet-evm-precompile-substrate-ecdsa = { workspace = true } @@ -180,6 +181,7 @@ std = [ "pallet-evm-precompile-modexp/std", "pallet-evm-precompile-sha3fips/std", "pallet-evm-precompile-dapps-staking/std", + "pallet-evm-precompile-batch/std", "pallet-evm-precompile-sr25519/std", "pallet-evm-precompile-substrate-ecdsa/std", "pallet-evm-precompile-assets-erc20/std", @@ -257,6 +259,7 @@ runtime-benchmarks = [ "polkadot-runtime/runtime-benchmarks", "orml-xtokens/runtime-benchmarks", "astar-primitives/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", ] try-runtime = [ "fp-self-contained/try-runtime", diff --git a/runtime/shibuya/src/lib.rs b/runtime/shibuya/src/lib.rs index bc94244c62..8f70eee505 100644 --- a/runtime/shibuya/src/lib.rs +++ b/runtime/shibuya/src/lib.rs @@ -29,8 +29,7 @@ use frame_support::{ parameter_types, traits::{ AsEnsureOriginWithArg, ConstU32, Contains, Currency, EitherOfDiverse, EqualPrivilegeOnly, - FindAuthor, Get, Imbalance, InstanceFilter, Nothing, OnFinalize, OnUnbalanced, - WithdrawReasons, + FindAuthor, Get, Imbalance, InstanceFilter, OnUnbalanced, WithdrawReasons, }, weights::{ constants::{ @@ -69,11 +68,11 @@ use sp_runtime::{ use sp_std::prelude::*; pub use astar_primitives::{ - ethereum_checked::CheckedEthereumTransact, AccountId, Address, AssetId, Balance, BlockNumber, - Hash, Header, Index, Signature, + ethereum_checked::CheckedEthereumTransact, evm::EvmRevertCodeHandler, + xcm::AssetLocationIdConverter, AccountId, Address, AssetId, Balance, BlockNumber, Hash, Header, + Index, Signature, }; -use astar_primitives::xcm::AssetLocationIdConverter; use pallet_evm_precompile_assets_erc20::AddressToAssetId; #[cfg(any(feature = "std", test))] @@ -88,6 +87,7 @@ pub use sp_runtime::BuildStorage; mod chain_extensions; mod precompiles; +mod weights; mod xcm_config; pub type ShibuyaAssetLocationIdConverter = AssetLocationIdConverter; @@ -166,7 +166,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("shibuya"), impl_name: create_runtime_str!("shibuya"), authoring_version: 1, - spec_version: 106, + spec_version: 107, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 2, @@ -629,10 +629,12 @@ impl pallet_assets::Config for Runtime { type StringLimit = AssetsStringLimit; type Freezer = (); type Extra = (); - type WeightInfo = pallet_assets::weights::SubstrateWeight; + type WeightInfo = weights::pallet_assets::SubstrateWeight; type RemoveItemsLimit = ConstU32<1000>; type AssetIdParameter = Compact; - type CallbackHandle = (); + type CallbackHandle = EvmRevertCodeHandler; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = astar_primitives::benchmarks::AssetsBenchmarkHelper; } parameter_types! { @@ -662,6 +664,17 @@ parameter_types! { pub Schedule: pallet_contracts::Schedule = Default::default(); } +pub struct CallRuntimeFilter; +impl Contains for CallRuntimeFilter { + fn contains(c: &RuntimeCall) -> bool { + match *c { + RuntimeCall::Assets(pallet_assets::Call::transfer { .. }) => true, + RuntimeCall::Assets(pallet_assets::Call::transfer_approved { .. }) => true, + _ => false, + } + } +} + impl pallet_contracts::Config for Runtime { type Time = Timestamp; type Randomness = RandomnessCollectiveFlip; @@ -674,7 +687,7 @@ impl pallet_contracts::Config for Runtime { /// and make sure they are stable. Dispatchables exposed to contracts are not allowed to /// change because that would break already deployed contracts. The `Call` structure itself /// is not allowed to change the indices of existing pallets, too. - type CallFilter = Nothing; + type CallFilter = CallRuntimeFilter; type DepositPerItem = DepositPerItem; type DepositPerByte = DepositPerByte; type DefaultDepositLimit = DefaultDepositLimit; @@ -690,7 +703,7 @@ impl pallet_contracts::Config for Runtime { type AddressGenerator = pallet_contracts::DefaultAddressGenerator; type MaxCodeLen = ConstU32<{ 123 * 1024 }>; type MaxStorageKeyLen = ConstU32<128>; - type UnsafeUnstableInterface = ConstBool; + type UnsafeUnstableInterface = ConstBool; type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>; } @@ -1221,25 +1234,11 @@ impl pallet_proxy::Config for Runtime { type AnnouncementDepositFactor = AnnouncementDepositFactor; } -pub struct EvmRevertCodeHandler; -impl pallet_xc_asset_config::XcAssetChanged for EvmRevertCodeHandler { - fn xc_asset_registered(asset_id: AssetId) { - let address = Runtime::asset_id_to_address(asset_id); - pallet_evm::AccountCodes::::insert(address, vec![0x60, 0x00, 0x60, 0x00, 0xfd]); - } - - fn xc_asset_unregistered(asset_id: AssetId) { - let address = Runtime::asset_id_to_address(asset_id); - pallet_evm::AccountCodes::::remove(address); - } -} - impl pallet_xc_asset_config::Config for Runtime { type RuntimeEvent = RuntimeEvent; type AssetId = AssetId; - type XcAssetChanged = EvmRevertCodeHandler; // Good enough for testnet since we lack pallet-assets hooks for now - type ManagerOrigin = EitherOfDiverse, EnsureSigned>; + type ManagerOrigin = EnsureRoot; type WeightInfo = pallet_xc_asset_config::weights::SubstrateWeight; } @@ -1421,6 +1420,7 @@ mod benches { define_benchmarks!( [frame_benchmarking, BaselineBench::] [frame_system, SystemBench::] + [pallet_assets, Assets] [pallet_balances, Balances] [pallet_timestamp, Timestamp] [pallet_dapps_staking, DappsStaking] diff --git a/runtime/shibuya/src/precompiles.rs b/runtime/shibuya/src/precompiles.rs index d84839aa4b..3761e33ff4 100644 --- a/runtime/shibuya/src/precompiles.rs +++ b/runtime/shibuya/src/precompiles.rs @@ -23,6 +23,7 @@ use pallet_evm::{ PrecompileResult, PrecompileSet, }; use pallet_evm_precompile_assets_erc20::{AddressToAssetId, Erc20AssetsPrecompileSet}; +use pallet_evm_precompile_batch::BatchPrecompile; use pallet_evm_precompile_blake2::Blake2F; use pallet_evm_precompile_bn128::{Bn128Add, Bn128Mul, Bn128Pairing}; use pallet_evm_precompile_dapps_staking::DappsStakingWrapper; @@ -58,7 +59,8 @@ impl ShibuyaNetworkPrecompiles { /// under the precompile. pub fn used_addresses() -> impl Iterator { sp_std::vec![ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 1024, 1025, 1026, 1027, 20481, 20482, 20483, 20484, 20485 + 1, 2, 3, 4, 5, 6, 7, 8, 9, 1024, 1025, 1026, 1027, 20481, 20482, 20483, 20484, 20485, + 20486 ] .into_iter() .map(hash) @@ -73,6 +75,7 @@ where Erc20AssetsPrecompileSet: PrecompileSet, DappsStakingWrapper: Precompile, XcmPrecompile: Precompile, + BatchPrecompile: Precompile, XvmPrecompile>: Precompile, Dispatch: Precompile, R: pallet_evm::Config @@ -123,6 +126,8 @@ where a if a == hash(20485) => { Some(XvmPrecompile::>::execute(handle)) } + // Batch 0x5006 + a if a == hash(20486) => Some(BatchPrecompile::::execute(handle)), // If the address matches asset prefix, the we route through the asset precompile set a if &a.to_fixed_bytes()[0..4] == ASSET_PRECOMPILE_ADDRESS_PREFIX => { Erc20AssetsPrecompileSet::::new().execute(handle) diff --git a/runtime/shibuya/src/weights/mod.rs b/runtime/shibuya/src/weights/mod.rs new file mode 100644 index 0000000000..2db3ac12ab --- /dev/null +++ b/runtime/shibuya/src/weights/mod.rs @@ -0,0 +1,19 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +pub mod pallet_assets; diff --git a/runtime/shibuya/src/weights/pallet_assets.rs b/runtime/shibuya/src/weights/pallet_assets.rs new file mode 100644 index 0000000000..676729637e --- /dev/null +++ b/runtime/shibuya/src/weights/pallet_assets.rs @@ -0,0 +1,496 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +//! Autogenerated weights for pallet_assets +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-08-11, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `devserver-01`, CPU: `Intel(R) Xeon(R) E-2236 CPU @ 3.40GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("astar-dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/release/astar-collator +// benchmark +// pallet +// --chain=astar-dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_assets +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./benchmark-results/assets_weights.rs +// --template=./scripts/templates/weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weights for pallet_assets using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl pallet_assets::WeightInfo for SubstrateWeight { + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: EVM AccountCodes (r:1 w:1) + /// Proof Skipped: EVM AccountCodes (max_values: None, max_size: None, mode: Measured) + fn create() -> Weight { + // Proof Size summary in bytes: + // Measured: `659` + // Estimated: `4124` + // Minimum execution time: 35_155_000 picoseconds. + Weight::from_parts(35_760_000, 4124) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: EVM AccountCodes (r:1 w:1) + /// Proof Skipped: EVM AccountCodes (max_values: None, max_size: None, mode: Measured) + fn force_create() -> Weight { + // Proof Size summary in bytes: + // Measured: `519` + // Estimated: `3984` + // Minimum execution time: 20_004_000 picoseconds. + Weight::from_parts(20_348_000, 3984) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn start_destroy() -> Weight { + // Proof Size summary in bytes: + // Measured: `289` + // Estimated: `3687` + // Minimum execution time: 13_682_000 picoseconds. + Weight::from_parts(14_059_000, 3687) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1001 w:1000) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: System Account (r:1000 w:1000) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// The range of component `c` is `[0, 1000]`. + fn destroy_accounts(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `255 + c * (208 ±0)` + // Estimated: `3687 + c * (2621 ±0)` + // Minimum execution time: 17_910_000 picoseconds. + Weight::from_parts(18_110_000, 3687) + // Standard Error: 6_525 + .saturating_add(Weight::from_parts(11_623_553, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(c.into()))) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(c.into()))) + .saturating_add(Weight::from_parts(0, 2621).saturating_mul(c.into())) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1001 w:1000) + /// Proof: Assets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) + /// The range of component `a` is `[0, 1000]`. + fn destroy_approvals(a: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `438 + a * (86 ±0)` + // Estimated: `3687 + a * (2635 ±0)` + // Minimum execution time: 18_065_000 picoseconds. + Weight::from_parts(18_372_000, 3687) + // Standard Error: 4_985 + .saturating_add(Weight::from_parts(13_662_720, 0).saturating_mul(a.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(a.into()))) + .saturating_add(Weight::from_parts(0, 2635).saturating_mul(a.into())) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:0) + /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + /// Storage: EVM AccountCodes (r:0 w:1) + /// Proof Skipped: EVM AccountCodes (max_values: None, max_size: None, mode: Measured) + fn finish_destroy() -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 16_700_000 picoseconds. + Weight::from_parts(17_418_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + fn mint() -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 26_305_000 picoseconds. + Weight::from_parts(27_014_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + fn burn() -> Weight { + // Proof Size summary in bytes: + // Measured: `375` + // Estimated: `3687` + // Minimum execution time: 30_525_000 picoseconds. + Weight::from_parts(31_113_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:2 w:2) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `447` + // Estimated: `6232` + // Minimum execution time: 42_955_000 picoseconds. + Weight::from_parts(43_309_000, 6232) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:2 w:2) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn transfer_keep_alive() -> Weight { + // Proof Size summary in bytes: + // Measured: `447` + // Estimated: `6232` + // Minimum execution time: 37_951_000 picoseconds. + Weight::from_parts(38_330_000, 6232) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:2 w:2) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn force_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `447` + // Estimated: `6232` + // Minimum execution time: 43_061_000 picoseconds. + Weight::from_parts(43_561_000, 6232) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + fn freeze() -> Weight { + // Proof Size summary in bytes: + // Measured: `375` + // Estimated: `3687` + // Minimum execution time: 17_259_000 picoseconds. + Weight::from_parts(17_516_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + fn thaw() -> Weight { + // Proof Size summary in bytes: + // Measured: `375` + // Estimated: `3687` + // Minimum execution time: 17_165_000 picoseconds. + Weight::from_parts(17_547_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn freeze_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `289` + // Estimated: `3687` + // Minimum execution time: 13_634_000 picoseconds. + Weight::from_parts(14_069_000, 3687) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn thaw_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `289` + // Estimated: `3687` + // Minimum execution time: 13_456_000 picoseconds. + Weight::from_parts(13_770_000, 3687) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:0) + /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + fn transfer_ownership() -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 16_083_000 picoseconds. + Weight::from_parts(16_343_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn set_team() -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 14_443_000 picoseconds. + Weight::from_parts(14_818_000, 3687) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:1) + /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + /// The range of component `n` is `[0, 50]`. + /// The range of component `s` is `[0, 50]`. + fn set_metadata(_n: u32, _s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 28_260_000 picoseconds. + Weight::from_parts(29_181_263, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:1) + /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + fn clear_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `431` + // Estimated: `3687` + // Minimum execution time: 28_580_000 picoseconds. + Weight::from_parts(28_826_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:1) + /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + /// The range of component `n` is `[0, 50]`. + /// The range of component `s` is `[0, 50]`. + fn force_set_metadata(n: u32, s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `94` + // Estimated: `3687` + // Minimum execution time: 14_605_000 picoseconds. + Weight::from_parts(15_107_642, 3687) + // Standard Error: 450 + .saturating_add(Weight::from_parts(6_608, 0).saturating_mul(n.into())) + // Standard Error: 450 + .saturating_add(Weight::from_parts(2_783, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:1) + /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + fn force_clear_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `431` + // Estimated: `3687` + // Minimum execution time: 27_925_000 picoseconds. + Weight::from_parts(28_323_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn force_asset_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 13_904_000 picoseconds. + Weight::from_parts(14_431_000, 3687) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1 w:1) + /// Proof: Assets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) + fn approve_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `289` + // Estimated: `3687` + // Minimum execution time: 31_152_000 picoseconds. + Weight::from_parts(31_715_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1 w:1) + /// Proof: Assets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) + /// Storage: Assets Account (r:2 w:2) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn transfer_approved() -> Weight { + // Proof Size summary in bytes: + // Measured: `680` + // Estimated: `6232` + // Minimum execution time: 63_252_000 picoseconds. + Weight::from_parts(63_718_000, 6232) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1 w:1) + /// Proof: Assets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) + fn cancel_approval() -> Weight { + // Proof Size summary in bytes: + // Measured: `471` + // Estimated: `3687` + // Minimum execution time: 34_375_000 picoseconds. + Weight::from_parts(34_640_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1 w:1) + /// Proof: Assets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) + fn force_cancel_approval() -> Weight { + // Proof Size summary in bytes: + // Measured: `471` + // Estimated: `3687` + // Minimum execution time: 34_159_000 picoseconds. + Weight::from_parts(34_693_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn set_min_balance() -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 15_114_000 picoseconds. + Weight::from_parts(15_271_000, 3687) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn touch() -> Weight { + // Proof Size summary in bytes: + // Measured: `429` + // Estimated: `3687` + // Minimum execution time: 34_356_000 picoseconds. + Weight::from_parts(34_856_000, 3687) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn touch_other() -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 31_063_000 picoseconds. + Weight::from_parts(31_331_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn refund() -> Weight { + // Proof Size summary in bytes: + // Measured: `567` + // Estimated: `3687` + // Minimum execution time: 31_678_000 picoseconds. + Weight::from_parts(31_941_000, 3687) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn refund_other() -> Weight { + // Proof Size summary in bytes: + // Measured: `426` + // Estimated: `3687` + // Minimum execution time: 28_601_000 picoseconds. + Weight::from_parts(28_966_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + fn block() -> Weight { + // Proof Size summary in bytes: + // Measured: `375` + // Estimated: `3687` + // Minimum execution time: 17_545_000 picoseconds. + Weight::from_parts(17_959_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } +} diff --git a/runtime/shiden/Cargo.toml b/runtime/shiden/Cargo.toml index 2d3c6e0e4c..d020642239 100644 --- a/runtime/shiden/Cargo.toml +++ b/runtime/shiden/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shiden-runtime" -version = "5.16.1" +version = "5.17.0" build = "build.rs" authors.workspace = true edition.workspace = true @@ -229,6 +229,7 @@ runtime-benchmarks = [ "pallet-collator-selection/runtime-benchmarks", "polkadot-runtime/runtime-benchmarks", "astar-primitives/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", ] try-runtime = [ "fp-self-contained/try-runtime", diff --git a/runtime/shiden/src/lib.rs b/runtime/shiden/src/lib.rs index 6a87d6f7e6..7bb2b1cf82 100644 --- a/runtime/shiden/src/lib.rs +++ b/runtime/shiden/src/lib.rs @@ -68,10 +68,10 @@ use sp_runtime::{ use sp_std::prelude::*; pub use astar_primitives::{ - AccountId, Address, AssetId, Balance, BlockNumber, Hash, Header, Index, Signature, + evm::EvmRevertCodeHandler, xcm::AssetLocationIdConverter, AccountId, Address, AssetId, Balance, + BlockNumber, Hash, Header, Index, Signature, }; -use astar_primitives::xcm::AssetLocationIdConverter; use pallet_evm_precompile_assets_erc20::AddressToAssetId; #[cfg(any(feature = "std", test))] @@ -85,6 +85,7 @@ pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; pub use sp_runtime::BuildStorage; mod precompiles; +mod weights; mod xcm_config; pub type ShidenAssetLocationIdConverter = AssetLocationIdConverter; @@ -143,7 +144,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("shiden"), impl_name: create_runtime_str!("shiden"), authoring_version: 1, - spec_version: 104, + spec_version: 105, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 2, @@ -583,10 +584,12 @@ impl pallet_assets::Config for Runtime { type StringLimit = AssetsStringLimit; type Freezer = (); type Extra = (); - type WeightInfo = pallet_assets::weights::SubstrateWeight; + type WeightInfo = weights::pallet_assets::SubstrateWeight; type RemoveItemsLimit = ConstU32<1000>; type AssetIdParameter = Compact; - type CallbackHandle = (); + type CallbackHandle = EvmRevertCodeHandler; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = astar_primitives::benchmarks::AssetsBenchmarkHelper; } parameter_types! { @@ -822,23 +825,9 @@ impl pallet_sudo::Config for Runtime { type WeightInfo = pallet_sudo::weights::SubstrateWeight; } -pub struct EvmRevertCodeHandler; -impl pallet_xc_asset_config::XcAssetChanged for EvmRevertCodeHandler { - fn xc_asset_registered(asset_id: AssetId) { - let address = Runtime::asset_id_to_address(asset_id); - pallet_evm::AccountCodes::::insert(address, vec![0x60, 0x00, 0x60, 0x00, 0xfd]); - } - - fn xc_asset_unregistered(asset_id: AssetId) { - let address = Runtime::asset_id_to_address(asset_id); - pallet_evm::AccountCodes::::remove(address); - } -} - impl pallet_xc_asset_config::Config for Runtime { type RuntimeEvent = RuntimeEvent; type AssetId = AssetId; - type XcAssetChanged = EvmRevertCodeHandler; type ManagerOrigin = EnsureRoot; type WeightInfo = pallet_xc_asset_config::weights::SubstrateWeight; } @@ -1153,6 +1142,7 @@ mod benches { define_benchmarks!( [frame_benchmarking, BaselineBench::] [frame_system, SystemBench::] + [pallet_assets, Assets] [pallet_balances, Balances] [pallet_timestamp, Timestamp] [pallet_dapps_staking, DappsStaking] diff --git a/runtime/shiden/src/weights/mod.rs b/runtime/shiden/src/weights/mod.rs new file mode 100644 index 0000000000..2db3ac12ab --- /dev/null +++ b/runtime/shiden/src/weights/mod.rs @@ -0,0 +1,19 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +pub mod pallet_assets; diff --git a/runtime/shiden/src/weights/pallet_assets.rs b/runtime/shiden/src/weights/pallet_assets.rs new file mode 100644 index 0000000000..676729637e --- /dev/null +++ b/runtime/shiden/src/weights/pallet_assets.rs @@ -0,0 +1,496 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +//! Autogenerated weights for pallet_assets +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-08-11, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `devserver-01`, CPU: `Intel(R) Xeon(R) E-2236 CPU @ 3.40GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("astar-dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/release/astar-collator +// benchmark +// pallet +// --chain=astar-dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_assets +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./benchmark-results/assets_weights.rs +// --template=./scripts/templates/weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weights for pallet_assets using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl pallet_assets::WeightInfo for SubstrateWeight { + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: EVM AccountCodes (r:1 w:1) + /// Proof Skipped: EVM AccountCodes (max_values: None, max_size: None, mode: Measured) + fn create() -> Weight { + // Proof Size summary in bytes: + // Measured: `659` + // Estimated: `4124` + // Minimum execution time: 35_155_000 picoseconds. + Weight::from_parts(35_760_000, 4124) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: EVM AccountCodes (r:1 w:1) + /// Proof Skipped: EVM AccountCodes (max_values: None, max_size: None, mode: Measured) + fn force_create() -> Weight { + // Proof Size summary in bytes: + // Measured: `519` + // Estimated: `3984` + // Minimum execution time: 20_004_000 picoseconds. + Weight::from_parts(20_348_000, 3984) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn start_destroy() -> Weight { + // Proof Size summary in bytes: + // Measured: `289` + // Estimated: `3687` + // Minimum execution time: 13_682_000 picoseconds. + Weight::from_parts(14_059_000, 3687) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1001 w:1000) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: System Account (r:1000 w:1000) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// The range of component `c` is `[0, 1000]`. + fn destroy_accounts(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `255 + c * (208 ±0)` + // Estimated: `3687 + c * (2621 ±0)` + // Minimum execution time: 17_910_000 picoseconds. + Weight::from_parts(18_110_000, 3687) + // Standard Error: 6_525 + .saturating_add(Weight::from_parts(11_623_553, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(c.into()))) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(c.into()))) + .saturating_add(Weight::from_parts(0, 2621).saturating_mul(c.into())) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1001 w:1000) + /// Proof: Assets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) + /// The range of component `a` is `[0, 1000]`. + fn destroy_approvals(a: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `438 + a * (86 ±0)` + // Estimated: `3687 + a * (2635 ±0)` + // Minimum execution time: 18_065_000 picoseconds. + Weight::from_parts(18_372_000, 3687) + // Standard Error: 4_985 + .saturating_add(Weight::from_parts(13_662_720, 0).saturating_mul(a.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(a.into()))) + .saturating_add(Weight::from_parts(0, 2635).saturating_mul(a.into())) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:0) + /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + /// Storage: EVM AccountCodes (r:0 w:1) + /// Proof Skipped: EVM AccountCodes (max_values: None, max_size: None, mode: Measured) + fn finish_destroy() -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 16_700_000 picoseconds. + Weight::from_parts(17_418_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + fn mint() -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 26_305_000 picoseconds. + Weight::from_parts(27_014_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + fn burn() -> Weight { + // Proof Size summary in bytes: + // Measured: `375` + // Estimated: `3687` + // Minimum execution time: 30_525_000 picoseconds. + Weight::from_parts(31_113_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:2 w:2) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `447` + // Estimated: `6232` + // Minimum execution time: 42_955_000 picoseconds. + Weight::from_parts(43_309_000, 6232) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:2 w:2) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn transfer_keep_alive() -> Weight { + // Proof Size summary in bytes: + // Measured: `447` + // Estimated: `6232` + // Minimum execution time: 37_951_000 picoseconds. + Weight::from_parts(38_330_000, 6232) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:2 w:2) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn force_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `447` + // Estimated: `6232` + // Minimum execution time: 43_061_000 picoseconds. + Weight::from_parts(43_561_000, 6232) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + fn freeze() -> Weight { + // Proof Size summary in bytes: + // Measured: `375` + // Estimated: `3687` + // Minimum execution time: 17_259_000 picoseconds. + Weight::from_parts(17_516_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + fn thaw() -> Weight { + // Proof Size summary in bytes: + // Measured: `375` + // Estimated: `3687` + // Minimum execution time: 17_165_000 picoseconds. + Weight::from_parts(17_547_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn freeze_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `289` + // Estimated: `3687` + // Minimum execution time: 13_634_000 picoseconds. + Weight::from_parts(14_069_000, 3687) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn thaw_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `289` + // Estimated: `3687` + // Minimum execution time: 13_456_000 picoseconds. + Weight::from_parts(13_770_000, 3687) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:0) + /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + fn transfer_ownership() -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 16_083_000 picoseconds. + Weight::from_parts(16_343_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn set_team() -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 14_443_000 picoseconds. + Weight::from_parts(14_818_000, 3687) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:1) + /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + /// The range of component `n` is `[0, 50]`. + /// The range of component `s` is `[0, 50]`. + fn set_metadata(_n: u32, _s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 28_260_000 picoseconds. + Weight::from_parts(29_181_263, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:1) + /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + fn clear_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `431` + // Estimated: `3687` + // Minimum execution time: 28_580_000 picoseconds. + Weight::from_parts(28_826_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:1) + /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + /// The range of component `n` is `[0, 50]`. + /// The range of component `s` is `[0, 50]`. + fn force_set_metadata(n: u32, s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `94` + // Estimated: `3687` + // Minimum execution time: 14_605_000 picoseconds. + Weight::from_parts(15_107_642, 3687) + // Standard Error: 450 + .saturating_add(Weight::from_parts(6_608, 0).saturating_mul(n.into())) + // Standard Error: 450 + .saturating_add(Weight::from_parts(2_783, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:1) + /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + fn force_clear_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `431` + // Estimated: `3687` + // Minimum execution time: 27_925_000 picoseconds. + Weight::from_parts(28_323_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn force_asset_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 13_904_000 picoseconds. + Weight::from_parts(14_431_000, 3687) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1 w:1) + /// Proof: Assets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) + fn approve_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `289` + // Estimated: `3687` + // Minimum execution time: 31_152_000 picoseconds. + Weight::from_parts(31_715_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1 w:1) + /// Proof: Assets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) + /// Storage: Assets Account (r:2 w:2) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn transfer_approved() -> Weight { + // Proof Size summary in bytes: + // Measured: `680` + // Estimated: `6232` + // Minimum execution time: 63_252_000 picoseconds. + Weight::from_parts(63_718_000, 6232) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1 w:1) + /// Proof: Assets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) + fn cancel_approval() -> Weight { + // Proof Size summary in bytes: + // Measured: `471` + // Estimated: `3687` + // Minimum execution time: 34_375_000 picoseconds. + Weight::from_parts(34_640_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1 w:1) + /// Proof: Assets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) + fn force_cancel_approval() -> Weight { + // Proof Size summary in bytes: + // Measured: `471` + // Estimated: `3687` + // Minimum execution time: 34_159_000 picoseconds. + Weight::from_parts(34_693_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn set_min_balance() -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 15_114_000 picoseconds. + Weight::from_parts(15_271_000, 3687) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn touch() -> Weight { + // Proof Size summary in bytes: + // Measured: `429` + // Estimated: `3687` + // Minimum execution time: 34_356_000 picoseconds. + Weight::from_parts(34_856_000, 3687) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn touch_other() -> Weight { + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3687` + // Minimum execution time: 31_063_000 picoseconds. + Weight::from_parts(31_331_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn refund() -> Weight { + // Proof Size summary in bytes: + // Measured: `567` + // Estimated: `3687` + // Minimum execution time: 31_678_000 picoseconds. + Weight::from_parts(31_941_000, 3687) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn refund_other() -> Weight { + // Proof Size summary in bytes: + // Measured: `426` + // Estimated: `3687` + // Minimum execution time: 28_601_000 picoseconds. + Weight::from_parts(28_966_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(146), added: 2621, mode: MaxEncodedLen) + fn block() -> Weight { + // Proof Size summary in bytes: + // Measured: `375` + // Estimated: `3687` + // Minimum execution time: 17_545_000 picoseconds. + Weight::from_parts(17_959_000, 3687) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } +} diff --git a/scripts/templates/weight-template.hbs b/scripts/templates/weight-template.hbs index 0df6bef5d3..1bc7767bd6 100644 --- a/scripts/templates/weight-template.hbs +++ b/scripts/templates/weight-template.hbs @@ -1,4 +1,22 @@ {{header}} +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + //! Autogenerated weights for {{pallet}} //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION {{version}} diff --git a/tests/integration/Cargo.toml b/tests/integration/Cargo.toml index 9e291b6295..834f6dd902 100644 --- a/tests/integration/Cargo.toml +++ b/tests/integration/Cargo.toml @@ -8,10 +8,19 @@ homepage.workspace = true repository.workspace = true [dependencies] +hex = { workspace = true } +parity-scale-codec = { workspace = true } + +# frontier +pallet-evm = { workspace = true } + # frame dependencies frame-support = { workspace = true } frame-system = { workspace = true } +pallet-assets = { workspace = true } pallet-balances = { workspace = true } +pallet-contracts = { workspace = true } +pallet-contracts-primitives = { workspace = true } pallet-dapps-staking = { workspace = true } pallet-proxy = { workspace = true } pallet-utility = { workspace = true } @@ -19,7 +28,11 @@ sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -# runtime +# astar dependencies +pallet-ethereum-checked = { workspace = true } +pallet-evm-precompile-assets-erc20 = { workspace = true } + +astar-primitives = { workspace = true } astar-runtime = { workspace = true, features = ["std"], optional = true } shibuya-runtime = { workspace = true, features = ["std"], optional = true } shiden-runtime = { workspace = true, features = ["std"], optional = true } diff --git a/tests/integration/ink-contracts/README.md b/tests/integration/ink-contracts/README.md new file mode 100644 index 0000000000..24b9891009 --- /dev/null +++ b/tests/integration/ink-contracts/README.md @@ -0,0 +1,8 @@ +## Ink! Contracts + +This directory contains Ink! contracts which are used in integration tests. + +`.wasm` files in this directory are used by `pallet-contracts` scenarios, typically `src/xvm.rs`. The json +files are for informational purposes only and are not consumed by the tests. + +The source code for the contracts can be found at https://github.com/AstarNetwork/ink-test-contracts diff --git a/tests/integration/ink-contracts/call_xvm_payable.json b/tests/integration/ink-contracts/call_xvm_payable.json new file mode 100644 index 0000000000..774d549f04 --- /dev/null +++ b/tests/integration/ink-contracts/call_xvm_payable.json @@ -0,0 +1,438 @@ +{ + "source": { + "hash": "0x2f161d6093ea4272bde71b8e3c8e81efbd7b15bc3b482ebd8bac2f3f43950af9", + "language": "ink! 4.2.1", + "compiler": "rustc 1.68.0-nightly", + "build_info": { + "build_mode": "Debug", + "cargo_contract_version": "3.0.1", + "rust_toolchain": "nightly-aarch64-apple-darwin", + "wasm_opt_settings": { + "keep_debug_symbols": false, + "optimization_passes": "Z" + } + } + }, + "contract": { + "name": "call_xvm_payable", + "version": "0.1.0", + "authors": [ + "[your_name] <[your_email]>" + ] + }, + "spec": { + "constructors": [ + { + "args": [], + "default": false, + "docs": [], + "label": "new", + "payable": false, + "returnType": { + "displayName": [ + "ink_primitives", + "ConstructorResult" + ], + "type": 0 + }, + "selector": "0x9bae9d5e" + } + ], + "docs": [], + "environment": { + "accountId": { + "displayName": [ + "AccountId" + ], + "type": 8 + }, + "balance": { + "displayName": [ + "Balance" + ], + "type": 10 + }, + "blockNumber": { + "displayName": [ + "BlockNumber" + ], + "type": 13 + }, + "chainExtension": { + "displayName": [ + "ChainExtension" + ], + "type": 14 + }, + "hash": { + "displayName": [ + "Hash" + ], + "type": 11 + }, + "maxEventTopics": 4, + "timestamp": { + "displayName": [ + "Timestamp" + ], + "type": 12 + } + }, + "events": [], + "lang_error": { + "displayName": [ + "ink", + "LangError" + ], + "type": 2 + }, + "messages": [ + { + "args": [ + { + "label": "target", + "type": { + "displayName": [ + "Vec" + ], + "type": 3 + } + }, + { + "label": "input", + "type": { + "displayName": [ + "Vec" + ], + "type": 3 + } + } + ], + "default": false, + "docs": [], + "label": "call_xvm_payable", + "mutates": false, + "payable": true, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 5 + }, + "selector": "0x0000002a" + } + ] + }, + "storage": { + "root": { + "layout": { + "struct": { + "fields": [], + "name": "CallXvmPayable" + } + }, + "root_key": "0x00000000" + } + }, + "types": [ + { + "id": 0, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 1 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 2 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 1 + }, + { + "name": "E", + "type": 2 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 1, + "type": { + "def": { + "tuple": [] + } + } + }, + { + "id": 2, + "type": { + "def": { + "variant": { + "variants": [ + { + "index": 1, + "name": "CouldNotReadInput" + } + ] + } + }, + "path": [ + "ink_primitives", + "LangError" + ] + } + }, + { + "id": 3, + "type": { + "def": { + "sequence": { + "type": 4 + } + } + } + }, + { + "id": 4, + "type": { + "def": { + "primitive": "u8" + } + } + }, + { + "id": 5, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 6 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 2 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 6 + }, + { + "name": "E", + "type": 2 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 6, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 3 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 7 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 3 + }, + { + "name": "E", + "type": 7 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 7, + "type": { + "def": { + "variant": { + "variants": [ + { + "index": 0, + "name": "InvalidVmId" + }, + { + "index": 1, + "name": "SameVmCallNotAllowed" + }, + { + "index": 2, + "name": "InvalidTarget" + }, + { + "index": 3, + "name": "InputTooLarge" + }, + { + "index": 4, + "name": "BadOrigin" + }, + { + "index": 5, + "name": "ExecutionFailed" + } + ] + } + }, + "path": [ + "call_xvm_payable", + "XvmCallError" + ] + } + }, + { + "id": 8, + "type": { + "def": { + "composite": { + "fields": [ + { + "type": 9, + "typeName": "[u8; 32]" + } + ] + } + }, + "path": [ + "ink_primitives", + "types", + "AccountId" + ] + } + }, + { + "id": 9, + "type": { + "def": { + "array": { + "len": 32, + "type": 4 + } + } + } + }, + { + "id": 10, + "type": { + "def": { + "primitive": "u128" + } + } + }, + { + "id": 11, + "type": { + "def": { + "composite": { + "fields": [ + { + "type": 9, + "typeName": "[u8; 32]" + } + ] + } + }, + "path": [ + "ink_primitives", + "types", + "Hash" + ] + } + }, + { + "id": 12, + "type": { + "def": { + "primitive": "u64" + } + } + }, + { + "id": 13, + "type": { + "def": { + "primitive": "u32" + } + } + }, + { + "id": 14, + "type": { + "def": { + "variant": {} + }, + "path": [ + "call_xvm_payable", + "XvmCall" + ] + } + } + ], + "version": "4" +} \ No newline at end of file diff --git a/tests/integration/ink-contracts/call_xvm_payable.wasm b/tests/integration/ink-contracts/call_xvm_payable.wasm new file mode 100644 index 0000000000..9d2b538e7c Binary files /dev/null and b/tests/integration/ink-contracts/call_xvm_payable.wasm differ diff --git a/tests/integration/ink-contracts/payable.json b/tests/integration/ink-contracts/payable.json new file mode 100644 index 0000000000..5be916d5c7 --- /dev/null +++ b/tests/integration/ink-contracts/payable.json @@ -0,0 +1,287 @@ +{ + "source": { + "hash": "0xcfbb81744c83a08b460d939a434f4c9876a6901f6a4a48e1516004632c5d02f0", + "language": "ink! 4.2.1", + "compiler": "rustc 1.68.0-nightly", + "build_info": { + "build_mode": "Debug", + "cargo_contract_version": "3.0.1", + "rust_toolchain": "nightly-aarch64-apple-darwin", + "wasm_opt_settings": { + "keep_debug_symbols": false, + "optimization_passes": "Z" + } + } + }, + "contract": { + "name": "payable", + "version": "0.1.0", + "authors": [ + "[your_name] <[your_email]>" + ] + }, + "spec": { + "constructors": [ + { + "args": [], + "default": false, + "docs": [], + "label": "new", + "payable": false, + "returnType": { + "displayName": [ + "ink_primitives", + "ConstructorResult" + ], + "type": 0 + }, + "selector": "0x9bae9d5e" + } + ], + "docs": [], + "environment": { + "accountId": { + "displayName": [ + "AccountId" + ], + "type": 3 + }, + "balance": { + "displayName": [ + "Balance" + ], + "type": 6 + }, + "blockNumber": { + "displayName": [ + "BlockNumber" + ], + "type": 9 + }, + "chainExtension": { + "displayName": [ + "ChainExtension" + ], + "type": 10 + }, + "hash": { + "displayName": [ + "Hash" + ], + "type": 7 + }, + "maxEventTopics": 4, + "timestamp": { + "displayName": [ + "Timestamp" + ], + "type": 8 + } + }, + "events": [], + "lang_error": { + "displayName": [ + "ink", + "LangError" + ], + "type": 2 + }, + "messages": [ + { + "args": [], + "default": false, + "docs": [], + "label": "deposit", + "mutates": false, + "payable": true, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 0 + }, + "selector": "0x0000002a" + } + ] + }, + "storage": { + "root": { + "layout": { + "struct": { + "fields": [], + "name": "Payable" + } + }, + "root_key": "0x00000000" + } + }, + "types": [ + { + "id": 0, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 1 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 2 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 1 + }, + { + "name": "E", + "type": 2 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 1, + "type": { + "def": { + "tuple": [] + } + } + }, + { + "id": 2, + "type": { + "def": { + "variant": { + "variants": [ + { + "index": 1, + "name": "CouldNotReadInput" + } + ] + } + }, + "path": [ + "ink_primitives", + "LangError" + ] + } + }, + { + "id": 3, + "type": { + "def": { + "composite": { + "fields": [ + { + "type": 4, + "typeName": "[u8; 32]" + } + ] + } + }, + "path": [ + "ink_primitives", + "types", + "AccountId" + ] + } + }, + { + "id": 4, + "type": { + "def": { + "array": { + "len": 32, + "type": 5 + } + } + } + }, + { + "id": 5, + "type": { + "def": { + "primitive": "u8" + } + } + }, + { + "id": 6, + "type": { + "def": { + "primitive": "u128" + } + } + }, + { + "id": 7, + "type": { + "def": { + "composite": { + "fields": [ + { + "type": 4, + "typeName": "[u8; 32]" + } + ] + } + }, + "path": [ + "ink_primitives", + "types", + "Hash" + ] + } + }, + { + "id": 8, + "type": { + "def": { + "primitive": "u64" + } + } + }, + { + "id": 9, + "type": { + "def": { + "primitive": "u32" + } + } + }, + { + "id": 10, + "type": { + "def": { + "variant": {} + }, + "path": [ + "ink_env", + "types", + "NoChainExtension" + ] + } + } + ], + "version": "4" +} \ No newline at end of file diff --git a/tests/integration/ink-contracts/payable.wasm b/tests/integration/ink-contracts/payable.wasm new file mode 100644 index 0000000000..34587fc018 Binary files /dev/null and b/tests/integration/ink-contracts/payable.wasm differ diff --git a/tests/integration/src/assets.rs b/tests/integration/src/assets.rs new file mode 100644 index 0000000000..7deb49e18d --- /dev/null +++ b/tests/integration/src/assets.rs @@ -0,0 +1,81 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +use crate::setup::*; + +use astar_primitives::evm::EVM_REVERT_CODE; +use pallet_evm_precompile_assets_erc20::AddressToAssetId; + +#[test] +fn asset_create_and_destroy_work_for_evm_revert_code() { + new_test_ext().execute_with(|| { + let asset_id = 19; + let precompile_address = Runtime::asset_id_to_address(asset_id); + + // Asset creation results in insertion of the revert opt code at the precompile address + assert!( + !pallet_evm::AccountCodes::::contains_key(&precompile_address), + "Precompile address should be empty." + ); + assert_ok!(Assets::create( + RuntimeOrigin::signed(ALICE), + asset_id.into(), + ALICE.into(), + 1, + )); + assert_eq!( + pallet_evm::AccountCodes::::get(&precompile_address), + EVM_REVERT_CODE.to_vec(), + "Precompile address should contain the revert code." + ); + + // Asset destroy results in removal of the revert opt code from the precompile address + assert_ok!(Assets::start_destroy( + RuntimeOrigin::signed(ALICE), + asset_id.into(), + )); + assert_ok!(Assets::finish_destroy( + RuntimeOrigin::signed(ALICE), + asset_id.into(), + )); + assert!( + !pallet_evm::AccountCodes::::contains_key(&precompile_address), + "After asset is destroyed, precompile address should be empty." + ); + }); +} + +#[test] +fn asset_create_fails_if_account_code_is_non_empty() { + new_test_ext().execute_with(|| { + let asset_id = 19; + let precompile_address = Runtime::asset_id_to_address(asset_id); + + // Asset registration must fail if the precompile address is not empty + pallet_evm::AccountCodes::::insert(&precompile_address, EVM_REVERT_CODE.to_vec()); + assert_noop!( + Assets::create( + RuntimeOrigin::signed(ALICE), + asset_id.into(), + ALICE.into(), + 1, + ), + pallet_assets::Error::::CallbackFailed + ); + }); +} diff --git a/tests/integration/src/lib.rs b/tests/integration/src/lib.rs index 8757043330..c36fc93edc 100644 --- a/tests/integration/src/lib.rs +++ b/tests/integration/src/lib.rs @@ -25,3 +25,9 @@ mod setup; #[cfg(any(feature = "shibuya", feature = "shiden", feature = "astar"))] mod proxy; + +#[cfg(any(feature = "shibuya", feature = "shiden", feature = "astar"))] +mod assets; + +#[cfg(feature = "shibuya")] +mod xvm; diff --git a/tests/integration/src/setup.rs b/tests/integration/src/setup.rs index a263abe4a6..594ca745b0 100644 --- a/tests/integration/src/setup.rs +++ b/tests/integration/src/setup.rs @@ -19,21 +19,99 @@ //! Runtime integration tests setup & imports. pub use frame_support::{ - assert_ok, + assert_noop, assert_ok, traits::{OnFinalize, OnIdle, OnInitialize}, weights::Weight, }; -pub use sp_core::H160; +pub use pallet_evm::AddressMapping; +pub use sp_core::{H160, H256, U256}; pub use sp_runtime::{AccountId32, MultiAddress}; +pub use astar_primitives::ethereum_checked::AccountMapping; + #[cfg(feature = "shibuya")] pub use shibuya::*; #[cfg(feature = "shibuya")] mod shibuya { + use super::*; pub use shibuya_runtime::*; /// 1 SBY. pub const UNIT: Balance = SBY; + + // TODO: once Account Unification is finished, remove `alith` and `alicia`, + // which are mocks of two way account/address mapping. + + /// H160 address mapped from `ALICE`. + pub fn alith() -> H160 { + h160_from(ALICE) + } + + /// `AccountId32` mapped from `alith()`. + pub fn alicia() -> AccountId32 { + account_id_from(alith()) + } + + /// Convert `H160` to `AccountId32`. + pub fn account_id_from(address: H160) -> AccountId32 { + ::AddressMapping::into_account_id(address) + } + + /// Convert `AccountId32` to `H160`. + pub fn h160_from(account_id: AccountId32) -> H160 { + ::AccountMapping::into_h160(account_id) + } + + /// Deploy an EVM contract with code. + pub fn deploy_evm_contract(code: &str) -> H160 { + assert_ok!(EVM::create2( + RuntimeOrigin::root(), + alith(), + hex::decode(code).expect("invalid code hex"), + H256::zero(), + U256::zero(), + 1_000_000, + U256::from(DefaultBaseFeePerGas::get()), + None, + None, + vec![], + )); + match System::events() + .iter() + .last() + .expect("no event found") + .event + { + RuntimeEvent::EVM(pallet_evm::Event::Created { address }) => address, + _ => panic!("Deploy failed."), + } + } + + /// Deploy a WASM contract with its name. (The code is in `resource/`.) + pub fn deploy_wasm_contract(name: &str) -> AccountId32 { + let path = format!("ink-contracts/{}.wasm", name); + let code = std::fs::read(path).expect("invalid path"); + let instantiate_result = Contracts::bare_instantiate( + ALICE, + 0, + Weight::from_parts(10_000_000_000, 1024 * 1024), + None, + pallet_contracts_primitives::Code::Upload(code), + // `new` constructor + hex::decode("9bae9d5e").expect("invalid data hex"), + vec![], + pallet_contracts::DebugInfo::Skip, + pallet_contracts::CollectEvents::Skip, + ); + + let address = instantiate_result + .result + .expect("instantiation failed") + .account_id; + // On instantiation, the contract got existential deposit. + assert_eq!(Balances::free_balance(&address), ExistentialDeposit::get(),); + address + } } #[cfg(feature = "shiden")] @@ -107,6 +185,8 @@ pub fn new_test_ext() -> sp_io::TestExternalities { (ALICE, INITIAL_AMOUNT), (BOB, INITIAL_AMOUNT), (CAT, INITIAL_AMOUNT), + #[cfg(feature = "shibuya")] + (alicia(), INITIAL_AMOUNT), ]) .build() } diff --git a/tests/integration/src/xvm.rs b/tests/integration/src/xvm.rs new file mode 100644 index 0000000000..fb3ba9b72b --- /dev/null +++ b/tests/integration/src/xvm.rs @@ -0,0 +1,378 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +use crate::setup::*; + +use astar_primitives::xvm::{Context, VmId, XvmCall}; +use frame_support::{traits::Currency, weights::Weight}; +use parity_scale_codec::Encode; +use sp_runtime::MultiAddress; + +/* + +pragma solidity >=0.8.2 <0.9.0; + +contract Payable { + address payable public owner; + + constructor() payable { + owner = payable(msg.sender); + } + + // 0xd0e30db0 + function deposit() public payable {} + + // 0x3ccfd60b + function withdraw() public { + uint amount = address(this).balance; + (bool success, ) = owner.call{value: amount}(""); + require(success, "Failed to withdraw Ether"); + } +} + + */ +const EVM_PAYABLE: &str = "6080604052336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102d6806100536000396000f3fe6080604052600436106100345760003560e01c80633ccfd60b146100395780638da5cb5b14610050578063d0e30db01461007b575b600080fd5b34801561004557600080fd5b5061004e610085565b005b34801561005c57600080fd5b5061006561015b565b60405161007291906101c2565b60405180910390f35b61008361017f565b005b600047905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16826040516100d19061020e565b60006040518083038185875af1925050503d806000811461010e576040519150601f19603f3d011682016040523d82523d6000602084013e610113565b606091505b5050905080610157576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161014e90610280565b60405180910390fd5b5050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101ac82610181565b9050919050565b6101bc816101a1565b82525050565b60006020820190506101d760008301846101b3565b92915050565b600081905092915050565b50565b60006101f86000836101dd565b9150610203826101e8565b600082019050919050565b6000610219826101eb565b9150819050919050565b600082825260208201905092915050565b7f4661696c656420746f2077697468647261772045746865720000000000000000600082015250565b600061026a601883610223565b915061027582610234565b602082019050919050565b600060208201905081810360008301526102998161025d565b905091905056fea2646970667358221220bd8883b6a524d12ac9c29f105fdd1a0221a0436a79002f2a04e69d252596a62a64736f6c63430008120033"; + +/* WASM payable: + +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +mod payable { + #[ink(storage)] + pub struct Payable {} + + impl Payable { + #[ink(constructor)] + pub fn new() -> Self { + Self {} + } + + #[ink(message, payable, selector = 42)] + pub fn deposit(&self) {} + } +} + +*/ +const WASM_PAYABLE_NAME: &'static str = "payable"; + +/* Call WASM payable: + +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.8.2 <0.9.0; + +interface XVM { + function xvm_call( + uint8 vm_id, + bytes calldata to, + bytes calldata input, + uint256 value + ) external payable returns (bool success, bytes memory data); +} + +contract CallXVMPayble { + function call_xvm_payable(bytes calldata to, bytes calldata input, uint256 value) external payable returns (bool success, bytes memory data) { + return XVM(0x0000000000000000000000000000000000005005).xvm_call(0x1F, to, input, value); + } +} + + */ +const CALL_WASM_PAYBLE: &str = "608060405234801561001057600080fd5b506105e6806100206000396000f3fe60806040526004361061001e5760003560e01c80634012b91414610023575b600080fd5b61003d600480360381019061003891906101a3565b610054565b60405161004b9291906102e3565b60405180910390f35b6000606061500573ffffffffffffffffffffffffffffffffffffffff1663e5d9bac0601f89898989896040518763ffffffff1660e01b815260040161009e969594939291906103b0565b6000604051808303816000875af11580156100bd573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052508101906100e69190610554565b915091509550959350505050565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b60008083601f84011261012d5761012c610108565b5b8235905067ffffffffffffffff81111561014a5761014961010d565b5b60208301915083600182028301111561016657610165610112565b5b9250929050565b6000819050919050565b6101808161016d565b811461018b57600080fd5b50565b60008135905061019d81610177565b92915050565b6000806000806000606086880312156101bf576101be6100fe565b5b600086013567ffffffffffffffff8111156101dd576101dc610103565b5b6101e988828901610117565b9550955050602086013567ffffffffffffffff81111561020c5761020b610103565b5b61021888828901610117565b9350935050604061022b8882890161018e565b9150509295509295909350565b60008115159050919050565b61024d81610238565b82525050565b600081519050919050565b600082825260208201905092915050565b60005b8381101561028d578082015181840152602081019050610272565b60008484015250505050565b6000601f19601f8301169050919050565b60006102b582610253565b6102bf818561025e565b93506102cf81856020860161026f565b6102d881610299565b840191505092915050565b60006040820190506102f86000830185610244565b818103602083015261030a81846102aa565b90509392505050565b6000819050919050565b600060ff82169050919050565b6000819050919050565b600061034f61034a61034584610313565b61032a565b61031d565b9050919050565b61035f81610334565b82525050565b82818337600083830152505050565b6000610380838561025e565b935061038d838584610365565b61039683610299565b840190509392505050565b6103aa8161016d565b82525050565b60006080820190506103c56000830189610356565b81810360208301526103d8818789610374565b905081810360408301526103ed818587610374565b90506103fc60608301846103a1565b979650505050505050565b61041081610238565b811461041b57600080fd5b50565b60008151905061042d81610407565b92915050565b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61047082610299565b810181811067ffffffffffffffff8211171561048f5761048e610438565b5b80604052505050565b60006104a26100f4565b90506104ae8282610467565b919050565b600067ffffffffffffffff8211156104ce576104cd610438565b5b6104d782610299565b9050602081019050919050565b60006104f76104f2846104b3565b610498565b90508281526020810184848401111561051357610512610433565b5b61051e84828561026f565b509392505050565b600082601f83011261053b5761053a610108565b5b815161054b8482602086016104e4565b91505092915050565b6000806040838503121561056b5761056a6100fe565b5b60006105798582860161041e565b925050602083015167ffffffffffffffff81111561059a57610599610103565b5b6105a685828601610526565b915050925092905056fea264697066735822122047908cecfa9ace275a4ba96e787bb0d1541ec599c370983693c6cd9c1f5b7dbe64736f6c63430008120033"; + +/* Call EVM Payable: + +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +use ink::env::{DefaultEnvironment, Environment}; +use ink::prelude::vec::Vec; + +#[ink::contract(env = CustomEnvironment)] +mod call_xvm_payable { + use super::*; + + #[ink(storage)] + pub struct CallXvmPayable {} + + impl CallXvmPayable { + #[ink(constructor)] + pub fn new() -> Self { + Self {} + } + + #[ink(message, payable, selector = 42)] + pub fn call_xvm_payable( + &self, + target: Vec, + input: Vec, + ) -> CallResult { + let value = Self::env().transferred_value(); + // Calling EVM + Self::env().extension().call(0x0F, target, input, value) + } + } +} + +pub type CallResult = u32; + +#[ink::chain_extension] +pub trait XvmCall { + type ErrorCode = u32; + + #[ink(extension = 0x00010001, handle_status = false)] + fn call(vm_id: u8, target: Vec, input: Vec, value: u128) -> CallResult; +} + +pub enum CustomEnvironment {} +impl Environment for CustomEnvironment { + const MAX_EVENT_TOPICS: usize = ::MAX_EVENT_TOPICS; + + type AccountId = ::AccountId; + type Balance = ::Balance; + type Hash = ::Hash; + type BlockNumber = ::BlockNumber; + type Timestamp = ::Timestamp; + + type ChainExtension = XvmCall; +} + + */ +const CALL_EVM_PAYBLE_NAME: &'static str = "call_xvm_payable"; + +#[test] +fn evm_payable_call_via_xvm_works() { + new_test_ext().execute_with(|| { + let evm_payable_addr = deploy_evm_contract(EVM_PAYABLE); + + let value = UNIT; + assert_ok!(Xvm::call( + Context { + source_vm_id: VmId::Wasm, + weight_limit: Weight::from_parts(1_000_000_000, 1024 * 1024), + }, + VmId::Evm, + ALICE, + evm_payable_addr.as_ref().to_vec(), + // Calling `deposit` + hex::decode("d0e30db0").expect("invalid selector hex"), + value, + )); + assert_eq!( + Balances::free_balance(account_id_from(evm_payable_addr)), + value + ); + + assert_ok!(Xvm::call( + Context { + source_vm_id: VmId::Wasm, + weight_limit: Weight::from_parts(10_000_000_000, 1024 * 1024), + }, + VmId::Evm, + ALICE, + evm_payable_addr.as_ref().to_vec(), + // `Calling withdraw` + hex::decode("3ccfd60b").expect("invalid selector hex"), + 0, + )); + assert_eq!( + Balances::free_balance(account_id_from(evm_payable_addr)), + ExistentialDeposit::get(), + ); + }); +} + +#[test] +fn wasm_payable_call_via_xvm_works() { + new_test_ext().execute_with(|| { + let contract_addr = deploy_wasm_contract(WASM_PAYABLE_NAME); + + let prev_balance = Balances::free_balance(&contract_addr); + let value = UNIT; + assert_ok!(Xvm::call( + Context { + source_vm_id: VmId::Evm, + weight_limit: Weight::from_parts(10_000_000_000, 1024 * 1024), + }, + VmId::Wasm, + ALICE, + MultiAddress::::Id(contract_addr.clone()).encode(), + // Calling `deposit` + hex::decode("0000002a").expect("invalid selector hex"), + value + )); + assert_eq!( + Balances::free_balance(contract_addr.clone()), + value + prev_balance + ); + }); +} + +#[test] +fn calling_wasm_payable_from_evm_fails_if_caller_contract_balance_below_ed() { + new_test_ext().execute_with(|| { + let wasm_payable_addr = deploy_wasm_contract(WASM_PAYABLE_NAME); + let call_wasm_payable_addr = deploy_evm_contract(CALL_WASM_PAYBLE); + + let value = 1_000_000_000; + assert_ok!(EVM::call( + RuntimeOrigin::root(), + alith(), + call_wasm_payable_addr.clone(), + // to: 0x00a8f69d59df362b69a8d4acdb9001eb3e1b8d067b8fdaa70081aed945bde5c48c + // input: 0x0000002a (deposit) + // value: 1000000000 + hex::decode("4012b914000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000000000002100a8f69d59df362b69a8d4acdb9001eb3e1b8d067b8fdaa70081aed945bde5c48c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000002a00000000000000000000000000000000000000000000000000000000").expect("invalid call input hex"), + U256::from(value), + 1_000_000, + U256::from(DefaultBaseFeePerGas::get()), + None, + None, + vec![], + )); + + // TODO: after XVM error propagation finished, assert `pallet-evm` execution error + // and update balance assertions. + + // Transfer to EVM contract ok. + assert_eq!( + Balances::free_balance(&account_id_from(call_wasm_payable_addr)), + value, + ); + // Transfer from EVM contract to wasm Contract err. + assert_eq!( + Balances::free_balance(&wasm_payable_addr), + ExistentialDeposit::get(), + ); + }); +} + +#[test] +fn calling_wasm_payable_from_evm_works() { + new_test_ext().execute_with(|| { + let wasm_payable_addr = deploy_wasm_contract(WASM_PAYABLE_NAME); + let call_wasm_payable_addr = deploy_evm_contract(CALL_WASM_PAYBLE); + + let _ = Balances::deposit_creating(&account_id_from(call_wasm_payable_addr.clone()), ExistentialDeposit::get()); + + let prev_wasm_payable_balance = Balances::free_balance(&wasm_payable_addr); + let value = 1_000_000_000; + assert_ok!(EVM::call( + RuntimeOrigin::root(), + alith(), + call_wasm_payable_addr.clone(), + // to: 0x00a8f69d59df362b69a8d4acdb9001eb3e1b8d067b8fdaa70081aed945bde5c48c + // input: 0x0000002a (deposit) + // value: 1000000000 + hex::decode("4012b914000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000000000002100a8f69d59df362b69a8d4acdb9001eb3e1b8d067b8fdaa70081aed945bde5c48c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000002a00000000000000000000000000000000000000000000000000000000").expect("invalid call input hex"), + U256::from(value), + 1_000_000, + U256::from(DefaultBaseFeePerGas::get()), + None, + None, + vec![], + )); + let recieved = Balances::free_balance(&wasm_payable_addr) - prev_wasm_payable_balance; + assert_eq!(recieved, value); + }); +} + +#[test] +fn calling_evm_payable_from_wasm_works() { + new_test_ext().execute_with(|| { + let evm_payable_addr = deploy_evm_contract(EVM_PAYABLE); + let wasm_address = deploy_wasm_contract(CALL_EVM_PAYBLE_NAME); + + let value = UNIT; + + // TODO: after Account Unification finished, remove this mock account. + // It is needed now because currently the `AccountMapping` and `AddressMapping` are + // both one way mapping. + let mock_unified_wasm_account = account_id_from(h160_from(wasm_address.clone())); + let _ = Balances::deposit_creating(&mock_unified_wasm_account, value); + + let evm_payable = evm_payable_addr.as_ref().to_vec(); + let deposit_func = hex::decode("d0e30db0").expect("invalid deposit function hex"); + let input = hex::decode("0000002a") + .expect("invalid selector hex") + .iter() + .chain(evm_payable.encode().iter()) + .chain(deposit_func.encode().iter()) + .cloned() + .collect::>(); + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + MultiAddress::Id(wasm_address.clone()), + value, + Weight::from_parts(10_000_000_000, 1024 * 1024), + None, + input, + )); + + assert_eq!( + Balances::free_balance(account_id_from(evm_payable_addr)), + value + ); + + // TODO: after Account Unification finished, enable the wasm address balance check + // and remove the mock account balance check. + // assert_eq!(Balances::free_balance(&wasm_address), ExistentialDeposit::get()); + assert_eq!(Balances::free_balance(&mock_unified_wasm_account), 0); + }); +} + +#[test] +fn reentrance_not_allowed() { + new_test_ext().execute_with(|| { + // Call path: WASM -> EVM -> WASM + let call_evm_payable_address = deploy_wasm_contract(CALL_EVM_PAYBLE_NAME); + let call_wasm_payable_addr = deploy_evm_contract(CALL_WASM_PAYBLE); + let _ = deploy_wasm_contract(WASM_PAYABLE_NAME); + + // to: 0x00a8f69d59df362b69a8d4acdb9001eb3e1b8d067b8fdaa70081aed945bde5c48c + // input: 0x0000002a (deposit) + // value: 1000000000 + let call_wasm_payable_input = hex::decode("4012b914000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000000000002100a8f69d59df362b69a8d4acdb9001eb3e1b8d067b8fdaa70081aed945bde5c48c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000002a00000000000000000000000000000000000000000000000000000000").expect("invalid call input hex"); + let input = hex::decode("0000002a") + .expect("invalid selector hex") + .iter() + .chain(call_wasm_payable_addr.as_ref().to_vec().encode().iter()) + .chain(call_wasm_payable_input.encode().iter()) + .cloned() + .collect::>(); + assert_ok!( + Contracts::call( + RuntimeOrigin::signed(ALICE), + MultiAddress::Id(call_evm_payable_address.clone()), + 0, + Weight::from_parts(10_000_000_000, 1024 * 1024), + None, + input, + ) + ); + + // TODO: after XVM error propagation finished, replace with assert `ReentranceDenied` + // error. + let wasm_entrance_count = System::events().iter().filter(|record| { + match record.event { + RuntimeEvent::Contracts(pallet_contracts::Event::Called { .. }) => true, + _ => false, + } + }).count(); + assert_eq!(wasm_entrance_count, 1); + }); +} diff --git a/tests/xcm-simulator/src/mocks/parachain.rs b/tests/xcm-simulator/src/mocks/parachain.rs index 8be1841ff6..0c00aa5d48 100644 --- a/tests/xcm-simulator/src/mocks/parachain.rs +++ b/tests/xcm-simulator/src/mocks/parachain.rs @@ -156,6 +156,8 @@ impl pallet_assets::Config for Runtime { type RemoveItemsLimit = ConstU32<100>; type CallbackHandle = (); type WeightInfo = pallet_assets::weights::SubstrateWeight; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); } impl pallet_timestamp::Config for Runtime { @@ -391,7 +393,6 @@ parameter_types! { impl pallet_xc_asset_config::Config for Runtime { type RuntimeEvent = RuntimeEvent; type AssetId = AssetId; - type XcAssetChanged = (); type ManagerOrigin = EnsureRoot; type WeightInfo = pallet_xc_asset_config::weights::SubstrateWeight; }