diff --git a/Cargo.lock b/Cargo.lock index a055895845..3d9585834e 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", @@ -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", @@ -7022,11 +7024,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", @@ -7355,6 +7359,7 @@ dependencies = [ "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-transactor", + "pallet-xcm-weight-trader", "parachains-common", "parity-scale-codec", "polkadot-core-primitives", @@ -10577,6 +10582,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 088d6e6319..2ea8e23544 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/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/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 8a9ff1de6b..66f0f92507 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 } @@ -108,6 +110,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 ea0620921b..ef94f52b87 100644 --- a/runtime/common/src/migrations.rs +++ b/runtime/common/src/migrations.rs @@ -48,11 +48,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: + pallet_xcm::Config + pallet_transaction_payment::Config + pallet_xcm_weight_trader::Config, Runtime::AccountId: Default, BlockNumberFor: Into, { @@ -192,6 +299,8 @@ where // completed in runtime 2900 // Box::new(remove_pallet_democracy), // Box::new(remove_collectives_addresses), + // 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 5e873b73b9..a99872c50e 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::*; @@ -1455,6 +1450,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, } } @@ -1535,6 +1531,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/xcm_mock/mod.rs b/runtime/moonbase/tests/xcm_mock/mod.rs index 6c23384729..4a9e567d99 100644 --- a/runtime/moonbase/tests/xcm_mock/mod.rs +++ b/runtime/moonbase/tests/xcm_mock/mod.rs @@ -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 0d7fd72050..7dbc8584aa 100644 --- a/runtime/moonbase/tests/xcm_mock/parachain.rs +++ b/runtime/moonbase/tests/xcm_mock/parachain.rs @@ -44,17 +44,17 @@ 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, Case, ConvertedConcreteId, EnsureXcmOrigin, - FixedRateOfFungible, FixedWeightBounds, FungibleAdapter as XcmCurrencyAdapter, - FungiblesAdapter, IsConcrete, NoChecking, ParentAsSuperuser, ParentIsPreset, - RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountKey20AsNative, SovereignSignedViaLocation, TakeWeightCredit, WithComputedOrigin, + AllowTopLevelPaidExecutionFrom, Case, ConvertedConcreteId, EnsureXcmOrigin, FixedWeightBounds, + FungibleAdapter as XcmCurrencyAdapter, FungiblesAdapter, IsConcrete, NoChecking, + ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, + SiblingParachainConvertsVia, SignedAccountKey20AsNative, SovereignSignedViaLocation, + TakeWeightCredit, WithComputedOrigin, }; use xcm_executor::{traits::JustTry, Config, XcmExecutor}; @@ -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! { @@ -332,10 +320,9 @@ parameter_types! { pub const MaxAssetsIntoHolding: u32 = 64; pub AssetHubLocation: Location = Location::new(1, [Parachain(1000)]); - pub const RelayLocation: Location = Location::parent(); pub RelayLocationFilter: AssetFilter = Wild(AllOf { fun: WildFungible, - id: xcm::prelude::AssetId(RelayLocation::get()), + id: xcm::prelude::AssetId(Location::parent()), }); pub RelayChainNativeAssetFromAssetHub: (AssetFilter, Location) = ( @@ -370,14 +357,7 @@ impl Config for XcmConfig { 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; @@ -847,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; } @@ -1096,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_tests.rs b/runtime/moonbase/tests/xcm_tests.rs index f894ce5329..e9234554ff 100644 --- a/runtime/moonbase/tests/xcm_tests.rs +++ b/runtime/moonbase/tests/xcm_tests.rs @@ -35,13 +35,47 @@ use xcm::latest::prelude::{ 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() { @@ -63,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 @@ -119,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 { @@ -211,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 @@ -228,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 @@ -318,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 @@ -390,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 @@ -407,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 @@ -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, 0)); }); // 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, 0)); }); let dest = Location { @@ -717,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 { @@ -779,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 { @@ -857,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 { @@ -897,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); }); @@ -932,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. @@ -981,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( @@ -1151,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 @@ -1307,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 @@ -1462,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( @@ -1735,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 { @@ -1889,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 { @@ -2043,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); @@ -2197,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(|| { @@ -2330,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 @@ -2410,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 @@ -2471,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 @@ -2541,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 @@ -2652,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(|| { @@ -2770,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(), @@ -2784,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 { @@ -2967,12 +2866,7 @@ fn send_dot_from_moonbeam_to_statemint_via_xtokens_transfer() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - relay_location, - 0u128, - 0 - )); + XcmWeightTrader::set_asset_price(Location::parent(), 0u128); }); let parachain_beneficiary_absolute: Location = Junction::AccountKey20 { @@ -3125,12 +3019,7 @@ fn send_dot_from_moonbeam_to_statemint_via_xtokens_transfer_with_fee() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - relay_location, - 0u128, - 0 - )); + XcmWeightTrader::set_asset_price(Location::parent(), 0u128); }); let parachain_beneficiary_absolute: Location = Junction::AccountKey20 { @@ -3287,12 +3176,7 @@ fn send_dot_from_moonbeam_to_statemint_via_xtokens_transfer_multiasset() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - relay_location, - 0u128, - 0 - )); + XcmWeightTrader::set_asset_price(Location::parent(), 0u128); }); let parachain_beneficiary_absolute: Location = Junction::AccountKey20 { @@ -3464,12 +3348,7 @@ fn send_dot_from_moonbeam_to_statemint_via_xtokens_transfer_multicurrencies() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - relay_location, - 0u128, - 0 - )); + XcmWeightTrader::set_asset_price(Location::parent(), 0u128); assert_ok!(AssetManager::register_foreign_asset( parachain::RuntimeOrigin::root(), @@ -3478,12 +3357,7 @@ fn send_dot_from_moonbeam_to_statemint_via_xtokens_transfer_multicurrencies() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - statemint_location_asset, - 0u128, - 1 - )); + XcmWeightTrader::set_asset_price(statemint_asset, 0u128); }); let parachain_beneficiary_absolute: Location = Junction::AccountKey20 { @@ -3720,12 +3594,7 @@ fn send_dot_from_moonbeam_to_statemint_via_xtokens_transfer_multiassets() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - relay_location, - 0u128, - 0 - )); + XcmWeightTrader::set_asset_price(Location::parent(), 0u128); assert_ok!(AssetManager::register_foreign_asset( parachain::RuntimeOrigin::root(), @@ -3734,12 +3603,7 @@ fn send_dot_from_moonbeam_to_statemint_via_xtokens_transfer_multiassets() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - statemint_location_asset, - 0u128, - 1 - )); + XcmWeightTrader::set_asset_price(statemint_asset.clone(), 0u128); }); let parachain_beneficiary_absolute: Location = Junction::AccountKey20 { @@ -4255,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(); }); @@ -4355,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(); }); @@ -4469,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(); }); @@ -4598,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(); }); @@ -4723,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 2702d931fe..d1290a630b 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; @@ -1453,6 +1448,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/xcm_mock/mod.rs b/runtime/moonbeam/tests/xcm_mock/mod.rs index 96ba4b9ec4..231196031e 100644 --- a/runtime/moonbeam/tests/xcm_mock/mod.rs +++ b/runtime/moonbeam/tests/xcm_mock/mod.rs @@ -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 5bffb0e38c..3f41bcad58 100644 --- a/runtime/moonbeam/tests/xcm_mock/parachain.rs +++ b/runtime/moonbeam/tests/xcm_mock/parachain.rs @@ -45,17 +45,17 @@ 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, Case, ConvertedConcreteId, EnsureXcmOrigin, - FixedRateOfFungible, FixedWeightBounds, FungibleAdapter as XcmCurrencyAdapter, - FungiblesAdapter, IsConcrete, NoChecking, ParentAsSuperuser, ParentIsPreset, - RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountKey20AsNative, SovereignSignedViaLocation, TakeWeightCredit, WithComputedOrigin, + AllowTopLevelPaidExecutionFrom, Case, ConvertedConcreteId, EnsureXcmOrigin, FixedWeightBounds, + FungibleAdapter as XcmCurrencyAdapter, FungiblesAdapter, IsConcrete, NoChecking, + ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, + SiblingParachainConvertsVia, SignedAccountKey20AsNative, SovereignSignedViaLocation, + TakeWeightCredit, WithComputedOrigin, }; use xcm_executor::{traits::JustTry, Config, XcmExecutor}; @@ -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! { @@ -323,10 +312,9 @@ parameter_types! { pub const MaxAssetsIntoHolding: u32 = 64; pub AssetHubLocation: Location = Location::new(1, [Parachain(1000)]); - pub const RelayLocation: Location = Location::parent(); pub RelayLocationFilter: AssetFilter = Wild(AllOf { fun: WildFungible, - id: xcm::prelude::AssetId(RelayLocation::get()), + id: xcm::prelude::AssetId(Location::parent()), }); pub RelayChainNativeAssetFromAssetHub: (AssetFilter, Location) = ( @@ -361,14 +349,7 @@ impl Config for XcmConfig { 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; @@ -832,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; } @@ -1081,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, @@ -1101,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_tests.rs b/runtime/moonbeam/tests/xcm_tests.rs index e711d0f10f..1fdaa220d3 100644 --- a/runtime/moonbeam/tests/xcm_tests.rs +++ b/runtime/moonbeam/tests/xcm_tests.rs @@ -35,7 +35,7 @@ use xcm::latest::prelude::{ 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; @@ -43,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() { @@ -65,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 @@ -123,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 { @@ -214,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 @@ -231,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 { @@ -320,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 @@ -393,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 @@ -410,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 { @@ -507,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 @@ -606,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 )); }); @@ -668,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 )); }); @@ -746,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 { @@ -821,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 )); }); @@ -870,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( @@ -1030,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 @@ -1186,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 @@ -1341,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( @@ -1614,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 { @@ -1768,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 { @@ -1922,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); @@ -2054,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 @@ -2134,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 @@ -2195,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 @@ -2265,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 @@ -2376,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(|| { @@ -2446,6 +2368,7 @@ fn test_statemint_like() { } #[test] + fn send_statemint_asset_from_para_a_to_statemint_with_relay_fee() { MockNet::reset(); @@ -2495,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(), @@ -2509,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 { @@ -2693,12 +2606,7 @@ fn send_dot_from_moonbeam_to_statemint_via_xtokens_transfer() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - relay_location, - 0u128, - 0 - )); + XcmWeightTrader::set_asset_price(Location::parent(), 0u128); }); let parachain_beneficiary_absolute: Location = Junction::AccountKey20 { @@ -2851,12 +2759,7 @@ fn send_dot_from_moonbeam_to_statemint_via_xtokens_transfer_with_fee() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - relay_location, - 0u128, - 0 - )); + XcmWeightTrader::set_asset_price(Location::parent(), 0u128); }); let parachain_beneficiary_absolute: Location = Junction::AccountKey20 { @@ -3013,12 +2916,7 @@ fn send_dot_from_moonbeam_to_statemint_via_xtokens_transfer_multiasset() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - relay_location, - 0u128, - 0 - )); + XcmWeightTrader::set_asset_price(Location::parent(), 0u128); }); let parachain_beneficiary_absolute: Location = Junction::AccountKey20 { @@ -3190,12 +3088,7 @@ fn send_dot_from_moonbeam_to_statemint_via_xtokens_transfer_multicurrencies() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - relay_location, - 0u128, - 0 - )); + XcmWeightTrader::set_asset_price(Location::parent(), 0u128); assert_ok!(AssetManager::register_foreign_asset( parachain::RuntimeOrigin::root(), @@ -3204,12 +3097,7 @@ fn send_dot_from_moonbeam_to_statemint_via_xtokens_transfer_multicurrencies() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - statemint_location_asset, - 0u128, - 1 - )); + XcmWeightTrader::set_asset_price(statemint_asset.clone(), 0u128); }); let parachain_beneficiary_absolute: Location = Junction::AccountKey20 { @@ -3446,12 +3334,7 @@ fn send_dot_from_moonbeam_to_statemint_via_xtokens_transfer_multiassets() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - relay_location, - 0u128, - 0 - )); + XcmWeightTrader::set_asset_price(Location::parent(), 0u128); assert_ok!(AssetManager::register_foreign_asset( parachain::RuntimeOrigin::root(), @@ -3460,12 +3343,7 @@ fn send_dot_from_moonbeam_to_statemint_via_xtokens_transfer_multiassets() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - statemint_location_asset, - 0u128, - 1 - )); + XcmWeightTrader::set_asset_price(statemint_asset.clone(), 0u128); }); let parachain_beneficiary_absolute: Location = Junction::AccountKey20 { @@ -3981,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(); }); @@ -4079,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(); }); @@ -4191,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(); }); @@ -4318,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(); }); @@ -4441,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 b8cada57c6..4c75b4111d 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")] @@ -1456,6 +1451,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/xcm_mock/mod.rs b/runtime/moonriver/tests/xcm_mock/mod.rs index 9b8f3ea50d..cb1cacc488 100644 --- a/runtime/moonriver/tests/xcm_mock/mod.rs +++ b/runtime/moonriver/tests/xcm_mock/mod.rs @@ -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 22cf8ec9fb..e71bc1e21e 100644 --- a/runtime/moonriver/tests/xcm_mock/parachain.rs +++ b/runtime/moonriver/tests/xcm_mock/parachain.rs @@ -43,17 +43,17 @@ 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, Case, ConvertedConcreteId, EnsureXcmOrigin, - FixedRateOfFungible, FixedWeightBounds, FungibleAdapter as XcmCurrencyAdapter, - FungiblesAdapter, IsConcrete, NoChecking, ParentAsSuperuser, ParentIsPreset, - RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountKey20AsNative, SovereignSignedViaLocation, TakeWeightCredit, WithComputedOrigin, + AllowTopLevelPaidExecutionFrom, Case, ConvertedConcreteId, EnsureXcmOrigin, FixedWeightBounds, + FungibleAdapter as XcmCurrencyAdapter, FungiblesAdapter, IsConcrete, NoChecking, + ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, + SiblingParachainConvertsVia, SignedAccountKey20AsNative, SovereignSignedViaLocation, + TakeWeightCredit, WithComputedOrigin, }; use xcm_executor::{traits::JustTry, Config, XcmExecutor}; @@ -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! { @@ -323,10 +312,9 @@ parameter_types! { pub const MaxAssetsIntoHolding: u32 = 64; pub AssetHubLocation: Location = Location::new(1, [Parachain(1000)]); - pub const RelayLocation: Location = Location::parent(); pub RelayLocationFilter: AssetFilter = Wild(AllOf { fun: WildFungible, - id: xcm::prelude::AssetId(RelayLocation::get()), + id: xcm::prelude::AssetId(Location::parent()), }); pub RelayChainNativeAssetFromAssetHub: (AssetFilter, Location) = ( @@ -361,14 +349,7 @@ impl Config for XcmConfig { 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; @@ -837,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; } @@ -1087,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_tests.rs b/runtime/moonriver/tests/xcm_tests.rs index 59e0cbc2b4..ceee8aa521 100644 --- a/runtime/moonriver/tests/xcm_tests.rs +++ b/runtime/moonriver/tests/xcm_tests.rs @@ -31,7 +31,7 @@ use xcm::latest::prelude::{ 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; @@ -44,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() { @@ -65,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 @@ -121,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 { @@ -211,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 @@ -228,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 @@ -317,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 @@ -390,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 @@ -407,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 @@ -506,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 @@ -605,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 { @@ -761,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 )); }); @@ -823,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 )); }); @@ -901,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 { @@ -976,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 )); }); @@ -1025,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( @@ -1185,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 @@ -1341,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 @@ -1496,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( @@ -1769,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 { @@ -1923,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 { @@ -2077,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); @@ -2232,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(|| { @@ -2364,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 @@ -2444,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 @@ -2505,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 @@ -2575,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 @@ -2686,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(|| { @@ -2804,12 +2716,7 @@ fn send_statemine_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(), @@ -2818,12 +2725,7 @@ fn send_statemine_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 { @@ -3001,12 +2903,7 @@ fn send_dot_from_moonbeam_to_statemine_via_xtokens_transfer() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - relay_location, - 0u128, - 0 - )); + XcmWeightTrader::set_asset_price(Location::parent(), 0u128); }); let parachain_beneficiary_absolute: Location = Junction::AccountKey20 { @@ -3159,12 +3056,7 @@ fn send_dot_from_moonbeam_to_statemine_via_xtokens_transfer_with_fee() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - relay_location, - 0u128, - 0 - )); + XcmWeightTrader::set_asset_price(Location::parent(), 0u128); }); let parachain_beneficiary_absolute: Location = Junction::AccountKey20 { @@ -3321,12 +3213,7 @@ fn send_dot_from_moonbeam_to_statemine_via_xtokens_transfer_multiasset() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - relay_location, - 0u128, - 0 - )); + XcmWeightTrader::set_asset_price(Location::parent(), 0u128); }); let parachain_beneficiary_absolute: Location = Junction::AccountKey20 { @@ -3498,12 +3385,7 @@ fn send_dot_from_moonbeam_to_statemine_via_xtokens_transfer_multicurrencies() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - relay_location, - 0u128, - 0 - )); + XcmWeightTrader::set_asset_price(Location::parent(), 0u128); assert_ok!(AssetManager::register_foreign_asset( parachain::RuntimeOrigin::root(), @@ -3512,12 +3394,7 @@ fn send_dot_from_moonbeam_to_statemine_via_xtokens_transfer_multicurrencies() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - statemine_location_asset, - 0u128, - 1 - )); + XcmWeightTrader::set_asset_price(statemine_asset.clone(), 0u128); }); let parachain_beneficiary_absolute: Location = Junction::AccountKey20 { @@ -3754,12 +3631,7 @@ fn send_dot_from_moonbeam_to_statemine_via_xtokens_transfer_multiassets() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - relay_location, - 0u128, - 0 - )); + XcmWeightTrader::set_asset_price(Location::parent(), 0u128); assert_ok!(AssetManager::register_foreign_asset( parachain::RuntimeOrigin::root(), @@ -3768,12 +3640,7 @@ fn send_dot_from_moonbeam_to_statemine_via_xtokens_transfer_multiassets() { 1u128, true )); - assert_ok!(AssetManager::set_asset_units_per_second( - parachain::RuntimeOrigin::root(), - statemine_location_asset, - 0u128, - 1 - )); + XcmWeightTrader::set_asset_price(statemine_asset.clone(), 0u128); }); let parachain_beneficiary_absolute: Location = Junction::AccountKey20 { @@ -4290,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(); }); @@ -4388,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(); }); @@ -4500,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(); }); @@ -4627,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(); }); @@ -4750,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 () => {