diff --git a/Cargo.lock b/Cargo.lock index 51ccdcf0cf..04ab8f622e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8106,7 +8106,7 @@ dependencies = [ [[package]] name = "orml-traits" version = "0.10.0" -source = "git+https://github.com/moonbeam-foundation/open-runtime-module-library?branch=moonbeam-polkadot-v1.11.0#b86bc7dda8cc59e57c8b5dcfa813ebdbf76bee0e" +source = "git+https://github.com/moonbeam-foundation/open-runtime-module-library?branch=moonbeam-polkadot-v1.11.0#9af82f3e9cfe81cbe8cb5e530184e214093c8e6e" dependencies = [ "frame-support", "impl-trait-for-tuples", @@ -8126,7 +8126,7 @@ dependencies = [ [[package]] name = "orml-utilities" version = "0.10.0" -source = "git+https://github.com/moonbeam-foundation/open-runtime-module-library?branch=moonbeam-polkadot-v1.11.0#b86bc7dda8cc59e57c8b5dcfa813ebdbf76bee0e" +source = "git+https://github.com/moonbeam-foundation/open-runtime-module-library?branch=moonbeam-polkadot-v1.11.0#9af82f3e9cfe81cbe8cb5e530184e214093c8e6e" dependencies = [ "frame-support", "parity-scale-codec", @@ -8141,7 +8141,7 @@ dependencies = [ [[package]] name = "orml-xcm-support" version = "0.10.0" -source = "git+https://github.com/moonbeam-foundation/open-runtime-module-library?branch=moonbeam-polkadot-v1.11.0#b86bc7dda8cc59e57c8b5dcfa813ebdbf76bee0e" +source = "git+https://github.com/moonbeam-foundation/open-runtime-module-library?branch=moonbeam-polkadot-v1.11.0#9af82f3e9cfe81cbe8cb5e530184e214093c8e6e" dependencies = [ "frame-support", "orml-traits", @@ -8155,7 +8155,7 @@ dependencies = [ [[package]] name = "orml-xtokens" version = "0.10.0" -source = "git+https://github.com/moonbeam-foundation/open-runtime-module-library?branch=moonbeam-polkadot-v1.11.0#b86bc7dda8cc59e57c8b5dcfa813ebdbf76bee0e" +source = "git+https://github.com/moonbeam-foundation/open-runtime-module-library?branch=moonbeam-polkadot-v1.11.0#9af82f3e9cfe81cbe8cb5e530184e214093c8e6e" dependencies = [ "frame-support", "frame-system", diff --git a/pallets/xcm-transactor/src/lib.rs b/pallets/xcm-transactor/src/lib.rs index a6ba5b2024..b9019ddb90 100644 --- a/pallets/xcm-transactor/src/lib.rs +++ b/pallets/xcm-transactor/src/lib.rs @@ -1050,9 +1050,19 @@ pub mod pallet { transact_message.0.insert(0, DescendOrigin(interior)); // Send to destination chain - let (ticket, _price) = + let (ticket, price) = T::XcmSender::validate(&mut Some(dest), &mut Some(transact_message)) .map_err(|_| Error::::ErrorValidating)?; + + // Extract XCM Delivery fees from the origin account. + // Same way as the XCM executor does. + // See https://github.com/paritytech/polkadot-sdk/blob/release-crates-io-v1.11.0 + // /polkadot/xcm/xcm-executor/src/lib.rs#L445-L447 + for asset in price.inner() { + T::AssetTransactor::withdraw_asset(&asset, &origin_as_mult, None) + .map_err(|_| Error::::UnableToWithdrawAsset)?; + } + T::XcmSender::deliver(ticket).map_err(|_| Error::::ErrorDelivering)?; Ok(()) diff --git a/precompiles/xtokens/src/tests.rs b/precompiles/xtokens/src/tests.rs index 3240504d0e..4b8ab14fb4 100644 --- a/precompiles/xtokens/src/tests.rs +++ b/precompiles/xtokens/src/tests.rs @@ -94,7 +94,7 @@ fn transfer_self_reserve_works() { weight: 4_000_000, }, ) - .expect_cost(2000) + .expect_cost(3000) .expect_no_logs() .execute_returns(()); @@ -139,7 +139,7 @@ fn transfer_to_reserve_works() { weight: 4_000_000, }, ) - .expect_cost(3000) + .expect_cost(4000) .expect_no_logs() .execute_returns(()); @@ -186,7 +186,7 @@ fn transfer_to_reserve_with_unlimited_weight_works() { weight: u64::MAX, }, ) - .expect_cost(3000) + .expect_cost(4000) .expect_no_logs() .execute_returns(()); @@ -235,7 +235,7 @@ fn transfer_to_reserve_with_fee_works() { weight: 4_000_000, }, ) - .expect_cost(3000) + .expect_cost(4000) .expect_no_logs() .execute_returns(()); @@ -290,7 +290,7 @@ fn transfer_non_reserve_to_non_reserve_works() { weight: 4_000_000, }, ) - .expect_cost(3000) + .expect_cost(4000) .expect_no_logs() .execute_returns(()); @@ -339,7 +339,7 @@ fn transfer_non_reserve_to_non_reserve_with_fee_works() { weight: 4_000_000, }, ) - .expect_cost(3000) + .expect_cost(4000) .expect_no_logs() .execute_returns(()); @@ -394,7 +394,7 @@ fn transfer_multi_asset_to_reserve_works() { weight: 4_000_000, }, ) - .expect_cost(3000) + .expect_cost(4000) .expect_no_logs() .execute_returns(()); @@ -442,7 +442,7 @@ fn transfer_multi_asset_self_reserve_works() { weight: 4_000_000, }, ) - .expect_cost(2000) + .expect_cost(3000) .expect_no_logs() .execute_returns(()); @@ -490,7 +490,7 @@ fn transfer_multi_asset_self_reserve_with_fee_works() { weight: 4_000_000, }, ) - .expect_cost(2000) + .expect_cost(3000) .expect_no_logs() .execute_returns(()); @@ -542,7 +542,7 @@ fn transfer_multi_asset_non_reserve_to_non_reserve() { weight: 4_000_000, }, ) - .expect_cost(3000) + .expect_cost(4000) .expect_no_logs() .execute_returns(()); @@ -591,7 +591,7 @@ fn transfer_multi_asset_non_reserve_to_non_reserve_with_fee() { weight: 4_000_000, }, ) - .expect_cost(3000) + .expect_cost(4000) .expect_no_logs() .execute_returns(()); @@ -645,7 +645,7 @@ fn transfer_multi_currencies() { weight: 4_000_000, }, ) - .expect_cost(3000) + .expect_cost(4000) .expect_no_logs() .execute_returns(()); @@ -718,7 +718,7 @@ fn transfer_multi_assets() { weight: 4_000_000, }, ) - .expect_cost(3000) + .expect_cost(4000) .expect_no_logs() .execute_returns(()); diff --git a/runtime/moonbase/src/xcm_config.rs b/runtime/moonbase/src/xcm_config.rs index d6ae2444ce..210c3c628b 100644 --- a/runtime/moonbase/src/xcm_config.rs +++ b/runtime/moonbase/src/xcm_config.rs @@ -18,8 +18,8 @@ //! use super::{ - governance, AccountId, AssetId, AssetManager, Balance, Balances, DealWithFees, - EmergencyParaXcm, Erc20XcmBridge, MaintenanceMode, MessageQueue, ParachainInfo, + currency::MICROUNIT, governance, AccountId, AssetId, AssetManager, Balance, Balances, + DealWithFees, EmergencyParaXcm, Erc20XcmBridge, MaintenanceMode, MessageQueue, ParachainInfo, ParachainSystem, Perbill, PolkadotXcm, Runtime, RuntimeBlockWeights, RuntimeCall, RuntimeEvent, RuntimeOrigin, Treasury, XcmpQueue, }; @@ -73,6 +73,7 @@ use sp_std::{ }; use orml_traits::parameter_type_with_key; +use polkadot_runtime_common::xcm_sender::ExponentialPrice; use crate::governance::referenda::{FastGeneralAdminOrRoot, GeneralAdminOrRoot}; @@ -85,7 +86,6 @@ parameter_types! { pub UniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get()), Parachain(ParachainInfo::parachain_id().into())].into(); - // Self Reserve location, defines the multilocation identifiying the self-reserve currency // This is used to match it also against our Balances pallet when we receive such // a Location: (Self Balances pallet index) @@ -96,6 +96,12 @@ parameter_types! { PalletInstance(::index() as u8) ].into() }; + + // XCM BaseDeliveryFee + pub const BaseDeliveryFee: u128 = 100 * MICROUNIT; + + // Cost of every XCM transaction byte + pub const TransactionByteFee: u128 = 100; } /// Type for specifying how a `Location` can be converted into an `AccountId`. This is used @@ -321,11 +327,19 @@ impl xcm_executor::Config for XcmExecutorConfig { // Converts a Signed Local Origin into a Location pub type LocalOriginToLocation = SignedToAccountId20; +/// XCM Delivery fees for sibling chains. +pub type PriceForSiblingParachainDelivery = + ExponentialPrice; + +/// XCM Delivery fees for the relay chain. +pub type PriceForParentDelivery = + ExponentialPrice; + /// The means for routing XCM messages which are not for local execution into the right message /// queues. pub type XcmRouter = ( // Two routers - use UMP to communicate with the relay chain: - cumulus_primitives_utility::ParentAsUmp, + cumulus_primitives_utility::ParentAsUmp, // ..and XCMP to communicate with the sibling chains. XcmpQueue, ); @@ -376,9 +390,7 @@ impl cumulus_pallet_xcmp_queue::Config for Runtime { type ControllerOrigin = EnsureRoot; type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; type WeightInfo = moonbeam_weights::cumulus_pallet_xcmp_queue::WeightInfo; - type PriceForSiblingDelivery = polkadot_runtime_common::xcm_sender::NoPriceForMessageDelivery< - cumulus_primitives_core::ParaId, - >; + type PriceForSiblingDelivery = PriceForSiblingParachainDelivery; } parameter_types! { diff --git a/runtime/moonbase/tests/integration_test.rs b/runtime/moonbase/tests/integration_test.rs index d6779855f2..d648904b37 100644 --- a/runtime/moonbase/tests/integration_test.rs +++ b/runtime/moonbase/tests/integration_test.rs @@ -68,6 +68,7 @@ use sha3::{Digest, Keccak256}; use sp_core::{crypto::UncheckedFrom, ByteArray, Pair, H160, H256, U256}; use sp_runtime::{DispatchError, ModuleError}; use xcm::latest::prelude::*; +use xcm::latest::Assets as XcmAssets; type AuthorMappingPCall = pallet_evm_precompile_author_mapping::AuthorMappingPrecompileCall; @@ -1530,7 +1531,7 @@ fn xtokens_precompiles_transfer() { weight: 4_000_000, }, ) - .expect_cost(57639) + .expect_cost(57847) .expect_no_logs() .execute_returns(()) }) @@ -1582,7 +1583,7 @@ fn xtokens_precompiles_transfer_multiasset() { weight: 4_000_000, }, ) - .expect_cost(57639) + .expect_cost(57847) .expect_no_logs() .execute_returns(()); }) @@ -1624,7 +1625,7 @@ fn xtokens_precompiles_transfer_native() { weight: 4_000_000, }, ) - .expect_cost(16000) + .expect_cost(16208) .expect_no_logs() .execute_returns(()); }) @@ -2091,6 +2092,228 @@ fn transactor_cannot_use_more_than_max_weight() { }) } +#[test] +fn test_xcm_delivery_fees_in_xcm_transactor() { + ExtBuilder::default() + .with_balances(vec![ + (AccountId::from(ALICE), 2_000 * UNIT), + (AccountId::from(BOB), 1_000 * UNIT), + ]) + .with_xcm_assets(vec![XcmAssetInitialization { + asset_type: AssetType::Xcm(xcm::v3::Location::parent()), + metadata: AssetRegistrarMetadata { + name: b"RelayToken".to_vec(), + symbol: b"Relay".to_vec(), + decimals: 12, + is_frozen: false, + }, + balances: vec![(AccountId::from(ALICE), 1_000_000_000_000_000)], + is_sufficient: true, + }]) + .build() + .execute_with(|| { + let alice_initial_native_balance = 2_000 * UNIT; + + // Root sets the defaultXcm + assert_ok!(PolkadotXcm::force_default_xcm_version( + root_origin(), + Some(3) + )); + + // Root can set transact info + assert_ok!(XcmTransactor::set_transact_info( + root_origin(), + Box::new(xcm::VersionedLocation::V4(Location::parent())), + // Relay charges 1000 for every instruction, and we have 3, so 3000 + 3000.into(), + 20000000000.into(), + // 4 instructions in transact through signed + Some(4000.into()) + )); + + // Root can set transact info + assert_ok!(XcmTransactor::set_fee_per_second( + root_origin(), + Box::new(xcm::VersionedLocation::V4(Location::parent())), + 1, + )); + + // Execute transact_through_signed call + assert_ok!(XcmTransactor::transact_through_signed( + origin_of(AccountId::from(ALICE)), + Box::new(xcm::VersionedLocation::V4(Location::parent())), + CurrencyPayment { + currency: Currency::AsMultiLocation(Box::new(xcm::VersionedLocation::V4( + Location::parent() + ))), + fee_amount: None + }, + Vec::new(), + TransactWeights { + transact_required_weight_at_most: 4000000000.into(), + overall_weight: None + }, + false + )); + + // Delivery fee (total): + // DeliveryFeeFactor * [BaseDeliveryFee + (TransactionByteFee * XCM Msg Bytes)] + // + // DeliveryFeeFactor: 1 + // BaseDeliveryFee: 100000000000000 + // TransactionByteFee: 100 + // XCM Msg Bytes: 60 + + // Make sure delivery fees were deducted from the caller's account + assert_eq!( + Balances::free_balance(AccountId::from(ALICE)), + alice_initial_native_balance - 100000000006000, + ); + }) +} + +#[test] +fn test_xcm_delivery_fees_pallet_xcm_send() { + ExtBuilder::default() + .with_balances(vec![ + (AccountId::from(ALICE), 2_000 * UNIT), + (AccountId::from(BOB), 1_000 * UNIT), + ]) + .with_xcm_assets(vec![XcmAssetInitialization { + asset_type: AssetType::Xcm(xcm::v3::Location::parent()), + metadata: AssetRegistrarMetadata { + name: b"RelayToken".to_vec(), + symbol: b"Relay".to_vec(), + decimals: 12, + is_frozen: false, + }, + balances: vec![(AccountId::from(ALICE), 1_000_000_000_000_000)], + is_sufficient: true, + }]) + .build() + .execute_with(|| { + let alice_initial_native_balance = 2_000 * UNIT; + + // Set the default xcm version first + assert_ok!(PolkadotXcm::force_default_xcm_version( + root_origin(), + Some(3) + )); + + // Specify the relay as destination + let dest = Location { + parents: 1, + interior: [].into(), + }; + + // Build any message + let message = xcm::VersionedXcm::<()>::V4(Xcm(vec![ClearOrigin])); + + // Execute pallet-xcm send() + assert_ok!(PolkadotXcm::send( + ::RuntimeOrigin::signed(AccountId::from(ALICE)), + Box::new(dest.into()), + Box::new(message), + )); + + // Delivery fee (total): + // DeliveryFeeFactor * [BaseDeliveryFee + (TransactionByteFee * XCM Msg Bytes)] + // + // DeliveryFeeFactor: 1 + // BaseDeliveryFee: 100000000000000 + // TransactionByteFee: 100 + // XCM Msg Bytes: 27 + + let native_asset_location = Location::new(0, [PalletInstance(3u8)]); + let native_asset_fee = Asset::from((native_asset_location, 100000000002700u128)); + let expected_fee_assets: XcmAssets = XcmAssets::from(vec![native_asset_fee]); + + let fee_event_count = System::events() + .iter() + .filter(|r| match &r.event { + RuntimeEvent::PolkadotXcm(pallet_xcm::Event::FeesPaid { fees, .. }) => { + fees == &expected_fee_assets + } + _ => false, + }) + .count(); + + assert_eq!( + fee_event_count, 1, + "FeesPaid event should have been emitted" + ); + + // Make sure delivery fees were deducted from the caller's account + assert_eq!( + Balances::free_balance(AccountId::from(ALICE)), + alice_initial_native_balance - 100000000002700, + ); + }) +} + +#[test] +fn test_xcm_delivery_fees_through_xtokens() { + ExtBuilder::default() + .with_balances(vec![ + (AccountId::from(ALICE), 2_000 * UNIT), + (AccountId::from(BOB), 1_000 * UNIT), + ]) + .with_xcm_assets(vec![XcmAssetInitialization { + asset_type: AssetType::Xcm(xcm::v3::Location::parent()), + metadata: AssetRegistrarMetadata { + name: b"RelayToken".to_vec(), + symbol: b"Relay".to_vec(), + decimals: 12, + is_frozen: false, + }, + balances: vec![(AccountId::from(ALICE), 1_000_000_000_000_000)], + is_sufficient: true, + }]) + .build() + .execute_with(|| { + let alice_initial_native_balance = 2_000 * UNIT; + let source_location = AssetType::Xcm(xcm::v3::Location::parent()); + let dest = Location { + parents: 1, + interior: [AccountId32 { + network: None, + id: [1u8; 32], + }] + .into(), + }; + let source_id: moonbase_runtime::AssetId = source_location.clone().into(); + + // Root sets the defaultXcm + assert_ok!(PolkadotXcm::force_default_xcm_version( + root_origin(), + Some(3) + )); + + // Execute transfer through xTokens + assert_ok!(XTokens::transfer( + origin_of(AccountId::from(ALICE)), + moonbase_runtime::xcm_config::CurrencyId::ForeignAsset(source_id), + 100_000_000_000_000, + Box::new(xcm::VersionedLocation::V4(dest)), + WeightLimit::Limited(4000000000.into()) + )); + + // Delivery fee (total): + // DeliveryFeeFactor * [BaseDeliveryFee + (TransactionByteFee * XCM Msg Bytes)] + // + // DeliveryFeeFactor: 1 + // BaseDeliveryFee: 100000000000000 + // TransactionByteFee: 100 + // XCM Msg Bytes: 76 + + // Make sure delivery fees were deducted from the caller's account + assert_eq!( + Balances::free_balance(AccountId::from(ALICE)), + alice_initial_native_balance - 100000000007600, + ); + }) +} + #[test] fn root_can_use_hrmp_manage() { ExtBuilder::default() diff --git a/runtime/moonbeam/src/xcm_config.rs b/runtime/moonbeam/src/xcm_config.rs index 7f4e304916..4ba26cb20b 100644 --- a/runtime/moonbeam/src/xcm_config.rs +++ b/runtime/moonbeam/src/xcm_config.rs @@ -18,9 +18,10 @@ //! use super::{ - governance, AccountId, AssetId, AssetManager, Balance, Balances, DealWithFees, Erc20XcmBridge, - MaintenanceMode, MessageQueue, ParachainInfo, ParachainSystem, Perbill, PolkadotXcm, Runtime, - RuntimeBlockWeights, RuntimeCall, RuntimeEvent, RuntimeOrigin, Treasury, XcmpQueue, + currency::MICROGLMR, governance, AccountId, AssetId, AssetManager, Balance, Balances, + DealWithFees, Erc20XcmBridge, MaintenanceMode, MessageQueue, ParachainInfo, ParachainSystem, + Perbill, PolkadotXcm, Runtime, RuntimeBlockWeights, RuntimeCall, RuntimeEvent, RuntimeOrigin, + Treasury, XcmpQueue, }; use frame_support::{ @@ -71,6 +72,7 @@ use sp_std::{ }; use orml_traits::parameter_type_with_key; +use polkadot_runtime_common::xcm_sender::ExponentialPrice; use crate::governance::referenda::{FastGeneralAdminOrRoot, GeneralAdminOrRoot}; @@ -91,6 +93,12 @@ parameter_types! { PalletInstance(::index() as u8) ].into() }; + + // XCM BaseDeliveryFee + pub const BaseDeliveryFee: u128 = 100 * MICROGLMR; + + // Cost of every XCM transaction byte + pub const TransactionByteFee: u128 = 100; } /// Type for specifying how a `Location` can be converted into an `AccountId`. This is used @@ -317,11 +325,19 @@ type XcmExecutor = pallet_erc20_xcm_bridge::XcmExecutorWrapper< // Converts a Signed Local Origin into a Location pub type LocalOriginToLocation = SignedToAccountId20; +/// XCM Delivery fees for sibling chains. +pub type PriceForSiblingParachainDelivery = + ExponentialPrice; + +/// XCM Delivery fees for the relay chain. +pub type PriceForParentDelivery = + ExponentialPrice; + /// The means for routing XCM messages which are not for local execution into the right message /// queues. pub type XcmRouter = ( // Two routers - use UMP to communicate with the relay chain: - cumulus_primitives_utility::ParentAsUmp, + cumulus_primitives_utility::ParentAsUmp, // ..and XCMP to communicate with the sibling chains. XcmpQueue, ); @@ -367,9 +383,7 @@ impl cumulus_pallet_xcmp_queue::Config for Runtime { type ControllerOrigin = EnsureRoot; type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; type WeightInfo = moonbeam_weights::cumulus_pallet_xcmp_queue::WeightInfo; - type PriceForSiblingDelivery = polkadot_runtime_common::xcm_sender::NoPriceForMessageDelivery< - cumulus_primitives_core::ParaId, - >; + type PriceForSiblingDelivery = PriceForSiblingParachainDelivery; } parameter_types! { diff --git a/runtime/moonbeam/tests/integration_test.rs b/runtime/moonbeam/tests/integration_test.rs index 64b1952ecf..00a0ce7346 100644 --- a/runtime/moonbeam/tests/integration_test.rs +++ b/runtime/moonbeam/tests/integration_test.rs @@ -1945,7 +1945,7 @@ fn xtokens_precompile_transfer() { weight: 4_000_000, }, ) - .expect_cost(57639) + .expect_cost(57847) .expect_no_logs() .execute_returns(()) }) @@ -1997,7 +1997,7 @@ fn xtokens_precompile_transfer_multiasset() { weight: 4_000_000, }, ) - .expect_cost(57639) + .expect_cost(57847) .expect_no_logs() .execute_returns(()); }) @@ -2285,6 +2285,149 @@ fn transactor_cannot_use_more_than_max_weight() { }) } +#[test] +fn test_xcm_delivery_fees_in_xcm_transactor() { + ExtBuilder::default() + .with_balances(vec![ + (AccountId::from(ALICE), 2_000 * GLMR), + (AccountId::from(BOB), 1_000 * GLMR), + ]) + .with_xcm_assets(vec![XcmAssetInitialization { + asset_type: AssetType::Xcm(xcm::v3::Location::parent()), + metadata: AssetRegistrarMetadata { + name: b"RelayToken".to_vec(), + symbol: b"Relay".to_vec(), + decimals: 12, + is_frozen: false, + }, + balances: vec![(AccountId::from(ALICE), 1_000_000_000_000_000)], + is_sufficient: true, + }]) + .build() + .execute_with(|| { + let alice_initial_native_balance = 2_000 * GLMR; + + // Root sets the defaultXcm + assert_ok!(PolkadotXcm::force_default_xcm_version( + root_origin(), + Some(3) + )); + + // Root can set transact info + assert_ok!(XcmTransactor::set_transact_info( + root_origin(), + Box::new(xcm::VersionedLocation::V4(Location::parent())), + // Relay charges 1000 for every instruction, and we have 3, so 3000 + 3000.into(), + 20000000000.into(), + // 4 instructions in transact through signed + Some(4000.into()) + )); + + // Root can set transact info + assert_ok!(XcmTransactor::set_fee_per_second( + root_origin(), + Box::new(xcm::VersionedLocation::V4(Location::parent())), + 1, + )); + + // Execute transact_through_signed call + assert_ok!(XcmTransactor::transact_through_signed( + origin_of(AccountId::from(ALICE)), + Box::new(xcm::VersionedLocation::V4(Location::parent())), + CurrencyPayment { + currency: Currency::AsMultiLocation(Box::new(xcm::VersionedLocation::V4( + Location::parent() + ))), + fee_amount: None + }, + Vec::new(), + TransactWeights { + transact_required_weight_at_most: 4000000000.into(), + overall_weight: None + }, + false + )); + + // Delivery fee (total): + // DeliveryFeeFactor * [BaseDeliveryFee + (TransactionByteFee * XCM Msg Bytes)] + // + // DeliveryFeeFactor: 1 + // BaseDeliveryFee: 100000000000000 + // TransactionByteFee: 100 + // XCM Msg Bytes: 60 + + // Make sure delivery fees were deducted from the caller's account + assert_eq!( + Balances::free_balance(AccountId::from(ALICE)), + alice_initial_native_balance - 100000000006000, + ); + }) +} + +#[test] +fn test_xcm_delivery_fees_through_xtokens() { + ExtBuilder::default() + .with_balances(vec![ + (AccountId::from(ALICE), 2_000 * GLMR), + (AccountId::from(BOB), 1_000 * GLMR), + ]) + .with_xcm_assets(vec![XcmAssetInitialization { + asset_type: AssetType::Xcm(xcm::v3::Location::parent()), + metadata: AssetRegistrarMetadata { + name: b"RelayToken".to_vec(), + symbol: b"Relay".to_vec(), + decimals: 12, + is_frozen: false, + }, + balances: vec![(AccountId::from(ALICE), 1_000_000_000_000_000)], + is_sufficient: true, + }]) + .build() + .execute_with(|| { + let alice_initial_native_balance = 2_000 * GLMR; + let source_location = AssetType::Xcm(xcm::v3::Location::parent()); + let dest = Location { + parents: 1, + interior: [AccountId32 { + network: None, + id: [1u8; 32], + }] + .into(), + }; + let source_id: moonbeam_runtime::AssetId = source_location.clone().into(); + + // Root sets the defaultXcm + assert_ok!(PolkadotXcm::force_default_xcm_version( + root_origin(), + Some(3) + )); + + // Execute transfer through xTokens + assert_ok!(XTokens::transfer( + origin_of(AccountId::from(ALICE)), + moonbeam_runtime::xcm_config::CurrencyId::ForeignAsset(source_id), + 100_000_000_000_000, + Box::new(xcm::VersionedLocation::V4(dest)), + WeightLimit::Limited(4000000000.into()) + )); + + // Delivery fee (total): + // DeliveryFeeFactor * [BaseDeliveryFee + (TransactionByteFee * XCM Msg Bytes)] + // + // DeliveryFeeFactor: 1 + // BaseDeliveryFee: 100000000000000 + // TransactionByteFee: 100 + // XCM Msg Bytes: 76 + + // Make sure delivery fees were deducted from the caller's account + assert_eq!( + Balances::free_balance(AccountId::from(ALICE)), + alice_initial_native_balance - 100000000007600, + ); + }) +} + #[test] fn call_xtokens_with_fee() { ExtBuilder::default() diff --git a/runtime/moonriver/src/xcm_config.rs b/runtime/moonriver/src/xcm_config.rs index 60bc065dbd..3b9f2364dc 100644 --- a/runtime/moonriver/src/xcm_config.rs +++ b/runtime/moonriver/src/xcm_config.rs @@ -18,9 +18,10 @@ //! use super::{ - governance, AccountId, AssetId, AssetManager, Balance, Balances, DealWithFees, Erc20XcmBridge, - MaintenanceMode, MessageQueue, ParachainInfo, ParachainSystem, Perbill, PolkadotXcm, Runtime, - RuntimeBlockWeights, RuntimeCall, RuntimeEvent, RuntimeOrigin, Treasury, XcmpQueue, + currency::MICROMOVR, governance, AccountId, AssetId, AssetManager, Balance, Balances, + DealWithFees, Erc20XcmBridge, MaintenanceMode, MessageQueue, ParachainInfo, ParachainSystem, + Perbill, PolkadotXcm, Runtime, RuntimeBlockWeights, RuntimeCall, RuntimeEvent, RuntimeOrigin, + Treasury, XcmpQueue, }; use frame_support::{ @@ -71,6 +72,7 @@ use sp_std::{ }; use orml_traits::parameter_type_with_key; +use polkadot_runtime_common::xcm_sender::ExponentialPrice; use crate::governance::referenda::{FastGeneralAdminOrRoot, GeneralAdminOrRoot}; @@ -93,6 +95,12 @@ parameter_types! { PalletInstance(::index() as u8) ].into() }; + + // XCM BaseDeliveryFee + pub const BaseDeliveryFee: u128 = 100 * MICROMOVR; + + // Cost of every XCM transaction byte + pub const TransactionByteFee: u128 = 100; } /// Type for specifying how a `Location` can be converted into an `AccountId`. This is used @@ -325,11 +333,19 @@ type XcmExecutor = pallet_erc20_xcm_bridge::XcmExecutorWrapper< // Converts a Signed Local Origin into a Location pub type LocalOriginToLocation = SignedToAccountId20; +/// XCM Delivery fees for sibling chains. +pub type PriceForSiblingParachainDelivery = + ExponentialPrice; + +/// XCM Delivery fees for the relay chain. +pub type PriceForParentDelivery = + ExponentialPrice; + /// The means for routing XCM messages which are not for local execution into the right message /// queues. pub type XcmRouter = ( // Two routers - use UMP to communicate with the relay chain: - cumulus_primitives_utility::ParentAsUmp, + cumulus_primitives_utility::ParentAsUmp, // ..and XCMP to communicate with the sibling chains. XcmpQueue, ); @@ -375,9 +391,7 @@ impl cumulus_pallet_xcmp_queue::Config for Runtime { type ControllerOrigin = EnsureRoot; type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; type WeightInfo = moonbeam_weights::cumulus_pallet_xcmp_queue::WeightInfo; - type PriceForSiblingDelivery = polkadot_runtime_common::xcm_sender::NoPriceForMessageDelivery< - cumulus_primitives_core::ParaId, - >; + type PriceForSiblingDelivery = PriceForSiblingParachainDelivery; } parameter_types! { diff --git a/runtime/moonriver/tests/integration_test.rs b/runtime/moonriver/tests/integration_test.rs index ad91b4dde3..a367c1e66f 100644 --- a/runtime/moonriver/tests/integration_test.rs +++ b/runtime/moonriver/tests/integration_test.rs @@ -1925,7 +1925,7 @@ fn xtokens_precompiles_transfer() { weight: 4_000_000, }, ) - .expect_cost(57639) + .expect_cost(57847) .expect_no_logs() .execute_returns(()) }) @@ -1977,7 +1977,7 @@ fn xtokens_precompiles_transfer_multiasset() { weight: 4_000_000, }, ) - .expect_cost(57639) + .expect_cost(57847) .expect_no_logs() .execute_returns(()); }) @@ -2114,6 +2114,149 @@ fn transactor_cannot_use_more_than_max_weight() { }) } +#[test] +fn test_xcm_delivery_fees_in_xcm_transactor() { + ExtBuilder::default() + .with_balances(vec![ + (AccountId::from(ALICE), 2_000 * MOVR), + (AccountId::from(BOB), 1_000 * MOVR), + ]) + .with_xcm_assets(vec![XcmAssetInitialization { + asset_type: AssetType::Xcm(xcm::v3::Location::parent()), + metadata: AssetRegistrarMetadata { + name: b"RelayToken".to_vec(), + symbol: b"Relay".to_vec(), + decimals: 12, + is_frozen: false, + }, + balances: vec![(AccountId::from(ALICE), 1_000_000_000_000_000)], + is_sufficient: true, + }]) + .build() + .execute_with(|| { + let alice_initial_native_balance = 2_000 * MOVR; + + // Root sets the defaultXcm + assert_ok!(PolkadotXcm::force_default_xcm_version( + root_origin(), + Some(3) + )); + + // Root can set transact info + assert_ok!(XcmTransactor::set_transact_info( + root_origin(), + Box::new(xcm::VersionedLocation::V4(Location::parent())), + // Relay charges 1000 for every instruction, and we have 3, so 3000 + 3000.into(), + 20000000000.into(), + // 4 instructions in transact through signed + Some(4000.into()) + )); + + // Root can set transact info + assert_ok!(XcmTransactor::set_fee_per_second( + root_origin(), + Box::new(xcm::VersionedLocation::V4(Location::parent())), + 1, + )); + + // Execute transact_through_signed call + assert_ok!(XcmTransactor::transact_through_signed( + origin_of(AccountId::from(ALICE)), + Box::new(xcm::VersionedLocation::V4(Location::parent())), + CurrencyPayment { + currency: Currency::AsMultiLocation(Box::new(xcm::VersionedLocation::V4( + Location::parent() + ))), + fee_amount: None + }, + Vec::new(), + TransactWeights { + transact_required_weight_at_most: 4000000000.into(), + overall_weight: None + }, + false + )); + + // Delivery fee (total): + // DeliveryFeeFactor * [BaseDeliveryFee + (TransactionByteFee * XCM Msg Bytes)] + // + // DeliveryFeeFactor: 1 + // BaseDeliveryFee: 100000000000000 + // TransactionByteFee: 100 + // XCM Msg Bytes: 60 + + // Make sure delivery fees were deducted from the caller's account + assert_eq!( + Balances::free_balance(AccountId::from(ALICE)), + alice_initial_native_balance - 100000000006000, + ); + }) +} + +#[test] +fn test_xcm_delivery_fees_through_xtokens() { + ExtBuilder::default() + .with_balances(vec![ + (AccountId::from(ALICE), 2_000 * MOVR), + (AccountId::from(BOB), 1_000 * MOVR), + ]) + .with_xcm_assets(vec![XcmAssetInitialization { + asset_type: AssetType::Xcm(xcm::v3::Location::parent()), + metadata: AssetRegistrarMetadata { + name: b"RelayToken".to_vec(), + symbol: b"Relay".to_vec(), + decimals: 12, + is_frozen: false, + }, + balances: vec![(AccountId::from(ALICE), 1_000_000_000_000_000)], + is_sufficient: true, + }]) + .build() + .execute_with(|| { + let alice_initial_native_balance = 2_000 * MOVR; + let source_location = AssetType::Xcm(xcm::v3::Location::parent()); + let dest = Location { + parents: 1, + interior: [AccountId32 { + network: None, + id: [1u8; 32], + }] + .into(), + }; + let source_id: moonriver_runtime::AssetId = source_location.clone().into(); + + // Root sets the defaultXcm + assert_ok!(PolkadotXcm::force_default_xcm_version( + root_origin(), + Some(3) + )); + + // Execute transfer through xTokens + assert_ok!(XTokens::transfer( + origin_of(AccountId::from(ALICE)), + moonriver_runtime::xcm_config::CurrencyId::ForeignAsset(source_id), + 100_000_000_000_000, + Box::new(xcm::VersionedLocation::V4(dest)), + WeightLimit::Limited(4000000000.into()) + )); + + // Delivery fee (total): + // DeliveryFeeFactor * [BaseDeliveryFee + (TransactionByteFee * XCM Msg Bytes)] + // + // DeliveryFeeFactor: 1 + // BaseDeliveryFee: 100000000000000 + // TransactionByteFee: 100 + // XCM Msg Bytes: 76 + + // Make sure delivery fees were deducted from the caller's account + assert_eq!( + Balances::free_balance(AccountId::from(ALICE)), + alice_initial_native_balance - 100000000007600, + ); + }) +} + #[test] fn transact_through_signed_precompile_works_v2() { ExtBuilder::default() diff --git a/test/helpers/block.ts b/test/helpers/block.ts index e2f8794d12..882a8425db 100644 --- a/test/helpers/block.ts +++ b/test/helpers/block.ts @@ -87,7 +87,8 @@ export const verifyBlockFees = async ( context: DevModeContext, fromBlockNumber: number, toBlockNumber: number, - expectedBalanceDiff: bigint + expectedBalanceDiff: bigint, + deliveryFees: bigint ) => { const api = context.polkadotJs(); debug(`========= Checking block ${fromBlockNumber}...${toBlockNumber}`); @@ -262,7 +263,7 @@ export const verifyBlockFees = async ( await api.at(blockDetails.block.hash) ).query.system.account(origin)) as any; - expect(txFees.toString()).to.eq( + expect((BigInt(txFees) + BigInt(deliveryFees)).toString()).to.eq( ( (((fromBalance.data.free.toBigInt() as any) - toBalance.data.free.toBigInt()) as any) - expectedBalanceDiff @@ -278,7 +279,7 @@ export const verifyBlockFees = async ( } ); - expect(fromPreSupply.toBigInt() - toSupply.toBigInt()).to.eq(sumBlockBurnt); + expect(BigInt(fromPreSupply) - BigInt(toSupply) - BigInt(deliveryFees)).to.eq(sumBlockBurnt); // Log difference in supply, we should be equal to the burnt fees // debug( @@ -292,11 +293,12 @@ export const verifyBlockFees = async ( export const verifyLatestBlockFees = async ( context: DevModeContext, - expectedBalanceDiff: bigint = BigInt(0) + expectedBalanceDiff: bigint = BigInt(0), + deliveryFees: bigint = BigInt(0) ) => { const signedBlock = await context.polkadotJs().rpc.chain.getBlock(); const blockNumber = Number(signedBlock.block.header.number); - return verifyBlockFees(context, blockNumber, blockNumber, expectedBalanceDiff); + return verifyBlockFees(context, blockNumber, blockNumber, expectedBalanceDiff, deliveryFees); }; export async function jumpToRound(context: DevModeContext, round: number): Promise { diff --git a/test/helpers/xcm.ts b/test/helpers/xcm.ts index 63675960f2..d5555aafd1 100644 --- a/test/helpers/xcm.ts +++ b/test/helpers/xcm.ts @@ -1,4 +1,4 @@ -import { DevModeContext, customDevRpcRequest } from "@moonwall/cli"; +import { DevModeContext, customDevRpcRequest, expect } from "@moonwall/cli"; import { ALITH_ADDRESS } from "@moonwall/util"; import { AssetMetadata, XcmpMessageFormat } from "@polkadot/types/interfaces"; import { @@ -918,3 +918,52 @@ export const expectXcmEventMessage = async (context: DevModeContext, message: st }; type XcmCallback = (this: XcmFragment) => void; + +export const extractPaidDeliveryFees = async (context: DevModeContext) => { + const records = await context.polkadotJs().query.system.events(); + + const filteredEvents = records + .map(({ event }) => + context.polkadotJs().events.polkadotXcm.FeesPaid.is(event) ? event : undefined + ) + .filter((event) => event); + + return filteredEvents[0]!.data[1][0].fun.asFungible.toBigInt(); +}; + +export const getLastSentUmpMessageFee = async ( + context: DevModeContext, + baseDelivery: bigint, + txByteFee: bigint +) => { + const upwardMessages = await context.polkadotJs().query.parachainSystem.upwardMessages(); + expect(upwardMessages.length > 0, "There is no upward message").to.be.true; + const sentXcm = upwardMessages[0]; + + // We need to slice once to get to the actual message (version) + const messageBytes = sentXcm.slice(1); + + const txPrice = baseDelivery + txByteFee * BigInt(messageBytes.length); + const deliveryFeeFactor = await context + .polkadotJs() + .query.parachainSystem.upwardDeliveryFeeFactor(); + const fee = (BigInt(deliveryFeeFactor.toString()) * txPrice) / BigInt(10 ** 18); + return fee; +}; + +export const getLastSentHrmpMessageFee = async ( + context: DevModeContext, + paraId: number, + baseDelivery: bigint, + txByteFee: bigint +) => { + const sentXcm = await context.polkadotJs().query.xcmpQueue.outboundXcmpMessages(paraId, 0); + expect(sentXcm.length > 0, `There is no hrmp message for para id ${paraId}`).to.be.true; + // We need to slice 2 first bytes to get to the actual message (version plus HRMP) + const messageBytes = sentXcm.slice(2); + + const txPrice = baseDelivery + txByteFee * BigInt(messageBytes.length); + const deliveryFeeFactor = await context.polkadotJs().query.xcmpQueue.deliveryFeeFactor(paraId); + const fee = (BigInt(deliveryFeeFactor.toString()) * txPrice) / BigInt(10 ** 18); + return fee; +}; diff --git a/test/suites/dev/moonbase/test-precompile/test-precompile-xcm-transactor10.ts b/test/suites/dev/moonbase/test-precompile/test-precompile-xcm-transactor10.ts index 6a476192de..41cc53b3f0 100644 --- a/test/suites/dev/moonbase/test-precompile/test-precompile-xcm-transactor10.ts +++ b/test/suites/dev/moonbase/test-precompile/test-precompile-xcm-transactor10.ts @@ -10,6 +10,7 @@ import { registerForeignAsset, registerXcmTransactorAndContract, PRECOMPILE_XCM_TRANSACTOR_V3_ADDRESS, + getLastSentUmpMessageFee, } from "../../../../helpers"; const ADDRESS_RELAY_ASSETS = "0xffffffff1fcacbd218edc0eba20fc2308c778080"; @@ -19,6 +20,8 @@ describeSuite({ title: "Precompiles - xcm transactor V3", foundationMethods: "dev", testCases: ({ context, it, log }) => { + const baseDelivery: bigint = 100_000_000_000_000n; + const txByteFee = 100n; beforeAll(async () => { await registerForeignAsset(context, RELAY_SOURCE_LOCATION, relayAssetMetadata as any); await registerXcmTransactorAndContract(context); @@ -49,8 +52,10 @@ describeSuite({ const { result } = await context.createBlock(rawTx); expectEVMResult(result!.events, "Succeed"); + const xcmDeliveryFees = await getLastSentUmpMessageFee(context, baseDelivery, txByteFee); + // 1000 fee for the relay is paid with relay assets - await verifyLatestBlockFees(context); + await verifyLatestBlockFees(context, 0n, xcmDeliveryFees); }, }); }, diff --git a/test/suites/dev/moonbase/test-precompile/test-precompile-xcm-transactor3.ts b/test/suites/dev/moonbase/test-precompile/test-precompile-xcm-transactor3.ts index d8b78db6b2..e6568b8393 100644 --- a/test/suites/dev/moonbase/test-precompile/test-precompile-xcm-transactor3.ts +++ b/test/suites/dev/moonbase/test-precompile/test-precompile-xcm-transactor3.ts @@ -8,6 +8,7 @@ import { expectEVMResult, registerForeignAsset, registerXcmTransactorAndContract, + getLastSentUmpMessageFee, } from "../../../../helpers"; const ADDRESS_RELAY_ASSETS = "0xffffffff1fcacbd218edc0eba20fc2308c778080"; @@ -17,6 +18,8 @@ describeSuite({ title: "Precompiles - xcm transactor", foundationMethods: "dev", testCases: ({ context, it }) => { + const baseDelivery: bigint = 100_000_000_000_000n; + const txByteFee = 100n; beforeAll(async () => { await registerForeignAsset(context, RELAY_SOURCE_LOCATION, relayAssetMetadata as any); await registerXcmTransactorAndContract(context); @@ -45,8 +48,10 @@ describeSuite({ const { result } = await context.createBlock(rawTxn); expectEVMResult(result!.events, "Succeed"); + const xcmDeliveryFees = await getLastSentUmpMessageFee(context, baseDelivery, txByteFee); + // 1000 fee for the relay is paid with relay assets - await verifyLatestBlockFees(context); + await verifyLatestBlockFees(context, 0n, xcmDeliveryFees); }, }); }, diff --git a/test/suites/dev/moonbase/test-precompile/test-precompile-xcm-transactor4.ts b/test/suites/dev/moonbase/test-precompile/test-precompile-xcm-transactor4.ts index 57262ca817..e3d684fef9 100644 --- a/test/suites/dev/moonbase/test-precompile/test-precompile-xcm-transactor4.ts +++ b/test/suites/dev/moonbase/test-precompile/test-precompile-xcm-transactor4.ts @@ -5,6 +5,7 @@ import { verifyLatestBlockFees, expectEVMResult, registerXcmTransactorAndContract, + getLastSentUmpMessageFee, } from "../../../../helpers"; describeSuite({ @@ -12,6 +13,8 @@ describeSuite({ title: "Precompiles - xcm transactor", foundationMethods: "dev", testCases: ({ context, it }) => { + const baseDelivery: bigint = 100_000_000_000_000n; + const txByteFee = 100n; beforeAll(async () => { await registerXcmTransactorAndContract(context); }); @@ -38,8 +41,10 @@ describeSuite({ const { result } = await context.createBlock(rawTxn); expectEVMResult(result!.events, "Succeed"); + const xcmDeliveryFees = await getLastSentUmpMessageFee(context, baseDelivery, txByteFee); + // 1000 fee for the relay is paid with relay assets - await verifyLatestBlockFees(context); + await verifyLatestBlockFees(context, 0n, xcmDeliveryFees); }, }); }, diff --git a/test/suites/dev/moonbase/test-precompile/test-precompile-xcm-transactor5.ts b/test/suites/dev/moonbase/test-precompile/test-precompile-xcm-transactor5.ts index 393a42226a..0b0f7dd0cc 100644 --- a/test/suites/dev/moonbase/test-precompile/test-precompile-xcm-transactor5.ts +++ b/test/suites/dev/moonbase/test-precompile/test-precompile-xcm-transactor5.ts @@ -5,6 +5,7 @@ import { verifyLatestBlockFees, expectEVMResult, registerXcmTransactorAndContract, + getLastSentUmpMessageFee, } from "../../../../helpers"; describeSuite({ @@ -12,6 +13,8 @@ describeSuite({ title: "Precompiles - xcm transactor V2", foundationMethods: "dev", testCases: ({ context, it, log }) => { + const baseDelivery: bigint = 100_000_000_000_000n; + const txByteFee = 100n; beforeAll(async () => { await registerXcmTransactorAndContract(context); }); @@ -39,8 +42,10 @@ describeSuite({ const { result } = await context.createBlock(rawTxn); expectEVMResult(result!.events, "Succeed"); + const xcmDeliveryFees = await getLastSentUmpMessageFee(context, baseDelivery, txByteFee); + // 1000 fee for the relay is paid with relay assets - await verifyLatestBlockFees(context); + await verifyLatestBlockFees(context, 0n, xcmDeliveryFees); }, }); }, diff --git a/test/suites/dev/moonbase/test-precompile/test-precompile-xcm-transactor6.ts b/test/suites/dev/moonbase/test-precompile/test-precompile-xcm-transactor6.ts index 453e880373..7c54dc1e46 100644 --- a/test/suites/dev/moonbase/test-precompile/test-precompile-xcm-transactor6.ts +++ b/test/suites/dev/moonbase/test-precompile/test-precompile-xcm-transactor6.ts @@ -8,6 +8,7 @@ import { expectEVMResult, registerForeignAsset, registerXcmTransactorAndContract, + getLastSentUmpMessageFee, } from "../../../../helpers"; const ADDRESS_RELAY_ASSETS = "0xffffffff1fcacbd218edc0eba20fc2308c778080"; @@ -17,6 +18,8 @@ describeSuite({ title: "Precompiles - xcm transactor V2", foundationMethods: "dev", testCases: ({ context, it, log }) => { + const baseDelivery: bigint = 100_000_000_000_000n; + const txByteFee = 100n; beforeAll(async () => { await registerForeignAsset(context, RELAY_SOURCE_LOCATION, relayAssetMetadata as any); await registerXcmTransactorAndContract(context); @@ -47,8 +50,10 @@ describeSuite({ const { result } = await context.createBlock(rawTxn); expectEVMResult(result!.events, "Succeed"); + const xcmDeliveryFees = await getLastSentUmpMessageFee(context, baseDelivery, txByteFee); + // 1000 fee for the relay is paid with relay assets - await verifyLatestBlockFees(context); + await verifyLatestBlockFees(context, 0n, xcmDeliveryFees); }, }); }, diff --git a/test/suites/dev/moonbase/test-precompile/test-precompile-xcm-transactor9.ts b/test/suites/dev/moonbase/test-precompile/test-precompile-xcm-transactor9.ts index dd0ee0ce04..4f57ec53cf 100644 --- a/test/suites/dev/moonbase/test-precompile/test-precompile-xcm-transactor9.ts +++ b/test/suites/dev/moonbase/test-precompile/test-precompile-xcm-transactor9.ts @@ -7,6 +7,7 @@ import { expectEVMResult, registerXcmTransactorAndContract, PRECOMPILE_XCM_TRANSACTOR_V3_ADDRESS, + getLastSentUmpMessageFee, } from "../../../../helpers"; describeSuite({ @@ -14,6 +15,8 @@ describeSuite({ title: "Precompiles - xcm transactor V3", foundationMethods: "dev", testCases: ({ context, it, log }) => { + const baseDelivery: bigint = 100_000_000_000_000n; + const txByteFee = 100n; beforeAll(async () => { await registerXcmTransactorAndContract(context); }); @@ -43,8 +46,10 @@ describeSuite({ const { result } = await context.createBlock(rawTx); expectEVMResult(result!.events, "Succeed"); + const xcmDeliveryFees = await getLastSentUmpMessageFee(context, baseDelivery, txByteFee); + // 1000 fee for the relay is paid with relay assets - await verifyLatestBlockFees(context); + await verifyLatestBlockFees(context, 0n, xcmDeliveryFees); }, }); }, diff --git a/test/suites/dev/moonbase/test-precompile/test-precompile-xtokens.ts b/test/suites/dev/moonbase/test-precompile/test-precompile-xtokens.ts index d5872abc4a..fa783ab5c7 100644 --- a/test/suites/dev/moonbase/test-precompile/test-precompile-xtokens.ts +++ b/test/suites/dev/moonbase/test-precompile/test-precompile-xtokens.ts @@ -5,6 +5,7 @@ import { verifyLatestBlockFees, expectEVMResult, DEFAULT_TXN_MAX_BASE_FEE, + getLastSentUmpMessageFee, } from "../../../../helpers"; import { encodeFunctionData } from "viem"; @@ -13,6 +14,8 @@ describeSuite({ title: "Precompiles - xtokens", foundationMethods: "dev", testCases: ({ context, it, log }) => { + const baseDelivery: bigint = 100_000_000_000_000n; + const txByteFee = 100n; beforeAll(async function () { await context.deployContract!("XTokensInstance"); }); @@ -56,8 +59,10 @@ describeSuite({ const gasPrice = receipt.effectiveGasPrice; const fees = receipt.gasUsed * gasPrice; expectEVMResult(result!.events, "Succeed"); - await verifyLatestBlockFees(context, amountTransferred); - expect(balBefore - balAfter).to.equal(amountTransferred + fees); + + const xcmDeliveryFees = await getLastSentUmpMessageFee(context, baseDelivery, txByteFee); + await verifyLatestBlockFees(context, amountTransferred, xcmDeliveryFees); + expect(balBefore - balAfter).to.equal(amountTransferred + fees + xcmDeliveryFees); }, }); @@ -102,8 +107,9 @@ describeSuite({ const gasPrice = receipt.effectiveGasPrice; const fees = receipt.gasUsed * gasPrice; - expect(balBefore - balAfter).to.equal(amountTransferred + fee + fees); - await verifyLatestBlockFees(context, amountTransferred + fee); + const xcmDeliveryFees = await getLastSentUmpMessageFee(context, baseDelivery, txByteFee); + expect(balBefore - balAfter).to.equal(amountTransferred + fee + fees + xcmDeliveryFees); + await verifyLatestBlockFees(context, amountTransferred + fee, xcmDeliveryFees); }, }); @@ -162,8 +168,10 @@ describeSuite({ const gasPrice = receipt.effectiveGasPrice; const fees = receipt.gasUsed * gasPrice; - expect(balBefore - balAfter).to.equal(amountTransferred + fees); - await verifyLatestBlockFees(context, amountTransferred); + const xcmDeliveryFees = await getLastSentUmpMessageFee(context, baseDelivery, txByteFee); + + expect(balBefore - balAfter).to.equal(amountTransferred + fees + xcmDeliveryFees); + await verifyLatestBlockFees(context, amountTransferred, xcmDeliveryFees); }, }); @@ -223,8 +231,10 @@ describeSuite({ const gasPrice = receipt.effectiveGasPrice; const fees = receipt.gasUsed * gasPrice; - expect(balBefore - balAfter).to.equal(amountTransferred + fee + fees); - await verifyLatestBlockFees(context, amountTransferred + fee); + const xcmDeliveryFees = await getLastSentUmpMessageFee(context, baseDelivery, txByteFee); + + expect(balBefore - balAfter).to.equal(amountTransferred + fee + fees + xcmDeliveryFees); + await verifyLatestBlockFees(context, amountTransferred + fee, xcmDeliveryFees); }, }); @@ -273,8 +283,10 @@ describeSuite({ const gasPrice = receipt.effectiveGasPrice; const fees = receipt.gasUsed * gasPrice; - expect(balBefore - balAfter).to.equal(amountTransferred + fees); - await verifyLatestBlockFees(context, amountTransferred); + const xcmDeliveryFees = await getLastSentUmpMessageFee(context, baseDelivery, txByteFee); + + expect(balBefore - balAfter).to.equal(amountTransferred + fees + xcmDeliveryFees); + await verifyLatestBlockFees(context, amountTransferred, xcmDeliveryFees); }, }); @@ -346,9 +358,11 @@ describeSuite({ expectEVMResult(result!.events, "Succeed"); + const xcmDeliveryFees = await getLastSentUmpMessageFee(context, baseDelivery, txByteFee); + const fees = receipt.gasUsed * BigInt(DEFAULT_TXN_MAX_BASE_FEE); - expect(balBefore - balAfter).to.equal(amountTransferred + fees); - await verifyLatestBlockFees(context, amountTransferred); + expect(balBefore - balAfter).to.equal(amountTransferred + fees + xcmDeliveryFees); + await verifyLatestBlockFees(context, amountTransferred, xcmDeliveryFees); }, }); }, diff --git a/test/suites/dev/moonbase/test-xcm-v4/test-xcm-delivery-fees-send-hrmp.ts b/test/suites/dev/moonbase/test-xcm-v4/test-xcm-delivery-fees-send-hrmp.ts new file mode 100644 index 0000000000..1b1c543d54 --- /dev/null +++ b/test/suites/dev/moonbase/test-xcm-v4/test-xcm-delivery-fees-send-hrmp.ts @@ -0,0 +1,71 @@ +import { describeSuite, expect } from "@moonwall/cli"; +import { alith } from "@moonwall/util"; +import { + extractPaidDeliveryFees, + getLastSentHrmpMessageFee, + XcmFragment, + mockHrmpChannelExistanceTx, +} from "../../../../helpers/xcm"; + +describeSuite({ + id: "D014130", + title: "XCM Delivery fees - Send HRMP message", + foundationMethods: "dev", + testCases: ({ context, it }) => { + const baseDelivery: bigint = 100_000_000_000_000n; + const destinationPara = 2000; + const txByteFee = 100n; + + it({ + id: "T01", + title: "Should succeed calling XCM PolkadotXcm send horizontal", + test: async function () { + const mockHrmp2000Tx = context + .polkadotJs() + .tx.sudo.sudo(mockHrmpChannelExistanceTx(context, destinationPara, 1000, 102400, 102400)); + let aliceNonce = ( + await context.polkadotJs().query.system.account(alith.address) + ).nonce.toNumber(); + + const xcmMessage = new XcmFragment({ + assets: [], + }) + .clear_origin() + .as_v4(); + + const destMultilocation = { + parents: 1, + interior: { + X1: [ + { + Parachain: destinationPara, + }, + ], + }, + }; + + const dest = { + V4: destMultilocation, + }; + const tx = context.polkadotJs().tx.polkadotXcm.send(dest, xcmMessage); + + await context.createBlock( + [ + await mockHrmp2000Tx.signAsync(alith, { nonce: aliceNonce++ }), + await tx.signAsync(alith, { nonce: aliceNonce++ }), + ], + { allowFailures: false } + ); + + const fee = await getLastSentHrmpMessageFee( + context, + destinationPara, + baseDelivery, + txByteFee + ); + const paid = await extractPaidDeliveryFees(context); + expect(paid).to.be.equal(fee); + }, + }); + }, +}); diff --git a/test/suites/dev/moonbase/test-xcm-v4/test-xcm-delivery-fees-send-ump.ts b/test/suites/dev/moonbase/test-xcm-v4/test-xcm-delivery-fees-send-ump.ts new file mode 100644 index 0000000000..f466fab018 --- /dev/null +++ b/test/suites/dev/moonbase/test-xcm-v4/test-xcm-delivery-fees-send-ump.ts @@ -0,0 +1,46 @@ +import { describeSuite, expect } from "@moonwall/cli"; +import { alith } from "@moonwall/util"; +import { + MultiLocation, + extractPaidDeliveryFees, + getLastSentUmpMessageFee, + XcmFragment, +} from "../../../../helpers/xcm"; + +describeSuite({ + id: "D014131", + title: "XCM Delivery fees - Send upward message", + foundationMethods: "dev", + testCases: ({ context, it }) => { + const baseDelivery: bigint = 100_000_000_000_000n; + const txByteFee = 100n; + + it({ + id: "T01", + title: "Should succeed calling PolkadotXcm XCM send upwards", + test: async function () { + const xcmMessage = new XcmFragment({ + assets: [], + }) + .clear_origin() + .as_v4(); + + const destMultilocation: MultiLocation = { + parents: 1, + interior: { Here: null }, + }; + + const dest = { + V4: destMultilocation, + }; + const xcmSendTx = context.polkadotJs().tx.polkadotXcm.send(dest, xcmMessage); + + await context.createBlock(await xcmSendTx.signAsync(alith), { allowFailures: false }); + + const fee = await getLastSentUmpMessageFee(context, baseDelivery, txByteFee); + const paid = await extractPaidDeliveryFees(context); + expect(paid).to.be.equal(fee); + }, + }); + }, +}); diff --git a/test/suites/dev/moonbase/test-xcm-v4/test-xcm-delivery-fees-xcm-transactor-hrmp.ts b/test/suites/dev/moonbase/test-xcm-v4/test-xcm-delivery-fees-xcm-transactor-hrmp.ts new file mode 100644 index 0000000000..0acae44167 --- /dev/null +++ b/test/suites/dev/moonbase/test-xcm-v4/test-xcm-delivery-fees-xcm-transactor-hrmp.ts @@ -0,0 +1,95 @@ +import "@moonbeam-network/api-augment"; +import { describeSuite, expect } from "@moonwall/cli"; +import { alith } from "@moonwall/util"; +import { fromBytes } from "viem"; +import { getLastSentHrmpMessageFee, mockHrmpChannelExistanceTx } from "../../../../helpers"; + +describeSuite({ + id: "D014133", + title: "XCM Delivery fees - Send horizontal message through XCM Transactor", + foundationMethods: "dev", + testCases: ({ context, it }) => { + const baseDelivery: bigint = 100_000_000_000_000n; + const txByteFee = 100n; + const destParaId = 2000; + + it({ + id: "T01", + title: "Should succeed calling transactThroughSigned", + test: async function () { + const mockHrmp2000Tx = context + .polkadotJs() + .tx.sudo.sudo(mockHrmpChannelExistanceTx(context, destParaId, 1000, 102400, 102400)); + let aliceNonce = ( + await context.polkadotJs().query.system.account(alith.address) + ).nonce.toNumber(); + + // Parachain 2000 as destination + const dest = { + V4: { + parents: 1, + interior: { + X1: [{ Parachain: destParaId }], + }, + }, + }; + + const transactCall = fromBytes(new Uint8Array([0x01]), "hex"); + const transactWeights = context + .polkadotJs() + .createType("PalletXcmTransactorTransactWeights", { + transactRequiredWeightAtMost: { refTime: 10000, proofSize: 10000 }, + overallWeight: { Limited: { refTime: 10000, proofSize: 10000 } }, + }); + + const fee = context.polkadotJs().createType("PalletXcmTransactorCurrencyPayment", { + currency: { + AsMultiLocation: { + V4: { + parents: 1, + interior: { + X2: [{ Parachain: destParaId }, { PalletInstance: 3 }], + }, + }, + }, + }, + feeAmount: 10000, + }); + + const transactorTx = context + .polkadotJs() + .tx.xcmTransactor.transactThroughSigned( + dest, + fee as any, + transactCall, + transactWeights as any, + false + ); + + await context.createBlock( + [ + await mockHrmp2000Tx.signAsync(alith, { nonce: aliceNonce++ }), + await transactorTx.signAsync(alith, { nonce: aliceNonce++ }), + ], + { allowFailures: false } + ); + + const deliveryFee = await getLastSentHrmpMessageFee( + context, + destParaId, + baseDelivery, + txByteFee + ); + + // Delivery fee (total): + // DeliveryFeeFactor * [BaseDeliveryFee + (TransactionByteFee * XCM Msg Bytes)] + // + // DeliveryFeeFactor: 1 + // BaseDeliveryFee: 100000000000000 + // TransactionByteFee: 100 + // XCM Msg Bytes: 55 + expect(deliveryFee).to.be.equal(100000000005500n); + }, + }); + }, +}); diff --git a/test/suites/dev/moonbase/test-xcm-v4/test-xcm-delivery-fees-xcm-transactor-ump.ts b/test/suites/dev/moonbase/test-xcm-v4/test-xcm-delivery-fees-xcm-transactor-ump.ts new file mode 100644 index 0000000000..402199a199 --- /dev/null +++ b/test/suites/dev/moonbase/test-xcm-v4/test-xcm-delivery-fees-xcm-transactor-ump.ts @@ -0,0 +1,78 @@ +import "@moonbeam-network/api-augment"; +import { describeSuite, expect } from "@moonwall/cli"; +import { alith } from "@moonwall/util"; +import { fromBytes } from "viem"; +import { getLastSentUmpMessageFee } from "../../../../helpers"; + +describeSuite({ + id: "D014132", + title: "XCM Delivery fees - Send upward message through XCM Transactor", + foundationMethods: "dev", + testCases: ({ context, it }) => { + const baseDelivery: bigint = 100_000_000_000_000n; + const txByteFee = 100n; + + it({ + id: "T01", + title: "Should succeed calling transactThroughSigned", + test: async function () { + // Use the relay as destination + const dest = { + V4: { + parents: 1, + interior: { + Here: null, + }, + }, + }; + + const transactCall = fromBytes(new Uint8Array([0x01]), "hex"); + const transactWeights = context + .polkadotJs() + .createType("PalletXcmTransactorTransactWeights", { + transactRequiredWeightAtMost: { refTime: 10000, proofSize: 10000 }, + overallWeight: { Limited: { refTime: 10000, proofSize: 10000 } }, + }); + + const fee = context.polkadotJs().createType("PalletXcmTransactorCurrencyPayment", { + currency: { + AsMultiLocation: { + V4: { + parents: 1, + interior: { + Here: null, + }, + }, + }, + }, + feeAmount: 10000, + }); + + await context.createBlock( + context + .polkadotJs() + .tx.xcmTransactor.transactThroughSigned( + dest, + fee as any, + transactCall, + transactWeights as any, + false + ) + .signAsync(alith), + { allowFailures: false } + ); + + const deliveryFee = await getLastSentUmpMessageFee(context, baseDelivery, txByteFee); + + // Delivery fee (total): + // DeliveryFeeFactor * [BaseDeliveryFee + (TransactionByteFee * XCM Msg Bytes)] + // + // DeliveryFeeFactor: 1 + // BaseDeliveryFee: 100000000000000 + // TransactionByteFee: 100 + // XCM Msg Bytes: 51 + expect(deliveryFee).to.be.equal(100000000005100n); + }, + }); + }, +}); diff --git a/test/suites/dev/moonbase/test-xcm-v4/test-xcm-delivery-fees-xtokens-hrmp.ts b/test/suites/dev/moonbase/test-xcm-v4/test-xcm-delivery-fees-xtokens-hrmp.ts new file mode 100644 index 0000000000..027ddc4e92 --- /dev/null +++ b/test/suites/dev/moonbase/test-xcm-v4/test-xcm-delivery-fees-xtokens-hrmp.ts @@ -0,0 +1,75 @@ +import "@moonbeam-network/api-augment"; +import { describeSuite, expect } from "@moonwall/cli"; +import { alith, GLMR } from "@moonwall/util"; +import { getLastSentHrmpMessageFee, mockHrmpChannelExistanceTx } from "../../../../helpers"; + +describeSuite({ + id: "D014135", + title: "XCM Delivery fees - Send horizontal message through Xtokens pallet", + foundationMethods: "dev", + testCases: ({ context, it }) => { + const baseDelivery: bigint = 100_000_000_000_000n; + const txByteFee = 100n; + const destParaId = 2000; + + it({ + id: "T01", + title: "Should succeed calling transfer", + test: async function () { + const mockHrmp2000Tx = context + .polkadotJs() + .tx.sudo.sudo(mockHrmpChannelExistanceTx(context, destParaId, 1000, 102400, 102400)); + let aliceNonce = ( + await context.polkadotJs().query.system.account(alith.address) + ).nonce.toNumber(); + + // 32 byte account + const destination_address = + "0101010101010101010101010101010101010101010101010101010101010101"; + + const xTokensTx = context.polkadotJs().tx.xTokens.transfer( + "SelfReserve", + 100n * GLMR, + { + V4: { + parents: 1n, + interior: { + X2: [ + { Parachain: destParaId }, + { AccountId32: { network: null, key: destination_address } }, + ], + }, + }, + } as any, + { + Limited: { refTime: 4000000000, proofSize: 64 * 1024 }, + } + ); + + await context.createBlock( + [ + await mockHrmp2000Tx.signAsync(alith, { nonce: aliceNonce++ }), + await xTokensTx.signAsync(alith, { nonce: aliceNonce++ }), + ], + { allowFailures: false } + ); + + const deliveryFee = await getLastSentHrmpMessageFee( + context, + destParaId, + baseDelivery, + txByteFee + ); + + // Delivery fee (total): + // DeliveryFeeFactor * [BaseDeliveryFee + (TransactionByteFee * XCM Msg Bytes)] + // + // DeliveryFeeFactor: 1 + // BaseDeliveryFee: 100000000000000 + // TransactionByteFee: 100 + // XCM Msg Bytes: 89 + expect(deliveryFee).to.be.equal(100000000008900n); + }, + }); + }, +}); diff --git a/test/suites/dev/moonbase/test-xcm-v4/test-xcm-delivery-fees-xtokens-ump.ts b/test/suites/dev/moonbase/test-xcm-v4/test-xcm-delivery-fees-xtokens-ump.ts new file mode 100644 index 0000000000..36b8505873 --- /dev/null +++ b/test/suites/dev/moonbase/test-xcm-v4/test-xcm-delivery-fees-xtokens-ump.ts @@ -0,0 +1,57 @@ +import "@moonbeam-network/api-augment"; +import { describeSuite, expect } from "@moonwall/cli"; +import { alith, GLMR } from "@moonwall/util"; +import { getLastSentUmpMessageFee } from "../../../../helpers"; + +describeSuite({ + id: "D014134", + title: "XCM Delivery fees - Send upward message through Xtokens pallet", + foundationMethods: "dev", + testCases: ({ context, it }) => { + const baseDelivery: bigint = 100_000_000_000_000n; + const txByteFee = 100n; + + it({ + id: "T01", + title: "Should succeed calling transfer", + test: async function () { + // 32 byte account + const destination_address = + "0101010101010101010101010101010101010101010101010101010101010101"; + + await context.createBlock( + context + .polkadotJs() + .tx.xTokens.transfer( + "SelfReserve", + 100n * GLMR, + { + V4: { + parents: 1n, + interior: { + X1: [{ AccountId32: { network: null, key: destination_address } }], + }, + }, + } as any, + { + Limited: { refTime: 4000000000, proofSize: 64 * 1024 }, + } + ) + .signAsync(alith), + { allowFailures: false } + ); + + const deliveryFee = await getLastSentUmpMessageFee(context, baseDelivery, txByteFee); + + // Delivery fee (total): + // DeliveryFeeFactor * [BaseDeliveryFee + (TransactionByteFee * XCM Msg Bytes)] + // + // DeliveryFeeFactor: 1 + // BaseDeliveryFee: 100000000000000 + // TransactionByteFee: 100 + // XCM Msg Bytes: 89 + expect(deliveryFee).to.be.equal(100000000008900n); + }, + }); + }, +}); diff --git a/test/suites/dev/moonbase/test-xcm-v4/test-xcm-erc20-transfer-two-ERC20.ts b/test/suites/dev/moonbase/test-xcm-v4/test-xcm-erc20-transfer-two-ERC20.ts index ab0b2a7a46..e9b32791d9 100644 --- a/test/suites/dev/moonbase/test-xcm-v4/test-xcm-erc20-transfer-two-ERC20.ts +++ b/test/suites/dev/moonbase/test-xcm-v4/test-xcm-erc20-transfer-two-ERC20.ts @@ -8,6 +8,7 @@ import { XcmFragmentConfig, injectHrmpMessageAndSeal, sovereignAccountOfSibling, + getLastSentUmpMessageFee, } from "../../../../helpers/xcm.js"; export const ERC20_TOTAL_SUPPLY = 1_000_000_000n; @@ -17,6 +18,8 @@ describeSuite({ title: "Mock XCM - Send two local ERC20", foundationMethods: "dev", testCases: ({ context, it, log }) => { + const baseDelivery: bigint = 100_000_000_000_000n; + const txByteFee = 100n; let erc20ContractAddress1: string; let erc20ContractAddress2: string; @@ -87,8 +90,10 @@ describeSuite({ await polkadotJs.query.system.account(ALITH_ADDRESS) ).data.free.toBigInt(); + const xcmDeliveryFee = await getLastSentUmpMessageFee(context, baseDelivery, txByteFee); + // Fees should have been spent - expect(balanceAfter).to.equal(balanceBefore - fees); + expect(balanceAfter).to.equal(balanceBefore - fees - xcmDeliveryFee); // Erc20 tokens of the first contract should have been spent expect( diff --git a/test/suites/dev/moonbase/test-xcm-v4/test-xcm-erc20-transfer.ts b/test/suites/dev/moonbase/test-xcm-v4/test-xcm-erc20-transfer.ts index 01eacd6993..cd25a28de9 100644 --- a/test/suites/dev/moonbase/test-xcm-v4/test-xcm-erc20-transfer.ts +++ b/test/suites/dev/moonbase/test-xcm-v4/test-xcm-erc20-transfer.ts @@ -9,6 +9,7 @@ import { XcmFragmentConfig, injectHrmpMessageAndSeal, sovereignAccountOfSibling, + getLastSentUmpMessageFee, } from "../../../../helpers/xcm.js"; export const ERC20_TOTAL_SUPPLY = 1_000_000_000n; @@ -18,6 +19,8 @@ describeSuite({ title: "Mock XCM - Send local erc20", foundationMethods: "dev", testCases: ({ context, it }) => { + const baseDelivery: bigint = 100_000_000_000_000n; + const txByteFee = 100n; let erc20ContractAddress: string; let polkadotJs: ApiPromise; @@ -75,9 +78,10 @@ describeSuite({ ).data.free.toBigInt(); const fees = await getTransactionFees(context, result!.hash); + const xcmDeliveryFee = await getLastSentUmpMessageFee(context, baseDelivery, txByteFee); // Fees should have been spent - expect(balanceAfter).to.equal(balanceBefore - fees); + expect(balanceAfter).to.equal(balanceBefore - fees - xcmDeliveryFee); expect( await context.readContract!({