diff --git a/Cargo.lock b/Cargo.lock index 3b518e57b1..b4b9f80ec5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6419,6 +6419,7 @@ dependencies = [ "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-transactor", + "pallet-xcm-weight-trader", "parachains-common", "parity-scale-codec", "polkadot-core-primitives", @@ -6482,7 +6483,7 @@ dependencies = [ [[package]] name = "moonbeam-cli" -version = "0.40.0" +version = "0.41.0" dependencies = [ "clap", "clap-num", @@ -6518,7 +6519,7 @@ dependencies = [ [[package]] name = "moonbeam-cli-opt" -version = "0.40.0" +version = "0.41.0" dependencies = [ "account", "bip32", @@ -6931,6 +6932,7 @@ dependencies = [ "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-transactor", + "pallet-xcm-weight-trader", "parachains-common", "parity-scale-codec", "polkadot-core-primitives", @@ -7023,11 +7025,13 @@ dependencies = [ "pallet-scheduler", "pallet-sudo", "pallet-timestamp", + "pallet-transaction-payment", "pallet-treasury", "pallet-utility", "pallet-whitelist", "pallet-xcm", "pallet-xcm-transactor", + "pallet-xcm-weight-trader", "parity-scale-codec", "precompile-utils", "sp-api", @@ -7046,7 +7050,7 @@ dependencies = [ [[package]] name = "moonbeam-service" -version = "0.40.0" +version = "0.41.0" dependencies = [ "async-backing-primitives", "async-io 1.13.0", @@ -7357,6 +7361,7 @@ dependencies = [ "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-transactor", + "pallet-xcm-weight-trader", "parachains-common", "parity-scale-codec", "polkadot-core-primitives", @@ -8136,7 +8141,7 @@ dependencies = [ [[package]] name = "orml-traits" version = "0.10.0" -source = "git+https://github.com/moonbeam-foundation/open-runtime-module-library?branch=moonbeam-polkadot-v1.11.0#b86bc7dda8cc59e57c8b5dcfa813ebdbf76bee0e" +source = "git+https://github.com/moonbeam-foundation/open-runtime-module-library?branch=moonbeam-polkadot-v1.11.0#f653f239532bd72b6bc4a7290db10790a38b0b92" dependencies = [ "frame-support", "impl-trait-for-tuples", @@ -8156,7 +8161,7 @@ dependencies = [ [[package]] name = "orml-utilities" version = "0.10.0" -source = "git+https://github.com/moonbeam-foundation/open-runtime-module-library?branch=moonbeam-polkadot-v1.11.0#b86bc7dda8cc59e57c8b5dcfa813ebdbf76bee0e" +source = "git+https://github.com/moonbeam-foundation/open-runtime-module-library?branch=moonbeam-polkadot-v1.11.0#f653f239532bd72b6bc4a7290db10790a38b0b92" dependencies = [ "frame-support", "parity-scale-codec", @@ -8171,7 +8176,7 @@ dependencies = [ [[package]] name = "orml-xcm-support" version = "0.10.0" -source = "git+https://github.com/moonbeam-foundation/open-runtime-module-library?branch=moonbeam-polkadot-v1.11.0#b86bc7dda8cc59e57c8b5dcfa813ebdbf76bee0e" +source = "git+https://github.com/moonbeam-foundation/open-runtime-module-library?branch=moonbeam-polkadot-v1.11.0#f653f239532bd72b6bc4a7290db10790a38b0b92" dependencies = [ "frame-support", "orml-traits", @@ -8185,7 +8190,7 @@ dependencies = [ [[package]] name = "orml-xtokens" version = "0.10.0" -source = "git+https://github.com/moonbeam-foundation/open-runtime-module-library?branch=moonbeam-polkadot-v1.11.0#b86bc7dda8cc59e57c8b5dcfa813ebdbf76bee0e" +source = "git+https://github.com/moonbeam-foundation/open-runtime-module-library?branch=moonbeam-polkadot-v1.11.0#f653f239532bd72b6bc4a7290db10790a38b0b92" dependencies = [ "frame-support", "frame-system", @@ -10581,6 +10586,27 @@ dependencies = [ "xcm-primitives 0.1.1", ] +[[package]] +name = "pallet-xcm-weight-trader" +version = "0.1.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-tracing", + "staging-xcm", + "staging-xcm-executor", + "xcm-fee-payment-runtime-api", +] + [[package]] name = "parachains-common" version = "7.0.0" diff --git a/Cargo.toml b/Cargo.toml index 048f42d276..43f469b2dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ members = [ "pallets/precompile-benchmarks", "pallets/proxy-genesis-companion", "pallets/xcm-transactor", + "pallets/xcm-weight-trader", "precompiles/balances-erc20", "precompiles/batch", "precompiles/call-permit", @@ -107,6 +108,7 @@ pallet-parachain-staking = { path = "pallets/parachain-staking", default-feature pallet-precompile-benchmarks = { path = "pallets/precompile-benchmarks", default-features = false } pallet-proxy-genesis-companion = { path = "pallets/proxy-genesis-companion", default-features = false } pallet-xcm-transactor = { path = "pallets/xcm-transactor", default-features = false } +pallet-xcm-weight-trader = { path = "pallets/xcm-weight-trader", default-features = false } precompile-foreign-asset-migrator = { path = "precompiles/foreign-asset-migrator", default-features = false } xcm-primitives = { path = "primitives/xcm", default-features = false } @@ -185,6 +187,7 @@ sp-runtime-interface = { git = "https://github.com/moonbeam-foundation/polkadot- sp-session = { git = "https://github.com/moonbeam-foundation/polkadot-sdk", branch = "moonbeam-polkadot-v1.11.0", default-features = false } sp-std = { git = "https://github.com/moonbeam-foundation/polkadot-sdk", branch = "moonbeam-polkadot-v1.11.0", default-features = false } sp-state-machine = { git = "https://github.com/moonbeam-foundation/polkadot-sdk", branch = "moonbeam-polkadot-v1.11.0", default-features = false } +sp-tracing = { git = "https://github.com/moonbeam-foundation/polkadot-sdk", branch = "moonbeam-polkadot-v1.11.0", default-features = false } sp-transaction-pool = { git = "https://github.com/moonbeam-foundation/polkadot-sdk", branch = "moonbeam-polkadot-v1.11.0", default-features = false } sp-trie = { git = "https://github.com/moonbeam-foundation/polkadot-sdk", branch = "moonbeam-polkadot-v1.11.0", default-features = false } sp-version = { git = "https://github.com/moonbeam-foundation/polkadot-sdk", branch = "moonbeam-polkadot-v1.11.0", default-features = false } diff --git a/README.md b/README.md index 3a0bcbc507..39b45bff5e 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Docker images are published for every tagged release. Learn more with `moonbeam ```bash # Join the public testnet -docker run --network="host" moonbeamfoundation/moonbeam:v0.39.0 --chain alphanet +docker run --network="host" moonbeamfoundation/moonbeam:v0.40.0 --chain alphanet ``` You can find more detailed instructions to [run a full node in our TestNet](https://docs.moonbeam.network/node-operators/networks/run-a-node/overview/) @@ -28,7 +28,7 @@ locally. You can quickly set up a single node without a relay chain backing it u ```bash # Run a dev service node -docker run --network="host" moonbeamfoundation/moonbeam:v0.39.0 --dev +docker run --network="host" moonbeamfoundation/moonbeam:v0.40.0 --dev ``` For more information, see our detailed instructions to [run a development node](https://docs.moonbeam.network/builders/get-started/networks/moonbeam-dev/) @@ -39,10 +39,10 @@ The above command will start the node in instant seal mode. It creates a block w ```bash # Author a block every 6 seconds. -docker run --network="host" moonbeamfoundation/moonbeam:v0.39.0 --dev --sealing 6000 +docker run --network="host" moonbeamfoundation/moonbeam:v0.40.0 --dev --sealing 6000 # Manually control the block authorship and finality -docker run --network="host" moonbeamfoundation/moonbeam:v0.39.0 --dev --sealing manual +docker run --network="host" moonbeamfoundation/moonbeam:v0.40.0 --dev --sealing manual ``` ### Prefunded Development Addresses diff --git a/node/cli-opt/Cargo.toml b/node/cli-opt/Cargo.toml index 691d07b916..dcdab5e995 100644 --- a/node/cli-opt/Cargo.toml +++ b/node/cli-opt/Cargo.toml @@ -4,7 +4,7 @@ authors = { workspace = true } edition = "2021" homepage = "https://moonbeam.network" license = "GPL-3.0-only" -version = "0.40.0" +version = "0.41.0" [dependencies] bip32 = { workspace = true, features = ["bip39"] } diff --git a/node/cli/Cargo.toml b/node/cli/Cargo.toml index 2abb7aa2e5..227bf7f3e0 100644 --- a/node/cli/Cargo.toml +++ b/node/cli/Cargo.toml @@ -2,7 +2,7 @@ name = "moonbeam-cli" authors = { workspace = true } edition = "2021" -version = "0.40.0" +version = "0.41.0" [dependencies] clap = { workspace = true, features = ["derive"] } diff --git a/node/service/Cargo.toml b/node/service/Cargo.toml index 5fbf6d898b..d298c2c23d 100644 --- a/node/service/Cargo.toml +++ b/node/service/Cargo.toml @@ -4,7 +4,7 @@ authors = { workspace = true } edition = "2021" homepage = "https://moonbeam.network" license = "GPL-3.0-only" -version = "0.40.0" +version = "0.41.0" [dependencies] async-io = { workspace = true } diff --git a/pallets/asset-manager/src/benchmarks.rs b/pallets/asset-manager/src/benchmarks.rs index a444a0ae1b..e4c1ec26d7 100644 --- a/pallets/asset-manager/src/benchmarks.rs +++ b/pallets/asset-manager/src/benchmarks.rs @@ -36,47 +36,7 @@ benchmarks! { assert_eq!(Pallet::::asset_id_type(asset_id), Some(asset_type)); } - set_asset_units_per_second { - // We make it dependent on the number of existing assets already - let x in 5..100; - for i in 0..x { - let asset_type: T::ForeignAssetType = Location::new( - 0, - X1(GeneralIndex(i as u128)) - ).into(); - let metadata = T::AssetRegistrarMetadata::default(); - let amount = 1u32.into(); - Pallet::::register_foreign_asset( - RawOrigin::Root.into(), - asset_type.clone(), - metadata, - amount, - true - )?; - Pallet::::set_asset_units_per_second(RawOrigin::Root.into(), asset_type.clone(), 1, i)?; - } - - // does not really matter what we register, as long as it is different than the previous - let asset_type = T::ForeignAssetType::default(); - let metadata = T::AssetRegistrarMetadata::default(); - let amount = 1u32.into(); - let asset_id: T::AssetId = asset_type.clone().into(); - Pallet::::register_foreign_asset( - RawOrigin::Root.into(), - asset_type.clone(), - metadata, - amount, - true - )?; - - }: _(RawOrigin::Root, asset_type.clone(), 1, x) - verify { - assert!(Pallet::::supported_fee_payment_assets().contains(&asset_type)); - assert_eq!(Pallet::::asset_type_units_per_second(asset_type), Some(1)); - } - change_existing_asset_type { - // We make it dependent on the number of existing assets already let x in 5..100; for i in 0..x { let asset_type: T::ForeignAssetType = Location::new(0, X1(GeneralIndex(i as u128))).into(); @@ -89,7 +49,6 @@ benchmarks! { amount, true )?; - Pallet::::set_asset_units_per_second(RawOrigin::Root.into(), asset_type.clone(), 1, i)?; } let new_asset_type = T::ForeignAssetType::default(); @@ -101,40 +60,9 @@ benchmarks! { }: _(RawOrigin::Root, asset_id_to_be_changed, new_asset_type.clone(), x) verify { assert_eq!(Pallet::::asset_id_type(asset_id_to_be_changed), Some(new_asset_type.clone())); - assert_eq!(Pallet::::asset_type_units_per_second(&new_asset_type), Some(1)); - assert!(Pallet::::supported_fee_payment_assets().contains(&new_asset_type)); - } - - remove_supported_asset { - // We make it dependent on the number of existing assets already - let x in 5..100; - for i in 0..x { - let asset_type: T::ForeignAssetType = Location::new(0, X1(GeneralIndex(i as u128))).into(); - let metadata = T::AssetRegistrarMetadata::default(); - let amount = 1u32.into(); - Pallet::::register_foreign_asset( - RawOrigin::Root.into(), - asset_type.clone(), - metadata, - amount, - true - )?; - Pallet::::set_asset_units_per_second(RawOrigin::Root.into(), asset_type.clone(), 1, i)?; - } - let asset_type_to_be_removed: T::ForeignAssetType = Location::new( - 0, - X1(GeneralIndex((x-1) as u128)) - ).into(); - // We try to remove the last asset type - }: _(RawOrigin::Root, asset_type_to_be_removed.clone(), x) - verify { - assert!(!Pallet::::supported_fee_payment_assets().contains(&asset_type_to_be_removed)); - assert_eq!(Pallet::::asset_type_units_per_second(asset_type_to_be_removed), None); } remove_existing_asset_type { - // We make it dependent on the number of existing assets already - // Worst case is we need to remove it from SupportedAAssetsFeePayment too let x in 5..100; for i in 0..x { let asset_type: T::ForeignAssetType = Location::new(0, X1(GeneralIndex(i as u128))).into(); @@ -147,7 +75,6 @@ benchmarks! { amount, true )?; - Pallet::::set_asset_units_per_second(RawOrigin::Root.into(), asset_type.clone(), 1, i)?; } let asset_type_to_be_removed: T::ForeignAssetType = Location::new( @@ -158,8 +85,6 @@ benchmarks! { }: _(RawOrigin::Root, asset_id, x) verify { assert!(Pallet::::asset_id_type(asset_id).is_none()); - assert!(Pallet::::asset_type_units_per_second(&asset_type_to_be_removed).is_none()); - assert!(!Pallet::::supported_fee_payment_assets().contains(&asset_type_to_be_removed)); } } diff --git a/pallets/asset-manager/src/lib.rs b/pallets/asset-manager/src/lib.rs index e7ee00c3e9..4d5813a326 100644 --- a/pallets/asset-manager/src/lib.rs +++ b/pallets/asset-manager/src/lib.rs @@ -27,8 +27,6 @@ //! //! This pallet has eight extrinsics: register_foreign_asset, which registers a foreign //! asset in this pallet and creates the asset as dictated by the AssetRegistrar trait. -//! set_asset_units_per_second: which sets the unit per second that should be charged for -//! a particular asset. //! change_existing_asset_type: which allows to update the correspondence between AssetId and //! AssetType //! remove_supported_asset: which removes an asset from the supported assets for fee payment @@ -57,7 +55,6 @@ pub mod pallet { use frame_system::pallet_prelude::*; use parity_scale_codec::HasCompact; use sp_runtime::traits::{AccountIdConversion, AtLeast32BitUnsigned}; - use sp_std::vec::Vec; #[pallet::pallet] #[pallet::without_storage_info] @@ -105,28 +102,6 @@ pub mod pallet { } } - impl xcm_primitives::UnitsToWeightRatio for Pallet { - fn payment_is_supported(asset_type: T::ForeignAssetType) -> bool { - SupportedFeePaymentAssets::::get() - .binary_search(&asset_type) - .is_ok() - } - fn get_units_per_second(asset_type: T::ForeignAssetType) -> Option { - AssetTypeUnitsPerSecond::::get(asset_type) - } - #[cfg(feature = "runtime-benchmarks")] - fn set_units_per_second(asset_type: T::ForeignAssetType, fee_per_second: u128) { - // Grab supported assets - let mut supported_assets = SupportedFeePaymentAssets::::get(); - // Only if the asset is not supported we need to push it - if let Err(index) = supported_assets.binary_search(&asset_type) { - supported_assets.insert(index, asset_type.clone()); - SupportedFeePaymentAssets::::put(supported_assets); - } - AssetTypeUnitsPerSecond::::insert(&asset_type, &fee_per_second); - } - } - #[pallet::config] pub trait Config: frame_system::Config { type RuntimeEvent: From> + IsType<::RuntimeEvent>; @@ -177,10 +152,8 @@ pub mod pallet { metadata: T::AssetRegistrarMetadata, }, /// Changed the amount of units we are charging per execution second for a given asset - UnitsPerSecondChanged { - asset_type: T::ForeignAssetType, - units_per_second: u128, - }, + #[deprecated(note = "Should not be used")] + UnitsPerSecondChanged, /// Changed the xcm type mapping for a given asset id ForeignAssetXcmLocationChanged { asset_id: T::AssetId, @@ -218,21 +191,6 @@ pub mod pallet { pub type AssetTypeId = StorageMap<_, Blake2_128Concat, T::ForeignAssetType, T::AssetId>; - /// Stores the units per second for local execution for a AssetType. - /// This is used to know how to charge for XCM execution in a particular - /// asset - /// Not all assets might contain units per second, hence the different storage - #[pallet::storage] - #[pallet::getter(fn asset_type_units_per_second)] - pub type AssetTypeUnitsPerSecond = - StorageMap<_, Blake2_128Concat, T::ForeignAssetType, u128>; - - // Supported fee asset payments - #[pallet::storage] - #[pallet::getter(fn supported_fee_payment_assets)] - pub type SupportedFeePaymentAssets = - StorageValue<_, Vec, ValueQuery>; - #[pallet::call] impl Pallet { /// Register new asset with the asset manager @@ -275,68 +233,19 @@ pub mod pallet { Ok(()) } - /// Change the amount of units we are charging per execution second - /// for a given ForeignAssetType - #[pallet::call_index(1)] - #[pallet::weight(T::WeightInfo::set_asset_units_per_second(*num_assets_weight_hint))] - pub fn set_asset_units_per_second( - origin: OriginFor, - asset_type: T::ForeignAssetType, - units_per_second: u128, - num_assets_weight_hint: u32, - ) -> DispatchResult { - T::ForeignAssetModifierOrigin::ensure_origin(origin)?; - - // Ensure such an assetId does not exist - ensure!( - AssetTypeId::::get(&asset_type).is_some(), - Error::::AssetDoesNotExist - ); - - // Grab supported assets - let mut supported_assets = SupportedFeePaymentAssets::::get(); - - ensure!( - num_assets_weight_hint >= (supported_assets.len() as u32), - Error::::TooLowNumAssetsWeightHint - ); - - // Only if the asset is not supported we need to push it - if let Err(index) = supported_assets.binary_search(&asset_type) { - supported_assets.insert(index, asset_type.clone()); - SupportedFeePaymentAssets::::put(supported_assets); - } - - AssetTypeUnitsPerSecond::::insert(&asset_type, &units_per_second); - - Self::deposit_event(Event::UnitsPerSecondChanged { - asset_type, - units_per_second, - }); - Ok(()) - } - /// Change the xcm type mapping for a given assetId /// We also change this if the previous units per second where pointing at the old /// assetType #[pallet::call_index(2)] - #[pallet::weight(T::WeightInfo::change_existing_asset_type(*num_assets_weight_hint))] + #[pallet::weight(T::WeightInfo::change_existing_asset_type())] pub fn change_existing_asset_type( origin: OriginFor, asset_id: T::AssetId, new_asset_type: T::ForeignAssetType, - num_assets_weight_hint: u32, + _num_assets_weight_hint: u32, ) -> DispatchResult { T::ForeignAssetModifierOrigin::ensure_origin(origin)?; - // Grab supported assets - let mut supported_assets = SupportedFeePaymentAssets::::get(); - - ensure!( - num_assets_weight_hint >= (supported_assets.len() as u32), - Error::::TooLowNumAssetsWeightHint - ); - let previous_asset_type = AssetIdType::::get(&asset_id).ok_or(Error::::AssetDoesNotExist)?; @@ -347,26 +256,6 @@ pub mod pallet { // Remove previous asset type info AssetTypeId::::remove(&previous_asset_type); - // Change AssetTypeUnitsPerSecond - if let Some(units) = AssetTypeUnitsPerSecond::::get(&previous_asset_type) { - // Only if the old asset is supported we need to remove it - if let Ok(index) = supported_assets.binary_search(&previous_asset_type) { - supported_assets.remove(index); - } - - // Only if the new asset is not supported we need to push it - if let Err(index) = supported_assets.binary_search(&new_asset_type) { - supported_assets.insert(index, new_asset_type.clone()); - } - - // Insert supported fee payment assets - SupportedFeePaymentAssets::::put(supported_assets); - - // Remove previous asset type info - AssetTypeUnitsPerSecond::::remove(&previous_asset_type); - AssetTypeUnitsPerSecond::::insert(&new_asset_type, units); - } - Self::deposit_event(Event::ForeignAssetXcmLocationChanged { asset_id, new_asset_type, @@ -374,56 +263,16 @@ pub mod pallet { Ok(()) } - #[pallet::call_index(3)] - #[pallet::weight(T::WeightInfo::remove_supported_asset(*num_assets_weight_hint))] - pub fn remove_supported_asset( - origin: OriginFor, - asset_type: T::ForeignAssetType, - num_assets_weight_hint: u32, - ) -> DispatchResult { - T::ForeignAssetModifierOrigin::ensure_origin(origin)?; - - // Grab supported assets - let mut supported_assets = SupportedFeePaymentAssets::::get(); - - ensure!( - num_assets_weight_hint >= (supported_assets.len() as u32), - Error::::TooLowNumAssetsWeightHint - ); - - // Only if the old asset is supported we need to remove it - if let Ok(index) = supported_assets.binary_search(&asset_type) { - supported_assets.remove(index); - } - - // Insert - SupportedFeePaymentAssets::::put(supported_assets); - - // Remove - AssetTypeUnitsPerSecond::::remove(&asset_type); - - Self::deposit_event(Event::SupportedAssetRemoved { asset_type }); - Ok(()) - } - /// Remove a given assetId -> assetType association #[pallet::call_index(4)] - #[pallet::weight(T::WeightInfo::remove_existing_asset_type(*num_assets_weight_hint))] + #[pallet::weight(T::WeightInfo::remove_existing_asset_type())] pub fn remove_existing_asset_type( origin: OriginFor, asset_id: T::AssetId, - num_assets_weight_hint: u32, + _num_assets_weight_hint: u32, ) -> DispatchResult { T::ForeignAssetModifierOrigin::ensure_origin(origin)?; - // Grab supported assets - let mut supported_assets = SupportedFeePaymentAssets::::get(); - - ensure!( - num_assets_weight_hint >= (supported_assets.len() as u32), - Error::::TooLowNumAssetsWeightHint - ); - let asset_type = AssetIdType::::get(&asset_id).ok_or(Error::::AssetDoesNotExist)?; @@ -431,15 +280,6 @@ pub mod pallet { AssetIdType::::remove(&asset_id); // Remove from AssetTypeId AssetTypeId::::remove(&asset_type); - // Remove previous asset type units per second - AssetTypeUnitsPerSecond::::remove(&asset_type); - - // Only if the old asset is supported we need to remove it - if let Ok(index) = supported_assets.binary_search(&asset_type) { - supported_assets.remove(index); - // Insert - SupportedFeePaymentAssets::::put(supported_assets); - } Self::deposit_event(Event::ForeignAssetRemoved { asset_id, @@ -457,27 +297,19 @@ pub mod pallet { let dispatch_info_weight = T::AssetRegistrar::destroy_asset_dispatch_info_weight( *asset_id ); - T::WeightInfo::remove_existing_asset_type(*num_assets_weight_hint) + T::WeightInfo::remove_existing_asset_type() .saturating_add(dispatch_info_weight) })] pub fn destroy_foreign_asset( origin: OriginFor, asset_id: T::AssetId, - num_assets_weight_hint: u32, + _num_assets_weight_hint: u32, ) -> DispatchResult { T::ForeignAssetModifierOrigin::ensure_origin(origin)?; T::AssetRegistrar::destroy_foreign_asset(asset_id) .map_err(|_| Error::::ErrorDestroyingAsset)?; - // Grab supported assets - let mut supported_assets = SupportedFeePaymentAssets::::get(); - - ensure!( - num_assets_weight_hint >= (supported_assets.len() as u32), - Error::::TooLowNumAssetsWeightHint - ); - let asset_type = AssetIdType::::get(&asset_id).ok_or(Error::::AssetDoesNotExist)?; @@ -485,15 +317,6 @@ pub mod pallet { AssetIdType::::remove(&asset_id); // Remove from AssetTypeId AssetTypeId::::remove(&asset_type); - // Remove previous asset type units per second - AssetTypeUnitsPerSecond::::remove(&asset_type); - - // Only if the old asset is supported we need to remove it - if let Ok(index) = supported_assets.binary_search(&asset_type) { - supported_assets.remove(index); - // Insert - SupportedFeePaymentAssets::::put(supported_assets); - } Self::deposit_event(Event::ForeignAssetDestroyed { asset_id, diff --git a/pallets/asset-manager/src/tests.rs b/pallets/asset-manager/src/tests.rs index dee27f5229..aa2037b727 100644 --- a/pallets/asset-manager/src/tests.rs +++ b/pallets/asset-manager/src/tests.rs @@ -75,44 +75,6 @@ fn test_asset_exists_error() { }); } -#[test] -fn test_root_can_change_units_per_second() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(AssetManager::register_foreign_asset( - RuntimeOrigin::root(), - MockAssetType::MockAsset(1), - 0u32.into(), - 1u32.into(), - true - )); - - assert_ok!(AssetManager::set_asset_units_per_second( - RuntimeOrigin::root(), - MockAssetType::MockAsset(1), - 200u128.into(), - 0 - )); - - assert_eq!( - AssetManager::asset_type_units_per_second(MockAssetType::MockAsset(1)).unwrap(), - 200 - ); - assert!(AssetManager::supported_fee_payment_assets().contains(&MockAssetType::MockAsset(1))); - - expect_events(vec![ - crate::Event::ForeignAssetRegistered { - asset_id: 1, - asset: MockAssetType::MockAsset(1), - metadata: 0, - }, - crate::Event::UnitsPerSecondChanged { - asset_type: MockAssetType::MockAsset(1), - units_per_second: 200, - }, - ]) - }); -} - #[test] fn test_regular_user_cannot_call_extrinsics() { ExtBuilder::default().build().execute_with(|| { @@ -127,16 +89,6 @@ fn test_regular_user_cannot_call_extrinsics() { sp_runtime::DispatchError::BadOrigin ); - assert_noop!( - AssetManager::set_asset_units_per_second( - RuntimeOrigin::signed(1), - MockAssetType::MockAsset(1), - 200u128.into(), - 0 - ), - sp_runtime::DispatchError::BadOrigin - ); - assert_noop!( AssetManager::change_existing_asset_type( RuntimeOrigin::signed(1), @@ -160,13 +112,6 @@ fn test_root_can_change_asset_id_type() { true )); - assert_ok!(AssetManager::set_asset_units_per_second( - RuntimeOrigin::root(), - MockAssetType::MockAsset(1), - 200u128.into(), - 0 - )); - assert_ok!(AssetManager::change_existing_asset_type( RuntimeOrigin::root(), 1, @@ -174,16 +119,7 @@ fn test_root_can_change_asset_id_type() { 1 )); - // New one contains the new asset type units per second - assert_eq!( - AssetManager::asset_type_units_per_second(MockAssetType::MockAsset(2)).unwrap(), - 200 - ); - - // Old one does not contain units per second - assert!(AssetManager::asset_type_units_per_second(MockAssetType::MockAsset(1)).is_none()); - - // New associations are stablished + // New associations are established assert_eq!( AssetManager::asset_id_type(1).unwrap(), MockAssetType::MockAsset(2) @@ -202,10 +138,6 @@ fn test_root_can_change_asset_id_type() { asset: MockAssetType::MockAsset(1), metadata: 0, }, - crate::Event::UnitsPerSecondChanged { - asset_type: MockAssetType::MockAsset(1), - units_per_second: 200, - }, crate::Event::ForeignAssetXcmLocationChanged { asset_id: 1, new_asset_type: MockAssetType::MockAsset(2), @@ -214,153 +146,9 @@ fn test_root_can_change_asset_id_type() { }); } -#[test] -fn test_change_units_per_second_after_setting_it_once() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(AssetManager::register_foreign_asset( - RuntimeOrigin::root(), - MockAssetType::MockAsset(1), - 0u32.into(), - 1u32.into(), - true, - )); - - assert_ok!(AssetManager::set_asset_units_per_second( - RuntimeOrigin::root(), - MockAssetType::MockAsset(1), - 200u128.into(), - 0 - )); - - assert_eq!( - AssetManager::asset_type_units_per_second(MockAssetType::MockAsset(1)).unwrap(), - 200 - ); - assert!(AssetManager::supported_fee_payment_assets().contains(&MockAssetType::MockAsset(1))); - - assert_ok!(AssetManager::set_asset_units_per_second( - RuntimeOrigin::root(), - MockAssetType::MockAsset(1), - 100u128.into(), - 1 - )); - - assert_eq!( - AssetManager::asset_type_units_per_second(MockAssetType::MockAsset(1)).unwrap(), - 100 - ); - assert!(AssetManager::supported_fee_payment_assets().contains(&MockAssetType::MockAsset(1))); - - expect_events(vec![ - crate::Event::ForeignAssetRegistered { - asset_id: 1, - asset: MockAssetType::MockAsset(1), - metadata: 0, - }, - crate::Event::UnitsPerSecondChanged { - asset_type: MockAssetType::MockAsset(1), - units_per_second: 200, - }, - crate::Event::UnitsPerSecondChanged { - asset_type: MockAssetType::MockAsset(1), - units_per_second: 100, - }, - ]); - }); -} - -#[test] -fn test_root_can_change_units_per_second_and_then_remove() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(AssetManager::register_foreign_asset( - RuntimeOrigin::root(), - MockAssetType::MockAsset(1), - 0u32.into(), - 1u32.into(), - true, - )); - - assert_ok!(AssetManager::set_asset_units_per_second( - RuntimeOrigin::root(), - MockAssetType::MockAsset(1), - 200u128.into(), - 0 - )); - - assert_eq!( - AssetManager::asset_type_units_per_second(MockAssetType::MockAsset(1)).unwrap(), - 200 - ); - assert!(AssetManager::supported_fee_payment_assets().contains(&MockAssetType::MockAsset(1))); - - assert_ok!(AssetManager::remove_supported_asset( - RuntimeOrigin::root(), - MockAssetType::MockAsset(1), - 1, - )); - - assert!( - !AssetManager::supported_fee_payment_assets().contains(&MockAssetType::MockAsset(1)) - ); - - expect_events(vec![ - crate::Event::ForeignAssetRegistered { - asset_id: 1, - asset: MockAssetType::MockAsset(1), - metadata: 0, - }, - crate::Event::UnitsPerSecondChanged { - asset_type: MockAssetType::MockAsset(1), - units_per_second: 200, - }, - crate::Event::SupportedAssetRemoved { - asset_type: MockAssetType::MockAsset(1), - }, - ]); - }); -} - -#[test] -fn test_weight_hint_error() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(AssetManager::register_foreign_asset( - RuntimeOrigin::root(), - MockAssetType::MockAsset(1), - 0u32.into(), - 1u32.into(), - true, - )); - - assert_ok!(AssetManager::set_asset_units_per_second( - RuntimeOrigin::root(), - MockAssetType::MockAsset(1), - 200u128.into(), - 0 - )); - - assert_noop!( - AssetManager::remove_supported_asset( - RuntimeOrigin::root(), - MockAssetType::MockAsset(1), - 0 - ), - Error::::TooLowNumAssetsWeightHint - ); - }); -} - #[test] fn test_asset_id_non_existent_error() { ExtBuilder::default().build().execute_with(|| { - assert_noop!( - AssetManager::set_asset_units_per_second( - RuntimeOrigin::root(), - MockAssetType::MockAsset(1), - 200u128.into(), - 0 - ), - Error::::AssetDoesNotExist - ); assert_noop!( AssetManager::change_existing_asset_type( RuntimeOrigin::root(), @@ -384,13 +172,6 @@ fn test_root_can_remove_asset_association() { true )); - assert_ok!(AssetManager::set_asset_units_per_second( - RuntimeOrigin::root(), - MockAssetType::MockAsset(1), - 200u128.into(), - 0 - )); - assert_ok!(AssetManager::remove_existing_asset_type( RuntimeOrigin::root(), 1, @@ -401,19 +182,12 @@ fn test_root_can_remove_asset_association() { assert!(AssetManager::asset_type_id(MockAssetType::MockAsset(1)).is_none()); assert!(AssetManager::asset_id_type(1).is_none()); - // Units per second removed - assert!(AssetManager::asset_type_units_per_second(MockAssetType::MockAsset(1)).is_none()); - expect_events(vec![ crate::Event::ForeignAssetRegistered { asset_id: 1, asset: MockAssetType::MockAsset(1), metadata: 0, }, - crate::Event::UnitsPerSecondChanged { - asset_type: MockAssetType::MockAsset(1), - units_per_second: 200, - }, crate::Event::ForeignAssetRemoved { asset_id: 1, asset_type: MockAssetType::MockAsset(1), @@ -443,9 +217,6 @@ fn test_removing_without_asset_units_per_second_does_not_panic() { assert!(AssetManager::asset_type_id(MockAssetType::MockAsset(1)).is_none()); assert!(AssetManager::asset_id_type(1).is_none()); - // Units per second removed - assert!(AssetManager::asset_type_units_per_second(MockAssetType::MockAsset(1)).is_none()); - expect_events(vec![ crate::Event::ForeignAssetRegistered { asset_id: 1, @@ -481,9 +252,6 @@ fn test_destroy_foreign_asset_also_removes_everything() { assert!(AssetManager::asset_type_id(MockAssetType::MockAsset(1)).is_none()); assert!(AssetManager::asset_id_type(1).is_none()); - // Units per second removed - assert!(AssetManager::asset_type_units_per_second(MockAssetType::MockAsset(1)).is_none()); - expect_events(vec![ crate::Event::ForeignAssetRegistered { asset_id: 1, diff --git a/pallets/asset-manager/src/weights.rs b/pallets/asset-manager/src/weights.rs index 6d4fb9e6f8..5bab822ae8 100644 --- a/pallets/asset-manager/src/weights.rs +++ b/pallets/asset-manager/src/weights.rs @@ -53,10 +53,8 @@ use sp_std::marker::PhantomData; /// Weight functions needed for pallet_asset_manager. pub trait WeightInfo { fn register_foreign_asset() -> Weight; - fn set_asset_units_per_second(x: u32, ) -> Weight; - fn change_existing_asset_type(x: u32, ) -> Weight; - fn remove_supported_asset(x: u32, ) -> Weight; - fn remove_existing_asset_type(x: u32, ) -> Weight; + fn change_existing_asset_type() -> Weight; + fn remove_existing_asset_type() -> Weight; } /// Weights for pallet_asset_manager using the Substrate node and recommended hardware. @@ -79,25 +77,6 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } - /// Storage: AssetManager AssetTypeId (r:1 w:0) - /// Proof Skipped: AssetManager AssetTypeId (max_values: None, max_size: None, mode: Measured) - /// Storage: AssetManager SupportedFeePaymentAssets (r:1 w:1) - /// Proof Skipped: AssetManager SupportedFeePaymentAssets (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: AssetManager AssetTypeUnitsPerSecond (r:0 w:1) - /// Proof Skipped: AssetManager AssetTypeUnitsPerSecond (max_values: None, max_size: None, mode: Measured) - /// The range of component `x` is `[5, 100]`. - fn set_asset_units_per_second(x: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `611 + x * (9 ±0)` - // Estimated: `6555 + x * (30 ±0)` - // Minimum execution time: 30_927_000 picoseconds. - Weight::from_parts(30_990_835, 6555) - // Standard Error: 2_254 - .saturating_add(Weight::from_parts(494_375, 0).saturating_mul(x.into())) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) - .saturating_add(Weight::from_parts(0, 30).saturating_mul(x.into())) - } /// Storage: AssetManager SupportedFeePaymentAssets (r:1 w:1) /// Proof Skipped: AssetManager SupportedFeePaymentAssets (max_values: Some(1), max_size: None, mode: Measured) /// Storage: AssetManager AssetIdType (r:1 w:1) @@ -107,34 +86,17 @@ impl WeightInfo for SubstrateWeight { /// Storage: AssetManager AssetTypeId (r:0 w:2) /// Proof Skipped: AssetManager AssetTypeId (max_values: None, max_size: None, mode: Measured) /// The range of component `x` is `[5, 100]`. - fn change_existing_asset_type(x: u32, ) -> Weight { + fn change_existing_asset_type() -> Weight { // Proof Size summary in bytes: // Measured: `926 + x * (13 ±0)` // Estimated: `11791 + x * (60 ±0)` // Minimum execution time: 42_959_000 picoseconds. Weight::from_parts(43_255_055, 11791) // Standard Error: 3_394 - .saturating_add(Weight::from_parts(543_897, 0).saturating_mul(x.into())) + .saturating_add(Weight::from_parts(543_897, 0)) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) - .saturating_add(Weight::from_parts(0, 60).saturating_mul(x.into())) - } - /// Storage: AssetManager SupportedFeePaymentAssets (r:1 w:1) - /// Proof Skipped: AssetManager SupportedFeePaymentAssets (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: AssetManager AssetTypeUnitsPerSecond (r:0 w:1) - /// Proof Skipped: AssetManager AssetTypeUnitsPerSecond (max_values: None, max_size: None, mode: Measured) - /// The range of component `x` is `[5, 100]`. - fn remove_supported_asset(x: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `196 + x * (5 ±0)` - // Estimated: `1871 + x * (10 ±0)` - // Minimum execution time: 25_453_000 picoseconds. - Weight::from_parts(24_977_319, 1871) - // Standard Error: 2_109 - .saturating_add(Weight::from_parts(407_717, 0).saturating_mul(x.into())) - .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) - .saturating_add(Weight::from_parts(0, 10).saturating_mul(x.into())) + .saturating_add(Weight::from_parts(0, 60)) } /// Storage: AssetManager SupportedFeePaymentAssets (r:1 w:1) /// Proof Skipped: AssetManager SupportedFeePaymentAssets (max_values: Some(1), max_size: None, mode: Measured) @@ -145,17 +107,17 @@ impl WeightInfo for SubstrateWeight { /// Storage: AssetManager AssetTypeId (r:0 w:1) /// Proof Skipped: AssetManager AssetTypeId (max_values: None, max_size: None, mode: Measured) /// The range of component `x` is `[5, 100]`. - fn remove_existing_asset_type(x: u32, ) -> Weight { + fn remove_existing_asset_type() -> Weight { // Proof Size summary in bytes: // Measured: `482 + x * (10 ±0)` // Estimated: `6910 + x * (40 ±0)` // Minimum execution time: 32_960_000 picoseconds. Weight::from_parts(33_257_599, 6910) // Standard Error: 2_430 - .saturating_add(Weight::from_parts(421_651, 0).saturating_mul(x.into())) + .saturating_add(Weight::from_parts(421_651, 0)) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) - .saturating_add(Weight::from_parts(0, 40).saturating_mul(x.into())) + .saturating_add(Weight::from_parts(0, 40)) } } @@ -178,25 +140,6 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } - /// Storage: AssetManager AssetTypeId (r:1 w:0) - /// Proof Skipped: AssetManager AssetTypeId (max_values: None, max_size: None, mode: Measured) - /// Storage: AssetManager SupportedFeePaymentAssets (r:1 w:1) - /// Proof Skipped: AssetManager SupportedFeePaymentAssets (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: AssetManager AssetTypeUnitsPerSecond (r:0 w:1) - /// Proof Skipped: AssetManager AssetTypeUnitsPerSecond (max_values: None, max_size: None, mode: Measured) - /// The range of component `x` is `[5, 100]`. - fn set_asset_units_per_second(x: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `611 + x * (9 ±0)` - // Estimated: `6555 + x * (30 ±0)` - // Minimum execution time: 30_927_000 picoseconds. - Weight::from_parts(30_990_835, 6555) - // Standard Error: 2_254 - .saturating_add(Weight::from_parts(494_375, 0).saturating_mul(x.into())) - .saturating_add(RocksDbWeight::get().reads(2_u64)) - .saturating_add(RocksDbWeight::get().writes(2_u64)) - .saturating_add(Weight::from_parts(0, 30).saturating_mul(x.into())) - } /// Storage: AssetManager SupportedFeePaymentAssets (r:1 w:1) /// Proof Skipped: AssetManager SupportedFeePaymentAssets (max_values: Some(1), max_size: None, mode: Measured) /// Storage: AssetManager AssetIdType (r:1 w:1) @@ -206,34 +149,17 @@ impl WeightInfo for () { /// Storage: AssetManager AssetTypeId (r:0 w:2) /// Proof Skipped: AssetManager AssetTypeId (max_values: None, max_size: None, mode: Measured) /// The range of component `x` is `[5, 100]`. - fn change_existing_asset_type(x: u32, ) -> Weight { + fn change_existing_asset_type() -> Weight { // Proof Size summary in bytes: // Measured: `926 + x * (13 ±0)` // Estimated: `11791 + x * (60 ±0)` // Minimum execution time: 42_959_000 picoseconds. Weight::from_parts(43_255_055, 11791) // Standard Error: 3_394 - .saturating_add(Weight::from_parts(543_897, 0).saturating_mul(x.into())) + .saturating_add(Weight::from_parts(543_897, 0)) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) - .saturating_add(Weight::from_parts(0, 60).saturating_mul(x.into())) - } - /// Storage: AssetManager SupportedFeePaymentAssets (r:1 w:1) - /// Proof Skipped: AssetManager SupportedFeePaymentAssets (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: AssetManager AssetTypeUnitsPerSecond (r:0 w:1) - /// Proof Skipped: AssetManager AssetTypeUnitsPerSecond (max_values: None, max_size: None, mode: Measured) - /// The range of component `x` is `[5, 100]`. - fn remove_supported_asset(x: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `196 + x * (5 ±0)` - // Estimated: `1871 + x * (10 ±0)` - // Minimum execution time: 25_453_000 picoseconds. - Weight::from_parts(24_977_319, 1871) - // Standard Error: 2_109 - .saturating_add(Weight::from_parts(407_717, 0).saturating_mul(x.into())) - .saturating_add(RocksDbWeight::get().reads(1_u64)) - .saturating_add(RocksDbWeight::get().writes(2_u64)) - .saturating_add(Weight::from_parts(0, 10).saturating_mul(x.into())) + .saturating_add(Weight::from_parts(0, 60)) } /// Storage: AssetManager SupportedFeePaymentAssets (r:1 w:1) /// Proof Skipped: AssetManager SupportedFeePaymentAssets (max_values: Some(1), max_size: None, mode: Measured) @@ -244,16 +170,16 @@ impl WeightInfo for () { /// Storage: AssetManager AssetTypeId (r:0 w:1) /// Proof Skipped: AssetManager AssetTypeId (max_values: None, max_size: None, mode: Measured) /// The range of component `x` is `[5, 100]`. - fn remove_existing_asset_type(x: u32, ) -> Weight { + fn remove_existing_asset_type() -> Weight { // Proof Size summary in bytes: // Measured: `482 + x * (10 ±0)` // Estimated: `6910 + x * (40 ±0)` // Minimum execution time: 32_960_000 picoseconds. Weight::from_parts(33_257_599, 6910) // Standard Error: 2_430 - .saturating_add(Weight::from_parts(421_651, 0).saturating_mul(x.into())) + .saturating_add(Weight::from_parts(421_651, 0)) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) - .saturating_add(Weight::from_parts(0, 40).saturating_mul(x.into())) + .saturating_add(Weight::from_parts(0, 40)) } } \ No newline at end of file diff --git a/pallets/moonbeam-foreign-assets/src/lib.rs b/pallets/moonbeam-foreign-assets/src/lib.rs index 5e826eb7a1..88611e928d 100644 --- a/pallets/moonbeam-foreign-assets/src/lib.rs +++ b/pallets/moonbeam-foreign-assets/src/lib.rs @@ -268,6 +268,11 @@ pub mod pallet { pub fn weight_of_erc20_transfer() -> Weight { T::GasWeightMapping::gas_to_weight(evm::ERC20_TRANSFER_GAS_LIMIT, true) } + #[cfg(feature = "runtime-benchmarks")] + pub fn set_asset(asset_location: Location, asset_id: AssetId) { + AssetsByLocation::::insert(&asset_location, (asset_id, AssetStatus::Active)); + AssetsById::::insert(&asset_id, asset_location); + } } #[pallet::call] diff --git a/pallets/xcm-weight-trader/Cargo.toml b/pallets/xcm-weight-trader/Cargo.toml new file mode 100644 index 0000000000..680c3c234f --- /dev/null +++ b/pallets/xcm-weight-trader/Cargo.toml @@ -0,0 +1,54 @@ +[package] +authors = {workspace = true} +description = "A pallet to trade weight for XCM execution" +edition = "2021" +name = "pallet-xcm-weight-trader" +version = "0.1.0" + +[dependencies] +log = {workspace = true} + +# Substrate +frame-support = {workspace = true} +frame-system = {workspace = true} +pallet-balances = {workspace = true} +parity-scale-codec = {workspace = true} +scale-info = {workspace = true, features = ["derive"]} +sp-core = {workspace = true} +sp-io = {workspace = true} +sp-runtime = {workspace = true} +sp-std = {workspace = true} + +# Polkadot +xcm = { workspace = true } +xcm-executor = { workspace = true } +xcm-fee-payment-runtime-api = { workspace = true } + +# Benchmarks +frame-benchmarking = {workspace = true, optional = true} + +[dev-dependencies] +frame-benchmarking = {workspace = true, features = ["std"]} +pallet-balances = {workspace = true, features = ["std", "insecure_zero_ed"]} +sp-tracing = {workspace = true, features = ["std"] } + +[features] +default = ["std"] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-system/runtime-benchmarks" +] +std = [ + "frame-support/std", + "frame-system/std", + "pallet-balances/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "xcm/std", + "xcm-executor/std", + "xcm-fee-payment-runtime-api/std", +] +try-runtime = ["frame-support/try-runtime"] \ No newline at end of file diff --git a/pallets/xcm-weight-trader/src/benchmarking.rs b/pallets/xcm-weight-trader/src/benchmarking.rs new file mode 100644 index 0000000000..1d711289eb --- /dev/null +++ b/pallets/xcm-weight-trader/src/benchmarking.rs @@ -0,0 +1,139 @@ +// Copyright 2024 Moonbeam foundation +// This file is part of Moonbeam. + +// Moonbeam is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonbeam is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonbeam. If not, see . + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; + +use frame_benchmarking::{v2::*, BenchmarkError}; +use frame_support::traits::EnsureOrigin; +use frame_system::EventRecord; + +fn assert_last_event(generic_event: ::RuntimeEvent) { + let events = frame_system::Pallet::::events(); + let system_event: ::RuntimeEvent = generic_event.into(); + // compare to the last event record + let EventRecord { event, .. } = &events[events.len() - 1]; + assert_eq!(event, &system_event); +} + +fn setup_one_asset() -> Result { + let origin = T::AddSupportedAssetOrigin::try_successful_origin() + .map_err(|_| BenchmarkError::Weightless)?; + + let location = T::NotFilteredLocation::get(); + + Pallet::::add_asset(origin, location.clone(), 1_000).expect("fail to setup asset"); + + Ok(location) +} + +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn add_asset() -> Result<(), BenchmarkError> { + let origin = T::AddSupportedAssetOrigin::try_successful_origin() + .map_err(|_| BenchmarkError::Weightless)?; + + let location = T::NotFilteredLocation::get(); + + #[extrinsic_call] + _(origin as T::RuntimeOrigin, location.clone(), 1_000); + + assert_last_event::( + Event::SupportedAssetAdded { + location, + relative_price: 1_000, + } + .into(), + ); + Ok(()) + } + + #[benchmark] + fn edit_asset() -> Result<(), BenchmarkError> { + // Setup one asset + let location = setup_one_asset::()?; + + let origin = T::EditSupportedAssetOrigin::try_successful_origin() + .map_err(|_| BenchmarkError::Weightless)?; + + #[extrinsic_call] + _(origin as T::RuntimeOrigin, location.clone(), 2_000); + + assert_last_event::( + Event::SupportedAssetEdited { + location, + relative_price: 2_000, + } + .into(), + ); + Ok(()) + } + + #[benchmark] + fn resume_asset_support() -> Result<(), BenchmarkError> { + // Setup one asset + let location = setup_one_asset::()?; + let pause_origin = T::PauseSupportedAssetOrigin::try_successful_origin() + .map_err(|_| BenchmarkError::Weightless)?; + Pallet::::pause_asset_support(pause_origin, location.clone()) + .expect("fail to pause asset"); + + let origin = T::ResumeSupportedAssetOrigin::try_successful_origin() + .map_err(|_| BenchmarkError::Weightless)?; + + #[extrinsic_call] + _(origin as T::RuntimeOrigin, location.clone()); + + assert_last_event::(Event::ResumeAssetSupport { location }.into()); + Ok(()) + } + + #[benchmark] + fn pause_asset_support() -> Result<(), BenchmarkError> { + // Setup one asset + let location = setup_one_asset::()?; + + let origin = T::PauseSupportedAssetOrigin::try_successful_origin() + .map_err(|_| BenchmarkError::Weightless)?; + + #[extrinsic_call] + _(origin as T::RuntimeOrigin, location.clone()); + + assert_last_event::(Event::PauseAssetSupport { location }.into()); + Ok(()) + } + + #[benchmark] + fn remove_asset() -> Result<(), BenchmarkError> { + // Setup one asset + let location = setup_one_asset::()?; + + let origin = T::RemoveSupportedAssetOrigin::try_successful_origin() + .map_err(|_| BenchmarkError::Weightless)?; + + #[extrinsic_call] + _(origin as T::RuntimeOrigin, location.clone()); + + assert_last_event::(Event::SupportedAssetRemoved { location }.into()); + Ok(()) + } + + impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test,); +} diff --git a/pallets/xcm-weight-trader/src/lib.rs b/pallets/xcm-weight-trader/src/lib.rs new file mode 100644 index 0000000000..6fef42f452 --- /dev/null +++ b/pallets/xcm-weight-trader/src/lib.rs @@ -0,0 +1,472 @@ +// Copyright 2024 Moonbeam foundation +// This file is part of Moonbeam. + +// Moonbeam is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonbeam is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonbeam. If not, see . + +//! # A pallet to trade weight for XCM execution + +#![allow(non_camel_case_types)] +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +pub mod weights; + +pub use pallet::*; +pub use weights::WeightInfo; + +use frame_support::pallet; +use frame_support::pallet_prelude::*; +use frame_support::traits::Contains; +use frame_support::weights::WeightToFee; +use frame_system::pallet_prelude::*; +use sp_runtime::traits::{Convert, Zero}; +use sp_std::vec::Vec; +use xcm::v4::{Asset, AssetId as XcmAssetId, Error as XcmError, Fungibility, Location, XcmContext}; +use xcm::{IntoVersion, VersionedAssetId}; +use xcm_executor::traits::{TransactAsset, WeightTrader}; +use xcm_fee_payment_runtime_api::Error as XcmPaymentApiError; + +pub const RELATIVE_PRICE_DECIMALS: u32 = 18; + +#[pallet] +pub mod pallet { + use super::*; + + /// Pallet for multi block migrations + #[pallet::pallet] + pub struct Pallet(PhantomData); + + /// Configuration trait of this pallet. + #[pallet::config] + pub trait Config: frame_system::Config { + /// Convert `T::AccountId` to `Location`. + type AccountIdToLocation: Convert; + + /// Origin that is allowed to register a supported asset + type AddSupportedAssetOrigin: EnsureOrigin; + + /// A filter to forbid some XCM Location to be supported for fees. + /// if you don't use it, put "Everything". + type AssetLocationFilter: Contains; + + /// How to withdraw and deposit an asset. + type AssetTransactor: TransactAsset; + + /// The native balance type. + type Balance: TryInto; + + /// Origin that is allowed to edit a supported asset units per seconds + type EditSupportedAssetOrigin: EnsureOrigin; + + /// XCM Location for native curreny + type NativeLocation: Get; + + /// Origin that is allowed to pause a supported asset + type PauseSupportedAssetOrigin: EnsureOrigin; + + /// Origin that is allowed to remove a supported asset + type RemoveSupportedAssetOrigin: EnsureOrigin; + + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Origin that is allowed to unpause a supported asset + type ResumeSupportedAssetOrigin: EnsureOrigin; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + + /// Convert a weight value into deductible native balance. + type WeightToFee: WeightToFee; + + /// Account that will receive xcm fees + type XcmFeesAccount: Get; + + /// The benchmarks need a location that pass the filter AssetLocationFilter + #[cfg(feature = "runtime-benchmarks")] + type NotFilteredLocation: Get; + } + + /// Stores all supported assets per XCM Location. + /// The u128 is the asset price relative to native asset with 18 decimals + /// The boolean specify if the support for this asset is active + #[pallet::storage] + #[pallet::getter(fn supported_assets)] + pub type SupportedAssets = StorageMap<_, Blake2_128Concat, Location, (bool, u128)>; + + #[pallet::error] + pub enum Error { + /// The given asset was already added + AssetAlreadyAdded, + /// The given asset was already paused + AssetAlreadyPaused, + /// The given asset was not found + AssetNotFound, + /// The given asset is not paused + AssetNotPaused, + /// XCM location filtered + XcmLocationFiltered, + /// The relative price cannot be zero + PriceCannotBeZero, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + /// New supported asset is registered + SupportedAssetAdded { + location: Location, + relative_price: u128, + }, + /// Changed the amount of units we are charging per execution second for a given asset + SupportedAssetEdited { + location: Location, + relative_price: u128, + }, + /// Pause support for a given asset + PauseAssetSupport { location: Location }, + /// Resume support for a given asset + ResumeAssetSupport { location: Location }, + /// Supported asset type for fee payment removed + SupportedAssetRemoved { location: Location }, + } + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::add_asset())] + pub fn add_asset( + origin: OriginFor, + location: Location, + relative_price: u128, + ) -> DispatchResult { + T::AddSupportedAssetOrigin::ensure_origin(origin)?; + + ensure!(relative_price != 0, Error::::PriceCannotBeZero); + ensure!( + !SupportedAssets::::contains_key(&location), + Error::::AssetAlreadyAdded + ); + ensure!( + T::AssetLocationFilter::contains(&location), + Error::::XcmLocationFiltered + ); + + SupportedAssets::::insert(&location, (true, relative_price)); + + Self::deposit_event(Event::SupportedAssetAdded { + location, + relative_price, + }); + + Ok(()) + } + + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::edit_asset())] + pub fn edit_asset( + origin: OriginFor, + location: Location, + relative_price: u128, + ) -> DispatchResult { + T::EditSupportedAssetOrigin::ensure_origin(origin)?; + + ensure!(relative_price != 0, Error::::PriceCannotBeZero); + + let enabled = SupportedAssets::::get(&location) + .ok_or(Error::::AssetNotFound)? + .0; + + SupportedAssets::::insert(&location, (enabled, relative_price)); + + Self::deposit_event(Event::SupportedAssetEdited { + location, + relative_price, + }); + + Ok(()) + } + + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::pause_asset_support())] + pub fn pause_asset_support(origin: OriginFor, location: Location) -> DispatchResult { + T::PauseSupportedAssetOrigin::ensure_origin(origin)?; + + match SupportedAssets::::get(&location) { + Some((true, relative_price)) => { + SupportedAssets::::insert(&location, (false, relative_price)); + Self::deposit_event(Event::PauseAssetSupport { location }); + Ok(()) + } + Some((false, _)) => Err(Error::::AssetAlreadyPaused.into()), + None => Err(Error::::AssetNotFound.into()), + } + } + + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::resume_asset_support())] + pub fn resume_asset_support(origin: OriginFor, location: Location) -> DispatchResult { + T::ResumeSupportedAssetOrigin::ensure_origin(origin)?; + + match SupportedAssets::::get(&location) { + Some((false, relative_price)) => { + SupportedAssets::::insert(&location, (true, relative_price)); + Self::deposit_event(Event::ResumeAssetSupport { location }); + Ok(()) + } + Some((true, _)) => Err(Error::::AssetNotPaused.into()), + None => Err(Error::::AssetNotFound.into()), + } + } + + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::remove_asset())] + pub fn remove_asset(origin: OriginFor, location: Location) -> DispatchResult { + T::RemoveSupportedAssetOrigin::ensure_origin(origin)?; + + ensure!( + SupportedAssets::::contains_key(&location), + Error::::AssetNotFound + ); + + SupportedAssets::::remove(&location); + + Self::deposit_event(Event::SupportedAssetRemoved { location }); + + Ok(()) + } + } + + impl Pallet { + pub fn get_asset_relative_price(location: &Location) -> Option { + if let Some((true, ratio)) = SupportedAssets::::get(location) { + Some(ratio) + } else { + None + } + } + pub fn query_acceptable_payment_assets( + xcm_version: xcm::Version, + ) -> Result, XcmPaymentApiError> { + if !matches!(xcm_version, 3 | 4) { + return Err(XcmPaymentApiError::UnhandledXcmVersion); + } + + let v4_assets = [VersionedAssetId::V4(XcmAssetId::from( + T::NativeLocation::get(), + ))] + .into_iter() + .chain( + SupportedAssets::::iter().filter_map(|(asset_location, (enabled, _))| { + enabled.then(|| VersionedAssetId::V4(XcmAssetId(asset_location))) + }), + ) + .collect::>(); + + if xcm_version == 3 { + v4_assets + .into_iter() + .map(|v4_asset| v4_asset.into_version(3)) + .collect::>() + .map_err(|_| XcmPaymentApiError::VersionedConversionFailed) + } else { + Ok(v4_assets) + } + } + pub fn query_weight_to_asset_fee( + weight: Weight, + asset: VersionedAssetId, + ) -> Result { + if let VersionedAssetId::V4(XcmAssetId(asset_location)) = asset + .into_version(4) + .map_err(|_| XcmPaymentApiError::VersionedConversionFailed)? + { + Trader::::compute_amount_to_charge(&weight, &asset_location).map_err(|e| match e + { + XcmError::AssetNotFound => XcmPaymentApiError::AssetNotFound, + _ => XcmPaymentApiError::WeightNotComputable, + }) + } else { + Err(XcmPaymentApiError::UnhandledXcmVersion) + } + } + #[cfg(any(feature = "std", feature = "runtime-benchmarks"))] + pub fn set_asset_price(asset_location: Location, relative_price: u128) { + SupportedAssets::::insert(&asset_location, (true, relative_price)); + } + } +} + +pub struct Trader(Weight, Option, core::marker::PhantomData); + +impl Trader { + fn compute_amount_to_charge( + weight: &Weight, + asset_location: &Location, + ) -> Result { + if *asset_location == ::NativeLocation::get() { + ::WeightToFee::weight_to_fee(&weight) + .try_into() + .map_err(|_| XcmError::Overflow) + } else if let Some(relative_price) = Pallet::::get_asset_relative_price(asset_location) { + if relative_price == 0u128 { + Ok(0u128) + } else { + let native_amount: u128 = ::WeightToFee::weight_to_fee(&weight) + .try_into() + .map_err(|_| XcmError::Overflow)?; + Ok(native_amount + .checked_mul(10u128.pow(RELATIVE_PRICE_DECIMALS)) + .ok_or(XcmError::Overflow)? + .checked_div(relative_price) + .ok_or(XcmError::Overflow)?) + } + } else { + Err(XcmError::AssetNotFound) + } + } +} + +impl WeightTrader for Trader { + fn new() -> Self { + Self(Weight::zero(), None, PhantomData) + } + fn buy_weight( + &mut self, + weight: Weight, + payment: xcm_executor::AssetsInHolding, + context: &XcmContext, + ) -> Result { + log::trace!( + target: "xcm::weight", + "UsingComponents::buy_weight weight: {:?}, payment: {:?}, context: {:?}", + weight, + payment, + context + ); + + // Can only call one time + if self.1.is_some() { + return Err(XcmError::NotWithdrawable); + } + + // Consistency check for tests only, we should never panic in release mode + debug_assert_eq!(self.0, Weight::zero()); + + // We support only one fee asset per buy, so we take the first one. + let first_asset = payment + .clone() + .fungible_assets_iter() + .next() + .ok_or(XcmError::AssetNotFound)?; + + match (first_asset.id, first_asset.fun) { + (XcmAssetId(location), Fungibility::Fungible(_)) => { + let amount: u128 = Self::compute_amount_to_charge(&weight, &location)?; + + // We don't need to proceed if the amount is 0 + // For cases (specially tests) where the asset is very cheap with respect + // to the weight needed + if amount.is_zero() { + return Ok(payment); + } + + let required = Asset { + fun: Fungibility::Fungible(amount), + id: XcmAssetId(location), + }; + let unused = payment + .checked_sub(required.clone()) + .map_err(|_| XcmError::TooExpensive)?; + + self.0 = weight; + self.1 = Some(required); + + Ok(unused) + } + _ => Err(XcmError::AssetNotFound), + } + } + + fn refund_weight(&mut self, actual_weight: Weight, context: &XcmContext) -> Option { + log::trace!( + target: "xcm-weight-trader", + "refund_weight weight: {:?}, context: {:?}, available weight: {:?}, asset: {:?}", + actual_weight, + context, + self.0, + self.1 + ); + if let Some(Asset { + fun: Fungibility::Fungible(initial_amount), + id: XcmAssetId(location), + }) = self.1.take() + { + if actual_weight == self.0 { + self.1 = Some(Asset { + fun: Fungibility::Fungible(initial_amount), + id: XcmAssetId(location), + }); + None + } else { + let weight = actual_weight.min(self.0); + let amount: u128 = + Self::compute_amount_to_charge(&weight, &location).unwrap_or(u128::MAX); + let final_amount = amount.min(initial_amount); + let amount_to_refund = initial_amount.saturating_sub(final_amount); + self.0 -= weight; + self.1 = Some(Asset { + fun: Fungibility::Fungible(final_amount), + id: XcmAssetId(location.clone()), + }); + log::trace!( + target: "xcm-weight-trader", + "refund_weight amount to refund: {:?}", + amount_to_refund + ); + Some(Asset { + fun: Fungibility::Fungible(amount_to_refund), + id: XcmAssetId(location), + }) + } + } else { + None + } + } +} + +impl Drop for Trader { + fn drop(&mut self) { + log::trace!( + target: "xcm-weight-trader", + "Dropping `Trader` instance: (weight: {:?}, asset: {:?})", + &self.0, + &self.1 + ); + if let Some(asset) = self.1.take() { + let res = T::AssetTransactor::deposit_asset( + &asset, + &T::AccountIdToLocation::convert(T::XcmFeesAccount::get()), + None, + ); + debug_assert!(res.is_ok()); + } + } +} diff --git a/pallets/xcm-weight-trader/src/mock.rs b/pallets/xcm-weight-trader/src/mock.rs new file mode 100644 index 0000000000..b1ce8166eb --- /dev/null +++ b/pallets/xcm-weight-trader/src/mock.rs @@ -0,0 +1,200 @@ +// Copyright 2024 Moonbeam foundation +// This file is part of Moonbeam. + +// Moonbeam is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonbeam is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonbeam. If not, see . + +//! A minimal runtime including the multi block migrations pallet + +use super::*; +use crate as pallet_xcm_weight_trader; +use frame_support::{ + construct_runtime, ord_parameter_types, parameter_types, + traits::{Currency, Everything}, + weights::{constants::RocksDbWeight, IdentityFee}, +}; +use frame_system::EnsureSignedBy; +use sp_core::H256; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, +}; +use xcm::v4::{Asset, Error as XcmError, Junction, Location, Result as XcmResult, XcmContext}; + +type AccountId = u64; +type Balance = u128; +type Block = frame_system::mocking::MockBlock; + +// Configure a mock runtime to test the pallet. +construct_runtime!( + pub enum Test + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + XcmWeightTrader: pallet_xcm_weight_trader::{Pallet, Call, Storage, Event}, + } +); + +parameter_types! { + pub const BlockHashCount: u32 = 250; + pub const SS58Prefix: u8 = 42; +} +impl frame_system::Config for Test { + type BaseCallFilter = Everything; + type DbWeight = RocksDbWeight; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeTask = RuntimeTask; + type Nonce = u64; + type Block = Block; + type RuntimeCall = RuntimeCall; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + 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>; + type SingleBlockMigrations = (); + type MultiBlockMigrator = (); + type PreInherents = (); + type PostInherents = (); + type PostTransactions = (); +} + +parameter_types! { + pub const ExistentialDeposit: u128 = 0; +} +impl pallet_balances::Config for Test { + type MaxReserves = (); + type ReserveIdentifier = (); + type MaxLocks = (); + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type RuntimeHoldReason = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeFreezeReason = (); +} + +pub struct AccountIdToLocation; +impl Convert for AccountIdToLocation { + fn convert(account: AccountId) -> Location { + Location::new( + 0, + [Junction::AccountIndex64 { + network: None, + index: account, + }], + ) + } +} + +pub struct AssetLocationFilter; +impl Contains for AssetLocationFilter { + fn contains(location: &Location) -> bool { + *location == ::NativeLocation::get() || *location == Location::parent() + } +} + +pub fn get_parent_asset_deposited() -> Option<(AccountId, Balance)> { + storage::unhashed::get_raw(b"____parent_asset_deposited") + .map(|output| Decode::decode(&mut output.as_slice()).expect("Decoding should work")) +} + +pub struct MockAssetTransactor; +impl TransactAsset for MockAssetTransactor { + fn deposit_asset(asset: &Asset, who: &Location, _context: Option<&XcmContext>) -> XcmResult { + match (asset.id.clone(), asset.fun.clone()) { + (XcmAssetId(location), Fungibility::Fungible(amount)) => { + let who = match who.interior.iter().next() { + Some(Junction::AccountIndex64 { index, .. }) => index, + _ => panic!("invalid location"), + }; + if location == ::NativeLocation::get() { + let _ = Balances::deposit_creating(who, amount); + Ok(()) + } else if location == Location::parent() { + storage::unhashed::put_raw( + b"____parent_asset_deposited", + (who, amount).encode().as_slice(), + ); + Ok(()) + } else { + Err(XcmError::AssetNotFound) + } + } + _ => Err(XcmError::AssetNotFound), + } + } +} + +ord_parameter_types! { + pub const AddAccount: u64 = 1; + pub const EditAccount: u64 = 2; + pub const PauseAccount: u64 = 3; + pub const ResumeAccount: u64 = 4; + pub const RemoveAccount: u64 = 5; +} + +parameter_types! { + pub NativeLocation: Location = Location::here(); + pub XcmFeesAccount: AccountId = 101; + pub NotFilteredLocation: Location = Location::parent(); +} + +impl Config for Test { + type AccountIdToLocation = AccountIdToLocation; + type AddSupportedAssetOrigin = EnsureSignedBy; + type AssetLocationFilter = AssetLocationFilter; + type AssetTransactor = MockAssetTransactor; + type Balance = Balance; + type EditSupportedAssetOrigin = EnsureSignedBy; + type NativeLocation = NativeLocation; + type PauseSupportedAssetOrigin = EnsureSignedBy; + type RemoveSupportedAssetOrigin = EnsureSignedBy; + type RuntimeEvent = RuntimeEvent; + type ResumeSupportedAssetOrigin = EnsureSignedBy; + type WeightInfo = (); + type WeightToFee = IdentityFee; + type XcmFeesAccount = XcmFeesAccount; + #[cfg(feature = "runtime-benchmarks")] + type NotFilteredLocation = NotFilteredLocation; +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + sp_tracing::try_init_simple(); + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + let balances = vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100)]; + pallet_balances::GenesisConfig:: { balances } + .assimilate_storage(&mut t) + .unwrap(); + + t.into() +} diff --git a/pallets/xcm-weight-trader/src/tests.rs b/pallets/xcm-weight-trader/src/tests.rs new file mode 100644 index 0000000000..daceed2445 --- /dev/null +++ b/pallets/xcm-weight-trader/src/tests.rs @@ -0,0 +1,704 @@ +// Copyright 2024 Moonbeam foundation +// This file is part of Moonbeam. + +// Moonbeam is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonbeam is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonbeam. If not, see . + +//! Unit testing +use { + crate::mock::*, + crate::{Error, Trader, XcmPaymentApiError}, + frame_support::pallet_prelude::Weight, + frame_support::{assert_noop, assert_ok}, + sp_runtime::DispatchError, + xcm::v4::{ + Asset, AssetId as XcmAssetId, Error as XcmError, Fungibility, Location, XcmContext, XcmHash, + }, + xcm::{IntoVersion, VersionedAssetId}, + xcm_executor::traits::WeightTrader, +}; + +fn xcm_fees_account() -> ::AccountId { + ::XcmFeesAccount::get() +} + +#[test] +fn test_add_supported_asset() { + new_test_ext().execute_with(|| { + // Call with bad origin + assert_noop!( + XcmWeightTrader::add_asset( + RuntimeOrigin::signed(EditAccount::get()), + Location::parent(), + 1_000, + ), + DispatchError::BadOrigin + ); + + // Call with invalid location + assert_noop!( + XcmWeightTrader::add_asset( + RuntimeOrigin::signed(AddAccount::get()), + Location::new(2, []), + 1_000, + ), + Error::::XcmLocationFiltered + ); + + // Call with invalid price + assert_noop!( + XcmWeightTrader::add_asset( + RuntimeOrigin::signed(AddAccount::get()), + Location::parent(), + 0, + ), + Error::::PriceCannotBeZero + ); + + // Call with the right origin + assert_ok!(XcmWeightTrader::add_asset( + RuntimeOrigin::signed(AddAccount::get()), + Location::parent(), + 1_000, + )); + + // The account should be supported + assert_eq!( + XcmWeightTrader::get_asset_relative_price(&Location::parent()), + Some(1_000), + ); + + // Check storage + assert_eq!( + crate::pallet::SupportedAssets::::get(&Location::parent()), + Some((true, 1_000)) + ); + + // Try to add the same asset twice (should fail) + assert_noop!( + XcmWeightTrader::add_asset( + RuntimeOrigin::signed(AddAccount::get()), + Location::parent(), + 1_000, + ), + Error::::AssetAlreadyAdded + ); + }) +} + +#[test] +fn test_edit_supported_asset() { + new_test_ext().execute_with(|| { + // Should not be able to edit an asset not added yet + assert_noop!( + XcmWeightTrader::edit_asset( + RuntimeOrigin::signed(EditAccount::get()), + Location::parent(), + 2_000, + ), + Error::::AssetNotFound + ); + + // Setup (add a supported asset) + assert_ok!(XcmWeightTrader::add_asset( + RuntimeOrigin::signed(AddAccount::get()), + Location::parent(), + 1_000, + )); + assert_eq!( + XcmWeightTrader::get_asset_relative_price(&Location::parent()), + Some(1_000), + ); + + // Call with bad origin + assert_noop!( + XcmWeightTrader::edit_asset( + RuntimeOrigin::signed(AddAccount::get()), + Location::parent(), + 2_000, + ), + DispatchError::BadOrigin + ); + + // Call with invalid price + assert_noop!( + XcmWeightTrader::edit_asset( + RuntimeOrigin::signed(EditAccount::get()), + Location::parent(), + 0, + ), + Error::::PriceCannotBeZero + ); + + // Call with right origin and valid params + assert_ok!(XcmWeightTrader::edit_asset( + RuntimeOrigin::signed(EditAccount::get()), + Location::parent(), + 2_000, + ),); + + // The account should be supported + assert_eq!( + XcmWeightTrader::get_asset_relative_price(&Location::parent()), + Some(2_000), + ); + + // Check storage + assert_eq!( + crate::pallet::SupportedAssets::::get(&Location::parent()), + Some((true, 2_000)) + ); + }) +} + +#[test] +fn test_pause_asset_support() { + new_test_ext().execute_with(|| { + // Should not be able to pause an asset not added yet + assert_noop!( + XcmWeightTrader::pause_asset_support( + RuntimeOrigin::signed(PauseAccount::get()), + Location::parent(), + ), + Error::::AssetNotFound + ); + + // Setup (add a supported asset) + assert_ok!(XcmWeightTrader::add_asset( + RuntimeOrigin::signed(AddAccount::get()), + Location::parent(), + 1_000, + )); + assert_eq!( + XcmWeightTrader::get_asset_relative_price(&Location::parent()), + Some(1_000), + ); + + // Call with bad origin + assert_noop!( + XcmWeightTrader::pause_asset_support( + RuntimeOrigin::signed(AddAccount::get()), + Location::parent(), + ), + DispatchError::BadOrigin + ); + + // Call with right origin and valid params + assert_ok!(XcmWeightTrader::pause_asset_support( + RuntimeOrigin::signed(PauseAccount::get()), + Location::parent(), + )); + + // The asset should be paused + assert_eq!( + XcmWeightTrader::get_asset_relative_price(&Location::parent()), + None, + ); + + // Check storage + assert_eq!( + crate::pallet::SupportedAssets::::get(&Location::parent()), + Some((false, 1_000)) + ); + + // Should not be able to pause an asset already paused + assert_noop!( + XcmWeightTrader::pause_asset_support( + RuntimeOrigin::signed(PauseAccount::get()), + Location::parent(), + ), + Error::::AssetAlreadyPaused + ); + + // Should be able to udpate relative price of paused asset + assert_ok!(XcmWeightTrader::edit_asset( + RuntimeOrigin::signed(EditAccount::get()), + Location::parent(), + 500 + )); + + // The asset should still be paused + assert_eq!( + XcmWeightTrader::get_asset_relative_price(&Location::parent()), + None, + ); + + // Check storage + assert_eq!( + crate::pallet::SupportedAssets::::get(&Location::parent()), + Some((false, 500)) + ); + }) +} + +#[test] +fn test_resume_asset_support() { + new_test_ext().execute_with(|| { + // Setup (add a supported asset and pause it) + assert_ok!(XcmWeightTrader::add_asset( + RuntimeOrigin::signed(AddAccount::get()), + Location::parent(), + 1_000, + )); + assert_ok!(XcmWeightTrader::pause_asset_support( + RuntimeOrigin::signed(PauseAccount::get()), + Location::parent(), + )); + assert_eq!( + crate::pallet::SupportedAssets::::get(&Location::parent()), + Some((false, 1_000)) + ); + + // Call with bad origin + assert_noop!( + XcmWeightTrader::resume_asset_support( + RuntimeOrigin::signed(AddAccount::get()), + Location::parent(), + ), + DispatchError::BadOrigin + ); + + // Call with invalid location + assert_noop!( + XcmWeightTrader::resume_asset_support( + RuntimeOrigin::signed(ResumeAccount::get()), + Location::new(2, []), + ), + Error::::AssetNotFound + ); + + // Call with right origin and valid params + assert_ok!(XcmWeightTrader::resume_asset_support( + RuntimeOrigin::signed(ResumeAccount::get()), + Location::parent(), + )); + + // The asset should be supported again + assert_eq!( + XcmWeightTrader::get_asset_relative_price(&Location::parent()), + Some(1_000), + ); + + // Check storage + assert_eq!( + crate::pallet::SupportedAssets::::get(&Location::parent()), + Some((true, 1_000)) + ); + + // Should not be able to resume an asset already active + assert_noop!( + XcmWeightTrader::resume_asset_support( + RuntimeOrigin::signed(ResumeAccount::get()), + Location::parent(), + ), + Error::::AssetNotPaused + ); + }) +} + +#[test] +fn test_remove_asset_support() { + new_test_ext().execute_with(|| { + // Should not be able to remove an asset not added yet + assert_noop!( + XcmWeightTrader::remove_asset( + RuntimeOrigin::signed(RemoveAccount::get()), + Location::parent(), + ), + Error::::AssetNotFound + ); + + // Setup (add a supported asset) + assert_ok!(XcmWeightTrader::add_asset( + RuntimeOrigin::signed(AddAccount::get()), + Location::parent(), + 1_000, + )); + assert_eq!( + XcmWeightTrader::get_asset_relative_price(&Location::parent()), + Some(1_000), + ); + + // Call with bad origin + assert_noop!( + XcmWeightTrader::remove_asset( + RuntimeOrigin::signed(AddAccount::get()), + Location::parent(), + ), + DispatchError::BadOrigin + ); + + // Call with right origin and valid params + assert_ok!(XcmWeightTrader::remove_asset( + RuntimeOrigin::signed(RemoveAccount::get()), + Location::parent(), + )); + + // The account should be removed + assert_eq!( + XcmWeightTrader::get_asset_relative_price(&Location::parent()), + None, + ); + + // Check storage + assert_eq!( + crate::pallet::SupportedAssets::::get(&Location::parent()), + None + ); + + // Should not be able to pause an asset already removed + assert_noop!( + XcmWeightTrader::remove_asset( + RuntimeOrigin::signed(RemoveAccount::get()), + Location::parent(), + ), + Error::::AssetNotFound + ); + }) +} + +#[test] +fn test_trader_native_asset() { + new_test_ext().execute_with(|| { + let weight_to_buy = Weight::from_parts(10_000, 0); + let dummy_xcm_context = XcmContext::with_message_id(XcmHash::default()); + + // Should not be able to buy weight with too low asset balance + assert_eq!( + Trader::::new().buy_weight( + weight_to_buy, + Asset { + fun: Fungibility::Fungible(9_999), + id: XcmAssetId(Location::here()), + } + .into(), + &dummy_xcm_context + ), + Err(XcmError::TooExpensive) + ); + + // Should not be able to buy weight with unsupported asset + assert_eq!( + Trader::::new().buy_weight( + weight_to_buy, + Asset { + fun: Fungibility::Fungible(10_000), + id: XcmAssetId(Location::parent()), + } + .into(), + &dummy_xcm_context + ), + Err(XcmError::AssetNotFound) + ); + + // Should not be able to buy weight without asset + assert_eq!( + Trader::::new().buy_weight(weight_to_buy, Default::default(), &dummy_xcm_context), + Err(XcmError::AssetNotFound) + ); + + // Should be able to buy weight with just enough native asset + let mut trader = Trader::::new(); + assert_eq!( + trader.buy_weight( + weight_to_buy, + Asset { + fun: Fungibility::Fungible(10_000), + id: XcmAssetId(Location::here()), + } + .into(), + &dummy_xcm_context + ), + Ok(Default::default()) + ); + + // Should not refund any funds + let actual_weight = weight_to_buy; + assert_eq!( + trader.refund_weight(actual_weight, &dummy_xcm_context), + None + ); + + // Should not be able to buy weight again with the same trader + assert_eq!( + trader.buy_weight( + weight_to_buy, + Asset { + fun: Fungibility::Fungible(10_000), + id: XcmAssetId(Location::here()), + } + .into(), + &dummy_xcm_context + ), + Err(XcmError::NotWithdrawable) + ); + + // Fees asset should be deposited into XcmFeesAccount + drop(trader); + assert_eq!(Balances::free_balance(&xcm_fees_account()), 10_000); + + // Should be able to buy weight with more native asset (and get back unused amount) + let mut trader = Trader::::new(); + assert_eq!( + trader.buy_weight( + weight_to_buy, + Asset { + fun: Fungibility::Fungible(11_000), + id: XcmAssetId(Location::here()), + } + .into(), + &dummy_xcm_context + ), + Ok(Asset { + fun: Fungibility::Fungible(1_000), + id: XcmAssetId(Location::here()), + } + .into()) + ); + + // Should be able to refund unused weights + let actual_weight = weight_to_buy.saturating_sub(Weight::from_parts(2_000, 0)); + assert_eq!( + trader.refund_weight(actual_weight, &dummy_xcm_context), + Some(Asset { + fun: Fungibility::Fungible(2_000), + id: XcmAssetId(Location::here()), + }) + ); + + // Fees asset should be deposited again into XcmFeesAccount (2 times cost minus one refund) + drop(trader); + assert_eq!( + Balances::free_balance(&xcm_fees_account()), + (2 * 10_000) - 2_000 + ); + }) +} + +#[test] +fn test_trader_parent_asset() { + new_test_ext().execute_with(|| { + let weight_to_buy = Weight::from_parts(10_000, 0); + let dummy_xcm_context = XcmContext::with_message_id(XcmHash::default()); + + // Setup (add a supported asset) + assert_ok!(XcmWeightTrader::add_asset( + RuntimeOrigin::signed(AddAccount::get()), + Location::parent(), + 500_000_000, + )); + assert_eq!( + XcmWeightTrader::get_asset_relative_price(&Location::parent()), + Some(500_000_000), + ); + + // Should be able to pay fees with registered asset + let mut trader = Trader::::new(); + assert_eq!( + trader.buy_weight( + weight_to_buy, + Asset { + fun: Fungibility::Fungible(22_000_000_000_000), + id: XcmAssetId(Location::parent()), + } + .into(), + &dummy_xcm_context + ), + Ok(Asset { + fun: Fungibility::Fungible(2_000_000_000_000), + id: XcmAssetId(Location::parent()), + } + .into()) + ); + + // Should be able to refund unused weights + let actual_weight = weight_to_buy.saturating_sub(Weight::from_parts(2_000, 0)); + assert_eq!( + trader.refund_weight(actual_weight, &dummy_xcm_context), + Some(Asset { + fun: Fungibility::Fungible(4_000_000_000_000), + id: XcmAssetId(Location::parent()), + }) + ); + + // Fees asset should be deposited into XcmFeesAccount + drop(trader); + assert_eq!( + get_parent_asset_deposited(), + Some((xcm_fees_account(), 20_000_000_000_000 - 4_000_000_000_000)) + ); + + // Should not be able to buy weight if the asset is not a first position + assert_eq!( + Trader::::new().buy_weight( + weight_to_buy, + vec![ + Asset { + fun: Fungibility::Fungible(10), + id: XcmAssetId(Location::here()), + }, + Asset { + fun: Fungibility::Fungible(30_000), + id: XcmAssetId(Location::parent()), + } + ] + .into(), + &dummy_xcm_context + ), + Err(XcmError::TooExpensive) + ); + }) +} + +#[test] +fn test_query_acceptable_payment_assets() { + new_test_ext().execute_with(|| { + // By default, only native asset should be supported + assert_eq!( + XcmWeightTrader::query_acceptable_payment_assets(4), + Ok(vec![VersionedAssetId::V4(XcmAssetId( + ::NativeLocation::get() + ))]) + ); + + // We should support XCMv3 + assert_eq!( + XcmWeightTrader::query_acceptable_payment_assets(3), + Ok(vec![VersionedAssetId::V4(XcmAssetId( + ::NativeLocation::get() + )) + .into_version(3) + .expect("native location should be convertible to v3")]) + ); + + // We should not support XCMv2 + assert_eq!( + XcmWeightTrader::query_acceptable_payment_assets(2), + Err(XcmPaymentApiError::UnhandledXcmVersion) + ); + + // Setup (add a supported asset) + assert_ok!(XcmWeightTrader::add_asset( + RuntimeOrigin::signed(AddAccount::get()), + Location::parent(), + 500_000_000, + )); + assert_eq!( + XcmWeightTrader::get_asset_relative_price(&Location::parent()), + Some(500_000_000), + ); + + // We should support parent asset now + assert_eq!( + XcmWeightTrader::query_acceptable_payment_assets(4), + Ok(vec![ + VersionedAssetId::V4(XcmAssetId(::NativeLocation::get())), + VersionedAssetId::V4(XcmAssetId(Location::parent())) + ]) + ); + + // Setup: pause parent asset + assert_ok!(XcmWeightTrader::pause_asset_support( + RuntimeOrigin::signed(PauseAccount::get()), + Location::parent(), + )); + assert_eq!( + XcmWeightTrader::get_asset_relative_price(&Location::parent()), + None + ); + + // We should not support paused assets + assert_eq!( + XcmWeightTrader::query_acceptable_payment_assets(4), + Ok(vec![VersionedAssetId::V4(XcmAssetId( + ::NativeLocation::get() + )),]) + ); + }) +} + +#[test] +fn test_query_weight_to_asset_fee() { + new_test_ext().execute_with(|| { + let native_asset = + VersionedAssetId::V4(XcmAssetId(::NativeLocation::get())); + let parent_asset = VersionedAssetId::V4(XcmAssetId(Location::parent())); + let weight_to_buy = Weight::from_parts(10_000, 0); + + // Native asset price should be 1:1 + assert_eq!( + XcmWeightTrader::query_weight_to_asset_fee(weight_to_buy, native_asset.clone()), + Ok(10_000) + ); + + // Should not be able to query fees for an unsupported asset + assert_eq!( + XcmWeightTrader::query_weight_to_asset_fee(weight_to_buy, parent_asset.clone()), + Err(XcmPaymentApiError::AssetNotFound) + ); + + // Setup (add a supported asset) + assert_ok!(XcmWeightTrader::add_asset( + RuntimeOrigin::signed(AddAccount::get()), + Location::parent(), + 500_000_000, + )); + assert_eq!( + XcmWeightTrader::get_asset_relative_price(&Location::parent()), + Some(500_000_000), + ); + + // Parent asset price should be 0.5 + assert_eq!( + XcmWeightTrader::query_weight_to_asset_fee(weight_to_buy, parent_asset.clone()), + Ok(2 * 10_000_000_000_000) + ); + + // Setup: pause parent asset + assert_ok!(XcmWeightTrader::pause_asset_support( + RuntimeOrigin::signed(PauseAccount::get()), + Location::parent(), + )); + assert_eq!( + XcmWeightTrader::get_asset_relative_price(&Location::parent()), + None + ); + + // We should not support paused assets + assert_eq!( + XcmWeightTrader::query_weight_to_asset_fee(weight_to_buy, parent_asset.clone()), + Err(XcmPaymentApiError::AssetNotFound) + ); + + // Setup: unpause parent asset and edit price + assert_ok!(XcmWeightTrader::resume_asset_support( + RuntimeOrigin::signed(ResumeAccount::get()), + Location::parent(), + )); + assert_ok!(XcmWeightTrader::edit_asset( + RuntimeOrigin::signed(EditAccount::get()), + Location::parent(), + 2_000_000_000, + )); + assert_eq!( + XcmWeightTrader::get_asset_relative_price(&Location::parent()), + Some(2_000_000_000), + ); + + // We should support unpaused asset with new price + assert_eq!( + XcmWeightTrader::query_weight_to_asset_fee(weight_to_buy, parent_asset), + Ok(10_000_000_000_000 / 2) + ); + }) +} diff --git a/pallets/xcm-weight-trader/src/weights.rs b/pallets/xcm-weight-trader/src/weights.rs new file mode 100644 index 0000000000..972a946eba --- /dev/null +++ b/pallets/xcm-weight-trader/src/weights.rs @@ -0,0 +1,50 @@ +// Copyright 2024 Moonbeam foundation +// This file is part of Moonbeam. + +// Moonbeam is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonbeam is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonbeam. If not, see . + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_xcm_weight_trader +pub trait WeightInfo { + fn add_asset() -> Weight; + fn edit_asset() -> Weight; + fn pause_asset_support() -> Weight; + fn resume_asset_support() -> Weight; + fn remove_asset() -> Weight; +} + +// For tests only +impl WeightInfo for () { + fn add_asset() -> Weight { + Weight::default() + } + fn edit_asset() -> Weight { + Weight::default() + } + fn pause_asset_support() -> Weight { + Weight::default() + } + fn resume_asset_support() -> Weight { + Weight::default() + } + fn remove_asset() -> Weight { + Weight::default() + } +} \ No newline at end of file diff --git a/precompiles/xtokens/src/tests.rs b/precompiles/xtokens/src/tests.rs index 65f5eff676..ce2dff8a85 100644 --- a/precompiles/xtokens/src/tests.rs +++ b/precompiles/xtokens/src/tests.rs @@ -94,7 +94,7 @@ fn transfer_self_reserve_works() { weight: 4_000_000, }, ) - .expect_cost(2000) + .expect_cost(3000) .expect_no_logs() .execute_returns(()); @@ -139,7 +139,7 @@ fn transfer_to_reserve_works() { weight: 4_000_000, }, ) - .expect_cost(3000) + .expect_cost(4000) .expect_no_logs() .execute_returns(()); @@ -186,7 +186,7 @@ fn transfer_to_reserve_with_unlimited_weight_works() { weight: u64::MAX, }, ) - .expect_cost(3000) + .expect_cost(4000) .expect_no_logs() .execute_returns(()); @@ -235,7 +235,7 @@ fn transfer_to_reserve_with_fee_works() { weight: 4_000_000, }, ) - .expect_cost(3000) + .expect_cost(4000) .expect_no_logs() .execute_returns(()); @@ -290,7 +290,7 @@ fn transfer_non_reserve_to_non_reserve_works() { weight: 4_000_000, }, ) - .expect_cost(3000) + .expect_cost(4000) .expect_no_logs() .execute_returns(()); @@ -339,7 +339,7 @@ fn transfer_non_reserve_to_non_reserve_with_fee_works() { weight: 4_000_000, }, ) - .expect_cost(3000) + .expect_cost(4000) .expect_no_logs() .execute_returns(()); @@ -394,7 +394,7 @@ fn transfer_multi_asset_to_reserve_works() { weight: 4_000_000, }, ) - .expect_cost(3000) + .expect_cost(4000) .expect_no_logs() .execute_returns(()); @@ -442,7 +442,7 @@ fn transfer_multi_asset_self_reserve_works() { weight: 4_000_000, }, ) - .expect_cost(2000) + .expect_cost(3000) .expect_no_logs() .execute_returns(()); @@ -490,7 +490,7 @@ fn transfer_multi_asset_self_reserve_with_fee_works() { weight: 4_000_000, }, ) - .expect_cost(2000) + .expect_cost(3000) .expect_no_logs() .execute_returns(()); @@ -542,7 +542,7 @@ fn transfer_multi_asset_non_reserve_to_non_reserve() { weight: 4_000_000, }, ) - .expect_cost(3000) + .expect_cost(4000) .expect_no_logs() .execute_returns(()); @@ -591,7 +591,7 @@ fn transfer_multi_asset_non_reserve_to_non_reserve_with_fee() { weight: 4_000_000, }, ) - .expect_cost(3000) + .expect_cost(4000) .expect_no_logs() .execute_returns(()); @@ -645,7 +645,7 @@ fn transfer_multi_currencies() { weight: 4_000_000, }, ) - .expect_cost(3000) + .expect_cost(4000) .expect_no_logs() .execute_returns(()); @@ -718,7 +718,7 @@ fn transfer_multi_assets() { weight: 4_000_000, }, ) - .expect_cost(3000) + .expect_cost(4000) .expect_no_logs() .execute_returns(()); diff --git a/primitives/xcm/src/fee_handlers.rs b/primitives/xcm/src/fee_handlers.rs deleted file mode 100644 index c496bc37c9..0000000000 --- a/primitives/xcm/src/fee_handlers.rs +++ /dev/null @@ -1,466 +0,0 @@ -// Copyright 2019-2022 PureStake Inc. -// This file is part of Moonbeam. - -// Moonbeam is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Moonbeam is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Moonbeam. If not, see . - -// We need to know how to charge for incoming assets -// We assume AssetIdInfoGetter is implemented and is capable of getting how much units we should -// charge for a given asset -// This takes the first fungible asset, and takes whatever UnitPerSecondGetter establishes -// UnitsToWeightRatio trait, which needs to be implemented by AssetIdInfoGetter - -use cumulus_primitives_core::XcmContext; -use frame_support::{ - pallet_prelude::Weight, - traits::{tokens::fungibles::Mutate, Get}, - weights::constants::WEIGHT_REF_TIME_PER_SECOND, -}; -use sp_runtime::traits::Zero; -use sp_std::marker::PhantomData; -use xcm::latest::{Asset, AssetId as xcmAssetId, Error as XcmError, Fungibility, Location}; - -use xcm_builder::TakeRevenue; -use xcm_executor::traits::{MatchesFungibles, WeightTrader}; - -pub struct FirstAssetTrader< - AssetType: TryFrom + Clone, - AssetIdInfoGetter: UnitsToWeightRatio, - R: TakeRevenue, ->( - Weight, - Option<(Location, u128, u128)>, // id, amount, units_per_second - PhantomData<(AssetType, AssetIdInfoGetter, R)>, -); -impl< - AssetType: TryFrom + Clone, - AssetIdInfoGetter: UnitsToWeightRatio, - R: TakeRevenue, - > WeightTrader for FirstAssetTrader -{ - fn new() -> Self { - FirstAssetTrader(Weight::zero(), None, PhantomData) - } - fn buy_weight( - &mut self, - weight: Weight, - payment: xcm_executor::AssetsInHolding, - _context: &XcmContext, - ) -> Result { - // can only call one time - if self.1.is_some() { - // TODO: better error - return Err(XcmError::NotWithdrawable); - } - - assert_eq!(self.0, Weight::zero()); - let first_asset = payment - .clone() - .fungible_assets_iter() - .next() - .ok_or(XcmError::TooExpensive)?; - - // We are only going to check first asset for now. This should be sufficient for simple token - // transfers. We will see later if we change this. - match (first_asset.id, first_asset.fun) { - (xcmAssetId(location), Fungibility::Fungible(_)) => { - let asset_type: AssetType = location - .clone() - .try_into() - .map_err(|_| XcmError::InvalidLocation)?; - // Shortcut if we know the asset is not supported - // This involves the same db read per block, mitigating any attack based on - // non-supported assets - if !AssetIdInfoGetter::payment_is_supported(asset_type.clone()) { - return Err(XcmError::TooExpensive); - } - if let Some(units_per_second) = AssetIdInfoGetter::get_units_per_second(asset_type) - { - // TODO handle proof size payment - let amount = units_per_second.saturating_mul(weight.ref_time() as u128) - / (WEIGHT_REF_TIME_PER_SECOND as u128); - - // We dont need to proceed if the amount is 0 - // For cases (specially tests) where the asset is very cheap with respect - // to the weight needed - if amount.is_zero() { - return Ok(payment); - } - - let required = Asset { - fun: Fungibility::Fungible(amount), - id: xcmAssetId(location.clone()), - }; - let unused = payment - .checked_sub(required) - .map_err(|_| XcmError::TooExpensive)?; - - self.0 = weight; - self.1 = Some((location, amount, units_per_second)); - - return Ok(unused); - } else { - return Err(XcmError::TooExpensive); - }; - } - _ => return Err(XcmError::TooExpensive), - } - } - - // Refund weight. We will refund in whatever asset is stored in self. - fn refund_weight(&mut self, weight: Weight, _context: &XcmContext) -> Option { - if let Some((location, prev_amount, units_per_second)) = self.1.clone() { - let weight = weight.min(self.0); - self.0 -= weight; - let amount = units_per_second * (weight.ref_time() as u128) - / (WEIGHT_REF_TIME_PER_SECOND as u128); - let amount = amount.min(prev_amount); - self.1 = Some(( - location.clone(), - prev_amount.saturating_sub(amount), - units_per_second, - )); - Some(Asset { - fun: Fungibility::Fungible(amount), - id: xcmAssetId(location.clone()), - }) - } else { - None - } - } -} - -/// Deal with spent fees, deposit them as dictated by R -impl< - AssetType: TryFrom + Clone, - AssetIdInfoGetter: UnitsToWeightRatio, - R: TakeRevenue, - > Drop for FirstAssetTrader -{ - fn drop(&mut self) { - if let Some((id, amount, _)) = self.1.clone() { - if amount > 0 { - R::take_revenue((id, amount).into()); - } - } - } -} - -/// XCM fee depositor to which we implement the TakeRevenue trait -/// It receives a fungibles::Mutate implemented argument, a matcher to convert Asset into -/// AssetId and amount, and the fee receiver account -pub struct XcmFeesToAccount( - PhantomData<(Assets, Matcher, AccountId, ReceiverAccount)>, -); -impl< - Assets: Mutate, - Matcher: MatchesFungibles, - AccountId: Clone + Eq, - ReceiverAccount: Get, - > TakeRevenue for XcmFeesToAccount -{ - fn take_revenue(revenue: Asset) { - match Matcher::matches_fungibles(&revenue) { - Ok((asset_id, amount)) => { - let ok = Assets::mint_into(asset_id, &ReceiverAccount::get(), amount).is_ok(); - debug_assert!(ok, "`mint_into` cannot generally fail; qed"); - } - Err(_) => log::debug!( - target: "xcm", - "take revenue failed matching fungible" - ), - } - } -} - -// Defines the trait to obtain the units per second of a give asset_type for local execution -// This parameter will be used to charge for fees upon asset_type deposit -pub trait UnitsToWeightRatio { - // Whether payment in a particular asset_type is suppotrted - fn payment_is_supported(asset_type: AssetType) -> bool; - // Get units per second from asset type - fn get_units_per_second(asset_type: AssetType) -> Option; - #[cfg(feature = "runtime-benchmarks")] - fn set_units_per_second(_asset_type: AssetType, _fee_per_second: u128) {} -} - -#[cfg(test)] -mod test { - use super::*; - use cumulus_primitives_core::XcmHash; - use xcm::latest::{AssetId, Fungibility, Junction, Junctions}; - use xcm_executor::AssetsInHolding; - - const ARBITRARY_ML: Location = Location { - parents: 0u8, - interior: Junctions::Here, - }; - const ARBITRARY_ID: AssetId = AssetId(ARBITRARY_ML); - - impl UnitsToWeightRatio for () { - fn payment_is_supported(_asset_type: Location) -> bool { - true - } - fn get_units_per_second(_asset_type: Location) -> Option { - // return WEIGHT_REF_TIME_PER_SECOND to cancel the division out in buy_weight() - // this should make weight and payment amounts directly comparable - Some(WEIGHT_REF_TIME_PER_SECOND as u128) - } - } - - #[test] - fn test_buy_weight_accounts_weight_properly() { - let amount = 1000u128; - - let mut payment = AssetsInHolding::new(); - let location = Location { - parents: 0u8, - interior: Junctions::Here, - }; - payment.subsume(Asset { - id: AssetId(location.clone()), - fun: Fungibility::Fungible(amount), - }); - - let mut trader: FirstAssetTrader = FirstAssetTrader::new(); - let ctx = XcmContext { - origin: Some(location), - message_id: XcmHash::default(), - topic: None, - }; - let unused = trader - .buy_weight((amount as u64).into(), payment.clone(), &ctx) - .expect("can buy weight once"); - assert!(unused.is_empty()); - assert_eq!(trader.0, 1000u64.into()); - } - - #[test] - fn cant_call_buy_weight_twice() { - let mut trader: FirstAssetTrader = FirstAssetTrader::new(); - - // should be able to buy once - let mut asset_one_payment = AssetsInHolding::new(); - let location = Location { - parents: 0u8, - interior: [Junction::Parachain(1000)].into(), - }; - asset_one_payment.subsume(Asset { - id: AssetId(location.clone()), - fun: Fungibility::Fungible(100u128), - }); - let ctx = XcmContext { - origin: Some(location), - message_id: XcmHash::default(), - topic: None, - }; - let buy_one_results = trader - .buy_weight(100u64.into(), asset_one_payment.clone(), &ctx) - .expect("can buy weight once"); - assert_eq!(buy_one_results.fungible.len(), 0); // no unused amount - assert_eq!(trader.0, 100u64.into()); - assert_eq!( - trader.1, - Some(( - Location { - parents: 0u8, - interior: [Junction::Parachain(1000)].into() - }, - 100, - WEIGHT_REF_TIME_PER_SECOND as u128 - )) - ); - - // but not twice - let mut asset_two_payment = xcm_executor::AssetsInHolding::new(); - let location = Location { - parents: 0u8, - interior: [Junction::Parachain(1001)].into(), - }; - asset_two_payment.subsume(Asset { - id: AssetId(location.clone()), - fun: Fungibility::Fungible(10_000u128), - }); - let ctx = XcmContext { - origin: Some(location), - message_id: XcmHash::default(), - topic: None, - }; - assert_eq!( - trader.buy_weight(10_000u64.into(), asset_two_payment.clone(), &ctx), - Err(XcmError::NotWithdrawable), - ); - - // state should be unchanged - assert_eq!(trader.0, 100u64.into()); - assert_eq!( - trader.1, - Some(( - Location { - parents: 0u8, - interior: [Junction::Parachain(1000)].into() - }, - 100, - WEIGHT_REF_TIME_PER_SECOND as u128 - )) - ); - } - - #[test] - fn can_call_refund_weight_with_all_weight() { - let amount = 1000u128; - - let mut payment = AssetsInHolding::new(); - payment.subsume(Asset { - id: ARBITRARY_ID, - fun: Fungibility::Fungible(amount), - }); - - let mut trader: FirstAssetTrader = FirstAssetTrader::new(); - let ctx = XcmContext { - origin: Some(ARBITRARY_ML), - message_id: XcmHash::default(), - topic: None, - }; - let unused = trader - .buy_weight((amount as u64).into(), payment.clone(), &ctx) - .expect("can buy weight once"); - assert!(unused.is_empty()); - assert_eq!(trader.0, 1000u64.into()); - - assert_eq!( - trader.refund_weight(1000u64.into(), &ctx), - Some(Asset { - fun: Fungibility::Fungible(1000), - id: ARBITRARY_ID, - }) - ); - } - - #[test] - fn can_call_refund_multiple_times() { - let amount = 1000u128; - - let mut payment = AssetsInHolding::new(); - payment.subsume(Asset { - id: ARBITRARY_ID, - fun: Fungibility::Fungible(amount), - }); - - let mut trader: FirstAssetTrader = FirstAssetTrader::new(); - let ctx = XcmContext { - origin: Some(ARBITRARY_ML), - message_id: XcmHash::default(), - topic: None, - }; - let unused = trader - .buy_weight((amount as u64).into(), payment.clone(), &ctx) - .expect("can buy weight once"); - assert!(unused.is_empty()); - assert_eq!(trader.0, 1000u64.into()); - - assert_eq!( - trader.refund_weight(100u64.into(), &ctx), - Some(Asset { - fun: Fungibility::Fungible(100), - id: ARBITRARY_ID, - }) - ); - - // should reflect 100 weight and 100 currency deducted - assert_eq!(trader.0, 900u64.into()); - assert_eq!(trader.1.clone().unwrap().1, 900); - - // can call again - assert_eq!( - trader.refund_weight(200u64.into(), &ctx), - Some(Asset { - fun: Fungibility::Fungible(200), - id: ARBITRARY_ID, - }) - ); - - // should reflect another 200 weight and 200 currency deducted - assert_eq!(trader.0, 700u64.into()); - assert_eq!(trader.1.clone().unwrap().1, 700); - } - - #[test] - fn refund_weight_caps_weight() { - let amount = 1000u128; - - let mut payment = AssetsInHolding::new(); - payment.subsume(Asset { - id: ARBITRARY_ID, - fun: Fungibility::Fungible(amount), - }); - let ctx = XcmContext { - origin: Some(ARBITRARY_ML), - message_id: XcmHash::default(), - topic: None, - }; - let mut trader: FirstAssetTrader = FirstAssetTrader::new(); - let unused = trader - .buy_weight((amount as u64).into(), payment.clone(), &ctx) - .expect("can buy weight once"); - assert!(unused.is_empty()); - assert_eq!(trader.0, 1000u64.into()); - - // can't call with more weight - assert_eq!( - trader.refund_weight(9999u64.into(), &ctx), - Some(Asset { - fun: Fungibility::Fungible(1000), - id: ARBITRARY_ID, - }) - ); - assert_eq!(trader.0, Weight::zero()); - } - - #[test] - fn refund_weight_caps_currency() { - let amount = 1000u128; - - let mut payment = AssetsInHolding::new(); - payment.subsume(Asset { - id: ARBITRARY_ID, - fun: Fungibility::Fungible(amount), - }); - - let ctx = XcmContext { - origin: Some(ARBITRARY_ML), - message_id: XcmHash::default(), - topic: None, - }; - let mut trader: FirstAssetTrader = FirstAssetTrader::new(); - let unused = trader - .buy_weight((amount as u64).into(), payment.clone(), &ctx) - .expect("can buy weight once"); - assert!(unused.is_empty()); - assert_eq!(trader.0, 1000u64.into()); - - // adjust weight so that it will allow a higher amount -- we want to see that the currency - // (self.1.1) is capped even when weight is not - trader.0 = trader.0.saturating_add(1000u64.into()); - - // can't call with more weight - assert_eq!( - trader.refund_weight(1500u64.into(), &ctx), - Some(Asset { - fun: Fungibility::Fungible(1000), - id: ARBITRARY_ID, - }) - ); - assert_eq!(trader.0, 500u64.into()); // still thinks we have unreturned weight - } -} diff --git a/primitives/xcm/src/lib.rs b/primitives/xcm/src/lib.rs index d61f62d7e3..d63f13327d 100644 --- a/primitives/xcm/src/lib.rs +++ b/primitives/xcm/src/lib.rs @@ -24,9 +24,6 @@ pub use asset_id_conversions::*; mod constants; pub use constants::*; -mod fee_handlers; -pub use fee_handlers::*; - mod origin_conversion; pub use origin_conversion::*; diff --git a/runtime/common/Cargo.toml b/runtime/common/Cargo.toml index 8eb4a37ee7..fda2a59bf3 100644 --- a/runtime/common/Cargo.toml +++ b/runtime/common/Cargo.toml @@ -27,6 +27,7 @@ pallet-precompile-benchmarks = { workspace = true } pallet-randomness = { workspace = true } pallet-relay-storage-roots = { workspace = true } pallet-xcm-transactor = { workspace = true } +pallet-xcm-weight-trader = { workspace = true } xcm-primitives = { workspace = true } # Substrate @@ -50,6 +51,7 @@ pallet-referenda = { workspace = true } pallet-scheduler = { workspace = true } pallet-sudo = { workspace = true } pallet-timestamp = { workspace = true } +pallet-transaction-payment = { workspace = true } pallet-treasury = { workspace = true } pallet-utility = { workspace = true } pallet-whitelist = { workspace = true } @@ -112,6 +114,8 @@ std = [ "pallet-xcm-transactor/std", "pallet-moonbeam-lazy-migrations/std", "pallet-identity/std", + "pallet-transaction-payment/std", + "pallet-xcm-weight-trader/std", "pallet-message-queue/std", "parity-scale-codec/std", "precompile-utils/std", diff --git a/runtime/common/src/apis.rs b/runtime/common/src/apis.rs index d9aa574eaa..f7df98d191 100644 --- a/runtime/common/src/apis.rs +++ b/runtime/common/src/apis.rs @@ -745,69 +745,13 @@ macro_rules! impl_runtime_apis_plus_common { fn query_acceptable_payment_assets( xcm_version: xcm::Version ) -> Result, XcmPaymentApiError> { - if !matches!(xcm_version, 3) { - return Err(XcmPaymentApiError::UnhandledXcmVersion); - } - - let self_reserve_location: Location = Location::try_from(xcm_config::SelfReserve::get()) - .map_err(|_| XcmPaymentApiError::VersionedConversionFailed)?; - - Ok([VersionedAssetId::V3(XcmAssetId::from(self_reserve_location))] - .into_iter() - .chain( - pallet_asset_manager::AssetTypeId::::iter_keys().filter_map(|asset_location| { - if !AssetManager::payment_is_supported(asset_location.clone()) { - return None; - } - - let location: Option = asset_location.into(); - if let Some(loc) = location { - return Some(VersionedAssetId::V3(loc.into())) - } - None - }) - ) - .filter_map(|asset| asset.into_version(xcm_version).ok()) - .collect()) + XcmWeightTrader::query_acceptable_payment_assets(xcm_version) } fn query_weight_to_asset_fee( weight: Weight, asset: VersionedAssetId ) -> Result { - let self_reserve_location: Location = Location::try_from(xcm_config::SelfReserve::get()) - .map_err(|_| XcmPaymentApiError::VersionedConversionFailed)?; - - let local_asset = VersionedAssetId::V3(XcmAssetId::from(self_reserve_location)); - let asset = asset - .into_version(3) - .map_err(|_| XcmPaymentApiError::VersionedConversionFailed)?; - - if asset == local_asset { - Ok(TransactionPayment::weight_to_fee(weight)) - }else { - let asset_v3: XcmAssetId = asset.try_into() - .map_err(|_| XcmPaymentApiError::VersionedConversionFailed)?; - - if let XcmAssetId::Concrete(asset_location) = asset_v3 { - let asset_type: AssetType = AssetType::from(asset_location); - if !AssetManager::payment_is_supported(asset_type.clone()) { - return Err(XcmPaymentApiError::AssetNotFound); - } - - let units_per_sec = AssetManager::get_units_per_second(asset_type); - if let None = units_per_sec { - return Err(XcmPaymentApiError::WeightNotComputable); - } - - let final_asset_fee = units_per_sec - .unwrap_or_default() - .saturating_mul(weight.ref_time() as u128) - / (WEIGHT_REF_TIME_PER_SECOND as u128); - - return Ok(final_asset_fee) - } - Err(XcmPaymentApiError::AssetNotFound) - } + XcmWeightTrader::query_weight_to_asset_fee(weight, asset) } fn query_xcm_weight(message: VersionedXcm<()>) -> Result { @@ -1001,19 +945,13 @@ macro_rules! impl_runtime_apis_plus_common { id: AssetId(location), fun: Fungible(_) } = asset { - ::AssetId, - ::ForeignAssetType> - >::set_asset_type_asset_id( - location.clone().try_into().expect("convert to v3"), + EvmForeignAssets::set_asset( + location.clone(), i as u128 ); - // set 1-1 - ::ForeignAssetType> - >::set_units_per_second( - location.clone().try_into().expect("convert to v3"), - 1_000_000_000_000u128 + XcmWeightTrader::set_asset_price( + location.clone(), + 1u128.pow(18) ); } } diff --git a/runtime/common/src/migrations.rs b/runtime/common/src/migrations.rs index 10d3f458c8..3bc8a26c88 100644 --- a/runtime/common/src/migrations.rs +++ b/runtime/common/src/migrations.rs @@ -100,12 +100,118 @@ where // } // } +#[derive(parity_scale_codec::Decode, Eq, Ord, PartialEq, PartialOrd)] +enum OldAssetType { + Xcm(xcm::v3::Location), +} + +pub struct MigrateXcmFeesAssetsMeatdata(PhantomData); +impl Migration for MigrateXcmFeesAssetsMeatdata +where + Runtime: pallet_transaction_payment::Config, + Runtime: pallet_xcm_weight_trader::Config, +{ + fn friendly_name(&self) -> &str { + "MM_MigrateXcmFeesAssetsMetadata" + } + + fn migrate(&self, _available_weight: Weight) -> Weight { + let supported_assets = + if let Some(supported_assets) = frame_support::storage::migration::get_storage_value::< + Vec, + >(b"AssetManager", b"SupportedFeePaymentAssets", &[]) + { + sp_std::collections::btree_set::BTreeSet::from_iter( + supported_assets + .into_iter() + .map(|OldAssetType::Xcm(location_v3)| location_v3), + ) + } else { + return Weight::default(); + }; + + let mut assets: Vec<(xcm::v4::Location, (bool, u128))> = Vec::new(); + + for (OldAssetType::Xcm(location_v3), units_per_seconds) in + frame_support::storage::migration::storage_key_iter::< + OldAssetType, + u128, + frame_support::Blake2_128Concat, + >(b"AssetManager", b"AssetTypeUnitsPerSecond") + { + let enabled = supported_assets.get(&location_v3).is_some(); + + if let Ok(location_v4) = location_v3.try_into() { + assets.push((location_v4, (enabled, units_per_seconds))); + } + } + + //***** Start mutate storage *****// + + // Write asset metadata in new pallet_xcm_weight_trader + use frame_support::weights::WeightToFee as _; + for (asset_location, (enabled, units_per_second)) in assets { + let native_amount_per_second: u128 = + ::WeightToFee::weight_to_fee( + &Weight::from_parts( + frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND, + 0, + ), + ) + .try_into() + .unwrap_or(u128::MAX); + let relative_price: u128 = native_amount_per_second + .saturating_mul(10u128.pow(pallet_xcm_weight_trader::RELATIVE_PRICE_DECIMALS)) + .saturating_div(units_per_second); + pallet_xcm_weight_trader::SupportedAssets::::insert( + asset_location, + (enabled, relative_price), + ); + } + + // Remove storage value AssetManager::SupportedFeePaymentAssets + frame_support::storage::unhashed::kill(&frame_support::storage::storage_prefix( + b"AssetManager", + b"SupportedFeePaymentAssets", + )); + + // Remove storage map AssetManager::AssetTypeUnitsPerSecond + let _ = frame_support::storage::migration::clear_storage_prefix( + b"AssetManager", + b"AssetTypeUnitsPerSecond", + &[], + None, + None, + ); + + Weight::default() + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade(&self) -> Result, sp_runtime::DispatchError> { + Ok(Default::default()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(&self, state: Vec) -> Result<(), sp_runtime::DispatchError> { + assert!(frame_support::storage::migration::storage_key_iter::< + OldAssetType, + u128, + frame_support::Blake2_128Concat, + >(b"AssetManager", b"AssetTypeUnitsPerSecond") + .next() + .is_none()); + + Ok(()) + } +} + pub struct CommonMigrations(PhantomData); impl GetMigrations for CommonMigrations where - Runtime: pallet_xcm::Config, - Runtime: frame_system::Config, + Runtime: + pallet_xcm::Config + pallet_transaction_payment::Config + pallet_xcm_weight_trader::Config, Runtime::AccountId: Default, BlockNumberFor: Into, { @@ -246,6 +352,8 @@ where // Box::new(remove_pallet_democracy), // Box::new(remove_collectives_addresses), // Box::new(MigrateCodeToStateTrieV1::(Default::default())), + // completed in runtime 3200 + Box::new(MigrateXcmFeesAssetsMeatdata::(Default::default())), // permanent migrations Box::new(MigrateToLatestXcmVersion::(Default::default())), ] diff --git a/runtime/common/src/weights/mod.rs b/runtime/common/src/weights/mod.rs index dae752bd3a..68b8f2c93f 100644 --- a/runtime/common/src/weights/mod.rs +++ b/runtime/common/src/weights/mod.rs @@ -51,3 +51,4 @@ pub mod pallet_utility; pub mod pallet_whitelist; pub mod pallet_xcm; pub mod pallet_xcm_transactor; +pub mod pallet_xcm_weight_trader; diff --git a/runtime/common/src/weights/pallet_asset_manager.rs b/runtime/common/src/weights/pallet_asset_manager.rs index 9d0ed109f6..0ea1ccf7f6 100644 --- a/runtime/common/src/weights/pallet_asset_manager.rs +++ b/runtime/common/src/weights/pallet_asset_manager.rs @@ -63,25 +63,6 @@ impl pallet_asset_manager::WeightInfo for WeightInfo .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } - /// Storage: `AssetManager::AssetTypeId` (r:1 w:0) - /// Proof: `AssetManager::AssetTypeId` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `AssetManager::SupportedFeePaymentAssets` (r:1 w:1) - /// Proof: `AssetManager::SupportedFeePaymentAssets` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `AssetManager::AssetTypeUnitsPerSecond` (r:0 w:1) - /// Proof: `AssetManager::AssetTypeUnitsPerSecond` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `x` is `[5, 100]`. - fn set_asset_units_per_second(x: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `611 + x * (9 ±0)` - // Estimated: `4000 + x * (10 ±0)` - // Minimum execution time: 19_578_000 picoseconds. - Weight::from_parts(18_705_391, 4000) - // Standard Error: 3_442 - .saturating_add(Weight::from_parts(792_309, 0).saturating_mul(x.into())) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) - .saturating_add(Weight::from_parts(0, 10).saturating_mul(x.into())) - } /// Storage: `AssetManager::SupportedFeePaymentAssets` (r:1 w:1) /// Proof: `AssetManager::SupportedFeePaymentAssets` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `AssetManager::AssetIdType` (r:1 w:1) @@ -91,34 +72,17 @@ impl pallet_asset_manager::WeightInfo for WeightInfo /// Storage: `AssetManager::AssetTypeId` (r:0 w:2) /// Proof: `AssetManager::AssetTypeId` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `x` is `[5, 100]`. - fn change_existing_asset_type(x: u32, ) -> Weight { + fn change_existing_asset_type() -> Weight { // Proof Size summary in bytes: // Measured: `926 + x * (13 ±0)` // Estimated: `4309 + x * (15 ±0)` // Minimum execution time: 29_180_000 picoseconds. Weight::from_parts(29_891_006, 4309) // Standard Error: 4_391 - .saturating_add(Weight::from_parts(874_012, 0).saturating_mul(x.into())) + .saturating_add(Weight::from_parts(874_012, 0)) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) - .saturating_add(Weight::from_parts(0, 15).saturating_mul(x.into())) - } - /// Storage: `AssetManager::SupportedFeePaymentAssets` (r:1 w:1) - /// Proof: `AssetManager::SupportedFeePaymentAssets` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `AssetManager::AssetTypeUnitsPerSecond` (r:0 w:1) - /// Proof: `AssetManager::AssetTypeUnitsPerSecond` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `x` is `[5, 100]`. - fn remove_supported_asset(x: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `196 + x * (5 ±0)` - // Estimated: `1678 + x * (5 ±0)` - // Minimum execution time: 15_115_000 picoseconds. - Weight::from_parts(13_493_610, 1678) - // Standard Error: 2_952 - .saturating_add(Weight::from_parts(694_325, 0).saturating_mul(x.into())) - .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) - .saturating_add(Weight::from_parts(0, 5).saturating_mul(x.into())) + .saturating_add(Weight::from_parts(0, 15)) } /// Storage: `AssetManager::SupportedFeePaymentAssets` (r:1 w:1) /// Proof: `AssetManager::SupportedFeePaymentAssets` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) @@ -129,16 +93,16 @@ impl pallet_asset_manager::WeightInfo for WeightInfo /// Storage: `AssetManager::AssetTypeId` (r:0 w:1) /// Proof: `AssetManager::AssetTypeId` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `x` is `[5, 100]`. - fn remove_existing_asset_type(x: u32, ) -> Weight { + fn remove_existing_asset_type() -> Weight { // Proof Size summary in bytes: // Measured: `482 + x * (10 ±0)` // Estimated: `3955 + x * (10 ±0)` // Minimum execution time: 21_219_000 picoseconds. Weight::from_parts(20_476_212, 3955) // Standard Error: 3_389 - .saturating_add(Weight::from_parts(716_188, 0).saturating_mul(x.into())) + .saturating_add(Weight::from_parts(716_188, 0)) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) - .saturating_add(Weight::from_parts(0, 10).saturating_mul(x.into())) + .saturating_add(Weight::from_parts(0, 10)) } } diff --git a/runtime/common/src/weights/pallet_xcm_weight_trader.rs b/runtime/common/src/weights/pallet_xcm_weight_trader.rs new file mode 100644 index 0000000000..6680f603b0 --- /dev/null +++ b/runtime/common/src/weights/pallet_xcm_weight_trader.rs @@ -0,0 +1,104 @@ +// Copyright 2024 Moonbeam foundation +// This file is part of Moonbeam. + +// Moonbeam is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonbeam is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonbeam. If not, see . + +//! Autogenerated weights for `pallet_xcm_weight_trader` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-08-26, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `MacBook-Pro-de-romarq.local`, CPU: `` +//! WASM-EXECUTION: Compiled, CHAIN: Some("moonbase-dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/release/moonbeam +// benchmark +// pallet +// --chain=moonbase-dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_xcm_weight_trader +// --extrinsic=* +// --wasm-execution=compiled +// --header=./file_header.txt +// --template=./benchmarking/frame-weight-template.hbs +// --output=./runtime/common/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weights for `pallet_xcm_weight_trader`. +pub struct WeightInfo(PhantomData); +impl pallet_xcm_weight_trader::WeightInfo for WeightInfo { + /// Storage: `XcmWeightTrader::SupportedAssets` (r:1 w:1) + /// Proof: `XcmWeightTrader::SupportedAssets` (`max_values`: None, `max_size`: Some(635), added: 3110, mode: `MaxEncodedLen`) + fn add_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `42` + // Estimated: `4100` + // Minimum execution time: 6_000_000 picoseconds. + Weight::from_parts(7_000_000, 4100) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `XcmWeightTrader::SupportedAssets` (r:1 w:1) + /// Proof: `XcmWeightTrader::SupportedAssets` (`max_values`: None, `max_size`: Some(635), added: 3110, mode: `MaxEncodedLen`) + fn edit_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `102` + // Estimated: `4100` + // Minimum execution time: 7_000_000 picoseconds. + Weight::from_parts(8_000_000, 4100) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `XcmWeightTrader::SupportedAssets` (r:1 w:1) + /// Proof: `XcmWeightTrader::SupportedAssets` (`max_values`: None, `max_size`: Some(635), added: 3110, mode: `MaxEncodedLen`) + fn resume_asset_support() -> Weight { + // Proof Size summary in bytes: + // Measured: `102` + // Estimated: `4100` + // Minimum execution time: 8_000_000 picoseconds. + Weight::from_parts(8_000_000, 4100) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `XcmWeightTrader::SupportedAssets` (r:1 w:1) + /// Proof: `XcmWeightTrader::SupportedAssets` (`max_values`: None, `max_size`: Some(635), added: 3110, mode: `MaxEncodedLen`) + fn pause_asset_support() -> Weight { + // Proof Size summary in bytes: + // Measured: `102` + // Estimated: `4100` + // Minimum execution time: 7_000_000 picoseconds. + Weight::from_parts(8_000_000, 4100) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `XcmWeightTrader::SupportedAssets` (r:1 w:1) + /// Proof: `XcmWeightTrader::SupportedAssets` (`max_values`: None, `max_size`: Some(635), added: 3110, mode: `MaxEncodedLen`) + fn remove_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `102` + // Estimated: `4100` + // Minimum execution time: 7_000_000 picoseconds. + Weight::from_parts(8_000_000, 4100) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } +} diff --git a/runtime/moonbase/Cargo.toml b/runtime/moonbase/Cargo.toml index 3e7071a1a6..e1b95f5aaf 100644 --- a/runtime/moonbase/Cargo.toml +++ b/runtime/moonbase/Cargo.toml @@ -45,6 +45,7 @@ pallet-precompile-benchmarks = { workspace = true } pallet-proxy-genesis-companion = { workspace = true } pallet-randomness = { workspace = true } pallet-xcm-transactor = { workspace = true } +pallet-xcm-weight-trader = { workspace = true } # Moonbeam precompiles pallet-evm-precompile-author-mapping = { workspace = true } @@ -297,6 +298,7 @@ std = [ "pallet-whitelist/std", "pallet-xcm-transactor/std", "pallet-xcm/std", + "pallet-xcm-weight-trader/std", "parachain-info/std", "parachains-common/std", "parity-scale-codec/std", @@ -389,6 +391,7 @@ runtime-benchmarks = [ "pallet-xcm-benchmarks/runtime-benchmarks", "pallet-xcm-transactor/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", + "pallet-xcm-weight-trader/runtime-benchmarks", "session-keys-primitives/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", diff --git a/runtime/moonbase/src/lib.rs b/runtime/moonbase/src/lib.rs index 552bdb27e9..bedbabd2e3 100644 --- a/runtime/moonbase/src/lib.rs +++ b/runtime/moonbase/src/lib.rs @@ -118,13 +118,8 @@ use sp_std::{ #[cfg(feature = "std")] use sp_version::NativeVersion; use sp_version::RuntimeVersion; -use xcm::{ - v3::{AssetId as XcmAssetId, Location}, - IntoVersion, VersionedAssetId, VersionedAssets, VersionedLocation, VersionedXcm, -}; -use xcm_config::AssetType; +use xcm::{VersionedAssetId, VersionedAssets, VersionedLocation, VersionedXcm}; use xcm_fee_payment_runtime_api::Error as XcmPaymentApiError; -use xcm_primitives::UnitsToWeightRatio; use runtime_params::*; @@ -1471,6 +1466,7 @@ construct_runtime! { EmergencyParaXcm: pallet_emergency_para_xcm::{Pallet, Call, Storage, Event} = 55, EvmForeignAssets: pallet_moonbeam_foreign_assets::{Pallet, Call, Storage, Event} = 56, Parameters: pallet_parameters = 57, + XcmWeightTrader: pallet_xcm_weight_trader::{Pallet, Call, Storage, Event} = 58, } } @@ -1551,6 +1547,7 @@ mod benches { [pallet_precompile_benchmarks, PrecompileBenchmarks] [pallet_moonbeam_lazy_migrations, MoonbeamLazyMigrations] [pallet_parameters, Parameters] + [pallet_xcm_weight_trader, XcmWeightTrader] ); } diff --git a/runtime/moonbase/src/xcm_config.rs b/runtime/moonbase/src/xcm_config.rs index 0dc065c641..253f898c6d 100644 --- a/runtime/moonbase/src/xcm_config.rs +++ b/runtime/moonbase/src/xcm_config.rs @@ -18,10 +18,10 @@ //! use super::{ - governance, AccountId, AssetId, AssetManager, Balance, Balances, DealWithFees, - EmergencyParaXcm, Erc20XcmBridge, EvmForeignAssets, MaintenanceMode, MessageQueue, - ParachainInfo, ParachainSystem, Perbill, PolkadotXcm, Runtime, RuntimeBlockWeights, - RuntimeCall, RuntimeEvent, RuntimeOrigin, Treasury, XcmpQueue, + governance, AccountId, AssetId, AssetManager, Balance, Balances, EmergencyParaXcm, + Erc20XcmBridge, EvmForeignAssets, MaintenanceMode, MessageQueue, ParachainInfo, + ParachainSystem, Perbill, PolkadotXcm, Runtime, RuntimeBlockWeights, RuntimeCall, RuntimeEvent, + RuntimeOrigin, Treasury, XcmpQueue, }; use crate::OpenTechCommitteeInstance; use moonbeam_runtime_common::weights as moonbase_weights; @@ -45,7 +45,7 @@ use xcm_builder::{ EnsureXcmOrigin, FungibleAdapter as XcmCurrencyAdapter, FungiblesAdapter, HashedDescription, NoChecking, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountKey20AsNative, SovereignSignedViaLocation, - TakeWeightCredit, UsingComponents, WeightInfoBounds, WithComputedOrigin, + TakeWeightCredit, WeightInfoBounds, WithComputedOrigin, }; use parachains_common::message_queue::{NarrowOriginToSibling, ParaIdToSibling}; @@ -61,8 +61,8 @@ use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; use orml_xcm_support::MultiNativeAsset; use xcm_primitives::{ AbsoluteAndRelativeReserve, AccountIdToCurrencyId, AccountIdToLocation, AsAssetType, - FirstAssetTrader, IsBridgedConcreteAssetFrom, SignedToAccountId20, UtilityAvailableCalls, - UtilityEncodeCall, XcmTransact, + IsBridgedConcreteAssetFrom, SignedToAccountId20, UtilityAvailableCalls, UtilityEncodeCall, + XcmTransact, }; use parity_scale_codec::{Decode, Encode}; @@ -232,23 +232,6 @@ parameter_types! { pub XcmFeesAccount: AccountId = Treasury::account_id(); } -/// This is the struct that will handle the revenue from xcm fees -/// We do not burn anything because we want to mimic exactly what -/// the sovereign account has -pub type XcmFeesToAccount = xcm_primitives::XcmFeesToAccount< - super::Assets, - ( - ConvertedConcreteId< - AssetId, - Balance, - AsAssetType, - JustTry, - >, - ), - AccountId, - XcmFeesAccount, ->; - // Our implementation of the Moonbeam Call // Attachs the right origin in case the call is made to pallet-ethereum-xcm #[cfg(not(feature = "evm-tracing"))] @@ -311,16 +294,7 @@ impl xcm_executor::Config for XcmExecutorConfig { // we use UsingComponents and the local way of handling fees // When we receive a non-reserve asset, we use AssetManager to fetch how many // units per second we should charge - type Trader = ( - UsingComponents< - ::WeightToFee, - SelfReserve, - AccountId, - Balances, - DealWithFees, - >, - FirstAssetTrader, - ); + type Trader = pallet_xcm_weight_trader::Trader; type ResponseHandler = PolkadotXcm; type SubscriptionService = PolkadotXcm; type AssetTrap = pallet_erc20_xcm_bridge::AssetTrapWrapper; @@ -380,7 +354,6 @@ impl pallet_xcm::Config for Runtime { type MaxLockers = ConstU32<8>; type MaxRemoteLockConsumers = ConstU32<0>; type RemoteLockConsumerIdentifier = (); - // TODO pallet-xcm weights type WeightInfo = moonbase_weights::pallet_xcm::WeightInfo; type AdminOrigin = EnsureRoot; } @@ -785,6 +758,33 @@ impl pallet_moonbeam_foreign_assets::Config for Runtime { type XcmLocationToH160 = LocationToH160; } +pub struct AssetFeesFilter; +impl frame_support::traits::Contains for AssetFeesFilter { + fn contains(location: &Location) -> bool { + location.parent_count() > 0 + && location.first_interior() != Erc20XcmBridgePalletLocation::get().first_interior() + } +} + +impl pallet_xcm_weight_trader::Config for Runtime { + type AccountIdToLocation = AccountIdToLocation; + type AddSupportedAssetOrigin = EnsureRoot; + type AssetLocationFilter = AssetFeesFilter; + type AssetTransactor = AssetTransactors; + type Balance = Balance; + type EditSupportedAssetOrigin = EnsureRoot; + type NativeLocation = SelfReserve; + type PauseSupportedAssetOrigin = EnsureRoot; + type RemoveSupportedAssetOrigin = EnsureRoot; + type RuntimeEvent = RuntimeEvent; + type ResumeSupportedAssetOrigin = EnsureRoot; + type WeightInfo = moonbase_weights::pallet_xcm_weight_trader::WeightInfo; + type WeightToFee = ::WeightToFee; + type XcmFeesAccount = XcmFeesAccount; + #[cfg(feature = "runtime-benchmarks")] + type NotFilteredLocation = RelayLocation; +} + #[cfg(feature = "runtime-benchmarks")] mod testing { use super::*; diff --git a/runtime/moonbase/tests/integration_test.rs b/runtime/moonbase/tests/integration_test.rs index c27f8085f9..841a55e0b7 100644 --- a/runtime/moonbase/tests/integration_test.rs +++ b/runtime/moonbase/tests/integration_test.rs @@ -1568,7 +1568,7 @@ fn xtokens_precompiles_transfer() { weight: 4_000_000, }, ) - .expect_cost(348090) + .expect_cost(348298) .expect_no_logs() // We expect an evm subcall ERC20.burnFrom .with_subcall_handle(move |subcall| { @@ -1659,7 +1659,7 @@ fn xtokens_precompiles_transfer_multiasset() { weight: 4_000_000, }, ) - .expect_cost(348090) + .expect_cost(348298) .expect_no_logs() // We expect an evm subcall ERC20.burnFrom .with_subcall_handle(move |subcall| { @@ -1743,7 +1743,7 @@ fn xtokens_precompiles_transfer_native() { weight: 4_000_000, }, ) - .expect_cost(16000) + .expect_cost(16208) .expect_no_logs() .execute_returns(()); }) diff --git a/runtime/moonbase/tests/xcm_mock/mod.rs b/runtime/moonbase/tests/xcm_mock/mod.rs index f556bc4710..4a9e567d99 100644 --- a/runtime/moonbase/tests/xcm_mock/mod.rs +++ b/runtime/moonbase/tests/xcm_mock/mod.rs @@ -106,7 +106,7 @@ decl_test_parachain! { Runtime = statemint_like::Runtime, XcmpMessageHandler = statemint_like::MsgQueue, DmpMessageHandler = statemint_like::MsgQueue, - new_ext = statemint_ext(4), + new_ext = statemint_ext(1000), } } @@ -118,7 +118,7 @@ decl_test_relay_chain! { XcmConfig = relay_chain::XcmConfig, MessageQueue = relay_chain::MessageQueue, System = relay_chain::System, - new_ext = relay_ext(vec![1, 2, 3, 4]), + new_ext = relay_ext(vec![1, 2, 3, 1000]), } } @@ -129,7 +129,7 @@ decl_test_network! { (1, ParaA), (2, ParaB), (3, ParaC), - (4, Statemint), + (1000, Statemint), ], } } @@ -270,3 +270,4 @@ pub type XTokens = orml_xtokens::Pallet; pub type RelayBalances = pallet_balances::Pallet; pub type ParaBalances = pallet_balances::Pallet; pub type XcmTransactor = pallet_xcm_transactor::Pallet; +pub type XcmWeightTrader = pallet_xcm_weight_trader::Pallet; diff --git a/runtime/moonbase/tests/xcm_mock/parachain.rs b/runtime/moonbase/tests/xcm_mock/parachain.rs index 4f39468545..7dbc8584aa 100644 --- a/runtime/moonbase/tests/xcm_mock/parachain.rs +++ b/runtime/moonbase/tests/xcm_mock/parachain.rs @@ -44,15 +44,15 @@ use pallet_ethereum::PostLogContent; use polkadot_core_primitives::BlockNumber as RelayBlockNumber; use polkadot_parachain::primitives::{Id as ParaId, Sibling}; use xcm::latest::{ - AssetId as XcmAssetId, Error as XcmError, ExecuteXcm, + Error as XcmError, ExecuteXcm, Junction::{PalletInstance, Parachain}, Location, NetworkId, Outcome, Xcm, }; use xcm_builder::{ AccountKey20Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, - AllowTopLevelPaidExecutionFrom, ConvertedConcreteId, EnsureXcmOrigin, FixedRateOfFungible, - FixedWeightBounds, FungibleAdapter as XcmCurrencyAdapter, FungiblesAdapter, IsConcrete, - NoChecking, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, + AllowTopLevelPaidExecutionFrom, Case, ConvertedConcreteId, EnsureXcmOrigin, FixedWeightBounds, + FungibleAdapter as XcmCurrencyAdapter, FungiblesAdapter, IsConcrete, NoChecking, + ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountKey20AsNative, SovereignSignedViaLocation, TakeWeightCredit, WithComputedOrigin, }; @@ -286,32 +286,20 @@ pub type XcmBarrier = ( parameter_types! { /// Xcm fees will go to the treasury account pub XcmFeesAccount: AccountId = Treasury::account_id(); + /// Parachain token units per second of execution + pub ParaTokensPerSecond: u128 = 1000000000000; } -/// This is the struct that will handle the revenue from xcm fees -pub type XcmFeesToAccount_ = xcm_primitives::XcmFeesToAccount< - Assets, - ( - ConvertedConcreteId< - AssetId, - Balance, - xcm_primitives::AsAssetType, - JustTry, - >, - ), - AccountId, - XcmFeesAccount, ->; +pub struct WeightToFee; +impl sp_weights::WeightToFee for WeightToFee { + type Balance = Balance; -parameter_types! { - // We cannot skip the native trader for some specific tests, so we will have to work with - // a native trader that charges same number of units as weight - // We use both the old and new anchoring logics - pub ParaTokensPerSecond: (XcmAssetId, u128, u128) = ( - AssetId(SelfReserve::get()), - 1000000000000, - 0, - ); + fn weight_to_fee(weight: &Weight) -> Self::Balance { + use sp_runtime::SaturatedConversion as _; + Self::Balance::saturated_from(weight.ref_time()) + .saturating_mul(ParaTokensPerSecond::get()) + .saturating_div(frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND as u128) + } } parameter_types! { @@ -330,6 +318,17 @@ parameter_types! { ].into() }; pub const MaxAssetsIntoHolding: u32 = 64; + + pub AssetHubLocation: Location = Location::new(1, [Parachain(1000)]); + pub RelayLocationFilter: AssetFilter = Wild(AllOf { + fun: WildFungible, + id: xcm::prelude::AssetId(Location::parent()), + }); + + pub RelayChainNativeAssetFromAssetHub: (AssetFilter, Location) = ( + RelayLocationFilter::get(), + AssetHubLocation::get() + ); } use frame_system::RawOrigin; @@ -338,27 +337,27 @@ use sp_runtime::DispatchErrorWithPostInfo; use xcm_executor::traits::CallDispatcher; moonbeam_runtime_common::impl_moonbeam_xcm_call!(); +type Reserves = ( + // Relaychain (DOT) from Asset Hub + Case, + // Assets which the reserve is the same as the origin. + orml_xcm_support::MultiNativeAsset< + xcm_primitives::AbsoluteAndRelativeReserve, + >, +); + pub struct XcmConfig; impl Config for XcmConfig { type RuntimeCall = RuntimeCall; type XcmSender = XcmRouter; type AssetTransactor = AssetTransactors; type OriginConverter = XcmOriginToTransactDispatchOrigin; - type IsReserve = orml_xcm_support::MultiNativeAsset< - xcm_primitives::AbsoluteAndRelativeReserve, - >; + type IsReserve = Reserves; type IsTeleporter = (); type UniversalLocation = UniversalLocation; type Barrier = XcmBarrier; type Weigher = FixedWeightBounds; - // We use three traders - // When we receive either representation of the self-reserve asset, - // When we receive a non-reserve asset, we use AssetManager to fetch how many - // units per second we should charge - type Trader = ( - FixedRateOfFungible, - xcm_primitives::FirstAssetTrader, - ); + type Trader = pallet_xcm_weight_trader::Trader; type ResponseHandler = PolkadotXcm; type SubscriptionService = PolkadotXcm; @@ -430,7 +429,7 @@ parameter_types! { parameter_type_with_key! { pub ParachainMinFee: |location: Location| -> Option { match (location.parents, location.first_interior()) { - (1, Some(Parachain(4u32))) => Some(50u128), + (1, Some(Parachain(1000u32))) => Some(50u128), _ => None, } }; @@ -828,6 +827,29 @@ impl pallet_xcm_transactor::Config for Runtime { type MaxHrmpFee = xcm_builder::Case; } +parameter_types! { + pub RelayLocation: Location = Location::parent(); +} + +impl pallet_xcm_weight_trader::Config for Runtime { + type AccountIdToLocation = xcm_primitives::AccountIdToLocation; + type AddSupportedAssetOrigin = EnsureRoot; + type AssetLocationFilter = Everything; + type AssetTransactor = AssetTransactors; + type Balance = Balance; + type EditSupportedAssetOrigin = EnsureRoot; + type NativeLocation = SelfReserve; + type PauseSupportedAssetOrigin = EnsureRoot; + type RemoveSupportedAssetOrigin = EnsureRoot; + type RuntimeEvent = RuntimeEvent; + type ResumeSupportedAssetOrigin = EnsureRoot; + type WeightInfo = (); + type WeightToFee = WeightToFee; + type XcmFeesAccount = XcmFeesAccount; + #[cfg(feature = "runtime-benchmarks")] + type NotFilteredLocation = RelayLocation; +} + parameter_types! { pub const MinimumPeriod: u64 = 1000; } @@ -1077,6 +1099,7 @@ construct_runtime!( XTokens: orml_xtokens, AssetManager: pallet_asset_manager, XcmTransactor: pallet_xcm_transactor, + XcmWeightTrader: pallet_xcm_weight_trader, Treasury: pallet_treasury, Proxy: pallet_proxy, diff --git a/runtime/moonbase/tests/xcm_mock/relay_chain.rs b/runtime/moonbase/tests/xcm_mock/relay_chain.rs index 15bb49b91e..f391b31d84 100644 --- a/runtime/moonbase/tests/xcm_mock/relay_chain.rs +++ b/runtime/moonbase/tests/xcm_mock/relay_chain.rs @@ -177,7 +177,7 @@ pub type XcmBarrier = ( parameter_types! { pub Kusama: AssetFilter = Wild(AllOf { fun: WildFungible, id: AssetId(KsmLocation::get()) }); - pub Statemine: Location = Parachain(4).into(); + pub Statemine: Location = Parachain(1000).into(); pub KusamaForStatemine: (AssetFilter, Location) = (Kusama::get(), Statemine::get()); } diff --git a/runtime/moonbase/tests/xcm_mock/statemint_like.rs b/runtime/moonbase/tests/xcm_mock/statemint_like.rs index 48a358c0a5..594919836f 100644 --- a/runtime/moonbase/tests/xcm_mock/statemint_like.rs +++ b/runtime/moonbase/tests/xcm_mock/statemint_like.rs @@ -18,19 +18,17 @@ use frame_support::{ construct_runtime, parameter_types, - traits::{AsEnsureOriginWithArg, Contains, Everything, Nothing}, + traits::{AsEnsureOriginWithArg, Contains, ContainsPair, Everything, Get, Nothing}, weights::Weight, }; use frame_system::{EnsureRoot, EnsureSigned}; - +use polkadot_core_primitives::BlockNumber as RelayBlockNumber; use sp_core::H256; use sp_runtime::{ traits::{ConstU32, Hash, IdentityLookup}, AccountId32, }; -use polkadot_core_primitives::BlockNumber as RelayBlockNumber; - use polkadot_parachain::primitives::Id as ParaId; use polkadot_parachain::primitives::Sibling; use sp_std::convert::TryFrom; @@ -290,8 +288,33 @@ pub type Barrier = ( parameter_types! { pub MatcherLocation: Location = Location::here(); pub const MaxAssetsIntoHolding: u32 = 64; + pub const RelayTokenLocation: Location = Location::parent(); } +// Copied from: +// +// https://github.com/paritytech/polkadot-sdk/blob/f4eb41773611008040c9d4d8a8e6b7323eccfca1/cumulus +// /parachains/common/src/xcm_config.rs#L118 +// +// The difference with the original "ConcreteAssetFromSystem" (which is used by AssetHub), +// is that in our tests we only need to check if the asset matches the relay one. +pub struct ConcreteAssetFromRelay(sp_std::marker::PhantomData); +impl> ContainsPair + for ConcreteAssetFromRelay +{ + fn contains(asset: &Asset, origin: &Location) -> bool { + let is_relay = match origin.unpack() { + // The Relay Chain + (1, []) => true, + // Others + _ => false, + }; + asset.id.0 == AssetLocation::get() && is_relay + } +} + +pub type TrustedTeleporters = (ConcreteAssetFromRelay,); + pub struct XcmConfig; impl Config for XcmConfig { type RuntimeCall = RuntimeCall; @@ -300,7 +323,7 @@ impl Config for XcmConfig { type OriginConverter = XcmOriginToTransactDispatchOrigin; type IsReserve = orml_xcm_support::MultiNativeAsset; - type IsTeleporter = (); + type IsTeleporter = TrustedTeleporters; type UniversalLocation = UniversalLocation; type Barrier = Barrier; type Weigher = FixedWeightBounds; diff --git a/runtime/moonbase/tests/xcm_tests.rs b/runtime/moonbase/tests/xcm_tests.rs index 24ad722407..e9234554ff 100644 --- a/runtime/moonbase/tests/xcm_tests.rs +++ b/runtime/moonbase/tests/xcm_tests.rs @@ -30,17 +30,52 @@ use pallet_xcm_transactor::{ use sp_runtime::traits::MaybeEquivalence; use sp_std::boxed::Box; use xcm::latest::prelude::{ - AccountId32, AccountKey20, All, BuyExecution, ClearOrigin, DepositAsset, GeneralIndex, - Junction, Junctions, Limited, Location, OriginKind, PalletInstance, Parachain, QueryResponse, - Reanchorable, Response, WeightLimit, WithdrawAsset, Xcm, + AccountId32, AccountKey20, All, Asset as XcmAsset, AssetId as XcmAssetId, Assets as XcmAssets, + BuyExecution, ClearOrigin, DepositAsset, Fungible, GeneralIndex, Junction, Junctions, Limited, + Location, OriginKind, PalletInstance, Parachain, QueryResponse, Reanchorable, Response, + WeightLimit, WithdrawAsset, Xcm, }; -use xcm::{VersionedLocation, WrapVersion}; +use xcm::{IntoVersion, VersionedLocation, WrapVersion}; use xcm_executor::traits::ConvertLocation; use xcm_mock::*; use xcm_primitives::{UtilityEncodeCall, DEFAULT_PROOF_SIZE}; use xcm_simulator::TestExt; mod common; use cumulus_primitives_core::relay_chain::HrmpChannelId; + +fn add_supported_asset(asset_type: parachain::AssetType, units_per_second: u128) -> Result<(), ()> { + let parachain::AssetType::Xcm(location_v3) = asset_type; + let VersionedLocation::V4(location_v4) = VersionedLocation::V3(location_v3) + .into_version(4) + .map_err(|_| ())? + else { + return Err(()); + }; + use frame_support::weights::WeightToFee as _; + let native_amount_per_second: u128 = + ::WeightToFee::weight_to_fee( + &Weight::from_parts( + frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND, + 0, + ), + ) + .try_into() + .map_err(|_| ())?; + let precision_factor = 10u128.pow(pallet_xcm_weight_trader::RELATIVE_PRICE_DECIMALS); + let relative_price: u128 = if units_per_second > 0u128 { + native_amount_per_second + .saturating_mul(precision_factor) + .saturating_div(units_per_second) + } else { + 0u128 + }; + pallet_xcm_weight_trader::SupportedAssets::::insert( + location_v4, + (true, relative_price), + ); + Ok(()) +} + // Send a relay asset (like DOT) to a parachain A #[test] fn receive_relay_asset_from_relay() { @@ -62,12 +97,7 @@ fn receive_relay_asset_from_relay() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0)); }); // Actually send relay asset to parachain @@ -118,12 +148,7 @@ fn send_relay_asset_to_relay() { true )); // Free execution - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location, 0)); }); let dest: Location = Junction::AccountKey20 { @@ -210,12 +235,7 @@ fn send_relay_asset_to_para_b() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location.clone(), - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0)); }); // Register asset in paraB. Free execution @@ -227,12 +247,7 @@ fn send_relay_asset_to_para_b() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location, 0)); }); // First send relay chain asset to Parachain A like in previous test @@ -317,12 +332,7 @@ fn send_para_a_asset_to_para_b() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location, 0)); }); // Send para A asset from para A to para B @@ -389,12 +399,7 @@ fn send_para_a_asset_from_para_b_to_para_c() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location.clone(), - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0)); }); // Register para A asset in parachain C. Free execution @@ -406,12 +411,7 @@ fn send_para_a_asset_from_para_b_to_para_c() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location, 0)); }); // Send para A asset to para B @@ -505,12 +505,7 @@ fn send_para_a_asset_to_para_b_and_back_to_para_a() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location, 0)); }); // Send para A asset to para B @@ -603,12 +598,7 @@ fn send_para_a_asset_to_para_b_and_back_to_para_a_with_new_reanchoring() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location, 0)); }); let dest = Location { @@ -716,12 +706,7 @@ fn receive_relay_asset_with_trader() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 2500000000000u128, - 0 - )); + assert_ok!(add_supported_asset(source_location, 2_500_000_000_000)); }); let dest: Location = Junction::AccountKey20 { @@ -778,12 +763,7 @@ fn send_para_a_asset_to_para_b_with_trader() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 2500000000000u128, - 0 - )); + assert_ok!(add_supported_asset(source_location, 2500000000000)); }); let dest = Location { @@ -856,12 +836,7 @@ fn send_para_a_asset_to_para_b_with_trader_and_fee() { true )); // With these units per second, 80K weight convrets to 1 asset unit - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 12500000u128, - 0 - )); + assert_ok!(add_supported_asset(source_location, 12500000)); }); let dest = Location { @@ -896,7 +871,7 @@ fn send_para_a_asset_to_para_b_with_trader_and_fee() { }); ParaB::execute_with(|| { - // free execution, full amount received because trully the xcm instruction does not cost + // free execution, full amount received because the xcm instruction does not cost // what it is specified assert_eq!(Assets::balance(source_id, &PARAALICE.into()), 101); }); @@ -931,12 +906,12 @@ fn error_when_not_paying_enough() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 2500000000000u128, - 0 - )); + assert_ok!(add_supported_asset(source_location, 2500000000000)); + }); + + ParaA::execute_with(|| { + // amount not received as it is not paying enough + assert_eq!(Assets::balance(source_id, &PARAALICE.into()), 0); }); // We are sending 100 tokens from relay. @@ -980,12 +955,7 @@ fn transact_through_derivative_multilocation() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 1u128, - 0 - )); + assert_ok!(add_supported_asset(source_location, 1)); // Root can set transact info assert_ok!(XcmTransactor::set_transact_info( @@ -1150,12 +1120,7 @@ fn transact_through_derivative_with_custom_fee_weight() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 1u128, - 0 - )); + assert_ok!(add_supported_asset(source_location, 1)); }); // Let's construct the call to know how much weight it is going to require @@ -1306,12 +1271,7 @@ fn transact_through_derivative_with_custom_fee_weight_refund() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 1u128, - 0 - )); + assert_ok!(add_supported_asset(source_location, 1)); }); // Let's construct the call to know how much weight it is going to require @@ -1461,12 +1421,7 @@ fn transact_through_sovereign() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 1u128, - 0 - )); + assert_ok!(add_supported_asset(source_location, 1)); // Root can set transact info assert_ok!(XcmTransactor::set_transact_info( @@ -1734,12 +1689,7 @@ fn transact_through_sovereign_with_custom_fee_weight() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 1u128, - 0 - )); + assert_ok!(add_supported_asset(source_location, 1)); }); let dest: Location = AccountKey20 { @@ -1888,12 +1838,7 @@ fn transact_through_sovereign_with_custom_fee_weight_refund() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 1u128, - 0 - )); + assert_ok!(add_supported_asset(source_location, 1)); }); let dest: Location = AccountKey20 { @@ -2042,12 +1987,7 @@ fn test_automatic_versioning_on_runtime_upgrade_with_relay() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location, 0)); }); let response = Response::Version(2); @@ -2196,12 +2136,7 @@ fn test_automatic_versioning_on_runtime_upgrade_with_para_b() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location, 0)); }); ParaA::execute_with(|| { @@ -2329,12 +2264,7 @@ fn receive_asset_with_no_sufficients_not_possible_if_non_existent_account() { 1u128, false )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location, 0)); }); // Actually send relay asset to parachain @@ -2409,12 +2339,7 @@ fn receive_assets_with_sufficients_true_allows_non_funded_account_to_receive_ass 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location, 0)); }); // Actually send relay asset to parachain @@ -2470,12 +2395,7 @@ fn evm_account_receiving_assets_should_handle_sufficients_ref_count() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location, 0)); }); // Actually send relay asset to parachain @@ -2540,12 +2460,7 @@ fn empty_account_should_not_be_reset() { 1u128, false )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location, 0)); }); // Send native token to evm_account @@ -2626,7 +2541,7 @@ fn test_statemint_like() { let statemint_asset_a_balances = Location::new( 1, [ - Parachain(4), + Parachain(1000), PalletInstance(5), xcm::latest::prelude::GeneralIndex(0u128), ], @@ -2651,12 +2566,7 @@ fn test_statemint_like() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location, 0)); }); Statemint::execute_with(|| { @@ -2736,7 +2646,11 @@ fn send_statemint_asset_from_para_a_to_statemint_with_relay_fee() { // Statemint asset let statemint_asset = Location::new( 1, - [Parachain(4u32), PalletInstance(5u8), GeneralIndex(10u128)], + [ + Parachain(1000u32), + PalletInstance(5u8), + GeneralIndex(10u128), + ], ); let statemint_location_asset = parachain::AssetType::Xcm( xcm_builder::WithLatestLocationConverter::convert(&statemint_asset).expect("convert to v3"), @@ -2765,12 +2679,7 @@ fn send_statemint_asset_from_para_a_to_statemint_with_relay_fee() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - relay_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(relay_location, 0)); assert_ok!(AssetManager::register_foreign_asset( parachain::RuntimeOrigin::root(), @@ -2779,12 +2688,7 @@ fn send_statemint_asset_from_para_a_to_statemint_with_relay_fee() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - statemint_location_asset, - 0u128, - 1 - )); + assert_ok!(add_supported_asset(statemint_location_asset, 0)); }); let parachain_beneficiary_from_relay: Location = Junction::AccountKey20 { @@ -2873,7 +2777,7 @@ fn send_statemint_asset_from_para_a_to_statemint_with_relay_fee() { let statemint_beneficiary = Location { parents: 1, interior: [ - Parachain(4), + Parachain(1000), AccountId32 { network: None, id: RELAYBOB.into(), @@ -2932,6 +2836,971 @@ fn send_statemint_asset_from_para_a_to_statemint_with_relay_fee() { }); } +#[test] +fn send_dot_from_moonbeam_to_statemint_via_xtokens_transfer() { + MockNet::reset(); + + // Relay asset + let relay_location = parachain::AssetType::Xcm(xcm::v3::Location::parent()); + let source_relay_id: parachain::AssetId = relay_location.clone().into(); + + let relay_asset_metadata = parachain::AssetMetadata { + name: b"RelayToken".to_vec(), + symbol: b"Relay".to_vec(), + decimals: 12, + }; + + let dest_para = Location::new(1, [Parachain(1)]); + + let sov = xcm_builder::SiblingParachainConvertsVia::< + polkadot_parachain::primitives::Sibling, + statemint_like::AccountId, + >::convert_location(&dest_para) + .unwrap(); + + ParaA::execute_with(|| { + assert_ok!(AssetManager::register_foreign_asset( + parachain::RuntimeOrigin::root(), + relay_location.clone(), + relay_asset_metadata, + 1u128, + true + )); + XcmWeightTrader::set_asset_price(Location::parent(), 0u128); + }); + + let parachain_beneficiary_absolute: Location = Junction::AccountKey20 { + network: None, + key: PARAALICE, + } + .into(); + + let statemint_beneficiary_absolute: Location = Junction::AccountId32 { + network: None, + id: RELAYALICE.into(), + } + .into(); + + // First we send relay chain asset to Alice in AssetHub (via teleport) + Relay::execute_with(|| { + assert_ok!(RelayChainPalletXcm::limited_teleport_assets( + relay_chain::RuntimeOrigin::signed(RELAYALICE), + Box::new(Parachain(1000).into()), + Box::new( + VersionedLocation::V4(statemint_beneficiary_absolute) + .clone() + .into() + ), + Box::new(([], 200).into()), + 0, + WeightLimit::Unlimited + )); + }); + + // Send DOTs from AssetHub to ParaA (Moonbeam) + Statemint::execute_with(|| { + // Check Alice received 200 tokens on AssetHub + assert_eq!( + StatemintBalances::free_balance(RELAYALICE), + INITIAL_BALANCE + 200 + ); + + assert_ok!(StatemintBalances::transfer_allow_death( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + sov, + 110000000000000 + )); + + // Now send those tokens to ParaA + assert_ok!(StatemintChainPalletXcm::limited_reserve_transfer_assets( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute.clone()) + .clone() + .into() + ), + Box::new((Location::parent(), 200).into()), + 0, + WeightLimit::Unlimited + )); + }); + + ParaA::execute_with(|| { + // Alice should have received the DOTs + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 200); + }); + + let dest = Location::new( + 1, + [ + Parachain(1000), + AccountId32 { + network: None, + id: RELAYBOB.into(), + }, + ], + ); + + // Finally we test that we are able to send back the DOTs to AssetHub from the ParaA + ParaA::execute_with(|| { + assert_ok!(XTokens::transfer( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + parachain::CurrencyId::ForeignAsset(source_relay_id), + 100, + Box::new(VersionedLocation::V4(dest)), + WeightLimit::Limited(Weight::from_parts(40000u64, DEFAULT_PROOF_SIZE)) + )); + + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 100); + }); + + Statemint::execute_with(|| { + // Check that Bob received the tokens back in AssetHub + assert_eq!( + StatemintBalances::free_balance(RELAYBOB), + INITIAL_BALANCE + 100 + ); + }); + + // Send back tokens from AH to ParaA from Bob's account + Statemint::execute_with(|| { + // Now send those tokens to ParaA + assert_ok!(StatemintChainPalletXcm::limited_reserve_transfer_assets( + statemint_like::RuntimeOrigin::signed(RELAYBOB), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute) + .clone() + .into() + ), + Box::new((Location::parent(), 100).into()), + 0, + WeightLimit::Unlimited + )); + + // 100 DOTs were deducted from Bob's account + assert_eq!(StatemintBalances::free_balance(RELAYBOB), INITIAL_BALANCE); + }); + + ParaA::execute_with(|| { + // Alice should have received 100 DOTs + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 200); + }); +} + +#[test] +fn send_dot_from_moonbeam_to_statemint_via_xtokens_transfer_with_fee() { + MockNet::reset(); + + // Relay asset + let relay_location = parachain::AssetType::Xcm(xcm::v3::Location::parent()); + let source_relay_id: parachain::AssetId = relay_location.clone().into(); + + let relay_asset_metadata = parachain::AssetMetadata { + name: b"RelayToken".to_vec(), + symbol: b"Relay".to_vec(), + decimals: 12, + }; + + let dest_para = Location::new(1, [Parachain(1)]); + + let sov = xcm_builder::SiblingParachainConvertsVia::< + polkadot_parachain::primitives::Sibling, + statemint_like::AccountId, + >::convert_location(&dest_para) + .unwrap(); + + ParaA::execute_with(|| { + assert_ok!(AssetManager::register_foreign_asset( + parachain::RuntimeOrigin::root(), + relay_location.clone(), + relay_asset_metadata, + 1u128, + true + )); + XcmWeightTrader::set_asset_price(Location::parent(), 0u128); + }); + + let parachain_beneficiary_absolute: Location = Junction::AccountKey20 { + network: None, + key: PARAALICE, + } + .into(); + + let statemint_beneficiary_absolute: Location = Junction::AccountId32 { + network: None, + id: RELAYALICE.into(), + } + .into(); + + // First we send relay chain asset to Alice in AssetHub (via teleport) + Relay::execute_with(|| { + assert_ok!(RelayChainPalletXcm::limited_teleport_assets( + relay_chain::RuntimeOrigin::signed(RELAYALICE), + Box::new(Parachain(1000).into()), + Box::new( + VersionedLocation::V4(statemint_beneficiary_absolute) + .clone() + .into() + ), + Box::new(([], 200).into()), + 0, + WeightLimit::Unlimited + )); + }); + + // Send DOTs from AssetHub to ParaA (Moonbeam) + Statemint::execute_with(|| { + // Check Alice received 200 tokens on AssetHub + assert_eq!( + StatemintBalances::free_balance(RELAYALICE), + INITIAL_BALANCE + 200 + ); + + assert_ok!(StatemintBalances::transfer_allow_death( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + sov, + 110000000000000 + )); + + // Now send those tokens to ParaA + assert_ok!(StatemintChainPalletXcm::limited_reserve_transfer_assets( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute.clone()) + .clone() + .into() + ), + Box::new((Location::parent(), 200).into()), + 0, + WeightLimit::Unlimited + )); + }); + + ParaA::execute_with(|| { + // Alice should have received the DOTs + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 200); + }); + + let dest = Location::new( + 1, + [ + Parachain(1000), + AccountId32 { + network: None, + id: RELAYBOB.into(), + }, + ], + ); + + // Finally we test that we are able to send back the DOTs to AssetHub from the ParaA + ParaA::execute_with(|| { + assert_ok!(XTokens::transfer_with_fee( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + parachain::CurrencyId::ForeignAsset(source_relay_id), + 100, + 10, + Box::new(VersionedLocation::V4(dest)), + WeightLimit::Limited(Weight::from_parts(40000u64, DEFAULT_PROOF_SIZE)) + )); + + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 90); + }); + + Statemint::execute_with(|| { + // Free execution: check that Bob received the tokens back in AssetHub + assert_eq!( + StatemintBalances::free_balance(RELAYBOB), + INITIAL_BALANCE + 110 + ); + }); + + // Send back tokens from AH to ParaA from Bob's account + Statemint::execute_with(|| { + // Now send those tokens to ParaA + assert_ok!(StatemintChainPalletXcm::limited_reserve_transfer_assets( + statemint_like::RuntimeOrigin::signed(RELAYBOB), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute) + .clone() + .into() + ), + Box::new((Location::parent(), 100).into()), + 0, + WeightLimit::Unlimited + )); + + // 100 DOTs were deducted from Bob's account + assert_eq!( + StatemintBalances::free_balance(RELAYBOB), + INITIAL_BALANCE + 10 + ); + }); + + ParaA::execute_with(|| { + // Alice should have received 100 DOTs + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 190); + }); +} + +#[test] +fn send_dot_from_moonbeam_to_statemint_via_xtokens_transfer_multiasset() { + MockNet::reset(); + + // Relay asset + let relay_location = parachain::AssetType::Xcm(xcm::v3::Location::parent()); + let source_relay_id: parachain::AssetId = relay_location.clone().into(); + + let relay_asset_metadata = parachain::AssetMetadata { + name: b"RelayToken".to_vec(), + symbol: b"Relay".to_vec(), + decimals: 12, + }; + + let dest_para = Location::new(1, [Parachain(1)]); + + let sov = xcm_builder::SiblingParachainConvertsVia::< + polkadot_parachain::primitives::Sibling, + statemint_like::AccountId, + >::convert_location(&dest_para) + .unwrap(); + + ParaA::execute_with(|| { + assert_ok!(AssetManager::register_foreign_asset( + parachain::RuntimeOrigin::root(), + relay_location.clone(), + relay_asset_metadata, + 1u128, + true + )); + XcmWeightTrader::set_asset_price(Location::parent(), 0u128); + }); + + let parachain_beneficiary_absolute: Location = Junction::AccountKey20 { + network: None, + key: PARAALICE, + } + .into(); + + let statemint_beneficiary_absolute: Location = Junction::AccountId32 { + network: None, + id: RELAYALICE.into(), + } + .into(); + + // First we send relay chain asset to Alice in AssetHub (via teleport) + Relay::execute_with(|| { + assert_ok!(RelayChainPalletXcm::limited_teleport_assets( + relay_chain::RuntimeOrigin::signed(RELAYALICE), + Box::new(Parachain(1000).into()), + Box::new( + VersionedLocation::V4(statemint_beneficiary_absolute) + .clone() + .into() + ), + Box::new(([], 200).into()), + 0, + WeightLimit::Unlimited + )); + }); + + // Send DOTs from AssetHub to ParaA (Moonbeam) + Statemint::execute_with(|| { + // Check Alice received 200 tokens on AssetHub + assert_eq!( + StatemintBalances::free_balance(RELAYALICE), + INITIAL_BALANCE + 200 + ); + + assert_ok!(StatemintBalances::transfer_allow_death( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + sov, + 110000000000000 + )); + + // Now send those tokens to ParaA + assert_ok!(StatemintChainPalletXcm::limited_reserve_transfer_assets( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute.clone()) + .clone() + .into() + ), + Box::new((Location::parent(), 200).into()), + 0, + WeightLimit::Unlimited + )); + }); + + ParaA::execute_with(|| { + // Alice should have received the DOTs + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 200); + }); + + let dest = Location::new( + 1, + [ + Parachain(1000), + AccountId32 { + network: None, + id: RELAYBOB.into(), + }, + ], + ); + + // Finally we test that we are able to send back the DOTs to AssetHub from the ParaA + ParaA::execute_with(|| { + assert_ok!(XTokens::transfer_multiasset( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + Box::new((Location::parent(), 100).into()), + Box::new(VersionedLocation::V4(dest)), + WeightLimit::Limited(Weight::from_parts(40000u64, DEFAULT_PROOF_SIZE)) + )); + + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 100); + }); + + Statemint::execute_with(|| { + // Check that Bob received the tokens back in AssetHub + assert_eq!( + StatemintBalances::free_balance(RELAYBOB), + INITIAL_BALANCE + 100 + ); + }); + + // Send back tokens from AH to ParaA from Bob's account + Statemint::execute_with(|| { + // Now send those tokens to ParaA + assert_ok!(StatemintChainPalletXcm::limited_reserve_transfer_assets( + statemint_like::RuntimeOrigin::signed(RELAYBOB), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute) + .clone() + .into() + ), + Box::new((Location::parent(), 100).into()), + 0, + WeightLimit::Unlimited + )); + + // 100 DOTs were deducted from Bob's account + assert_eq!(StatemintBalances::free_balance(RELAYBOB), INITIAL_BALANCE); + }); + + ParaA::execute_with(|| { + // Alice should have received 100 DOTs + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 200); + }); +} + +#[test] +fn send_dot_from_moonbeam_to_statemint_via_xtokens_transfer_multicurrencies() { + MockNet::reset(); + + // Relay asset + let relay_location = parachain::AssetType::Xcm(xcm::v3::Location::parent()); + let source_relay_id: parachain::AssetId = relay_location.clone().into(); + + let relay_asset_metadata = parachain::AssetMetadata { + name: b"RelayToken".to_vec(), + symbol: b"Relay".to_vec(), + decimals: 12, + }; + + // Statemint asset + let statemint_asset = Location::new( + 1, + [ + Parachain(1000u32), + PalletInstance(5u8), + GeneralIndex(10u128), + ], + ); + let statemint_location_asset = parachain::AssetType::Xcm( + xcm_builder::WithLatestLocationConverter::convert(&statemint_asset).expect("convert to v3"), + ); + let source_statemint_asset_id: parachain::AssetId = statemint_location_asset.clone().into(); + + let asset_metadata_statemint_asset = parachain::AssetMetadata { + name: b"USDC".to_vec(), + symbol: b"USDC".to_vec(), + decimals: 12, + }; + + let dest_para = Location::new(1, [Parachain(1)]); + + let sov = xcm_builder::SiblingParachainConvertsVia::< + polkadot_parachain::primitives::Sibling, + statemint_like::AccountId, + >::convert_location(&dest_para) + .unwrap(); + + ParaA::execute_with(|| { + assert_ok!(AssetManager::register_foreign_asset( + parachain::RuntimeOrigin::root(), + relay_location.clone(), + relay_asset_metadata, + 1u128, + true + )); + XcmWeightTrader::set_asset_price(Location::parent(), 0u128); + + assert_ok!(AssetManager::register_foreign_asset( + parachain::RuntimeOrigin::root(), + statemint_location_asset.clone(), + asset_metadata_statemint_asset, + 1u128, + true + )); + XcmWeightTrader::set_asset_price(statemint_asset, 0u128); + }); + + let parachain_beneficiary_absolute: Location = Junction::AccountKey20 { + network: None, + key: PARAALICE, + } + .into(); + + let statemint_beneficiary_absolute: Location = Junction::AccountId32 { + network: None, + id: RELAYALICE.into(), + } + .into(); + + // First we send relay chain asset to Alice in AssetHub (via teleport) + Relay::execute_with(|| { + assert_ok!(RelayChainPalletXcm::limited_teleport_assets( + relay_chain::RuntimeOrigin::signed(RELAYALICE), + Box::new(Parachain(1000).into()), + Box::new( + VersionedLocation::V4(statemint_beneficiary_absolute) + .clone() + .into() + ), + Box::new(([], 200).into()), + 0, + WeightLimit::Unlimited + )); + }); + + // Send DOTs and USDC from AssetHub to ParaA (Moonbeam) + Statemint::execute_with(|| { + // Check Alice received 200 tokens on AssetHub + assert_eq!( + StatemintBalances::free_balance(RELAYALICE), + INITIAL_BALANCE + 200 + ); + + assert_ok!(StatemintBalances::transfer_allow_death( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + sov, + 110000000000000 + )); + + statemint_like::PrefixChanger::set_prefix( + PalletInstance(::index() as u8).into(), + ); + + assert_ok!(StatemintAssets::create( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + 10, + RELAYALICE, + 1 + )); + + assert_ok!(StatemintAssets::mint( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + 10, + RELAYALICE, + 300000000000000 + )); + + // Now send relay tokens to ParaA + assert_ok!(StatemintChainPalletXcm::limited_reserve_transfer_assets( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute.clone()) + .clone() + .into() + ), + Box::new((Location::parent(), 200).into()), + 0, + WeightLimit::Unlimited + )); + + // Send USDC + assert_ok!(StatemintChainPalletXcm::limited_reserve_transfer_assets( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute.clone()) + .clone() + .into() + ), + Box::new( + ( + [ + xcm::latest::prelude::PalletInstance( + ::index() as u8 + ), + GeneralIndex(10), + ], + 125 + ) + .into() + ), + 0, + WeightLimit::Unlimited + )); + }); + + ParaA::execute_with(|| { + // Alice should have received the DOTs + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 200); + + // Alice has received 125 USDC + assert_eq!( + Assets::balance(source_statemint_asset_id, &PARAALICE.into()), + 125 + ); + }); + + let dest = Location::new( + 1, + [ + Parachain(1000), + AccountId32 { + network: None, + id: RELAYBOB.into(), + }, + ], + ); + + // Finally we test that we are able to send back the DOTs to AssetHub from the ParaA + ParaA::execute_with(|| { + assert_ok!(XTokens::transfer_multicurrencies( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + vec![ + ( + parachain::CurrencyId::ForeignAsset(source_statemint_asset_id), + 100 + ), + (parachain::CurrencyId::ForeignAsset(source_relay_id), 100) + ], + 1, + Box::new(VersionedLocation::V4(dest)), + WeightLimit::Limited(Weight::from_parts(80_000_000u64, 100_000u64)) + )); + + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 100); + }); + + Statemint::execute_with(|| { + // Check that Bob received relay tokens back in AssetHub + // (100 - MinXcmFee) + assert_eq!( + StatemintBalances::free_balance(RELAYBOB), + INITIAL_BALANCE + 50 + ); + + // Check that BOB received 100 USDC on AssetHub + assert_eq!(StatemintAssets::account_balances(RELAYBOB), vec![(10, 100)]); + }); + + // Send back tokens from AH to ParaA from Bob's account + Statemint::execute_with(|| { + let bob_previous_balance = StatemintBalances::free_balance(RELAYBOB); + + // Now send those tokens to ParaA + assert_ok!(StatemintChainPalletXcm::limited_reserve_transfer_assets( + statemint_like::RuntimeOrigin::signed(RELAYBOB), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute) + .clone() + .into() + ), + Box::new((Location::parent(), 100).into()), + 0, + WeightLimit::Unlimited + )); + + // 100 DOTs were deducted from Bob's account + assert_eq!( + StatemintBalances::free_balance(RELAYBOB), + bob_previous_balance - 100 + ); + }); + + ParaA::execute_with(|| { + // Alice should have received 100 DOTs + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 200); + }); +} + +#[test] +fn send_dot_from_moonbeam_to_statemint_via_xtokens_transfer_multiassets() { + MockNet::reset(); + + // Relay asset + let relay_location = parachain::AssetType::Xcm(xcm::v3::Location::parent()); + let source_relay_id: parachain::AssetId = relay_location.clone().into(); + + let relay_asset_metadata = parachain::AssetMetadata { + name: b"RelayToken".to_vec(), + symbol: b"Relay".to_vec(), + decimals: 12, + }; + + // Statemint asset + let statemint_asset = Location::new( + 1, + [ + Parachain(1000u32), + PalletInstance(5u8), + GeneralIndex(10u128), + ], + ); + let statemint_location_asset = parachain::AssetType::Xcm( + xcm_builder::WithLatestLocationConverter::convert(&statemint_asset).expect("convert to v3"), + ); + let source_statemint_asset_id: parachain::AssetId = statemint_location_asset.clone().into(); + + let asset_metadata_statemint_asset = parachain::AssetMetadata { + name: b"USDC".to_vec(), + symbol: b"USDC".to_vec(), + decimals: 12, + }; + + let dest_para = Location::new(1, [Parachain(1)]); + + let sov = xcm_builder::SiblingParachainConvertsVia::< + polkadot_parachain::primitives::Sibling, + statemint_like::AccountId, + >::convert_location(&dest_para) + .unwrap(); + + ParaA::execute_with(|| { + assert_ok!(AssetManager::register_foreign_asset( + parachain::RuntimeOrigin::root(), + relay_location.clone(), + relay_asset_metadata, + 1u128, + true + )); + XcmWeightTrader::set_asset_price(Location::parent(), 0u128); + + assert_ok!(AssetManager::register_foreign_asset( + parachain::RuntimeOrigin::root(), + statemint_location_asset.clone(), + asset_metadata_statemint_asset, + 1u128, + true + )); + XcmWeightTrader::set_asset_price(statemint_asset.clone(), 0u128); + }); + + let parachain_beneficiary_absolute: Location = Junction::AccountKey20 { + network: None, + key: PARAALICE, + } + .into(); + + let statemint_beneficiary_absolute: Location = Junction::AccountId32 { + network: None, + id: RELAYALICE.into(), + } + .into(); + + // First we send relay chain asset to Alice in AssetHub (via teleport) + Relay::execute_with(|| { + assert_ok!(RelayChainPalletXcm::limited_teleport_assets( + relay_chain::RuntimeOrigin::signed(RELAYALICE), + Box::new(Parachain(1000).into()), + Box::new( + VersionedLocation::V4(statemint_beneficiary_absolute) + .clone() + .into() + ), + Box::new(([], 200).into()), + 0, + WeightLimit::Unlimited + )); + }); + + // Send DOTs and USDC from AssetHub to ParaA (Moonbeam) + Statemint::execute_with(|| { + // Check Alice received 200 tokens on AssetHub + assert_eq!( + StatemintBalances::free_balance(RELAYALICE), + INITIAL_BALANCE + 200 + ); + + assert_ok!(StatemintBalances::transfer_allow_death( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + sov, + 110000000000000 + )); + + statemint_like::PrefixChanger::set_prefix( + PalletInstance(::index() as u8).into(), + ); + + assert_ok!(StatemintAssets::create( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + 10, + RELAYALICE, + 1 + )); + + assert_ok!(StatemintAssets::mint( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + 10, + RELAYALICE, + 300000000000000 + )); + + // Now send relay tokens to ParaA + assert_ok!(StatemintChainPalletXcm::limited_reserve_transfer_assets( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute.clone()) + .clone() + .into() + ), + Box::new((Location::parent(), 200).into()), + 0, + WeightLimit::Unlimited + )); + + // Send USDC + assert_ok!(StatemintChainPalletXcm::limited_reserve_transfer_assets( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute.clone()) + .clone() + .into() + ), + Box::new( + ( + [ + xcm::latest::prelude::PalletInstance( + ::index() as u8 + ), + GeneralIndex(10), + ], + 125 + ) + .into() + ), + 0, + WeightLimit::Unlimited + )); + }); + + ParaA::execute_with(|| { + // Alice should have received the DOTs + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 200); + + // Alice has received 125 USDC + assert_eq!( + Assets::balance(source_statemint_asset_id, &PARAALICE.into()), + 125 + ); + }); + + let dest = Location::new( + 1, + [ + Parachain(1000), + AccountId32 { + network: None, + id: RELAYBOB.into(), + }, + ], + ); + + let statemint_asset_to_send = XcmAsset { + id: XcmAssetId(statemint_asset), + fun: Fungible(100), + }; + + let relay_asset_to_send = XcmAsset { + id: XcmAssetId(Location::parent()), + fun: Fungible(100), + }; + + let assets_to_send: XcmAssets = + XcmAssets::from(vec![statemint_asset_to_send, relay_asset_to_send.clone()]); + + // For some reason the order of the assets is inverted when creating the array above. + // We need to use relay asset for fees, so we pick index 0. + assert_eq!(assets_to_send.get(0).unwrap(), &relay_asset_to_send); + + // Finally we test that we are able to send back the DOTs to AssetHub from the ParaA + ParaA::execute_with(|| { + assert_ok!(XTokens::transfer_multiassets( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + Box::new(assets_to_send.into()), + 0, + Box::new(VersionedLocation::V4(dest)), + WeightLimit::Limited(Weight::from_parts(80_000_000u64, 100_000u64)) + )); + + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 100); + }); + + Statemint::execute_with(|| { + // Check that Bob received relay tokens back in AssetHub + // (100 - MinXcmFee) + assert_eq!( + StatemintBalances::free_balance(RELAYBOB), + INITIAL_BALANCE + 50 + ); + + // Check that BOB received 100 USDC on AssetHub + assert_eq!(StatemintAssets::account_balances(RELAYBOB), vec![(10, 100)]); + }); + + // Send back tokens from AH to ParaA from Bob's account + Statemint::execute_with(|| { + let bob_previous_balance = StatemintBalances::free_balance(RELAYBOB); + + // Now send those tokens to ParaA + assert_ok!(StatemintChainPalletXcm::limited_reserve_transfer_assets( + statemint_like::RuntimeOrigin::signed(RELAYBOB), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute) + .clone() + .into() + ), + Box::new((Location::parent(), 100).into()), + 0, + WeightLimit::Unlimited + )); + + // 100 DOTs were deducted from Bob's account + assert_eq!( + StatemintBalances::free_balance(RELAYBOB), + bob_previous_balance - 100 + ); + }); + + ParaA::execute_with(|| { + // Alice should have received 100 DOTs + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 200); + }); +} + #[test] fn transact_through_signed_multilocation() { MockNet::reset(); @@ -3250,7 +4119,7 @@ fn transact_through_signed_multilocation_para_to_para() { assert_ok!(XcmTransactor::set_fee_per_second( parachain::RuntimeOrigin::root(), Box::new(xcm::VersionedLocation::V4(para_b_balances.clone())), - parachain::ParaTokensPerSecond::get().1 as u128, + parachain::ParaTokensPerSecond::get(), )); ancestry = parachain::UniversalLocation::get().into(); }); @@ -3350,7 +4219,7 @@ fn transact_through_signed_multilocation_para_to_para_refund() { assert_ok!(XcmTransactor::set_fee_per_second( parachain::RuntimeOrigin::root(), Box::new(xcm::VersionedLocation::V4(para_b_balances.clone())), - parachain::ParaTokensPerSecond::get().1 as u128, + parachain::ParaTokensPerSecond::get(), )); ancestry = parachain::UniversalLocation::get().into(); }); @@ -3464,7 +4333,7 @@ fn transact_through_signed_multilocation_para_to_para_ethereum() { assert_ok!(XcmTransactor::set_fee_per_second( parachain::RuntimeOrigin::root(), Box::new(xcm::VersionedLocation::V4(para_b_balances.clone())), - parachain::ParaTokensPerSecond::get().1 as u128, + parachain::ParaTokensPerSecond::get(), )); ancestry = parachain::UniversalLocation::get().into(); }); @@ -3593,7 +4462,7 @@ fn transact_through_signed_multilocation_para_to_para_ethereum_no_proxy_fails() assert_ok!(XcmTransactor::set_fee_per_second( parachain::RuntimeOrigin::root(), Box::new(xcm::VersionedLocation::V4(para_b_balances.clone())), - parachain::ParaTokensPerSecond::get().1 as u128, + parachain::ParaTokensPerSecond::get(), )); ancestry = parachain::UniversalLocation::get().into(); }); @@ -3718,7 +4587,7 @@ fn transact_through_signed_multilocation_para_to_para_ethereum_proxy_succeeds() assert_ok!(XcmTransactor::set_fee_per_second( parachain::RuntimeOrigin::root(), Box::new(xcm::VersionedLocation::V4(para_b_balances.clone())), - parachain::ParaTokensPerSecond::get().1 as u128, + parachain::ParaTokensPerSecond::get(), )); ancestry = parachain::UniversalLocation::get().into(); }); diff --git a/runtime/moonbeam/Cargo.toml b/runtime/moonbeam/Cargo.toml index 762e30795e..7d956c0b7b 100644 --- a/runtime/moonbeam/Cargo.toml +++ b/runtime/moonbeam/Cargo.toml @@ -46,6 +46,7 @@ pallet-precompile-benchmarks = { workspace = true } pallet-proxy-genesis-companion = { workspace = true } pallet-randomness = { workspace = true } pallet-xcm-transactor = { workspace = true } +pallet-xcm-weight-trader = { workspace = true } # Moonbeam precompiles pallet-evm-precompile-author-mapping = { workspace = true } @@ -287,6 +288,7 @@ std = [ "pallet-whitelist/std", "pallet-xcm-transactor/std", "pallet-xcm/std", + "pallet-xcm-weight-trader/std", "parachain-info/std", "parachains-common/std", "parity-scale-codec/std", @@ -373,6 +375,7 @@ runtime-benchmarks = [ "pallet-xcm-benchmarks/runtime-benchmarks", "pallet-xcm-transactor/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", + "pallet-xcm-weight-trader/runtime-benchmarks", "session-keys-primitives/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", diff --git a/runtime/moonbeam/src/lib.rs b/runtime/moonbeam/src/lib.rs index dafb0a6988..df56567dfc 100644 --- a/runtime/moonbeam/src/lib.rs +++ b/runtime/moonbeam/src/lib.rs @@ -98,13 +98,8 @@ use sp_runtime::{ Perquintill, SaturatedConversion, }; use sp_std::{convert::TryFrom, prelude::*}; -use xcm::{ - v3::{AssetId as XcmAssetId, Location}, - IntoVersion, VersionedAssetId, VersionedAssets, VersionedLocation, VersionedXcm, -}; -use xcm_config::AssetType; +use xcm::{VersionedAssetId, VersionedAssets, VersionedLocation, VersionedXcm}; use xcm_fee_payment_runtime_api::Error as XcmPaymentApiError; -use xcm_primitives::UnitsToWeightRatio; #[cfg(feature = "std")] use sp_version::NativeVersion; @@ -1469,6 +1464,7 @@ construct_runtime! { Erc20XcmBridge: pallet_erc20_xcm_bridge::{Pallet} = 110, MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event} = 111, EvmForeignAssets: pallet_moonbeam_foreign_assets::{Pallet, Call, Storage, Event} = 114, + XcmWeightTrader: pallet_xcm_weight_trader::{Pallet, Call, Storage, Event} = 115, EmergencyParaXcm: pallet_emergency_para_xcm::{Pallet, Call, Storage, Event} = 116, // Utils diff --git a/runtime/moonbeam/src/xcm_config.rs b/runtime/moonbeam/src/xcm_config.rs index 2c8a8031a4..7ffab1d7d9 100644 --- a/runtime/moonbeam/src/xcm_config.rs +++ b/runtime/moonbeam/src/xcm_config.rs @@ -18,10 +18,10 @@ //! use super::{ - governance, AccountId, AssetId, AssetManager, Balance, Balances, DealWithFees, - EmergencyParaXcm, Erc20XcmBridge, MaintenanceMode, MessageQueue, OpenTechCommitteeInstance, - ParachainInfo, ParachainSystem, Perbill, PolkadotXcm, Runtime, RuntimeBlockWeights, - RuntimeCall, RuntimeEvent, RuntimeOrigin, Treasury, XcmpQueue, + governance, AccountId, AssetId, AssetManager, Balance, Balances, EmergencyParaXcm, + Erc20XcmBridge, MaintenanceMode, MessageQueue, OpenTechCommitteeInstance, ParachainInfo, + ParachainSystem, Perbill, PolkadotXcm, Runtime, RuntimeBlockWeights, RuntimeCall, RuntimeEvent, + RuntimeOrigin, Treasury, XcmpQueue, }; use frame_support::{ @@ -45,7 +45,7 @@ use xcm_builder::{ EnsureXcmOrigin, FungibleAdapter as XcmCurrencyAdapter, FungiblesAdapter, HashedDescription, NoChecking, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountKey20AsNative, SovereignSignedViaLocation, - TakeWeightCredit, UsingComponents, WeightInfoBounds, WithComputedOrigin, + TakeWeightCredit, WeightInfoBounds, WithComputedOrigin, }; use parachains_common::message_queue::{NarrowOriginToSibling, ParaIdToSibling}; @@ -60,8 +60,8 @@ use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; use orml_xcm_support::MultiNativeAsset; use xcm_primitives::{ AbsoluteAndRelativeReserve, AccountIdToCurrencyId, AccountIdToLocation, AsAssetType, - FirstAssetTrader, IsBridgedConcreteAssetFrom, SignedToAccountId20, UtilityAvailableCalls, - UtilityEncodeCall, XcmTransact, + IsBridgedConcreteAssetFrom, SignedToAccountId20, UtilityAvailableCalls, UtilityEncodeCall, + XcmTransact, }; use parity_scale_codec::{Decode, Encode}; @@ -221,23 +221,6 @@ parameter_types! { pub XcmFeesAccount: AccountId = Treasury::account_id(); } -/// This is the struct that will handle the revenue from xcm fees -/// We do not burn anything because we want to mimic exactly what -/// the sovereign account has -pub type XcmFeesToAccount = xcm_primitives::XcmFeesToAccount< - super::Assets, - ( - ConvertedConcreteId< - AssetId, - Balance, - AsAssetType, - JustTry, - >, - ), - AccountId, - XcmFeesAccount, ->; - pub struct SafeCallFilter; impl frame_support::traits::Contains for SafeCallFilter { fn contains(_call: &RuntimeCall) -> bool { @@ -300,16 +283,7 @@ impl xcm_executor::Config for XcmExecutorConfig { // we use UsingComponents and the local way of handling fees // When we receive a non-reserve asset, we use AssetManager to fetch how many // units per second we should charge - type Trader = ( - UsingComponents< - ::WeightToFee, - SelfReserve, - AccountId, - Balances, - DealWithFees, - >, - FirstAssetTrader, - ); + type Trader = pallet_xcm_weight_trader::Trader; type ResponseHandler = PolkadotXcm; type SubscriptionService = PolkadotXcm; type AssetTrap = pallet_erc20_xcm_bridge::AssetTrapWrapper; @@ -369,7 +343,6 @@ impl pallet_xcm::Config for Runtime { type MaxLockers = ConstU32<8>; type MaxRemoteLockConsumers = ConstU32<0>; type RemoteLockConsumerIdentifier = (); - // TODO pallet-xcm weights type WeightInfo = moonbeam_weights::pallet_xcm::WeightInfo; type AdminOrigin = EnsureRoot; } @@ -750,6 +723,54 @@ impl pallet_moonbeam_foreign_assets::Config for Runtime { type XcmLocationToH160 = LocationToH160; } +pub struct AssetFeesFilter; +impl frame_support::traits::Contains for AssetFeesFilter { + fn contains(location: &Location) -> bool { + location.parent_count() > 0 + && location.first_interior() != Erc20XcmBridgePalletLocation::get().first_interior() + } +} + +pub type AddSupportedAssetOrigin = EitherOfDiverse< + EnsureRoot, + EitherOfDiverse< + pallet_collective::EnsureProportionMoreThan, + governance::custom_origins::GeneralAdmin, + >, +>; + +pub type EditSupportedAssetOrigin = EitherOfDiverse< + EnsureRoot, + EitherOfDiverse< + pallet_collective::EnsureProportionMoreThan, + governance::custom_origins::FastGeneralAdmin, + >, +>; + +pub type RemoveSupportedAssetOrigin = EitherOfDiverse< + EnsureRoot, + pallet_collective::EnsureProportionMoreThan, +>; + +impl pallet_xcm_weight_trader::Config for Runtime { + type AccountIdToLocation = AccountIdToLocation; + type AddSupportedAssetOrigin = AddSupportedAssetOrigin; + type AssetLocationFilter = AssetFeesFilter; + type AssetTransactor = AssetTransactors; + type Balance = Balance; + type EditSupportedAssetOrigin = EditSupportedAssetOrigin; + type NativeLocation = SelfReserve; + type PauseSupportedAssetOrigin = EditSupportedAssetOrigin; + type RemoveSupportedAssetOrigin = RemoveSupportedAssetOrigin; + type RuntimeEvent = RuntimeEvent; + type ResumeSupportedAssetOrigin = RemoveSupportedAssetOrigin; + type WeightInfo = moonbeam_weights::pallet_xcm_weight_trader::WeightInfo; + type WeightToFee = ::WeightToFee; + type XcmFeesAccount = XcmFeesAccount; + #[cfg(feature = "runtime-benchmarks")] + type NotFilteredLocation = RelayLocation; +} + #[cfg(feature = "runtime-benchmarks")] mod testing { use super::*; diff --git a/runtime/moonbeam/tests/integration_test.rs b/runtime/moonbeam/tests/integration_test.rs index 7035c7ef68..cc57f12aa7 100644 --- a/runtime/moonbeam/tests/integration_test.rs +++ b/runtime/moonbeam/tests/integration_test.rs @@ -1945,7 +1945,7 @@ fn xtokens_precompile_transfer() { weight: 4_000_000, }, ) - .expect_cost(196490) + .expect_cost(196698) .expect_no_logs() .execute_returns(()) }) @@ -1997,7 +1997,7 @@ fn xtokens_precompile_transfer_multiasset() { weight: 4_000_000, }, ) - .expect_cost(196490) + .expect_cost(196698) .expect_no_logs() .execute_returns(()); }) diff --git a/runtime/moonbeam/tests/xcm_mock/mod.rs b/runtime/moonbeam/tests/xcm_mock/mod.rs index d52700b810..231196031e 100644 --- a/runtime/moonbeam/tests/xcm_mock/mod.rs +++ b/runtime/moonbeam/tests/xcm_mock/mod.rs @@ -106,7 +106,7 @@ decl_test_parachain! { Runtime = statemint_like::Runtime, XcmpMessageHandler = statemint_like::MsgQueue, DmpMessageHandler = statemint_like::MsgQueue, - new_ext = statemint_ext(4), + new_ext = statemint_ext(1000), } } @@ -118,7 +118,7 @@ decl_test_relay_chain! { XcmConfig = relay_chain::XcmConfig, MessageQueue = relay_chain::MessageQueue, System = relay_chain::System, - new_ext = relay_ext(vec![1, 2, 3, 4]), + new_ext = relay_ext(vec![1, 2, 3, 1000]), } } @@ -129,7 +129,7 @@ decl_test_network! { (1, ParaA), (2, ParaB), (3, ParaC), - (4, Statemint), + (1000, Statemint), ], } } @@ -269,3 +269,4 @@ pub type XTokens = orml_xtokens::Pallet; pub type RelayBalances = pallet_balances::Pallet; pub type ParaBalances = pallet_balances::Pallet; pub type XcmTransactor = pallet_xcm_transactor::Pallet; +pub type XcmWeightTrader = pallet_xcm_weight_trader::Pallet; diff --git a/runtime/moonbeam/tests/xcm_mock/parachain.rs b/runtime/moonbeam/tests/xcm_mock/parachain.rs index 2fe3e12459..3f41bcad58 100644 --- a/runtime/moonbeam/tests/xcm_mock/parachain.rs +++ b/runtime/moonbeam/tests/xcm_mock/parachain.rs @@ -45,15 +45,15 @@ use pallet_ethereum::PostLogContent; use polkadot_core_primitives::BlockNumber as RelayBlockNumber; use polkadot_parachain::primitives::{Id as ParaId, Sibling}; use xcm::latest::{ - AssetId as XcmAssetId, Error as XcmError, ExecuteXcm, + Error as XcmError, ExecuteXcm, Junction::{PalletInstance, Parachain}, Location, NetworkId, Outcome, Xcm, }; use xcm_builder::{ AccountKey20Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, - AllowTopLevelPaidExecutionFrom, ConvertedConcreteId, EnsureXcmOrigin, FixedRateOfFungible, - FixedWeightBounds, FungibleAdapter as XcmCurrencyAdapter, FungiblesAdapter, IsConcrete, - NoChecking, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, + AllowTopLevelPaidExecutionFrom, Case, ConvertedConcreteId, EnsureXcmOrigin, FixedWeightBounds, + FungibleAdapter as XcmCurrencyAdapter, FungiblesAdapter, IsConcrete, NoChecking, + ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountKey20AsNative, SovereignSignedViaLocation, TakeWeightCredit, WithComputedOrigin, }; @@ -282,31 +282,20 @@ pub type XcmBarrier = ( parameter_types! { /// Xcm fees will go to the treasury account pub XcmFeesAccount: AccountId = Treasury::account_id(); + /// Parachain token units per second of execution + pub ParaTokensPerSecond: u128 = WEIGHT_REF_TIME_PER_SECOND as u128; } -/// This is the struct that will handle the revenue from xcm fees -pub type XcmFeesToAccount_ = xcm_primitives::XcmFeesToAccount< - Assets, - ( - ConvertedConcreteId< - AssetId, - Balance, - xcm_primitives::AsAssetType, - JustTry, - >, - ), - AccountId, - XcmFeesAccount, ->; +pub struct WeightToFee; +impl sp_weights::WeightToFee for WeightToFee { + type Balance = Balance; -parameter_types! { - // We cannot skip the native trader for some specific tests, so we will have to work with - // a native trader that charges same number of units as weight - pub ParaTokensPerSecond: (XcmAssetId, u128, u128) = ( - AssetId(SelfReserve::get()), - 1000000000000, - 0, - ); + fn weight_to_fee(weight: &Weight) -> Self::Balance { + use sp_runtime::SaturatedConversion as _; + Self::Balance::saturated_from(weight.ref_time()) + .saturating_mul(ParaTokensPerSecond::get()) + .saturating_div(frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND as u128) + } } parameter_types! { @@ -321,6 +310,17 @@ parameter_types! { ].into() }; pub const MaxAssetsIntoHolding: u32 = 64; + + pub AssetHubLocation: Location = Location::new(1, [Parachain(1000)]); + pub RelayLocationFilter: AssetFilter = Wild(AllOf { + fun: WildFungible, + id: xcm::prelude::AssetId(Location::parent()), + }); + + pub RelayChainNativeAssetFromAssetHub: (AssetFilter, Location) = ( + RelayLocationFilter::get(), + AssetHubLocation::get() + ); } use frame_system::RawOrigin; @@ -329,27 +329,27 @@ use sp_runtime::DispatchErrorWithPostInfo; use xcm_executor::traits::CallDispatcher; moonbeam_runtime_common::impl_moonbeam_xcm_call!(); +type Reserves = ( + // Relaychain (DOT) from Asset Hub + Case, + // Assets which the reserve is the same as the origin. + orml_xcm_support::MultiNativeAsset< + xcm_primitives::AbsoluteAndRelativeReserve, + >, +); + pub struct XcmConfig; impl Config for XcmConfig { type RuntimeCall = RuntimeCall; type XcmSender = XcmRouter; type AssetTransactor = AssetTransactors; type OriginConverter = XcmOriginToTransactDispatchOrigin; - type IsReserve = orml_xcm_support::MultiNativeAsset< - xcm_primitives::AbsoluteAndRelativeReserve, - >; + type IsReserve = Reserves; type IsTeleporter = (); type UniversalLocation = UniversalLocation; type Barrier = XcmBarrier; type Weigher = FixedWeightBounds; - // We use two traders - // When we receive the self-reserve asset, - // When we receive a non-reserve asset, we use AssetManager to fetch how many - // units per second we should charge - type Trader = ( - FixedRateOfFungible, - xcm_primitives::FirstAssetTrader, - ); + type Trader = pallet_xcm_weight_trader::Trader; type ResponseHandler = PolkadotXcm; type SubscriptionService = PolkadotXcm; type AssetTrap = PolkadotXcm; @@ -415,7 +415,7 @@ parameter_types! { parameter_type_with_key! { pub ParachainMinFee: |location: Location| -> Option { match (location.parents, location.first_interior()) { - (1, Some(Parachain(4u32))) => Some(50u128), + (1, Some(Parachain(1000u32))) => Some(50u128), _ => None, } }; @@ -813,6 +813,29 @@ impl pallet_xcm_transactor::Config for Runtime { type MaxHrmpFee = xcm_builder::Case; } +parameter_types! { + pub RelayLocation: Location = Location::parent(); +} + +impl pallet_xcm_weight_trader::Config for Runtime { + type AccountIdToLocation = xcm_primitives::AccountIdToLocation; + type AddSupportedAssetOrigin = EnsureRoot; + type AssetLocationFilter = Everything; + type AssetTransactor = AssetTransactors; + type Balance = Balance; + type EditSupportedAssetOrigin = EnsureRoot; + type NativeLocation = SelfReserve; + type PauseSupportedAssetOrigin = EnsureRoot; + type RemoveSupportedAssetOrigin = EnsureRoot; + type RuntimeEvent = RuntimeEvent; + type ResumeSupportedAssetOrigin = EnsureRoot; + type WeightInfo = (); + type WeightToFee = WeightToFee; + type XcmFeesAccount = XcmFeesAccount; + #[cfg(feature = "runtime-benchmarks")] + type NotFilteredLocation = RelayLocation; +} + parameter_types! { pub const MinimumPeriod: u64 = 1000; } @@ -1062,6 +1085,7 @@ construct_runtime!( XTokens: orml_xtokens, AssetManager: pallet_asset_manager, XcmTransactor: pallet_xcm_transactor, + XcmWeightTrader: pallet_xcm_weight_trader, Treasury: pallet_treasury, Proxy: pallet_proxy, @@ -1082,6 +1106,8 @@ pub(crate) fn para_events() -> Vec { use frame_support::traits::tokens::{PayFromAccount, UnityAssetBalanceConversion}; use frame_support::traits::{OnFinalize, OnInitialize, UncheckedOnRuntimeUpgrade}; +use sp_weights::constants::WEIGHT_REF_TIME_PER_SECOND; + pub(crate) fn on_runtime_upgrade() { VersionUncheckedMigrateToV1::::on_runtime_upgrade(); } diff --git a/runtime/moonbeam/tests/xcm_mock/relay_chain.rs b/runtime/moonbeam/tests/xcm_mock/relay_chain.rs index 15bb49b91e..f391b31d84 100644 --- a/runtime/moonbeam/tests/xcm_mock/relay_chain.rs +++ b/runtime/moonbeam/tests/xcm_mock/relay_chain.rs @@ -177,7 +177,7 @@ pub type XcmBarrier = ( parameter_types! { pub Kusama: AssetFilter = Wild(AllOf { fun: WildFungible, id: AssetId(KsmLocation::get()) }); - pub Statemine: Location = Parachain(4).into(); + pub Statemine: Location = Parachain(1000).into(); pub KusamaForStatemine: (AssetFilter, Location) = (Kusama::get(), Statemine::get()); } diff --git a/runtime/moonbeam/tests/xcm_mock/statemint_like.rs b/runtime/moonbeam/tests/xcm_mock/statemint_like.rs index 48a358c0a5..3b37a8e6c3 100644 --- a/runtime/moonbeam/tests/xcm_mock/statemint_like.rs +++ b/runtime/moonbeam/tests/xcm_mock/statemint_like.rs @@ -18,7 +18,7 @@ use frame_support::{ construct_runtime, parameter_types, - traits::{AsEnsureOriginWithArg, Contains, Everything, Nothing}, + traits::{AsEnsureOriginWithArg, Contains, ContainsPair, Everything, Get, Nothing}, weights::Weight, }; use frame_system::{EnsureRoot, EnsureSigned}; @@ -290,8 +290,33 @@ pub type Barrier = ( parameter_types! { pub MatcherLocation: Location = Location::here(); pub const MaxAssetsIntoHolding: u32 = 64; + pub const RelayTokenLocation: Location = Location::parent(); } +// Copied from: +// +// https://github.com/paritytech/polkadot-sdk/blob/f4eb41773611008040c9d4d8a8e6b7323eccfca1/cumulus +// /parachains/common/src/xcm_config.rs#L118 +// +// The difference with the original "ConcreteAssetFromSystem" (which is used by AssetHub), +// is that in our tests we only need to check if the asset matches the relay one. +pub struct ConcreteAssetFromRelay(sp_std::marker::PhantomData); +impl> ContainsPair + for ConcreteAssetFromRelay +{ + fn contains(asset: &Asset, origin: &Location) -> bool { + let is_relay = match origin.unpack() { + // The Relay Chain + (1, []) => true, + // Others + _ => false, + }; + asset.id.0 == AssetLocation::get() && is_relay + } +} + +pub type TrustedTeleporters = (ConcreteAssetFromRelay,); + pub struct XcmConfig; impl Config for XcmConfig { type RuntimeCall = RuntimeCall; @@ -300,7 +325,7 @@ impl Config for XcmConfig { type OriginConverter = XcmOriginToTransactDispatchOrigin; type IsReserve = orml_xcm_support::MultiNativeAsset; - type IsTeleporter = (); + type IsTeleporter = TrustedTeleporters; type UniversalLocation = UniversalLocation; type Barrier = Barrier; type Weigher = FixedWeightBounds; diff --git a/runtime/moonbeam/tests/xcm_tests.rs b/runtime/moonbeam/tests/xcm_tests.rs index ff3409abc9..1fdaa220d3 100644 --- a/runtime/moonbeam/tests/xcm_tests.rs +++ b/runtime/moonbeam/tests/xcm_tests.rs @@ -31,10 +31,11 @@ use pallet_xcm_transactor::{ use sp_core::ConstU32; use sp_runtime::traits::MaybeEquivalence; use xcm::latest::prelude::{ - AccountId32, AccountKey20, GeneralIndex, Junction, Junctions, Limited, Location, OriginKind, - PalletInstance, Parachain, QueryResponse, Reanchorable, Response, WeightLimit, Xcm, + AccountId32, AccountKey20, Asset as XcmAsset, AssetId as XcmAssetId, Assets as XcmAssets, + Fungible, GeneralIndex, Junction, Junctions, Limited, Location, OriginKind, PalletInstance, + Parachain, QueryResponse, Reanchorable, Response, WeightLimit, Xcm, }; -use xcm::{VersionedLocation, WrapVersion}; +use xcm::{IntoVersion, VersionedLocation, WrapVersion}; use xcm_executor::traits::ConvertLocation; use xcm_mock::parachain; use xcm_mock::relay_chain; @@ -42,6 +43,39 @@ use xcm_mock::*; use xcm_primitives::{UtilityEncodeCall, DEFAULT_PROOF_SIZE}; use xcm_simulator::TestExt; +fn add_supported_asset(asset_type: parachain::AssetType, units_per_second: u128) -> Result<(), ()> { + let parachain::AssetType::Xcm(location_v3) = asset_type; + let VersionedLocation::V4(location_v4) = VersionedLocation::V3(location_v3) + .into_version(4) + .map_err(|_| ())? + else { + return Err(()); + }; + use frame_support::weights::WeightToFee as _; + let native_amount_per_second: u128 = + ::WeightToFee::weight_to_fee( + &Weight::from_parts( + frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND, + 0, + ), + ) + .try_into() + .map_err(|_| ())?; + let precision_factor = 10u128.pow(pallet_xcm_weight_trader::RELATIVE_PRICE_DECIMALS); + let relative_price: u128 = if units_per_second > 0u128 { + native_amount_per_second + .saturating_mul(precision_factor) + .saturating_div(units_per_second) + } else { + 0u128 + }; + pallet_xcm_weight_trader::SupportedAssets::::insert( + location_v4, + (true, relative_price), + ); + Ok(()) +} + // Send a relay asset (like DOT) to a parachain A #[test] fn receive_relay_asset_from_relay() { @@ -64,12 +98,7 @@ fn receive_relay_asset_from_relay() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0u128)); }); // Actually send relay asset to parachain @@ -122,12 +151,7 @@ fn send_relay_asset_to_relay() { true )); // Free execution - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location, 0u128)); }); let dest: Location = Junction::AccountKey20 { @@ -213,12 +237,7 @@ fn send_relay_asset_to_para_b() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location.clone(), - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0u128)); }); // Register asset in paraB. Free execution @@ -230,12 +249,7 @@ fn send_relay_asset_to_para_b() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0u128)); }); let dest: Location = Junction::AccountKey20 { @@ -319,12 +333,7 @@ fn send_para_a_asset_to_para_b() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0u128)); }); // Send para A asset from para A to para B @@ -392,12 +401,7 @@ fn send_para_a_asset_from_para_b_to_para_c() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location.clone(), - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0u128)); }); // Register para A asset in parachain C. Free execution @@ -409,12 +413,7 @@ fn send_para_a_asset_from_para_b_to_para_c() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0u128)); }); let dest = Location { @@ -506,12 +505,7 @@ fn send_para_a_asset_to_para_b_and_back_to_para_a() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0u128)); }); // Send para A asset to para B @@ -605,11 +599,9 @@ fn receive_relay_asset_with_trader() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 2500000000000u128, - 0 + assert_ok!(add_supported_asset( + source_location.clone(), + 2500000000000u128 )); }); @@ -667,11 +659,9 @@ fn send_para_a_asset_to_para_b_with_trader() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 2500000000000u128, - 0 + assert_ok!(add_supported_asset( + source_location.clone(), + 2500000000000u128 )); }); @@ -745,12 +735,7 @@ fn send_para_a_asset_to_para_b_with_trader_and_fee() { true )); // With these units per second, 80K weight convrets to 1 asset unit - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 12500000u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 12500000u128)); }); let dest = Location { @@ -820,11 +805,9 @@ fn error_when_not_paying_enough() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 2500000000000u128, - 0 + assert_ok!(add_supported_asset( + source_location.clone(), + 2500000000000u128 )); }); @@ -869,12 +852,7 @@ fn transact_through_derivative_multilocation() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 1u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 1u128)); // Root can set transact info assert_ok!(XcmTransactor::set_transact_info( @@ -1029,12 +1007,7 @@ fn transact_through_derivative_with_custom_fee_weight() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 1u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 1u128)); }); // Let's construct the call to know how much weight it is going to require @@ -1185,12 +1158,7 @@ fn transact_through_derivative_with_custom_fee_weight_refund() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 1u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 1u128)); }); // Let's construct the call to know how much weight it is going to require @@ -1340,12 +1308,7 @@ fn transact_through_sovereign() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 1u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 1u128)); // Root can set transact info assert_ok!(XcmTransactor::set_transact_info( @@ -1613,12 +1576,7 @@ fn transact_through_sovereign_with_custom_fee_weight() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 1u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 1u128)); }); let dest: Location = AccountKey20 { @@ -1767,12 +1725,7 @@ fn transact_through_sovereign_with_custom_fee_weight_refund() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 1u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 1u128)); }); let dest: Location = AccountKey20 { @@ -1921,12 +1874,7 @@ fn test_automatic_versioning_on_runtime_upgrade_with_relay() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0u128)); }); let response = Response::Version(2); @@ -2053,12 +2001,7 @@ fn receive_asset_with_no_sufficients_not_possible_if_non_existent_account() { 1u128, false )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0u128)); }); // Actually send relay asset to parachain @@ -2133,12 +2076,7 @@ fn receive_assets_with_sufficients_true_allows_non_funded_account_to_receive_ass 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0u128)); }); // Actually send relay asset to parachain @@ -2194,12 +2132,7 @@ fn evm_account_receiving_assets_should_handle_sufficients_ref_count() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0u128)); }); // Actually send relay asset to parachain @@ -2264,12 +2197,7 @@ fn empty_account_should_not_be_reset() { 1u128, false )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0u128)); }); // Send native token to evm_account @@ -2350,7 +2278,7 @@ fn test_statemint_like() { let statemint_asset_a_balances = Location::new( 1, [ - Parachain(4), + Parachain(1000), PalletInstance(5), xcm::latest::prelude::GeneralIndex(0u128), ], @@ -2375,12 +2303,7 @@ fn test_statemint_like() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0u128)); }); Statemint::execute_with(|| { @@ -2445,6 +2368,7 @@ fn test_statemint_like() { } #[test] + fn send_statemint_asset_from_para_a_to_statemint_with_relay_fee() { MockNet::reset(); @@ -2461,7 +2385,11 @@ fn send_statemint_asset_from_para_a_to_statemint_with_relay_fee() { // Statemint asset let statemint_asset = Location::new( 1, - [Parachain(4u32), PalletInstance(5u8), GeneralIndex(10u128)], + [ + Parachain(1000u32), + PalletInstance(5u8), + GeneralIndex(10u128), + ], ); let statemint_location_asset = parachain::AssetType::Xcm( xcm_builder::WithLatestLocationConverter::convert(&statemint_asset).expect("convert to v3"), @@ -2490,12 +2418,7 @@ fn send_statemint_asset_from_para_a_to_statemint_with_relay_fee() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - relay_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(relay_location, 0u128)); assert_ok!(AssetManager::register_foreign_asset( parachain::RuntimeOrigin::root(), @@ -2504,12 +2427,7 @@ fn send_statemint_asset_from_para_a_to_statemint_with_relay_fee() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - statemint_location_asset, - 0u128, - 1 - )); + assert_ok!(add_supported_asset(statemint_location_asset, 0u128)); }); let parachain_beneficiary_from_relay: Location = Junction::AccountKey20 { @@ -2598,7 +2516,7 @@ fn send_statemint_asset_from_para_a_to_statemint_with_relay_fee() { let statemint_beneficiary = Location { parents: 1, interior: [ - Parachain(4), + Parachain(1000), AccountId32 { network: None, id: RELAYBOB.into(), @@ -2658,6 +2576,971 @@ fn send_statemint_asset_from_para_a_to_statemint_with_relay_fee() { }); } +#[test] +fn send_dot_from_moonbeam_to_statemint_via_xtokens_transfer() { + MockNet::reset(); + + // Relay asset + let relay_location = parachain::AssetType::Xcm(xcm::v3::Location::parent()); + let source_relay_id: parachain::AssetId = relay_location.clone().into(); + + let relay_asset_metadata = parachain::AssetMetadata { + name: b"RelayToken".to_vec(), + symbol: b"Relay".to_vec(), + decimals: 12, + }; + + let dest_para = Location::new(1, [Parachain(1)]); + + let sov = xcm_builder::SiblingParachainConvertsVia::< + polkadot_parachain::primitives::Sibling, + statemint_like::AccountId, + >::convert_location(&dest_para) + .unwrap(); + + ParaA::execute_with(|| { + assert_ok!(AssetManager::register_foreign_asset( + parachain::RuntimeOrigin::root(), + relay_location.clone(), + relay_asset_metadata, + 1u128, + true + )); + XcmWeightTrader::set_asset_price(Location::parent(), 0u128); + }); + + let parachain_beneficiary_absolute: Location = Junction::AccountKey20 { + network: None, + key: PARAALICE, + } + .into(); + + let statemint_beneficiary_absolute: Location = Junction::AccountId32 { + network: None, + id: RELAYALICE.into(), + } + .into(); + + // First we send relay chain asset to Alice in AssetHub (via teleport) + Relay::execute_with(|| { + assert_ok!(RelayChainPalletXcm::limited_teleport_assets( + relay_chain::RuntimeOrigin::signed(RELAYALICE), + Box::new(Parachain(1000).into()), + Box::new( + VersionedLocation::V4(statemint_beneficiary_absolute) + .clone() + .into() + ), + Box::new(([], 200).into()), + 0, + WeightLimit::Unlimited + )); + }); + + // Send DOTs from AssetHub to ParaA (Moonbeam) + Statemint::execute_with(|| { + // Check Alice received 200 tokens on AssetHub + assert_eq!( + StatemintBalances::free_balance(RELAYALICE), + INITIAL_BALANCE + 200 + ); + + assert_ok!(StatemintBalances::transfer_allow_death( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + sov, + 110000000000000 + )); + + // Now send those tokens to ParaA + assert_ok!(StatemintChainPalletXcm::limited_reserve_transfer_assets( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute.clone()) + .clone() + .into() + ), + Box::new((Location::parent(), 200).into()), + 0, + WeightLimit::Unlimited + )); + }); + + ParaA::execute_with(|| { + // Alice should have received the DOTs + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 200); + }); + + let dest = Location::new( + 1, + [ + Parachain(1000), + AccountId32 { + network: None, + id: RELAYBOB.into(), + }, + ], + ); + + // Finally we test that we are able to send back the DOTs to AssetHub from the ParaA + ParaA::execute_with(|| { + assert_ok!(XTokens::transfer( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + parachain::CurrencyId::ForeignAsset(source_relay_id), + 100, + Box::new(VersionedLocation::V4(dest)), + WeightLimit::Limited(Weight::from_parts(40000u64, DEFAULT_PROOF_SIZE)) + )); + + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 100); + }); + + Statemint::execute_with(|| { + // Check that Bob received the tokens back in AssetHub + assert_eq!( + StatemintBalances::free_balance(RELAYBOB), + INITIAL_BALANCE + 100 + ); + }); + + // Send back tokens from AH to ParaA from Bob's account + Statemint::execute_with(|| { + // Now send those tokens to ParaA + assert_ok!(StatemintChainPalletXcm::limited_reserve_transfer_assets( + statemint_like::RuntimeOrigin::signed(RELAYBOB), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute) + .clone() + .into() + ), + Box::new((Location::parent(), 100).into()), + 0, + WeightLimit::Unlimited + )); + + // 100 DOTs were deducted from Bob's account + assert_eq!(StatemintBalances::free_balance(RELAYBOB), INITIAL_BALANCE); + }); + + ParaA::execute_with(|| { + // Alice should have received 100 DOTs + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 200); + }); +} + +#[test] +fn send_dot_from_moonbeam_to_statemint_via_xtokens_transfer_with_fee() { + MockNet::reset(); + + // Relay asset + let relay_location = parachain::AssetType::Xcm(xcm::v3::Location::parent()); + let source_relay_id: parachain::AssetId = relay_location.clone().into(); + + let relay_asset_metadata = parachain::AssetMetadata { + name: b"RelayToken".to_vec(), + symbol: b"Relay".to_vec(), + decimals: 12, + }; + + let dest_para = Location::new(1, [Parachain(1)]); + + let sov = xcm_builder::SiblingParachainConvertsVia::< + polkadot_parachain::primitives::Sibling, + statemint_like::AccountId, + >::convert_location(&dest_para) + .unwrap(); + + ParaA::execute_with(|| { + assert_ok!(AssetManager::register_foreign_asset( + parachain::RuntimeOrigin::root(), + relay_location.clone(), + relay_asset_metadata, + 1u128, + true + )); + XcmWeightTrader::set_asset_price(Location::parent(), 0u128); + }); + + let parachain_beneficiary_absolute: Location = Junction::AccountKey20 { + network: None, + key: PARAALICE, + } + .into(); + + let statemint_beneficiary_absolute: Location = Junction::AccountId32 { + network: None, + id: RELAYALICE.into(), + } + .into(); + + // First we send relay chain asset to Alice in AssetHub (via teleport) + Relay::execute_with(|| { + assert_ok!(RelayChainPalletXcm::limited_teleport_assets( + relay_chain::RuntimeOrigin::signed(RELAYALICE), + Box::new(Parachain(1000).into()), + Box::new( + VersionedLocation::V4(statemint_beneficiary_absolute) + .clone() + .into() + ), + Box::new(([], 200).into()), + 0, + WeightLimit::Unlimited + )); + }); + + // Send DOTs from AssetHub to ParaA (Moonbeam) + Statemint::execute_with(|| { + // Check Alice received 200 tokens on AssetHub + assert_eq!( + StatemintBalances::free_balance(RELAYALICE), + INITIAL_BALANCE + 200 + ); + + assert_ok!(StatemintBalances::transfer_allow_death( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + sov, + 110000000000000 + )); + + // Now send those tokens to ParaA + assert_ok!(StatemintChainPalletXcm::limited_reserve_transfer_assets( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute.clone()) + .clone() + .into() + ), + Box::new((Location::parent(), 200).into()), + 0, + WeightLimit::Unlimited + )); + }); + + ParaA::execute_with(|| { + // Alice should have received the DOTs + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 200); + }); + + let dest = Location::new( + 1, + [ + Parachain(1000), + AccountId32 { + network: None, + id: RELAYBOB.into(), + }, + ], + ); + + // Finally we test that we are able to send back the DOTs to AssetHub from the ParaA + ParaA::execute_with(|| { + assert_ok!(XTokens::transfer_with_fee( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + parachain::CurrencyId::ForeignAsset(source_relay_id), + 100, + 10, + Box::new(VersionedLocation::V4(dest)), + WeightLimit::Limited(Weight::from_parts(40000u64, DEFAULT_PROOF_SIZE)) + )); + + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 90); + }); + + Statemint::execute_with(|| { + // Free execution: check that Bob received the tokens back in AssetHub + assert_eq!( + StatemintBalances::free_balance(RELAYBOB), + INITIAL_BALANCE + 110 + ); + }); + + // Send back tokens from AH to ParaA from Bob's account + Statemint::execute_with(|| { + // Now send those tokens to ParaA + assert_ok!(StatemintChainPalletXcm::limited_reserve_transfer_assets( + statemint_like::RuntimeOrigin::signed(RELAYBOB), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute) + .clone() + .into() + ), + Box::new((Location::parent(), 100).into()), + 0, + WeightLimit::Unlimited + )); + + // 100 DOTs were deducted from Bob's account + assert_eq!( + StatemintBalances::free_balance(RELAYBOB), + INITIAL_BALANCE + 10 + ); + }); + + ParaA::execute_with(|| { + // Alice should have received 100 DOTs + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 190); + }); +} + +#[test] +fn send_dot_from_moonbeam_to_statemint_via_xtokens_transfer_multiasset() { + MockNet::reset(); + + // Relay asset + let relay_location = parachain::AssetType::Xcm(xcm::v3::Location::parent()); + let source_relay_id: parachain::AssetId = relay_location.clone().into(); + + let relay_asset_metadata = parachain::AssetMetadata { + name: b"RelayToken".to_vec(), + symbol: b"Relay".to_vec(), + decimals: 12, + }; + + let dest_para = Location::new(1, [Parachain(1)]); + + let sov = xcm_builder::SiblingParachainConvertsVia::< + polkadot_parachain::primitives::Sibling, + statemint_like::AccountId, + >::convert_location(&dest_para) + .unwrap(); + + ParaA::execute_with(|| { + assert_ok!(AssetManager::register_foreign_asset( + parachain::RuntimeOrigin::root(), + relay_location.clone(), + relay_asset_metadata, + 1u128, + true + )); + XcmWeightTrader::set_asset_price(Location::parent(), 0u128); + }); + + let parachain_beneficiary_absolute: Location = Junction::AccountKey20 { + network: None, + key: PARAALICE, + } + .into(); + + let statemint_beneficiary_absolute: Location = Junction::AccountId32 { + network: None, + id: RELAYALICE.into(), + } + .into(); + + // First we send relay chain asset to Alice in AssetHub (via teleport) + Relay::execute_with(|| { + assert_ok!(RelayChainPalletXcm::limited_teleport_assets( + relay_chain::RuntimeOrigin::signed(RELAYALICE), + Box::new(Parachain(1000).into()), + Box::new( + VersionedLocation::V4(statemint_beneficiary_absolute) + .clone() + .into() + ), + Box::new(([], 200).into()), + 0, + WeightLimit::Unlimited + )); + }); + + // Send DOTs from AssetHub to ParaA (Moonbeam) + Statemint::execute_with(|| { + // Check Alice received 200 tokens on AssetHub + assert_eq!( + StatemintBalances::free_balance(RELAYALICE), + INITIAL_BALANCE + 200 + ); + + assert_ok!(StatemintBalances::transfer_allow_death( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + sov, + 110000000000000 + )); + + // Now send those tokens to ParaA + assert_ok!(StatemintChainPalletXcm::limited_reserve_transfer_assets( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute.clone()) + .clone() + .into() + ), + Box::new((Location::parent(), 200).into()), + 0, + WeightLimit::Unlimited + )); + }); + + ParaA::execute_with(|| { + // Alice should have received the DOTs + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 200); + }); + + let dest = Location::new( + 1, + [ + Parachain(1000), + AccountId32 { + network: None, + id: RELAYBOB.into(), + }, + ], + ); + + // Finally we test that we are able to send back the DOTs to AssetHub from the ParaA + ParaA::execute_with(|| { + assert_ok!(XTokens::transfer_multiasset( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + Box::new((Location::parent(), 100).into()), + Box::new(VersionedLocation::V4(dest)), + WeightLimit::Limited(Weight::from_parts(40000u64, DEFAULT_PROOF_SIZE)) + )); + + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 100); + }); + + Statemint::execute_with(|| { + // Check that Bob received the tokens back in AssetHub + assert_eq!( + StatemintBalances::free_balance(RELAYBOB), + INITIAL_BALANCE + 100 + ); + }); + + // Send back tokens from AH to ParaA from Bob's account + Statemint::execute_with(|| { + // Now send those tokens to ParaA + assert_ok!(StatemintChainPalletXcm::limited_reserve_transfer_assets( + statemint_like::RuntimeOrigin::signed(RELAYBOB), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute) + .clone() + .into() + ), + Box::new((Location::parent(), 100).into()), + 0, + WeightLimit::Unlimited + )); + + // 100 DOTs were deducted from Bob's account + assert_eq!(StatemintBalances::free_balance(RELAYBOB), INITIAL_BALANCE); + }); + + ParaA::execute_with(|| { + // Alice should have received 100 DOTs + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 200); + }); +} + +#[test] +fn send_dot_from_moonbeam_to_statemint_via_xtokens_transfer_multicurrencies() { + MockNet::reset(); + + // Relay asset + let relay_location = parachain::AssetType::Xcm(xcm::v3::Location::parent()); + let source_relay_id: parachain::AssetId = relay_location.clone().into(); + + let relay_asset_metadata = parachain::AssetMetadata { + name: b"RelayToken".to_vec(), + symbol: b"Relay".to_vec(), + decimals: 12, + }; + + // Statemint asset + let statemint_asset = Location::new( + 1, + [ + Parachain(1000u32), + PalletInstance(5u8), + GeneralIndex(10u128), + ], + ); + let statemint_location_asset = parachain::AssetType::Xcm( + xcm_builder::WithLatestLocationConverter::convert(&statemint_asset).expect("convert to v3"), + ); + let source_statemint_asset_id: parachain::AssetId = statemint_location_asset.clone().into(); + + let asset_metadata_statemint_asset = parachain::AssetMetadata { + name: b"USDC".to_vec(), + symbol: b"USDC".to_vec(), + decimals: 12, + }; + + let dest_para = Location::new(1, [Parachain(1)]); + + let sov = xcm_builder::SiblingParachainConvertsVia::< + polkadot_parachain::primitives::Sibling, + statemint_like::AccountId, + >::convert_location(&dest_para) + .unwrap(); + + ParaA::execute_with(|| { + assert_ok!(AssetManager::register_foreign_asset( + parachain::RuntimeOrigin::root(), + relay_location.clone(), + relay_asset_metadata, + 1u128, + true + )); + XcmWeightTrader::set_asset_price(Location::parent(), 0u128); + + assert_ok!(AssetManager::register_foreign_asset( + parachain::RuntimeOrigin::root(), + statemint_location_asset.clone(), + asset_metadata_statemint_asset, + 1u128, + true + )); + XcmWeightTrader::set_asset_price(statemint_asset.clone(), 0u128); + }); + + let parachain_beneficiary_absolute: Location = Junction::AccountKey20 { + network: None, + key: PARAALICE, + } + .into(); + + let statemint_beneficiary_absolute: Location = Junction::AccountId32 { + network: None, + id: RELAYALICE.into(), + } + .into(); + + // First we send relay chain asset to Alice in AssetHub (via teleport) + Relay::execute_with(|| { + assert_ok!(RelayChainPalletXcm::limited_teleport_assets( + relay_chain::RuntimeOrigin::signed(RELAYALICE), + Box::new(Parachain(1000).into()), + Box::new( + VersionedLocation::V4(statemint_beneficiary_absolute) + .clone() + .into() + ), + Box::new(([], 200).into()), + 0, + WeightLimit::Unlimited + )); + }); + + // Send DOTs and USDC from AssetHub to ParaA (Moonbeam) + Statemint::execute_with(|| { + // Check Alice received 200 tokens on AssetHub + assert_eq!( + StatemintBalances::free_balance(RELAYALICE), + INITIAL_BALANCE + 200 + ); + + assert_ok!(StatemintBalances::transfer_allow_death( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + sov, + 110000000000000 + )); + + statemint_like::PrefixChanger::set_prefix( + PalletInstance(::index() as u8).into(), + ); + + assert_ok!(StatemintAssets::create( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + 10, + RELAYALICE, + 1 + )); + + assert_ok!(StatemintAssets::mint( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + 10, + RELAYALICE, + 300000000000000 + )); + + // Now send relay tokens to ParaA + assert_ok!(StatemintChainPalletXcm::limited_reserve_transfer_assets( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute.clone()) + .clone() + .into() + ), + Box::new((Location::parent(), 200).into()), + 0, + WeightLimit::Unlimited + )); + + // Send USDC + assert_ok!(StatemintChainPalletXcm::limited_reserve_transfer_assets( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute.clone()) + .clone() + .into() + ), + Box::new( + ( + [ + xcm::latest::prelude::PalletInstance( + ::index() as u8 + ), + GeneralIndex(10), + ], + 125 + ) + .into() + ), + 0, + WeightLimit::Unlimited + )); + }); + + ParaA::execute_with(|| { + // Alice should have received the DOTs + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 200); + + // Alice has received 125 USDC + assert_eq!( + Assets::balance(source_statemint_asset_id, &PARAALICE.into()), + 125 + ); + }); + + let dest = Location::new( + 1, + [ + Parachain(1000), + AccountId32 { + network: None, + id: RELAYBOB.into(), + }, + ], + ); + + // Finally we test that we are able to send back the DOTs to AssetHub from the ParaA + ParaA::execute_with(|| { + assert_ok!(XTokens::transfer_multicurrencies( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + vec![ + ( + parachain::CurrencyId::ForeignAsset(source_statemint_asset_id), + 100 + ), + (parachain::CurrencyId::ForeignAsset(source_relay_id), 100) + ], + 1, + Box::new(VersionedLocation::V4(dest)), + WeightLimit::Limited(Weight::from_parts(80_000_000u64, 100_000u64)) + )); + + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 100); + }); + + Statemint::execute_with(|| { + // Check that Bob received relay tokens back in AssetHub + // (100 - MinXcmFee) + assert_eq!( + StatemintBalances::free_balance(RELAYBOB), + INITIAL_BALANCE + 50 + ); + + // Check that BOB received 100 USDC on AssetHub + assert_eq!(StatemintAssets::account_balances(RELAYBOB), vec![(10, 100)]); + }); + + // Send back tokens from AH to ParaA from Bob's account + Statemint::execute_with(|| { + let bob_previous_balance = StatemintBalances::free_balance(RELAYBOB); + + // Now send those tokens to ParaA + assert_ok!(StatemintChainPalletXcm::limited_reserve_transfer_assets( + statemint_like::RuntimeOrigin::signed(RELAYBOB), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute) + .clone() + .into() + ), + Box::new((Location::parent(), 100).into()), + 0, + WeightLimit::Unlimited + )); + + // 100 DOTs were deducted from Bob's account + assert_eq!( + StatemintBalances::free_balance(RELAYBOB), + bob_previous_balance - 100 + ); + }); + + ParaA::execute_with(|| { + // Alice should have received 100 DOTs + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 200); + }); +} + +#[test] +fn send_dot_from_moonbeam_to_statemint_via_xtokens_transfer_multiassets() { + MockNet::reset(); + + // Relay asset + let relay_location = parachain::AssetType::Xcm(xcm::v3::Location::parent()); + let source_relay_id: parachain::AssetId = relay_location.clone().into(); + + let relay_asset_metadata = parachain::AssetMetadata { + name: b"RelayToken".to_vec(), + symbol: b"Relay".to_vec(), + decimals: 12, + }; + + // Statemint asset + let statemint_asset = Location::new( + 1, + [ + Parachain(1000u32), + PalletInstance(5u8), + GeneralIndex(10u128), + ], + ); + let statemint_location_asset = parachain::AssetType::Xcm( + xcm_builder::WithLatestLocationConverter::convert(&statemint_asset).expect("convert to v3"), + ); + let source_statemint_asset_id: parachain::AssetId = statemint_location_asset.clone().into(); + + let asset_metadata_statemint_asset = parachain::AssetMetadata { + name: b"USDC".to_vec(), + symbol: b"USDC".to_vec(), + decimals: 12, + }; + + let dest_para = Location::new(1, [Parachain(1)]); + + let sov = xcm_builder::SiblingParachainConvertsVia::< + polkadot_parachain::primitives::Sibling, + statemint_like::AccountId, + >::convert_location(&dest_para) + .unwrap(); + + ParaA::execute_with(|| { + assert_ok!(AssetManager::register_foreign_asset( + parachain::RuntimeOrigin::root(), + relay_location.clone(), + relay_asset_metadata, + 1u128, + true + )); + XcmWeightTrader::set_asset_price(Location::parent(), 0u128); + + assert_ok!(AssetManager::register_foreign_asset( + parachain::RuntimeOrigin::root(), + statemint_location_asset.clone(), + asset_metadata_statemint_asset, + 1u128, + true + )); + XcmWeightTrader::set_asset_price(statemint_asset.clone(), 0u128); + }); + + let parachain_beneficiary_absolute: Location = Junction::AccountKey20 { + network: None, + key: PARAALICE, + } + .into(); + + let statemint_beneficiary_absolute: Location = Junction::AccountId32 { + network: None, + id: RELAYALICE.into(), + } + .into(); + + // First we send relay chain asset to Alice in AssetHub (via teleport) + Relay::execute_with(|| { + assert_ok!(RelayChainPalletXcm::limited_teleport_assets( + relay_chain::RuntimeOrigin::signed(RELAYALICE), + Box::new(Parachain(1000).into()), + Box::new( + VersionedLocation::V4(statemint_beneficiary_absolute) + .clone() + .into() + ), + Box::new(([], 200).into()), + 0, + WeightLimit::Unlimited + )); + }); + + // Send DOTs and USDC from AssetHub to ParaA (Moonbeam) + Statemint::execute_with(|| { + // Check Alice received 200 tokens on AssetHub + assert_eq!( + StatemintBalances::free_balance(RELAYALICE), + INITIAL_BALANCE + 200 + ); + + assert_ok!(StatemintBalances::transfer_allow_death( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + sov, + 110000000000000 + )); + + statemint_like::PrefixChanger::set_prefix( + PalletInstance(::index() as u8).into(), + ); + + assert_ok!(StatemintAssets::create( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + 10, + RELAYALICE, + 1 + )); + + assert_ok!(StatemintAssets::mint( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + 10, + RELAYALICE, + 300000000000000 + )); + + // Now send relay tokens to ParaA + assert_ok!(StatemintChainPalletXcm::limited_reserve_transfer_assets( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute.clone()) + .clone() + .into() + ), + Box::new((Location::parent(), 200).into()), + 0, + WeightLimit::Unlimited + )); + + // Send USDC + assert_ok!(StatemintChainPalletXcm::limited_reserve_transfer_assets( + statemint_like::RuntimeOrigin::signed(RELAYALICE), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute.clone()) + .clone() + .into() + ), + Box::new( + ( + [ + xcm::latest::prelude::PalletInstance( + ::index() as u8 + ), + GeneralIndex(10), + ], + 125 + ) + .into() + ), + 0, + WeightLimit::Unlimited + )); + }); + + ParaA::execute_with(|| { + // Alice should have received the DOTs + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 200); + + // Alice has received 125 USDC + assert_eq!( + Assets::balance(source_statemint_asset_id, &PARAALICE.into()), + 125 + ); + }); + + let dest = Location::new( + 1, + [ + Parachain(1000), + AccountId32 { + network: None, + id: RELAYBOB.into(), + }, + ], + ); + + let statemint_asset_to_send = XcmAsset { + id: XcmAssetId(statemint_asset), + fun: Fungible(100), + }; + + let relay_asset_to_send = XcmAsset { + id: XcmAssetId(Location::parent()), + fun: Fungible(100), + }; + + let assets_to_send: XcmAssets = + XcmAssets::from(vec![statemint_asset_to_send, relay_asset_to_send.clone()]); + + // For some reason the order of the assets is inverted when creating the array above. + // We need to use relay asset for fees, so we pick index 0. + assert_eq!(assets_to_send.get(0).unwrap(), &relay_asset_to_send); + + // Finally we test that we are able to send back the DOTs to AssetHub from the ParaA + ParaA::execute_with(|| { + assert_ok!(XTokens::transfer_multiassets( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + Box::new(assets_to_send.into()), + 0, + Box::new(VersionedLocation::V4(dest)), + WeightLimit::Limited(Weight::from_parts(80_000_000u64, 100_000u64)) + )); + + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 100); + }); + + Statemint::execute_with(|| { + // Check that Bob received relay tokens back in AssetHub + // (100 - MinXcmFee) + assert_eq!( + StatemintBalances::free_balance(RELAYBOB), + INITIAL_BALANCE + 50 + ); + + // Check that BOB received 100 USDC on AssetHub + assert_eq!(StatemintAssets::account_balances(RELAYBOB), vec![(10, 100)]); + }); + + // Send back tokens from AH to ParaA from Bob's account + Statemint::execute_with(|| { + let bob_previous_balance = StatemintBalances::free_balance(RELAYBOB); + + // Now send those tokens to ParaA + assert_ok!(StatemintChainPalletXcm::limited_reserve_transfer_assets( + statemint_like::RuntimeOrigin::signed(RELAYBOB), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute) + .clone() + .into() + ), + Box::new((Location::parent(), 100).into()), + 0, + WeightLimit::Unlimited + )); + + // 100 DOTs were deducted from Bob's account + assert_eq!( + StatemintBalances::free_balance(RELAYBOB), + bob_previous_balance - 100 + ); + }); + + ParaA::execute_with(|| { + // Alice should have received 100 DOTs + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 200); + }); +} + #[test] fn transact_through_signed_multilocation() { MockNet::reset(); @@ -2976,7 +3859,7 @@ fn transact_through_signed_multilocation_para_to_para() { assert_ok!(XcmTransactor::set_fee_per_second( parachain::RuntimeOrigin::root(), Box::new(xcm::VersionedLocation::V4(para_b_balances.clone())), - parachain::ParaTokensPerSecond::get().1 as u128, + parachain::ParaTokensPerSecond::get(), )); ancestry = parachain::UniversalLocation::get().into(); }); @@ -3074,7 +3957,7 @@ fn transact_through_signed_multilocation_para_to_para_refund() { assert_ok!(XcmTransactor::set_fee_per_second( parachain::RuntimeOrigin::root(), Box::new(xcm::VersionedLocation::V4(para_b_balances.clone())), - parachain::ParaTokensPerSecond::get().1 as u128, + parachain::ParaTokensPerSecond::get(), )); ancestry = parachain::UniversalLocation::get().into(); }); @@ -3186,7 +4069,7 @@ fn transact_through_signed_multilocation_para_to_para_ethereum() { assert_ok!(XcmTransactor::set_fee_per_second( parachain::RuntimeOrigin::root(), Box::new(xcm::VersionedLocation::V4(para_b_balances.clone())), - parachain::ParaTokensPerSecond::get().1 as u128, + parachain::ParaTokensPerSecond::get(), )); ancestry = parachain::UniversalLocation::get().into(); }); @@ -3313,7 +4196,7 @@ fn transact_through_signed_multilocation_para_to_para_ethereum_no_proxy_fails() assert_ok!(XcmTransactor::set_fee_per_second( parachain::RuntimeOrigin::root(), Box::new(xcm::VersionedLocation::V4(para_b_balances.clone())), - parachain::ParaTokensPerSecond::get().1 as u128, + parachain::ParaTokensPerSecond::get(), )); ancestry = parachain::UniversalLocation::get().into(); }); @@ -3436,7 +4319,7 @@ fn transact_through_signed_multilocation_para_to_para_ethereum_proxy_succeeds() assert_ok!(XcmTransactor::set_fee_per_second( parachain::RuntimeOrigin::root(), Box::new(xcm::VersionedLocation::V4(para_b_balances.clone())), - parachain::ParaTokensPerSecond::get().1 as u128, + parachain::ParaTokensPerSecond::get(), )); ancestry = parachain::UniversalLocation::get().into(); }); diff --git a/runtime/moonriver/Cargo.toml b/runtime/moonriver/Cargo.toml index 023262399a..cac100dbe2 100644 --- a/runtime/moonriver/Cargo.toml +++ b/runtime/moonriver/Cargo.toml @@ -46,6 +46,7 @@ pallet-precompile-benchmarks = { workspace = true } pallet-proxy-genesis-companion = { workspace = true } pallet-randomness = { workspace = true } pallet-xcm-transactor = { workspace = true } +pallet-xcm-weight-trader = { workspace = true } # Moonbeam precompiles pallet-evm-precompile-author-mapping = { workspace = true } @@ -287,6 +288,7 @@ std = [ "pallet-whitelist/std", "pallet-xcm-transactor/std", "pallet-xcm/std", + "pallet-xcm-weight-trader/std", "parachain-info/std", "parachains-common/std", "parity-scale-codec/std", @@ -378,6 +380,7 @@ runtime-benchmarks = [ "pallet-xcm-benchmarks/runtime-benchmarks", "pallet-xcm-transactor/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", + "pallet-xcm-weight-trader/runtime-benchmarks", "session-keys-primitives/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", diff --git a/runtime/moonriver/src/lib.rs b/runtime/moonriver/src/lib.rs index 2d311e26fe..b6b2e8b602 100644 --- a/runtime/moonriver/src/lib.rs +++ b/runtime/moonriver/src/lib.rs @@ -98,13 +98,8 @@ use sp_runtime::{ Perquintill, SaturatedConversion, }; use sp_std::{convert::TryFrom, prelude::*}; -use xcm::{ - v3::{AssetId as XcmAssetId, Location}, - IntoVersion, VersionedAssetId, VersionedAssets, VersionedLocation, VersionedXcm, -}; -use xcm_config::AssetType; +use xcm::{VersionedAssetId, VersionedAssets, VersionedLocation, VersionedXcm}; use xcm_fee_payment_runtime_api::Error as XcmPaymentApiError; -use xcm_primitives::UnitsToWeightRatio; use smallvec::smallvec; #[cfg(feature = "std")] @@ -1472,6 +1467,7 @@ construct_runtime! { Erc20XcmBridge: pallet_erc20_xcm_bridge::{Pallet} = 110, MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event} = 111, EvmForeignAssets: pallet_moonbeam_foreign_assets::{Pallet, Call, Storage, Event} = 114, + XcmWeightTrader: pallet_xcm_weight_trader::{Pallet, Call, Storage, Event} = 115, EmergencyParaXcm: pallet_emergency_para_xcm::{Pallet, Call, Storage, Event} = 116, // Utils diff --git a/runtime/moonriver/src/xcm_config.rs b/runtime/moonriver/src/xcm_config.rs index 9ebce3cd75..cf55c3db0b 100644 --- a/runtime/moonriver/src/xcm_config.rs +++ b/runtime/moonriver/src/xcm_config.rs @@ -18,10 +18,10 @@ //! use super::{ - governance, AccountId, AssetId, AssetManager, Balance, Balances, DealWithFees, - EmergencyParaXcm, Erc20XcmBridge, MaintenanceMode, MessageQueue, OpenTechCommitteeInstance, - ParachainInfo, ParachainSystem, Perbill, PolkadotXcm, Runtime, RuntimeBlockWeights, - RuntimeCall, RuntimeEvent, RuntimeOrigin, Treasury, XcmpQueue, + governance, AccountId, AssetId, AssetManager, Balance, Balances, EmergencyParaXcm, + Erc20XcmBridge, MaintenanceMode, MessageQueue, OpenTechCommitteeInstance, ParachainInfo, + ParachainSystem, Perbill, PolkadotXcm, Runtime, RuntimeBlockWeights, RuntimeCall, RuntimeEvent, + RuntimeOrigin, Treasury, XcmpQueue, }; use frame_support::{ @@ -45,7 +45,7 @@ use xcm_builder::{ EnsureXcmOrigin, FungibleAdapter as XcmCurrencyAdapter, FungiblesAdapter, HashedDescription, NoChecking, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountKey20AsNative, SovereignSignedViaLocation, - TakeWeightCredit, UsingComponents, WeightInfoBounds, WithComputedOrigin, + TakeWeightCredit, WeightInfoBounds, WithComputedOrigin, }; use parachains_common::message_queue::{NarrowOriginToSibling, ParaIdToSibling}; @@ -60,8 +60,8 @@ use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; use orml_xcm_support::MultiNativeAsset; use xcm_primitives::{ AbsoluteAndRelativeReserve, AccountIdToCurrencyId, AccountIdToLocation, AsAssetType, - FirstAssetTrader, IsBridgedConcreteAssetFrom, SignedToAccountId20, UtilityAvailableCalls, - UtilityEncodeCall, XcmTransact, + IsBridgedConcreteAssetFrom, SignedToAccountId20, UtilityAvailableCalls, UtilityEncodeCall, + XcmTransact, }; use parity_scale_codec::{Decode, Encode}; @@ -229,23 +229,6 @@ parameter_types! { pub XcmFeesAccount: AccountId = Treasury::account_id(); } -/// This is the struct that will handle the revenue from xcm fees -/// We do not burn anything because we want to mimic exactly what -/// the sovereign account has -pub type XcmFeesToAccount = xcm_primitives::XcmFeesToAccount< - super::Assets, - ( - ConvertedConcreteId< - AssetId, - Balance, - AsAssetType, - JustTry, - >, - ), - AccountId, - XcmFeesAccount, ->; - pub struct SafeCallFilter; impl frame_support::traits::Contains for SafeCallFilter { fn contains(_call: &RuntimeCall) -> bool { @@ -308,16 +291,7 @@ impl xcm_executor::Config for XcmExecutorConfig { // we use UsingComponents and the local way of handling fees // When we receive a non-reserve asset, we use AssetManager to fetch how many // units per second we should charge - type Trader = ( - UsingComponents< - ::WeightToFee, - SelfReserve, - AccountId, - Balances, - DealWithFees, - >, - FirstAssetTrader, - ); + type Trader = pallet_xcm_weight_trader::Trader; type ResponseHandler = PolkadotXcm; type SubscriptionService = PolkadotXcm; type AssetTrap = pallet_erc20_xcm_bridge::AssetTrapWrapper; @@ -377,7 +351,6 @@ impl pallet_xcm::Config for Runtime { type MaxLockers = ConstU32<8>; type MaxRemoteLockConsumers = ConstU32<0>; type RemoteLockConsumerIdentifier = (); - // TODO pallet-xcm weights type WeightInfo = moonriver_weights::pallet_xcm::WeightInfo; type AdminOrigin = EnsureRoot; } @@ -763,6 +736,54 @@ impl pallet_moonbeam_foreign_assets::Config for Runtime { type XcmLocationToH160 = LocationToH160; } +pub struct AssetFeesFilter; +impl frame_support::traits::Contains for AssetFeesFilter { + fn contains(location: &Location) -> bool { + location.parent_count() > 0 + && location.first_interior() != Erc20XcmBridgePalletLocation::get().first_interior() + } +} + +pub type AddSupportedAssetOrigin = EitherOfDiverse< + EnsureRoot, + EitherOfDiverse< + pallet_collective::EnsureProportionMoreThan, + governance::custom_origins::GeneralAdmin, + >, +>; + +pub type EditSupportedAssetOrigin = EitherOfDiverse< + EnsureRoot, + EitherOfDiverse< + pallet_collective::EnsureProportionMoreThan, + governance::custom_origins::FastGeneralAdmin, + >, +>; + +pub type RemoveSupportedAssetOrigin = EitherOfDiverse< + EnsureRoot, + pallet_collective::EnsureProportionMoreThan, +>; + +impl pallet_xcm_weight_trader::Config for Runtime { + type AccountIdToLocation = AccountIdToLocation; + type AddSupportedAssetOrigin = AddSupportedAssetOrigin; + type AssetLocationFilter = AssetFeesFilter; + type AssetTransactor = AssetTransactors; + type Balance = Balance; + type EditSupportedAssetOrigin = EditSupportedAssetOrigin; + type NativeLocation = SelfReserve; + type PauseSupportedAssetOrigin = EditSupportedAssetOrigin; + type RemoveSupportedAssetOrigin = RemoveSupportedAssetOrigin; + type RuntimeEvent = RuntimeEvent; + type ResumeSupportedAssetOrigin = RemoveSupportedAssetOrigin; + type WeightInfo = moonriver_weights::pallet_xcm_weight_trader::WeightInfo; + type WeightToFee = ::WeightToFee; + type XcmFeesAccount = XcmFeesAccount; + #[cfg(feature = "runtime-benchmarks")] + type NotFilteredLocation = RelayLocation; +} + #[cfg(feature = "runtime-benchmarks")] mod testing { use super::*; diff --git a/runtime/moonriver/tests/integration_test.rs b/runtime/moonriver/tests/integration_test.rs index 27761b3224..6f01317e82 100644 --- a/runtime/moonriver/tests/integration_test.rs +++ b/runtime/moonriver/tests/integration_test.rs @@ -1925,7 +1925,7 @@ fn xtokens_precompiles_transfer() { weight: 4_000_000, }, ) - .expect_cost(196490) + .expect_cost(196698) .expect_no_logs() .execute_returns(()) }) @@ -1977,7 +1977,7 @@ fn xtokens_precompiles_transfer_multiasset() { weight: 4_000_000, }, ) - .expect_cost(196490) + .expect_cost(196698) .expect_no_logs() .execute_returns(()); }) diff --git a/runtime/moonriver/tests/xcm_mock/mod.rs b/runtime/moonriver/tests/xcm_mock/mod.rs index 8d5d6e8c6c..cb1cacc488 100644 --- a/runtime/moonriver/tests/xcm_mock/mod.rs +++ b/runtime/moonriver/tests/xcm_mock/mod.rs @@ -106,7 +106,7 @@ decl_test_parachain! { Runtime = statemine_like::Runtime, XcmpMessageHandler = statemine_like::MsgQueue, DmpMessageHandler = statemine_like::MsgQueue, - new_ext = statemine_ext(4), + new_ext = statemine_ext(1000), } } @@ -118,7 +118,7 @@ decl_test_relay_chain! { XcmConfig = relay_chain::XcmConfig, MessageQueue = relay_chain::MessageQueue, System = relay_chain::System, - new_ext = relay_ext(vec![1, 2, 3, 4]), + new_ext = relay_ext(vec![1, 2, 3, 1000]), } } @@ -129,7 +129,7 @@ decl_test_network! { (1, ParaA), (2, ParaB), (3, ParaC), - (4, Statemine), + (1000, Statemine), ], } } @@ -271,3 +271,4 @@ pub type XTokens = orml_xtokens::Pallet; pub type RelayBalances = pallet_balances::Pallet; pub type ParaBalances = pallet_balances::Pallet; pub type XcmTransactor = pallet_xcm_transactor::Pallet; +pub type XcmWeightTrader = pallet_xcm_weight_trader::Pallet; diff --git a/runtime/moonriver/tests/xcm_mock/parachain.rs b/runtime/moonriver/tests/xcm_mock/parachain.rs index 4a7f38e165..e71bc1e21e 100644 --- a/runtime/moonriver/tests/xcm_mock/parachain.rs +++ b/runtime/moonriver/tests/xcm_mock/parachain.rs @@ -43,15 +43,15 @@ use pallet_ethereum::PostLogContent; use polkadot_core_primitives::BlockNumber as RelayBlockNumber; use polkadot_parachain::primitives::{Id as ParaId, Sibling}; use xcm::latest::{ - AssetId as XcmAssetId, Error as XcmError, ExecuteXcm, + Error as XcmError, ExecuteXcm, Junction::{PalletInstance, Parachain}, Location, NetworkId, Outcome, Xcm, }; use xcm_builder::{ AccountKey20Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, - AllowTopLevelPaidExecutionFrom, ConvertedConcreteId, EnsureXcmOrigin, FixedRateOfFungible, - FixedWeightBounds, FungibleAdapter as XcmCurrencyAdapter, FungiblesAdapter, IsConcrete, - NoChecking, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, + AllowTopLevelPaidExecutionFrom, Case, ConvertedConcreteId, EnsureXcmOrigin, FixedWeightBounds, + FungibleAdapter as XcmCurrencyAdapter, FungiblesAdapter, IsConcrete, NoChecking, + ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountKey20AsNative, SovereignSignedViaLocation, TakeWeightCredit, WithComputedOrigin, }; @@ -282,31 +282,20 @@ pub type XcmBarrier = ( parameter_types! { /// Xcm fees will go to the treasury account pub XcmFeesAccount: AccountId = Treasury::account_id(); + /// Parachain token units per second of execution + pub ParaTokensPerSecond: u128 = 1000000000000; } -/// This is the struct that will handle the revenue from xcm fees -pub type XcmFeesToAccount_ = xcm_primitives::XcmFeesToAccount< - Assets, - ( - ConvertedConcreteId< - AssetId, - Balance, - xcm_primitives::AsAssetType, - JustTry, - >, - ), - AccountId, - XcmFeesAccount, ->; +pub struct WeightToFee; +impl sp_weights::WeightToFee for WeightToFee { + type Balance = Balance; -parameter_types! { - // We cannot skip the native trader for some specific tests, so we will have to work with - // a native trader that charges same number of units as weight - pub ParaTokensPerSecond: (XcmAssetId, u128, u128) = ( - AssetId(SelfReserve::get()), - 1000000000000, - 0, - ); + fn weight_to_fee(weight: &Weight) -> Self::Balance { + use sp_runtime::SaturatedConversion as _; + Self::Balance::saturated_from(weight.ref_time()) + .saturating_mul(ParaTokensPerSecond::get()) + .saturating_div(frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND as u128) + } } parameter_types! { @@ -321,6 +310,17 @@ parameter_types! { ].into() }; pub const MaxAssetsIntoHolding: u32 = 64; + + pub AssetHubLocation: Location = Location::new(1, [Parachain(1000)]); + pub RelayLocationFilter: AssetFilter = Wild(AllOf { + fun: WildFungible, + id: xcm::prelude::AssetId(Location::parent()), + }); + + pub RelayChainNativeAssetFromAssetHub: (AssetFilter, Location) = ( + RelayLocationFilter::get(), + AssetHubLocation::get() + ); } use frame_system::RawOrigin; @@ -329,27 +329,27 @@ use sp_runtime::DispatchErrorWithPostInfo; use xcm_executor::traits::CallDispatcher; moonbeam_runtime_common::impl_moonbeam_xcm_call!(); +type Reserves = ( + // Relaychain (DOT) from Asset Hub + Case, + // Assets which the reserve is the same as the origin. + orml_xcm_support::MultiNativeAsset< + xcm_primitives::AbsoluteAndRelativeReserve, + >, +); + pub struct XcmConfig; impl Config for XcmConfig { type RuntimeCall = RuntimeCall; type XcmSender = XcmRouter; type AssetTransactor = AssetTransactors; type OriginConverter = XcmOriginToTransactDispatchOrigin; - type IsReserve = orml_xcm_support::MultiNativeAsset< - xcm_primitives::AbsoluteAndRelativeReserve, - >; + type IsReserve = Reserves; type IsTeleporter = (); type UniversalLocation = UniversalLocation; type Barrier = XcmBarrier; type Weigher = FixedWeightBounds; - // We use three traders - // When we receive either representation of the self-reserve asset, - // When we receive a non-reserve asset, we use AssetManager to fetch how many - // units per second we should charge - type Trader = ( - FixedRateOfFungible, - xcm_primitives::FirstAssetTrader, - ); + type Trader = pallet_xcm_weight_trader::Trader; type ResponseHandler = PolkadotXcm; type SubscriptionService = PolkadotXcm; type AssetTrap = PolkadotXcm; @@ -420,7 +420,7 @@ parameter_types! { parameter_type_with_key! { pub ParachainMinFee: |location: Location| -> Option { match (location.parents, location.first_interior()) { - (1, Some(Parachain(4u32))) => Some(50u128), + (1, Some(Parachain(1000u32))) => Some(50u128), _ => None, } }; @@ -818,6 +818,29 @@ impl pallet_xcm_transactor::Config for Runtime { type MaxHrmpFee = xcm_builder::Case; } +parameter_types! { + pub RelayLocation: Location = Location::parent(); +} + +impl pallet_xcm_weight_trader::Config for Runtime { + type AccountIdToLocation = xcm_primitives::AccountIdToLocation; + type AddSupportedAssetOrigin = EnsureRoot; + type AssetLocationFilter = Everything; + type AssetTransactor = AssetTransactors; + type Balance = Balance; + type EditSupportedAssetOrigin = EnsureRoot; + type NativeLocation = SelfReserve; + type PauseSupportedAssetOrigin = EnsureRoot; + type RemoveSupportedAssetOrigin = EnsureRoot; + type RuntimeEvent = RuntimeEvent; + type ResumeSupportedAssetOrigin = EnsureRoot; + type WeightInfo = (); + type WeightToFee = WeightToFee; + type XcmFeesAccount = XcmFeesAccount; + #[cfg(feature = "runtime-benchmarks")] + type NotFilteredLocation = RelayLocation; +} + parameter_types! { pub const MinimumPeriod: u64 = 1000; } @@ -1068,6 +1091,7 @@ construct_runtime!( XTokens: orml_xtokens, AssetManager: pallet_asset_manager, XcmTransactor: pallet_xcm_transactor, + XcmWeightTrader: pallet_xcm_weight_trader, Treasury: pallet_treasury, Proxy: pallet_proxy, diff --git a/runtime/moonriver/tests/xcm_mock/relay_chain.rs b/runtime/moonriver/tests/xcm_mock/relay_chain.rs index 15bb49b91e..f391b31d84 100644 --- a/runtime/moonriver/tests/xcm_mock/relay_chain.rs +++ b/runtime/moonriver/tests/xcm_mock/relay_chain.rs @@ -177,7 +177,7 @@ pub type XcmBarrier = ( parameter_types! { pub Kusama: AssetFilter = Wild(AllOf { fun: WildFungible, id: AssetId(KsmLocation::get()) }); - pub Statemine: Location = Parachain(4).into(); + pub Statemine: Location = Parachain(1000).into(); pub KusamaForStatemine: (AssetFilter, Location) = (Kusama::get(), Statemine::get()); } diff --git a/runtime/moonriver/tests/xcm_mock/statemine_like.rs b/runtime/moonriver/tests/xcm_mock/statemine_like.rs index 6168450597..f5d2b46cdf 100644 --- a/runtime/moonriver/tests/xcm_mock/statemine_like.rs +++ b/runtime/moonriver/tests/xcm_mock/statemine_like.rs @@ -18,7 +18,7 @@ use frame_support::{ construct_runtime, parameter_types, - traits::{AsEnsureOriginWithArg, Contains, Everything, Nothing}, + traits::{AsEnsureOriginWithArg, Contains, ContainsPair, Everything, Get, Nothing}, weights::Weight, }; use frame_system::{EnsureRoot, EnsureSigned}; @@ -290,8 +290,33 @@ pub type Barrier = ( parameter_types! { pub MatcherLocation: Location = Location::here(); pub const MaxAssetsIntoHolding: u32 = 64; + pub const RelayTokenLocation: Location = Location::parent(); } +// Copied from: +// +// https://github.com/paritytech/polkadot-sdk/blob/f4eb41773611008040c9d4d8a8e6b7323eccfca1/cumulus +// /parachains/common/src/xcm_config.rs#L118 +// +// The difference with the original "ConcreteAssetFromSystem" (which is used by AssetHub), +// is that in our tests we only need to check if the asset matches the relay one. +pub struct ConcreteAssetFromRelay(sp_std::marker::PhantomData); +impl> ContainsPair + for ConcreteAssetFromRelay +{ + fn contains(asset: &Asset, origin: &Location) -> bool { + let is_relay = match origin.unpack() { + // The Relay Chain + (1, []) => true, + // Others + _ => false, + }; + asset.id.0 == AssetLocation::get() && is_relay + } +} + +pub type TrustedTeleporters = (ConcreteAssetFromRelay,); + pub struct XcmConfig; impl Config for XcmConfig { type RuntimeCall = RuntimeCall; @@ -300,7 +325,7 @@ impl Config for XcmConfig { type OriginConverter = XcmOriginToTransactDispatchOrigin; type IsReserve = orml_xcm_support::MultiNativeAsset; - type IsTeleporter = (); + type IsTeleporter = TrustedTeleporters; type UniversalLocation = UniversalLocation; type Barrier = Barrier; type Weigher = FixedWeightBounds; diff --git a/runtime/moonriver/tests/xcm_tests.rs b/runtime/moonriver/tests/xcm_tests.rs index 2ca25f5530..ceee8aa521 100644 --- a/runtime/moonriver/tests/xcm_tests.rs +++ b/runtime/moonriver/tests/xcm_tests.rs @@ -26,11 +26,12 @@ use frame_support::{ use sp_core::ConstU32; use sp_runtime::traits::MaybeEquivalence; use xcm::latest::prelude::{ - AccountId32, AccountKey20, All, BuyExecution, ClearOrigin, DepositAsset, GeneralIndex, - Junction, Junctions, Limited, Location, OriginKind, PalletInstance, Parachain, QueryResponse, - Reanchorable, Response, WeightLimit, WithdrawAsset, Xcm, + AccountId32, AccountKey20, All, Asset as XcmAsset, AssetId as XcmAssetId, Assets as XcmAssets, + BuyExecution, ClearOrigin, DepositAsset, Fungible, GeneralIndex, Junction, Junctions, Limited, + Location, OriginKind, PalletInstance, Parachain, QueryResponse, Reanchorable, Response, + WeightLimit, WithdrawAsset, Xcm, }; -use xcm::{VersionedLocation, WrapVersion}; +use xcm::{IntoVersion, VersionedLocation, WrapVersion}; use xcm_executor::traits::ConvertLocation; use xcm_mock::parachain; use xcm_mock::relay_chain; @@ -43,6 +44,39 @@ use pallet_xcm_transactor::{ }; use xcm_primitives::{UtilityEncodeCall, DEFAULT_PROOF_SIZE}; +fn add_supported_asset(asset_type: parachain::AssetType, units_per_second: u128) -> Result<(), ()> { + let parachain::AssetType::Xcm(location_v3) = asset_type; + let VersionedLocation::V4(location_v4) = VersionedLocation::V3(location_v3) + .into_version(4) + .map_err(|_| ())? + else { + return Err(()); + }; + use frame_support::weights::WeightToFee as _; + let native_amount_per_second: u128 = + ::WeightToFee::weight_to_fee( + &Weight::from_parts( + frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND, + 0, + ), + ) + .try_into() + .map_err(|_| ())?; + let precision_factor = 10u128.pow(pallet_xcm_weight_trader::RELATIVE_PRICE_DECIMALS); + let relative_price: u128 = if units_per_second > 0u128 { + native_amount_per_second + .saturating_mul(precision_factor) + .saturating_div(units_per_second) + } else { + 0u128 + }; + pallet_xcm_weight_trader::SupportedAssets::::insert( + location_v4, + (true, relative_price), + ); + Ok(()) +} + // Send a relay asset (like DOT) to a parachain A #[test] fn receive_relay_asset_from_relay() { @@ -64,12 +98,7 @@ fn receive_relay_asset_from_relay() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0u128)); }); // Actually send relay asset to parachain @@ -120,12 +149,7 @@ fn send_relay_asset_to_relay() { true )); // free execution - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0u128)); }); let dest: Location = Junction::AccountKey20 { @@ -210,12 +234,7 @@ fn send_relay_asset_to_para_b() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location.clone(), - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0u128)); }); // Register asset in paraB. Free execution @@ -227,12 +246,7 @@ fn send_relay_asset_to_para_b() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0u128)); }); // First send relay chain asset to Parachain A like in previous test @@ -316,12 +330,7 @@ fn send_para_a_asset_to_para_b() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0u128)); }); // Send para A asset from para A to para B @@ -389,12 +398,7 @@ fn send_para_a_asset_from_para_b_to_para_c() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location.clone(), - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0u128)); }); // Register para A asset in parachain C. Free execution @@ -406,12 +410,7 @@ fn send_para_a_asset_from_para_b_to_para_c() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0u128)); }); // Send para A asset to para B @@ -505,12 +504,7 @@ fn send_para_a_asset_to_para_b_and_back_to_para_a() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0u128)); }); // Send para A asset to para B @@ -604,12 +598,7 @@ fn send_para_a_asset_to_para_b_and_back_to_para_a_with_new_reanchoring() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0u128)); }); let dest = Location { @@ -760,11 +749,9 @@ fn receive_relay_asset_with_trader() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 2500000000000u128, - 0 + assert_ok!(add_supported_asset( + source_location.clone(), + 2500000000000u128 )); }); @@ -822,11 +809,9 @@ fn send_para_a_asset_to_para_b_with_trader() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 2500000000000u128, - 0 + assert_ok!(add_supported_asset( + source_location.clone(), + 2500000000000u128 )); }); @@ -900,12 +885,7 @@ fn send_para_a_asset_to_para_b_with_trader_and_fee() { true )); // With these units per second, 80K weight convrets to 1 asset unit - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 12500000u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 12500000u128)); }); let dest = Location { @@ -975,11 +955,9 @@ fn error_when_not_paying_enough() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 2500000000000u128, - 0 + assert_ok!(add_supported_asset( + source_location.clone(), + 2500000000000u128 )); }); @@ -1024,12 +1002,7 @@ fn transact_through_derivative_multilocation() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 1u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 1u128)); // Root can set transact info assert_ok!(XcmTransactor::set_transact_info( @@ -1184,12 +1157,7 @@ fn transact_through_derivative_with_custom_fee_weight() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 1u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 1u128)); }); // Let's construct the call to know how much weight it is going to require @@ -1340,12 +1308,7 @@ fn transact_through_derivative_with_custom_fee_weight_refund() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 1u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 1u128)); }); // Let's construct the call to know how much weight it is going to require @@ -1495,12 +1458,7 @@ fn transact_through_sovereign() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 1u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 1u128)); // Root can set transact info assert_ok!(XcmTransactor::set_transact_info( @@ -1768,12 +1726,7 @@ fn transact_through_sovereign_with_custom_fee_weight() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 1u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 1u128)); }); let dest: Location = AccountKey20 { @@ -1922,12 +1875,7 @@ fn transact_through_sovereign_with_custom_fee_weight_refund() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 1u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 1u128)); }); let dest: Location = AccountKey20 { @@ -2076,12 +2024,7 @@ fn test_automatic_versioning_on_runtime_upgrade_with_relay() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0u128)); }); let response = Response::Version(2); @@ -2231,12 +2174,7 @@ fn test_automatic_versioning_on_runtime_upgrade_with_para_b() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0u128)); }); ParaA::execute_with(|| { @@ -2363,12 +2301,7 @@ fn receive_asset_with_no_sufficients_not_possible_if_non_existent_account() { 1u128, false )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0u128)); }); // Actually send relay asset to parachain @@ -2443,12 +2376,7 @@ fn receive_assets_with_sufficients_true_allows_non_funded_account_to_receive_ass 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0u128)); }); // Actually send relay asset to parachain @@ -2504,12 +2432,7 @@ fn evm_account_receiving_assets_should_handle_sufficients_ref_count() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0u128)); }); // Actually send relay asset to parachain @@ -2574,12 +2497,7 @@ fn empty_account_should_not_be_reset() { 1u128, false )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0u128)); }); // Send native token to evm_account @@ -2660,7 +2578,7 @@ fn test_statemine_like() { let statemine_asset_a_balances = Location::new( 1, [ - Parachain(4), + Parachain(1000), PalletInstance(5), xcm::latest::prelude::GeneralIndex(0u128), ], @@ -2685,12 +2603,7 @@ fn test_statemine_like() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - source_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(source_location.clone(), 0u128)); }); Statemine::execute_with(|| { @@ -2754,7 +2667,7 @@ fn test_statemine_like() { } #[test] -fn send_statemint_asset_from_para_a_to_statemine_with_relay_fee() { +fn send_statemine_asset_from_para_a_to_statemine_with_relay_fee() { MockNet::reset(); // Relay asset @@ -2770,7 +2683,11 @@ fn send_statemint_asset_from_para_a_to_statemine_with_relay_fee() { // Statemine asset let statemine_asset = Location::new( 1, - [Parachain(4u32), PalletInstance(5u8), GeneralIndex(10u128)], + [ + Parachain(1000u32), + PalletInstance(5u8), + GeneralIndex(10u128), + ], ); let statemine_location_asset = parachain::AssetType::Xcm( xcm_builder::WithLatestLocationConverter::convert(&statemine_asset).expect("convert to v3"), @@ -2799,12 +2716,7 @@ fn send_statemint_asset_from_para_a_to_statemine_with_relay_fee() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - relay_location, - 0u128, - 0 - )); + assert_ok!(add_supported_asset(relay_location.clone(), 0u128)); assert_ok!(AssetManager::register_foreign_asset( parachain::RuntimeOrigin::root(), @@ -2813,12 +2725,7 @@ fn send_statemint_asset_from_para_a_to_statemine_with_relay_fee() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - statemine_location_asset, - 0u128, - 1 - )); + assert_ok!(add_supported_asset(statemine_location_asset.clone(), 0u128)); }); let parachain_beneficiary_from_relay: Location = Junction::AccountKey20 { @@ -2872,7 +2779,7 @@ fn send_statemint_asset_from_para_a_to_statemine_with_relay_fee() { )); // Send statemine USDC asset to Alice in Parachain A - let parachain_beneficiary_from_statemint: Location = AccountKey20 { + let parachain_beneficiary_from_statemine: Location = AccountKey20 { network: None, key: PARAALICE, } @@ -2883,7 +2790,7 @@ fn send_statemint_asset_from_para_a_to_statemine_with_relay_fee() { statemine_like::RuntimeOrigin::signed(RELAYALICE), Box::new(Location::new(1, [Parachain(1)]).into()), Box::new( - VersionedLocation::V4(parachain_beneficiary_from_statemint) + VersionedLocation::V4(parachain_beneficiary_from_statemine) .clone() .into() ), @@ -2907,7 +2814,7 @@ fn send_statemint_asset_from_para_a_to_statemine_with_relay_fee() { let statemine_beneficiary = Location { parents: 1, interior: [ - Parachain(4), + Parachain(1000), AccountId32 { network: None, id: RELAYBOB.into(), @@ -2966,6 +2873,971 @@ fn send_statemint_asset_from_para_a_to_statemine_with_relay_fee() { }); } +#[test] +fn send_dot_from_moonbeam_to_statemine_via_xtokens_transfer() { + MockNet::reset(); + + // Relay asset + let relay_location = parachain::AssetType::Xcm(xcm::v3::Location::parent()); + let source_relay_id: parachain::AssetId = relay_location.clone().into(); + + let relay_asset_metadata = parachain::AssetMetadata { + name: b"RelayToken".to_vec(), + symbol: b"Relay".to_vec(), + decimals: 12, + }; + + let dest_para = Location::new(1, [Parachain(1)]); + + let sov = xcm_builder::SiblingParachainConvertsVia::< + polkadot_parachain::primitives::Sibling, + statemine_like::AccountId, + >::convert_location(&dest_para) + .unwrap(); + + ParaA::execute_with(|| { + assert_ok!(AssetManager::register_foreign_asset( + parachain::RuntimeOrigin::root(), + relay_location.clone(), + relay_asset_metadata, + 1u128, + true + )); + XcmWeightTrader::set_asset_price(Location::parent(), 0u128); + }); + + let parachain_beneficiary_absolute: Location = Junction::AccountKey20 { + network: None, + key: PARAALICE, + } + .into(); + + let statemine_beneficiary_absolute: Location = Junction::AccountId32 { + network: None, + id: RELAYALICE.into(), + } + .into(); + + // First we send relay chain asset to Alice in AssetHub (via teleport) + Relay::execute_with(|| { + assert_ok!(RelayChainPalletXcm::limited_teleport_assets( + relay_chain::RuntimeOrigin::signed(RELAYALICE), + Box::new(Parachain(1000).into()), + Box::new( + VersionedLocation::V4(statemine_beneficiary_absolute) + .clone() + .into() + ), + Box::new(([], 200).into()), + 0, + WeightLimit::Unlimited + )); + }); + + // Send DOTs from AssetHub to ParaA (Moonbeam) + Statemine::execute_with(|| { + // Check Alice received 200 tokens on AssetHub + assert_eq!( + StatemineBalances::free_balance(RELAYALICE), + INITIAL_BALANCE + 200 + ); + + assert_ok!(StatemineBalances::transfer_allow_death( + statemine_like::RuntimeOrigin::signed(RELAYALICE), + sov, + 110000000000000 + )); + + // Now send those tokens to ParaA + assert_ok!(StatemineChainPalletXcm::limited_reserve_transfer_assets( + statemine_like::RuntimeOrigin::signed(RELAYALICE), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute.clone()) + .clone() + .into() + ), + Box::new((Location::parent(), 200).into()), + 0, + WeightLimit::Unlimited + )); + }); + + ParaA::execute_with(|| { + // Alice should have received the DOTs + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 200); + }); + + let dest = Location::new( + 1, + [ + Parachain(1000), + AccountId32 { + network: None, + id: RELAYBOB.into(), + }, + ], + ); + + // Finally we test that we are able to send back the DOTs to AssetHub from the ParaA + ParaA::execute_with(|| { + assert_ok!(XTokens::transfer( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + parachain::CurrencyId::ForeignAsset(source_relay_id), + 100, + Box::new(VersionedLocation::V4(dest)), + WeightLimit::Limited(Weight::from_parts(40000u64, DEFAULT_PROOF_SIZE)) + )); + + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 100); + }); + + Statemine::execute_with(|| { + // Check that Bob received the tokens back in AssetHub + assert_eq!( + StatemineBalances::free_balance(RELAYBOB), + INITIAL_BALANCE + 100 + ); + }); + + // Send back tokens from AH to ParaA from Bob's account + Statemine::execute_with(|| { + // Now send those tokens to ParaA + assert_ok!(StatemineChainPalletXcm::limited_reserve_transfer_assets( + statemine_like::RuntimeOrigin::signed(RELAYBOB), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute) + .clone() + .into() + ), + Box::new((Location::parent(), 100).into()), + 0, + WeightLimit::Unlimited + )); + + // 100 DOTs were deducted from Bob's account + assert_eq!(StatemineBalances::free_balance(RELAYBOB), INITIAL_BALANCE); + }); + + ParaA::execute_with(|| { + // Alice should have received 100 DOTs + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 200); + }); +} + +#[test] +fn send_dot_from_moonbeam_to_statemine_via_xtokens_transfer_with_fee() { + MockNet::reset(); + + // Relay asset + let relay_location = parachain::AssetType::Xcm(xcm::v3::Location::parent()); + let source_relay_id: parachain::AssetId = relay_location.clone().into(); + + let relay_asset_metadata = parachain::AssetMetadata { + name: b"RelayToken".to_vec(), + symbol: b"Relay".to_vec(), + decimals: 12, + }; + + let dest_para = Location::new(1, [Parachain(1)]); + + let sov = xcm_builder::SiblingParachainConvertsVia::< + polkadot_parachain::primitives::Sibling, + statemine_like::AccountId, + >::convert_location(&dest_para) + .unwrap(); + + ParaA::execute_with(|| { + assert_ok!(AssetManager::register_foreign_asset( + parachain::RuntimeOrigin::root(), + relay_location.clone(), + relay_asset_metadata, + 1u128, + true + )); + XcmWeightTrader::set_asset_price(Location::parent(), 0u128); + }); + + let parachain_beneficiary_absolute: Location = Junction::AccountKey20 { + network: None, + key: PARAALICE, + } + .into(); + + let statemine_beneficiary_absolute: Location = Junction::AccountId32 { + network: None, + id: RELAYALICE.into(), + } + .into(); + + // First we send relay chain asset to Alice in AssetHub (via teleport) + Relay::execute_with(|| { + assert_ok!(RelayChainPalletXcm::limited_teleport_assets( + relay_chain::RuntimeOrigin::signed(RELAYALICE), + Box::new(Parachain(1000).into()), + Box::new( + VersionedLocation::V4(statemine_beneficiary_absolute) + .clone() + .into() + ), + Box::new(([], 200).into()), + 0, + WeightLimit::Unlimited + )); + }); + + // Send DOTs from AssetHub to ParaA (Moonbeam) + Statemine::execute_with(|| { + // Check Alice received 200 tokens on AssetHub + assert_eq!( + StatemineBalances::free_balance(RELAYALICE), + INITIAL_BALANCE + 200 + ); + + assert_ok!(StatemineBalances::transfer_allow_death( + statemine_like::RuntimeOrigin::signed(RELAYALICE), + sov, + 110000000000000 + )); + + // Now send those tokens to ParaA + assert_ok!(StatemineChainPalletXcm::limited_reserve_transfer_assets( + statemine_like::RuntimeOrigin::signed(RELAYALICE), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute.clone()) + .clone() + .into() + ), + Box::new((Location::parent(), 200).into()), + 0, + WeightLimit::Unlimited + )); + }); + + ParaA::execute_with(|| { + // Alice should have received the DOTs + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 200); + }); + + let dest = Location::new( + 1, + [ + Parachain(1000), + AccountId32 { + network: None, + id: RELAYBOB.into(), + }, + ], + ); + + // Finally we test that we are able to send back the DOTs to AssetHub from the ParaA + ParaA::execute_with(|| { + assert_ok!(XTokens::transfer_with_fee( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + parachain::CurrencyId::ForeignAsset(source_relay_id), + 100, + 10, + Box::new(VersionedLocation::V4(dest)), + WeightLimit::Limited(Weight::from_parts(40000u64, DEFAULT_PROOF_SIZE)) + )); + + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 90); + }); + + Statemine::execute_with(|| { + // Free execution: check that Bob received the tokens back in AssetHub + assert_eq!( + StatemineBalances::free_balance(RELAYBOB), + INITIAL_BALANCE + 110 + ); + }); + + // Send back tokens from AH to ParaA from Bob's account + Statemine::execute_with(|| { + // Now send those tokens to ParaA + assert_ok!(StatemineChainPalletXcm::limited_reserve_transfer_assets( + statemine_like::RuntimeOrigin::signed(RELAYBOB), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute) + .clone() + .into() + ), + Box::new((Location::parent(), 100).into()), + 0, + WeightLimit::Unlimited + )); + + // 100 DOTs were deducted from Bob's account + assert_eq!( + StatemineBalances::free_balance(RELAYBOB), + INITIAL_BALANCE + 10 + ); + }); + + ParaA::execute_with(|| { + // Alice should have received 100 DOTs + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 190); + }); +} + +#[test] +fn send_dot_from_moonbeam_to_statemine_via_xtokens_transfer_multiasset() { + MockNet::reset(); + + // Relay asset + let relay_location = parachain::AssetType::Xcm(xcm::v3::Location::parent()); + let source_relay_id: parachain::AssetId = relay_location.clone().into(); + + let relay_asset_metadata = parachain::AssetMetadata { + name: b"RelayToken".to_vec(), + symbol: b"Relay".to_vec(), + decimals: 12, + }; + + let dest_para = Location::new(1, [Parachain(1)]); + + let sov = xcm_builder::SiblingParachainConvertsVia::< + polkadot_parachain::primitives::Sibling, + statemine_like::AccountId, + >::convert_location(&dest_para) + .unwrap(); + + ParaA::execute_with(|| { + assert_ok!(AssetManager::register_foreign_asset( + parachain::RuntimeOrigin::root(), + relay_location.clone(), + relay_asset_metadata, + 1u128, + true + )); + XcmWeightTrader::set_asset_price(Location::parent(), 0u128); + }); + + let parachain_beneficiary_absolute: Location = Junction::AccountKey20 { + network: None, + key: PARAALICE, + } + .into(); + + let statemine_beneficiary_absolute: Location = Junction::AccountId32 { + network: None, + id: RELAYALICE.into(), + } + .into(); + + // First we send relay chain asset to Alice in AssetHub (via teleport) + Relay::execute_with(|| { + assert_ok!(RelayChainPalletXcm::limited_teleport_assets( + relay_chain::RuntimeOrigin::signed(RELAYALICE), + Box::new(Parachain(1000).into()), + Box::new( + VersionedLocation::V4(statemine_beneficiary_absolute) + .clone() + .into() + ), + Box::new(([], 200).into()), + 0, + WeightLimit::Unlimited + )); + }); + + // Send DOTs from AssetHub to ParaA (Moonbeam) + Statemine::execute_with(|| { + // Check Alice received 200 tokens on AssetHub + assert_eq!( + StatemineBalances::free_balance(RELAYALICE), + INITIAL_BALANCE + 200 + ); + + assert_ok!(StatemineBalances::transfer_allow_death( + statemine_like::RuntimeOrigin::signed(RELAYALICE), + sov, + 110000000000000 + )); + + // Now send those tokens to ParaA + assert_ok!(StatemineChainPalletXcm::limited_reserve_transfer_assets( + statemine_like::RuntimeOrigin::signed(RELAYALICE), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute.clone()) + .clone() + .into() + ), + Box::new((Location::parent(), 200).into()), + 0, + WeightLimit::Unlimited + )); + }); + + ParaA::execute_with(|| { + // Alice should have received the DOTs + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 200); + }); + + let dest = Location::new( + 1, + [ + Parachain(1000), + AccountId32 { + network: None, + id: RELAYBOB.into(), + }, + ], + ); + + // Finally we test that we are able to send back the DOTs to AssetHub from the ParaA + ParaA::execute_with(|| { + assert_ok!(XTokens::transfer_multiasset( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + Box::new((Location::parent(), 100).into()), + Box::new(VersionedLocation::V4(dest)), + WeightLimit::Limited(Weight::from_parts(40000u64, DEFAULT_PROOF_SIZE)) + )); + + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 100); + }); + + Statemine::execute_with(|| { + // Check that Bob received the tokens back in AssetHub + assert_eq!( + StatemineBalances::free_balance(RELAYBOB), + INITIAL_BALANCE + 100 + ); + }); + + // Send back tokens from AH to ParaA from Bob's account + Statemine::execute_with(|| { + // Now send those tokens to ParaA + assert_ok!(StatemineChainPalletXcm::limited_reserve_transfer_assets( + statemine_like::RuntimeOrigin::signed(RELAYBOB), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute) + .clone() + .into() + ), + Box::new((Location::parent(), 100).into()), + 0, + WeightLimit::Unlimited + )); + + // 100 DOTs were deducted from Bob's account + assert_eq!(StatemineBalances::free_balance(RELAYBOB), INITIAL_BALANCE); + }); + + ParaA::execute_with(|| { + // Alice should have received 100 DOTs + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 200); + }); +} + +#[test] +fn send_dot_from_moonbeam_to_statemine_via_xtokens_transfer_multicurrencies() { + MockNet::reset(); + + // Relay asset + let relay_location = parachain::AssetType::Xcm(xcm::v3::Location::parent()); + let source_relay_id: parachain::AssetId = relay_location.clone().into(); + + let relay_asset_metadata = parachain::AssetMetadata { + name: b"RelayToken".to_vec(), + symbol: b"Relay".to_vec(), + decimals: 12, + }; + + // Statemine asset + let statemine_asset = Location::new( + 1, + [ + Parachain(1000u32), + PalletInstance(5u8), + GeneralIndex(10u128), + ], + ); + let statemine_location_asset = parachain::AssetType::Xcm( + xcm_builder::WithLatestLocationConverter::convert(&statemine_asset).expect("convert to v3"), + ); + let source_statemine_asset_id: parachain::AssetId = statemine_location_asset.clone().into(); + + let asset_metadata_statemine_asset = parachain::AssetMetadata { + name: b"USDC".to_vec(), + symbol: b"USDC".to_vec(), + decimals: 12, + }; + + let dest_para = Location::new(1, [Parachain(1)]); + + let sov = xcm_builder::SiblingParachainConvertsVia::< + polkadot_parachain::primitives::Sibling, + statemine_like::AccountId, + >::convert_location(&dest_para) + .unwrap(); + + ParaA::execute_with(|| { + assert_ok!(AssetManager::register_foreign_asset( + parachain::RuntimeOrigin::root(), + relay_location.clone(), + relay_asset_metadata, + 1u128, + true + )); + XcmWeightTrader::set_asset_price(Location::parent(), 0u128); + + assert_ok!(AssetManager::register_foreign_asset( + parachain::RuntimeOrigin::root(), + statemine_location_asset.clone(), + asset_metadata_statemine_asset, + 1u128, + true + )); + XcmWeightTrader::set_asset_price(statemine_asset.clone(), 0u128); + }); + + let parachain_beneficiary_absolute: Location = Junction::AccountKey20 { + network: None, + key: PARAALICE, + } + .into(); + + let statemine_beneficiary_absolute: Location = Junction::AccountId32 { + network: None, + id: RELAYALICE.into(), + } + .into(); + + // First we send relay chain asset to Alice in AssetHub (via teleport) + Relay::execute_with(|| { + assert_ok!(RelayChainPalletXcm::limited_teleport_assets( + relay_chain::RuntimeOrigin::signed(RELAYALICE), + Box::new(Parachain(1000).into()), + Box::new( + VersionedLocation::V4(statemine_beneficiary_absolute) + .clone() + .into() + ), + Box::new(([], 200).into()), + 0, + WeightLimit::Unlimited + )); + }); + + // Send DOTs and USDC from AssetHub to ParaA (Moonbeam) + Statemine::execute_with(|| { + // Check Alice received 200 tokens on AssetHub + assert_eq!( + StatemineBalances::free_balance(RELAYALICE), + INITIAL_BALANCE + 200 + ); + + assert_ok!(StatemineBalances::transfer_allow_death( + statemine_like::RuntimeOrigin::signed(RELAYALICE), + sov, + 110000000000000 + )); + + statemine_like::PrefixChanger::set_prefix( + PalletInstance(::index() as u8).into(), + ); + + assert_ok!(StatemineAssets::create( + statemine_like::RuntimeOrigin::signed(RELAYALICE), + 10, + RELAYALICE, + 1 + )); + + assert_ok!(StatemineAssets::mint( + statemine_like::RuntimeOrigin::signed(RELAYALICE), + 10, + RELAYALICE, + 300000000000000 + )); + + // Now send relay tokens to ParaA + assert_ok!(StatemineChainPalletXcm::limited_reserve_transfer_assets( + statemine_like::RuntimeOrigin::signed(RELAYALICE), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute.clone()) + .clone() + .into() + ), + Box::new((Location::parent(), 200).into()), + 0, + WeightLimit::Unlimited + )); + + // Send USDC + assert_ok!(StatemineChainPalletXcm::limited_reserve_transfer_assets( + statemine_like::RuntimeOrigin::signed(RELAYALICE), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute.clone()) + .clone() + .into() + ), + Box::new( + ( + [ + xcm::latest::prelude::PalletInstance( + ::index() as u8 + ), + GeneralIndex(10), + ], + 125 + ) + .into() + ), + 0, + WeightLimit::Unlimited + )); + }); + + ParaA::execute_with(|| { + // Alice should have received the DOTs + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 200); + + // Alice has received 125 USDC + assert_eq!( + Assets::balance(source_statemine_asset_id, &PARAALICE.into()), + 125 + ); + }); + + let dest = Location::new( + 1, + [ + Parachain(1000), + AccountId32 { + network: None, + id: RELAYBOB.into(), + }, + ], + ); + + // Finally we test that we are able to send back the DOTs to AssetHub from the ParaA + ParaA::execute_with(|| { + assert_ok!(XTokens::transfer_multicurrencies( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + vec![ + ( + parachain::CurrencyId::ForeignAsset(source_statemine_asset_id), + 100 + ), + (parachain::CurrencyId::ForeignAsset(source_relay_id), 100) + ], + 1, + Box::new(VersionedLocation::V4(dest)), + WeightLimit::Limited(Weight::from_parts(80_000_000u64, 100_000u64)) + )); + + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 100); + }); + + Statemine::execute_with(|| { + // Check that Bob received relay tokens back in AssetHub + // (100 - MinXcmFee) + assert_eq!( + StatemineBalances::free_balance(RELAYBOB), + INITIAL_BALANCE + 50 + ); + + // Check that BOB received 100 USDC on AssetHub + assert_eq!(StatemineAssets::account_balances(RELAYBOB), vec![(10, 100)]); + }); + + // Send back tokens from AH to ParaA from Bob's account + Statemine::execute_with(|| { + let bob_previous_balance = StatemineBalances::free_balance(RELAYBOB); + + // Now send those tokens to ParaA + assert_ok!(StatemineChainPalletXcm::limited_reserve_transfer_assets( + statemine_like::RuntimeOrigin::signed(RELAYBOB), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute) + .clone() + .into() + ), + Box::new((Location::parent(), 100).into()), + 0, + WeightLimit::Unlimited + )); + + // 100 DOTs were deducted from Bob's account + assert_eq!( + StatemineBalances::free_balance(RELAYBOB), + bob_previous_balance - 100 + ); + }); + + ParaA::execute_with(|| { + // Alice should have received 100 DOTs + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 200); + }); +} + +#[test] +fn send_dot_from_moonbeam_to_statemine_via_xtokens_transfer_multiassets() { + MockNet::reset(); + + // Relay asset + let relay_location = parachain::AssetType::Xcm(xcm::v3::Location::parent()); + let source_relay_id: parachain::AssetId = relay_location.clone().into(); + + let relay_asset_metadata = parachain::AssetMetadata { + name: b"RelayToken".to_vec(), + symbol: b"Relay".to_vec(), + decimals: 12, + }; + + // Statemine asset + let statemine_asset = Location::new( + 1, + [ + Parachain(1000u32), + PalletInstance(5u8), + GeneralIndex(10u128), + ], + ); + let statemine_location_asset = parachain::AssetType::Xcm( + xcm_builder::WithLatestLocationConverter::convert(&statemine_asset).expect("convert to v3"), + ); + let source_statemine_asset_id: parachain::AssetId = statemine_location_asset.clone().into(); + + let asset_metadata_statemine_asset = parachain::AssetMetadata { + name: b"USDC".to_vec(), + symbol: b"USDC".to_vec(), + decimals: 12, + }; + + let dest_para = Location::new(1, [Parachain(1)]); + + let sov = xcm_builder::SiblingParachainConvertsVia::< + polkadot_parachain::primitives::Sibling, + statemine_like::AccountId, + >::convert_location(&dest_para) + .unwrap(); + + ParaA::execute_with(|| { + assert_ok!(AssetManager::register_foreign_asset( + parachain::RuntimeOrigin::root(), + relay_location.clone(), + relay_asset_metadata, + 1u128, + true + )); + XcmWeightTrader::set_asset_price(Location::parent(), 0u128); + + assert_ok!(AssetManager::register_foreign_asset( + parachain::RuntimeOrigin::root(), + statemine_location_asset.clone(), + asset_metadata_statemine_asset, + 1u128, + true + )); + XcmWeightTrader::set_asset_price(statemine_asset.clone(), 0u128); + }); + + let parachain_beneficiary_absolute: Location = Junction::AccountKey20 { + network: None, + key: PARAALICE, + } + .into(); + + let statemine_beneficiary_absolute: Location = Junction::AccountId32 { + network: None, + id: RELAYALICE.into(), + } + .into(); + + // First we send relay chain asset to Alice in AssetHub (via teleport) + Relay::execute_with(|| { + assert_ok!(RelayChainPalletXcm::limited_teleport_assets( + relay_chain::RuntimeOrigin::signed(RELAYALICE), + Box::new(Parachain(1000).into()), + Box::new( + VersionedLocation::V4(statemine_beneficiary_absolute) + .clone() + .into() + ), + Box::new(([], 200).into()), + 0, + WeightLimit::Unlimited + )); + }); + + // Send DOTs and USDC from AssetHub to ParaA (Moonbeam) + Statemine::execute_with(|| { + // Check Alice received 200 tokens on AssetHub + assert_eq!( + StatemineBalances::free_balance(RELAYALICE), + INITIAL_BALANCE + 200 + ); + + assert_ok!(StatemineBalances::transfer_allow_death( + statemine_like::RuntimeOrigin::signed(RELAYALICE), + sov, + 110000000000000 + )); + + statemine_like::PrefixChanger::set_prefix( + PalletInstance(::index() as u8).into(), + ); + + assert_ok!(StatemineAssets::create( + statemine_like::RuntimeOrigin::signed(RELAYALICE), + 10, + RELAYALICE, + 1 + )); + + assert_ok!(StatemineAssets::mint( + statemine_like::RuntimeOrigin::signed(RELAYALICE), + 10, + RELAYALICE, + 300000000000000 + )); + + // Now send relay tokens to ParaA + assert_ok!(StatemineChainPalletXcm::limited_reserve_transfer_assets( + statemine_like::RuntimeOrigin::signed(RELAYALICE), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute.clone()) + .clone() + .into() + ), + Box::new((Location::parent(), 200).into()), + 0, + WeightLimit::Unlimited + )); + + // Send USDC + assert_ok!(StatemineChainPalletXcm::limited_reserve_transfer_assets( + statemine_like::RuntimeOrigin::signed(RELAYALICE), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute.clone()) + .clone() + .into() + ), + Box::new( + ( + [ + xcm::latest::prelude::PalletInstance( + ::index() as u8 + ), + GeneralIndex(10), + ], + 125 + ) + .into() + ), + 0, + WeightLimit::Unlimited + )); + }); + + ParaA::execute_with(|| { + // Alice should have received the DOTs + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 200); + + // Alice has received 125 USDC + assert_eq!( + Assets::balance(source_statemine_asset_id, &PARAALICE.into()), + 125 + ); + }); + + let dest = Location::new( + 1, + [ + Parachain(1000), + AccountId32 { + network: None, + id: RELAYBOB.into(), + }, + ], + ); + + let statemine_asset_to_send = XcmAsset { + id: XcmAssetId(statemine_asset), + fun: Fungible(100), + }; + + let relay_asset_to_send = XcmAsset { + id: XcmAssetId(Location::parent()), + fun: Fungible(100), + }; + + let assets_to_send: XcmAssets = + XcmAssets::from(vec![statemine_asset_to_send, relay_asset_to_send.clone()]); + + // For some reason the order of the assets is inverted when creating the array above. + // We need to use relay asset for fees, so we pick index 0. + assert_eq!(assets_to_send.get(0).unwrap(), &relay_asset_to_send); + + // Finally we test that we are able to send back the DOTs to AssetHub from the ParaA + ParaA::execute_with(|| { + assert_ok!(XTokens::transfer_multiassets( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + Box::new(assets_to_send.into()), + 0, + Box::new(VersionedLocation::V4(dest)), + WeightLimit::Limited(Weight::from_parts(80_000_000u64, 100_000u64)) + )); + + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 100); + }); + + Statemine::execute_with(|| { + // Check that Bob received relay tokens back in AssetHub + // (100 - MinXcmFee) + assert_eq!( + StatemineBalances::free_balance(RELAYBOB), + INITIAL_BALANCE + 50 + ); + + // Check that BOB received 100 USDC on AssetHub + assert_eq!(StatemineAssets::account_balances(RELAYBOB), vec![(10, 100)]); + }); + + // Send back tokens from AH to ParaA from Bob's account + Statemine::execute_with(|| { + let bob_previous_balance = StatemineBalances::free_balance(RELAYBOB); + + // Now send those tokens to ParaA + assert_ok!(StatemineChainPalletXcm::limited_reserve_transfer_assets( + statemine_like::RuntimeOrigin::signed(RELAYBOB), + Box::new(Location::new(1, [Parachain(1)]).into()), + Box::new( + VersionedLocation::V4(parachain_beneficiary_absolute) + .clone() + .into() + ), + Box::new((Location::parent(), 100).into()), + 0, + WeightLimit::Unlimited + )); + + // 100 DOTs were deducted from Bob's account + assert_eq!( + StatemineBalances::free_balance(RELAYBOB), + bob_previous_balance - 100 + ); + }); + + ParaA::execute_with(|| { + // Alice should have received 100 DOTs + assert_eq!(Assets::balance(source_relay_id, &PARAALICE.into()), 200); + }); +} + #[test] fn transact_through_signed_multilocation() { MockNet::reset(); @@ -3285,7 +4157,7 @@ fn transact_through_signed_multilocation_para_to_para() { assert_ok!(XcmTransactor::set_fee_per_second( parachain::RuntimeOrigin::root(), Box::new(xcm::VersionedLocation::V4(para_b_balances.clone())), - parachain::ParaTokensPerSecond::get().1 as u128, + parachain::ParaTokensPerSecond::get(), )); ancestry = parachain::UniversalLocation::get().into(); }); @@ -3383,7 +4255,7 @@ fn transact_through_signed_multilocation_para_to_para_refund() { assert_ok!(XcmTransactor::set_fee_per_second( parachain::RuntimeOrigin::root(), Box::new(xcm::VersionedLocation::V4(para_b_balances.clone())), - parachain::ParaTokensPerSecond::get().1 as u128, + parachain::ParaTokensPerSecond::get(), )); ancestry = parachain::UniversalLocation::get().into(); }); @@ -3495,7 +4367,7 @@ fn transact_through_signed_multilocation_para_to_para_ethereum() { assert_ok!(XcmTransactor::set_fee_per_second( parachain::RuntimeOrigin::root(), Box::new(xcm::VersionedLocation::V4(para_b_balances.clone())), - parachain::ParaTokensPerSecond::get().1 as u128, + parachain::ParaTokensPerSecond::get(), )); ancestry = parachain::UniversalLocation::get().into(); }); @@ -3622,7 +4494,7 @@ fn transact_through_signed_multilocation_para_to_para_ethereum_no_proxy_fails() assert_ok!(XcmTransactor::set_fee_per_second( parachain::RuntimeOrigin::root(), Box::new(xcm::VersionedLocation::V4(para_b_balances.clone())), - parachain::ParaTokensPerSecond::get().1 as u128, + parachain::ParaTokensPerSecond::get(), )); ancestry = parachain::UniversalLocation::get().into(); }); @@ -3745,7 +4617,7 @@ fn transact_through_signed_multilocation_para_to_para_ethereum_proxy_succeeds() assert_ok!(XcmTransactor::set_fee_per_second( parachain::RuntimeOrigin::root(), Box::new(xcm::VersionedLocation::V4(para_b_balances.clone())), - parachain::ParaTokensPerSecond::get().1 as u128, + parachain::ParaTokensPerSecond::get(), )); ancestry = parachain::UniversalLocation::get().into(); }); diff --git a/test/helpers/assets.ts b/test/helpers/assets.ts index 942d885f4e..0c981a6592 100644 --- a/test/helpers/assets.ts +++ b/test/helpers/assets.ts @@ -10,7 +10,8 @@ import type { PalletEvmCodeMetadata, } from "@polkadot/types/lookup"; import type { AccountId20 } from "@polkadot/types/interfaces/runtime"; -import { encodeFunctionData, keccak256, parseAbi } from "viem"; +import { encodeFunctionData, parseAbi, keccak256 } from "viem"; +import { ApiPromise, WsProvider } from "@polkadot/api"; export const EVM_FOREIGN_ASSETS_PALLET_ACCOUNT = "0x6d6f646c666f7267617373740000000000000000"; export const ARBITRARY_ASSET_ID = 42259045809535163221576417993425387648n; @@ -50,6 +51,200 @@ export function assetContractAddress(assetId: bigint | string): `0x${string}` { return `0xffffffff${BigInt(assetId).toString(16)}`; } +const patchLocationV4recursively = (value: any) => { + // e.g. Convert this: { X1: { Parachain: 1000 } } to { X1: [ { Parachain: 1000 } ] } + if (value && typeof value == "object") { + if (Array.isArray(value)) { + return value.map(patchLocationV4recursively); + } + for (const k of Object.keys(value)) { + if (k === "Concrete" || k === "Abstract") { + return patchLocationV4recursively(value[k]); + } + if (k.match(/^X\d$/g) && !Array.isArray(value[k])) { + value[k] = Object.entries(value[k]).map(([k, v]) => ({ + [k]: patchLocationV4recursively(v), + })); + } else { + value[k] = patchLocationV4recursively(value[k]); + } + } + } + return value; +}; + +const runtimeApi = { + runtime: { + XcmPaymentApi: [ + { + methods: { + query_acceptable_payment_assets: { + description: "The API to query acceptable payment assets", + params: [ + { + name: "version", + type: "u32", + }, + ], + type: "Result, XcmPaymentApiError>", + }, + query_weight_to_asset_fee: { + description: "", + params: [ + { + name: "weight", + type: "WeightV2", + }, + { + name: "asset", + type: "XcmVersionedAssetId", + }, + ], + type: "Result", + }, + query_xcm_weight: { + description: "", + params: [ + { + name: "message", + type: "XcmVersionedXcm", + }, + ], + type: "Result", + }, + query_delivery_fees: { + description: "", + params: [ + { + name: "destination", + type: "XcmVersionedLocation", + }, + { + name: "message", + type: "XcmVersionedXcm", + }, + ], + type: "Result", + }, + }, + version: 1, + }, + ], + XcmWeightTrader: [ + { + methods: { + add_asset: { + description: "Add an asset to the supported assets", + params: [ + { + name: "asset", + type: "XcmVersionedAssetId", + }, + { + name: "relative_price", + type: "u128", + }, + ], + type: "Result<(), XcmPaymentApiError>", + }, + }, + version: 1, + }, + ], + }, + types: { + XcmPaymentApiError: { + _enum: { + Unimplemented: "Null", + VersionedConversionFailed: "Null", + WeightNotComputable: "Null", + UnhandledXcmVersion: "Null", + AssetNotFound: "Null", + }, + }, + }, +}; + +export async function calculateRelativePrice( + context: any, + unitsPerSecond: number +): Promise { + if (unitsPerSecond === 0) { + return 0n; + } + + const WEIGHT_REF_TIME_PER_SECOND = 1_000_000_000_000; + const weight = { + refTime: WEIGHT_REF_TIME_PER_SECOND, + proofSize: 0, + }; + + const nativeAmountPerSecond = await context + .polkadotJs() + .tx.transactionPaymentApi.queryWeightToFee(weight); + + const relativePriceDecimals = new BN(18); + const relativePrice = nativeAmountPerSecond + .mul(new BN(10).pow(relativePriceDecimals)) + .div(new BN(unitsPerSecond)); + + return relativePrice; +} + +function getSupportedAssedStorageKey(asset: any, context: any) { + const assetV4 = patchLocationV4recursively(asset); + + const module = xxhashAsU8a(new TextEncoder().encode("XcmWeightTrader"), 128); + const method = xxhashAsU8a(new TextEncoder().encode("SupportedAssets"), 128); + + const assetLocationU8a = context.polkadotJs().createType("StagingXcmV4Location", assetV4).toU8a(); + + const blake2concatStagingXcmV4Location = new Uint8Array([ + ...blake2AsU8a(assetLocationU8a, 128), + ...assetLocationU8a, + ]); + + return new Uint8Array([...module, ...method, ...blake2concatStagingXcmV4Location]); +} + +export async function addAssetToWeightTrader(asset: any, relativePrice: number, context: any) { + const assetV4 = patchLocationV4recursively(asset.Xcm); + + if (relativePrice == 0) { + const addAssetWithPlaceholderPrice = context + .polkadotJs() + .tx.sudo.sudo(context.polkadotJs().tx.xcmWeightTrader.addAsset(assetV4, 1n)); + const overallAssetKey = getSupportedAssedStorageKey(assetV4, context); + + const overrideAssetPrice = context.polkadotJs().tx.sudo.sudo( + context.polkadotJs().tx.system.setStorage([ + [ + u8aToHex(overallAssetKey), + "0x0100000000000000000000000000000000", // (enabled bool, 0 u128) + ], + ]) + ); + const batch = context + .polkadotJs() + .tx.utility.batch([addAssetWithPlaceholderPrice, overrideAssetPrice]); + + await context.createBlock(batch, { + expectEvents: [context.polkadotJs().events.xcmWeightTrader.SupportedAssetAdded], + allowFailures: false, + }); + } else { + await context.createBlock( + context + .polkadotJs() + .tx.sudo.sudo(context.polkadotJs().tx.xcmWeightTrader.addAsset(assetV4, relativePrice)), + { + expectEvents: [context.polkadotJs().events.xcmWeightTrader.SupportedAssetAdded], + allowFailures: false, + } + ); + } +} + // This registers an old foreign asset via the asset-manager pallet. // DEPRECATED: Please don't use for new tests export async function registerOldForeignAsset( @@ -59,7 +254,6 @@ export async function registerOldForeignAsset( unitsPerSecond?: number, numAssetsWeightHint?: number ) { - unitsPerSecond = unitsPerSecond != null ? unitsPerSecond : 0; const { result } = await context.createBlock( context .polkadotJs() @@ -67,26 +261,77 @@ export async function registerOldForeignAsset( context.polkadotJs().tx.assetManager.registerForeignAsset(asset, metadata, new BN(1), true) ) ); - // Look for assetId in events - const registeredAssetId = result!.events - .find(({ event: { section } }) => section.toString() === "assetManager")! - .event.data[0].toHex() - .replace(/,/g, ""); - // setAssetUnitsPerSecond + const polkadotJs = await ApiPromise.create({ + provider: new WsProvider(`ws://localhost:${process.env.MOONWALL_RPC_PORT}/`), + ...runtimeApi, + }); + + const WEIGHT_REF_TIME_PER_SECOND = 1_000_000_000_000; + const weight = { + refTime: WEIGHT_REF_TIME_PER_SECOND, + proofSize: 0, + }; + + const nativeAmountPerSecond = await context + .polkadotJs() + .call.transactionPaymentApi.queryWeightToFee(weight); + + const relativePriceDecimals = new BN(18); + const relativePrice = nativeAmountPerSecond + .mul(new BN(10).pow(relativePriceDecimals)) + .div(unitsPerSecond ? new BN(unitsPerSecond) : new BN(1)); + + const assetV4 = patchLocationV4recursively(asset.Xcm); const { result: result2 } = await context.createBlock( context .polkadotJs() - .tx.sudo.sudo( - context - .polkadotJs() - .tx.assetManager.setAssetUnitsPerSecond(asset, unitsPerSecond, numAssetsWeightHint!) - ), + .tx.sudo.sudo(context.polkadotJs().tx.xcmWeightTrader.addAsset(assetV4, relativePrice)), { - expectEvents: [context.polkadotJs().events.assetManager.UnitsPerSecondChanged], + expectEvents: [context.polkadotJs().events.xcmWeightTrader.SupportedAssetAdded], allowFailures: false, } ); + + // If no unitspersecond is provided, we add the asset to the supported assets + // and force-set the relative price to 0 + if (unitsPerSecond == null) { + const module = xxhashAsU8a(new TextEncoder().encode("XcmWeightTrader"), 128); + const method = xxhashAsU8a(new TextEncoder().encode("SupportedAssets"), 128); + + const assetLocationU8a = context + .polkadotJs() + .createType("StagingXcmV4Location", assetV4) + .toU8a(); + + const blake2concatStagingXcmV4Location = new Uint8Array([ + ...blake2AsU8a(assetLocationU8a, 128), + ...assetLocationU8a, + ]); + + const overallAssetKey = new Uint8Array([ + ...module, + ...method, + ...blake2concatStagingXcmV4Location, + ]); + + await context.createBlock( + context.polkadotJs().tx.sudo.sudo( + context.polkadotJs().tx.system.setStorage([ + [ + u8aToHex(overallAssetKey), + "0x0100000000000000000000000000000000", // (enabled bool, 0 u128) + ], + ]) + ) + ); + } + + const registeredAssetId = result!.events + .find(({ event: { section } }) => section.toString() === "assetManager")! + .event.data[0].toHex() + .replace(/,/g, ""); + // check asset in storage const registeredAsset = ( (await context.polkadotJs().query.assets.asset(registeredAssetId)) as any diff --git a/test/suites/dev/moonbase/test-maintenance/test-maintenance-filter2.ts b/test/suites/dev/moonbase/test-maintenance/test-maintenance-filter2.ts index bd633b956c..858846f45e 100644 --- a/test/suites/dev/moonbase/test-maintenance/test-maintenance-filter2.ts +++ b/test/suites/dev/moonbase/test-maintenance/test-maintenance-filter2.ts @@ -9,7 +9,11 @@ import { import { ALITH_ADDRESS, alith, baltathar } from "@moonwall/util"; import { u128 } from "@polkadot/types-codec"; import { PalletAssetsAssetAccount, PalletAssetsAssetDetails } from "@polkadot/types/lookup"; -import { RELAY_SOURCE_LOCATION, mockOldAssetBalance } from "../../../../helpers"; +import { + RELAY_SOURCE_LOCATION, + addAssetToWeightTrader, + mockOldAssetBalance, +} from "../../../../helpers"; const ARBITRARY_ASSET_ID = 42259045809535163221576417993425387648n; @@ -47,14 +51,8 @@ describeSuite({ baltathar.address ); - // setAssetUnitsPerSecond - await context.createBlock( - context - .polkadotJs() - .tx.sudo.sudo( - context.polkadotJs().tx.assetManager.setAssetUnitsPerSecond(RELAY_SOURCE_LOCATION, 0, 0) - ) - ); + // set relative price in xcmWeightTrader + await addAssetToWeightTrader(RELAY_SOURCE_LOCATION, 0, context); await execOpenTechCommitteeProposal( context, diff --git a/test/suites/dev/moonbase/test-maintenance/test-maintenance-filter3.ts b/test/suites/dev/moonbase/test-maintenance/test-maintenance-filter3.ts index edf0784781..a08fcf3b80 100644 --- a/test/suites/dev/moonbase/test-maintenance/test-maintenance-filter3.ts +++ b/test/suites/dev/moonbase/test-maintenance/test-maintenance-filter3.ts @@ -9,6 +9,7 @@ import { } from "@moonwall/cli"; import { ALITH_ADDRESS } from "@moonwall/util"; import { BN } from "@polkadot/util"; +import { addAssetToWeightTrader } from "../../../../helpers"; describeSuite({ id: "D012003", @@ -51,14 +52,8 @@ describeSuite({ assetId = events.event.data[0].toHex().replace(/,/g, ""); - // setAssetUnitsPerSecond - await context.createBlock( - context - .polkadotJs() - .tx.sudo.sudo( - context.polkadotJs().tx.assetManager.setAssetUnitsPerSecond(sourceLocation, 0, 0) - ) - ); + // set relative price in xcmWeightTrader + await addAssetToWeightTrader(sourceLocation, 0, context); }); beforeEach(async () => {