From 50ada66edbe6f440528cc1bcbc041d4dc3e27eed Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Wed, 24 May 2023 15:13:34 -0300 Subject: [PATCH 1/3] Add an example of para to relay reserve asset transfer --- examples/src/first_look.rs | 2 +- examples/src/holding_modifiers/mod.rs | 4 +- examples/src/simple_test_net/mod.rs | 108 +++++++++++++++++++++- examples/src/simple_test_net/parachain.rs | 25 ++--- examples/src/transfers/reserve.rs | 50 +++++++++- examples/src/transfers/teleport.rs | 2 +- 6 files changed, 167 insertions(+), 24 deletions(-) diff --git a/examples/src/first_look.rs b/examples/src/first_look.rs index a7bbe2d..a846c12 100644 --- a/examples/src/first_look.rs +++ b/examples/src/first_look.rs @@ -36,7 +36,7 @@ mod tests { assert_ok!(ParachainPalletXcm::execute( parachain::RuntimeOrigin::signed(ALICE), Box::new(xcm::VersionedXcm::from(message.clone())), - 10.into() + (100_000_000_000, 100_000_000_000).into() )); // Check if the funds are subtracted from the account of Alice and added to the account of Bob. diff --git a/examples/src/holding_modifiers/mod.rs b/examples/src/holding_modifiers/mod.rs index e22d329..264ca9a 100644 --- a/examples/src/holding_modifiers/mod.rs +++ b/examples/src/holding_modifiers/mod.rs @@ -82,7 +82,7 @@ mod tests { ParaA::execute_with(|| { assert_eq!(parachain::exchange_assets(), vec![(Here, 5 * CENTS).into()].into()); - assert_eq!(ParachainAssets::balance(1u128, &ALICE), INITIAL_BALANCE + 10 * CENTS); + assert_eq!(ParachainAssets::balance(0, &ALICE), INITIAL_BALANCE + 10 * CENTS); assert_eq!(ParachainBalances::free_balance(ALICE), INITIAL_BALANCE + 5 * CENTS); }) } @@ -130,7 +130,7 @@ mod tests { parachain::exchange_assets(), vec![(Parent, 5 * CENTS).into(), (Here, 5 * CENTS).into()].into() ); - assert_eq!(ParachainAssets::balance(1u128, &ALICE), INITIAL_BALANCE + 5 * CENTS); + assert_eq!(ParachainAssets::balance(0, &ALICE), INITIAL_BALANCE + 5 * CENTS); assert_eq!(ParachainBalances::free_balance(ALICE), INITIAL_BALANCE + 5 * CENTS); }) } diff --git a/examples/src/simple_test_net/mod.rs b/examples/src/simple_test_net/mod.rs index 8dc24b8..3046066 100644 --- a/examples/src/simple_test_net/mod.rs +++ b/examples/src/simple_test_net/mod.rs @@ -17,7 +17,10 @@ pub mod parachain; pub mod relay_chain; -use frame_support::{assert_ok, sp_tracing, traits::GenesisBuild}; +use core::{borrow::Borrow, marker::PhantomData}; + +use frame_support::{sp_tracing, traits::GenesisBuild}; +use sp_core::blake2_256; use xcm::prelude::*; use xcm_executor::traits::Convert; use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain}; @@ -56,7 +59,7 @@ decl_test_parachain! { Runtime = parachain::Runtime, XcmpMessageHandler = parachain::MsgQueue, DmpMessageHandler = parachain::MsgQueue, - new_ext = para_ext(3), + new_ext = para_ext(2), } } @@ -89,23 +92,66 @@ pub fn parachain_sovereign_account_id(para: u32) -> relay_chain::AccountId { relay_chain::SovereignAccountOf::convert(location.into()).unwrap() } +pub fn parachain_account_sovereign_account_id( + para: u32, + who: sp_runtime::AccountId32, +) -> relay_chain::AccountId { + let location = ( + Parachain(para), + AccountId32 { network: Some(relay_chain::RelayNetwork::get()), id: who.into() }, + ); + relay_chain::SovereignAccountOf::convert(location.into()).unwrap() +} + +pub fn sibling_sovereign_account_id(para: u32) -> parachain::AccountId { + let location = (Parent, Parachain(para)); + parachain::SovereignAccountOf::convert(location.into()).unwrap() +} + +pub fn sibling_account_sovereign_account_id( + para: u32, + who: sp_runtime::AccountId32, +) -> parachain::AccountId { + let location = (Parent, Parachain(para), AccountId32 { network: None, id: who.into() }); + parachain::SovereignAccountOf::convert(location.into()).unwrap() +} + +pub fn relay_account_sovereign_account_id(who: sp_runtime::AccountId32) -> parachain::AccountId { + let location = (Parent, AccountId32 { network: None, id: who.into() }); + parachain::SovereignAccountOf::convert(location.into()).unwrap() +} + pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { use parachain::{MsgQueue, Runtime, System}; let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let other_para_id: u32 = match para_id { + 1 => 2, + 2 => 1, + _ => panic!("No parachain exists with para_id = {para_id}"), + }; + pallet_balances::GenesisConfig:: { - balances: vec![(ALICE, INITIAL_BALANCE), (relay_sovereign_account_id(), INITIAL_BALANCE)], + balances: vec![ + (ALICE, INITIAL_BALANCE), + (relay_sovereign_account_id(), INITIAL_BALANCE), + (sibling_account_sovereign_account_id(other_para_id, ALICE), INITIAL_BALANCE), + ], } .assimilate_storage(&mut t) .unwrap(); pallet_assets::GenesisConfig:: { assets: vec![ - (1u128, ADMIN, false, 1u128), // Create derivative asset for relay's native token + (0u128, ADMIN, false, 1u128), // Create derivative asset for relay's native token + (other_para_id as u128, ADMIN, false, 1u128), // Create derivative asset for the other parachain's native token ], metadata: Default::default(), - accounts: vec![(1u128, ALICE, INITIAL_BALANCE)], + accounts: vec![ + (0u128, ALICE, INITIAL_BALANCE), + (other_para_id as u128, ALICE, INITIAL_BALANCE), + ], } .assimilate_storage(&mut t) .unwrap(); @@ -156,3 +202,55 @@ pub type ParachainPalletXcm = pallet_xcm::Pallet; pub type RelaychainBalances = pallet_balances::Pallet; pub type ParachainBalances = pallet_balances::Pallet; pub type ParachainAssets = pallet_assets::Pallet; + +/// Prefix for generating alias account for accounts coming +/// from chains that use 32 byte long representations. +pub const FOREIGN_CHAIN_PREFIX_PARA_32: [u8; 37] = *b"ForeignChainAliasAccountPrefix_Para32"; + +/// Prefix for generating alias account for accounts coming +/// from the relay chain using 32 byte long representations. +pub const FOREIGN_CHAIN_PREFIX_RELAY: [u8; 36] = *b"ForeignChainAliasAccountPrefix_Relay"; + +pub struct ForeignChainAliasAccount(PhantomData); +impl + Clone> Convert + for ForeignChainAliasAccount +{ + fn convert_ref(location: impl Borrow) -> Result { + let entropy = match location.borrow() { + // Used on the relay chain for sending paras that use 32 byte accounts + MultiLocation { + parents: 0, + interior: X2(Parachain(para_id), AccountId32 { id, .. }), + } => ForeignChainAliasAccount::::from_para_32(para_id, id, 0), + + // Used on para-chain for sending paras that use 32 byte accounts + MultiLocation { + parents: 1, + interior: X2(Parachain(para_id), AccountId32 { id, .. }), + } => ForeignChainAliasAccount::::from_para_32(para_id, id, 1), + + // Used on para-chain for sending from the relay chain + MultiLocation { parents: 1, interior: X1(AccountId32 { id, .. }) } => + ForeignChainAliasAccount::::from_relay_32(id, 1), + + // No other conversions provided + _ => return Err(()), + }; + + Ok(entropy.into()) + } + + fn reverse_ref(_: impl Borrow) -> Result { + Err(()) + } +} + +impl ForeignChainAliasAccount { + fn from_para_32(para_id: &u32, id: &[u8; 32], parents: u8) -> [u8; 32] { + (FOREIGN_CHAIN_PREFIX_PARA_32, para_id, id, parents).using_encoded(blake2_256) + } + + fn from_relay_32(id: &[u8; 32], parents: u8) -> [u8; 32] { + (FOREIGN_CHAIN_PREFIX_RELAY, id, parents).using_encoded(blake2_256) + } +} diff --git a/examples/src/simple_test_net/parachain.rs b/examples/src/simple_test_net/parachain.rs index 8ec22c2..3775dca 100644 --- a/examples/src/simple_test_net/parachain.rs +++ b/examples/src/simple_test_net/parachain.rs @@ -16,7 +16,7 @@ //! Parachain runtime mock. -use super::Balance; +use super::{Balance, ForeignChainAliasAccount}; use codec::{Decode, Encode}; use core::marker::PhantomData; use frame_support::{ @@ -43,11 +43,11 @@ use sp_runtime::{ use sp_std::{cell::RefCell, prelude::*}; use xcm::{latest::prelude::*, VersionedXcm}; use xcm_builder::{ - AccountId32Aliases, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, Case, - ConvertedConcreteId, CurrencyAdapter as XcmCurrencyAdapter, EnsureXcmOrigin, - FixedRateOfFungible, FixedWeightBounds, FungiblesAdapter, IsConcrete, NativeAsset, NoChecking, - NonFungiblesAdapter, ParentAsSuperuser, ParentIsPreset, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, + AccountId32Aliases, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, ConvertedConcreteId, + CurrencyAdapter as XcmCurrencyAdapter, EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, + FungiblesAdapter, IsConcrete, NativeAsset, NoChecking, NonFungiblesAdapter, ParentAsSuperuser, + ParentIsPreset, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, }; use xcm_executor::{ traits::{AssetExchange, Convert, JustTry}, @@ -58,6 +58,7 @@ pub type AccountId = AccountId32; pub type AssetIdForAssets = u128; pub type SovereignAccountOf = ( + ForeignChainAliasAccount, SiblingParachainConvertsVia, AccountId32Aliases, ParentIsPreset, @@ -220,15 +221,15 @@ parameter_types! { pub type LocalBalancesTransactor = XcmCurrencyAdapter, SovereignAccountOf, AccountId, ()>; -pub struct FromNativeAssetToFungible( - core::marker::PhantomData<(MultiLocation, AssetId)>, -); +pub struct FromMultiLocationToAsset(PhantomData<(MultiLocation, AssetId)>); impl Convert - for FromNativeAssetToFungible + for FromMultiLocationToAsset { fn convert(value: MultiLocation) -> Result { match value { - MultiLocation { parents: 1, interior: Here } => Ok(1 as AssetIdForAssets), + MultiLocation { parents: 1, interior: Here } => Ok(0 as AssetIdForAssets), + MultiLocation { parents: 1, interior: X1(Parachain(para_id)) } => + Ok(para_id as AssetIdForAssets), _ => Err(value), } } @@ -239,7 +240,7 @@ pub type ForeignAssetsTransactor = FungiblesAdapter< ConvertedConcreteId< AssetIdForAssets, Balance, - FromNativeAssetToFungible, + FromMultiLocationToAsset, JustTry, >, SovereignAccountOf, diff --git a/examples/src/transfers/reserve.rs b/examples/src/transfers/reserve.rs index 36bd7ac..46b8417 100644 --- a/examples/src/transfers/reserve.rs +++ b/examples/src/transfers/reserve.rs @@ -43,7 +43,7 @@ mod tests { (100_000_000_000, 100_000_000_000).into(), )); - assert_eq!(parachain::Assets::balance(1, &ALICE), INITIAL_BALANCE - withdraw_amount); + assert_eq!(parachain::Assets::balance(0, &ALICE), INITIAL_BALANCE - withdraw_amount); }); Relay::execute_with(|| { @@ -54,7 +54,7 @@ mod tests { }); ParaB::execute_with(|| { - assert_eq!(parachain::Assets::balance(1, &ALICE), INITIAL_BALANCE + withdraw_amount); + assert_eq!(parachain::Assets::balance(0, &ALICE), INITIAL_BALANCE + withdraw_amount); }); } @@ -102,7 +102,51 @@ mod tests { }); ParaB::execute_with(|| { - assert_eq!(parachain::Assets::balance(1, &ALICE), INITIAL_BALANCE + withdraw_amount); + assert_eq!(parachain::Assets::balance(0, &ALICE), INITIAL_BALANCE + withdraw_amount); + }); + } + + #[test] + fn reserve_backed_transfer_para_to_relay() { + MockNet::reset(); + + let withdraw_amount = 50 * CENTS; + + let message: Xcm = Xcm(vec![ + WithdrawAsset((Parent, withdraw_amount).into()), + InitiateReserveWithdraw { + assets: All.into(), + reserve: Parent.into(), + xcm: Xcm(vec![DepositAsset { + assets: All.into(), + beneficiary: Junction::AccountId32 { id: ALICE.into(), network: None }.into(), + }]), + }, + ]); + + ParaA::execute_with(|| { + assert_ok!(parachain::PolkadotXcm::execute( + parachain::RuntimeOrigin::signed(ALICE), + Box::new(xcm::VersionedXcm::V3(message.into())), + (100_000_000_000, 100_000_000_000).into(), + )); + + // ALICE's balance in the parachain decreases + assert_eq!(parachain::Assets::balance(0, &ALICE), INITIAL_BALANCE - withdraw_amount); + }); + + Relay::execute_with(|| { + // Parachain(1)'s sovereign account balance decreases + assert_eq!( + relay_chain::Balances::free_balance(parachain_sovereign_account_id(1)), + INITIAL_BALANCE - withdraw_amount + ); + + // ALICE's balance in the relay chain increases + assert_eq!( + relay_chain::Balances::free_balance(&ALICE), + INITIAL_BALANCE + withdraw_amount + ); }); } } diff --git a/examples/src/transfers/teleport.rs b/examples/src/transfers/teleport.rs index 0cc18d0..719bff3 100644 --- a/examples/src/transfers/teleport.rs +++ b/examples/src/transfers/teleport.rs @@ -49,7 +49,7 @@ mod tests { assert_eq!(parachain::MsgQueue::received_dmp(), vec![expected_message_received]); - assert_eq!(parachain::Assets::balance(1, &ALICE), INITIAL_BALANCE + teleport_amount); + assert_eq!(parachain::Assets::balance(0, &ALICE), INITIAL_BALANCE + teleport_amount); }); } From a8795f411b44327c002480d910da5b135002c713 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Thu, 25 May 2023 17:05:27 -0300 Subject: [PATCH 2/3] Enforce fees in barriers --- examples/src/expects/mod.rs | 72 +++++++++--- examples/src/first_look.rs | 9 +- examples/src/holding_modifiers/mod.rs | 6 +- examples/src/locks/mod.rs | 79 ++++++++----- examples/src/queries/mod.rs | 66 ++++++++--- examples/src/simple_test_net/mod.rs | 89 ++++++++++++--- examples/src/simple_test_net/parachain.rs | 68 +++++++++--- examples/src/simple_test_net/relay_chain.rs | 68 +++++++++--- examples/src/transact/mod.rs | 36 ++++-- examples/src/transfers/reserve.rs | 117 ++++++++++++++------ examples/src/transfers/teleport.rs | 116 ++++++------------- examples/src/trap_and_claim/mod.rs | 5 +- examples/src/version_subscription/mod.rs | 11 +- 13 files changed, 501 insertions(+), 241 deletions(-) diff --git a/examples/src/expects/mod.rs b/examples/src/expects/mod.rs index 976d9a5..a60ba39 100644 --- a/examples/src/expects/mod.rs +++ b/examples/src/expects/mod.rs @@ -7,7 +7,7 @@ mod tests { use xcm::latest::prelude::*; use xcm_simulator::TestExt; - const AMOUNT: u128 = 10; + const AMOUNT: u128 = 50 * CENTS; const QUERY_ID: u64 = 1234; /// Scenario: @@ -19,10 +19,16 @@ mod tests { #[test] fn expect_asset() { MockNet::reset(); + + let message_fee = relay_chain::estimate_message_fee(5); + ParaA::execute_with(|| { let message = Xcm(vec![ - WithdrawAsset((Here, AMOUNT).into()), - BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: WeightLimit::Unlimited }, + WithdrawAsset((Here, AMOUNT + message_fee).into()), + BuyExecution { + fees: (Here, message_fee).into(), + weight_limit: WeightLimit::Unlimited, + }, // Set the instructions that are executed when ExpectAsset does not pass. // In this case, reporting back an error to the Parachain. SetErrorHandler(Xcm(vec![ReportError(QueryResponseInfo { @@ -30,19 +36,24 @@ mod tests { query_id: QUERY_ID, max_weight: Weight::from_all(0), })])), - ExpectAsset((Here, AMOUNT + 10).into()), + ExpectAsset((Here, AMOUNT + 10 * CENTS).into()), // Add Instructions that do something with assets in holding when ExpectAsset passes. ]); assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone(),)); }); + let instruction_index_that_errored = 3; + // Check that QueryResponse message with ExpectationFalse error was received. ParaA::execute_with(|| { assert_eq!( parachain::MsgQueue::received_dmp(), vec![Xcm(vec![QueryResponse { query_id: QUERY_ID, - response: Response::ExecutionResult(Some((3, XcmError::ExpectationFalse))), + response: Response::ExecutionResult(Some(( + instruction_index_that_errored, + XcmError::ExpectationFalse + ))), max_weight: Weight::from_all(0), querier: Some(Here.into()), }])], @@ -58,10 +69,15 @@ mod tests { fn expect_origin() { MockNet::reset(); + let message_fee = relay_chain::estimate_message_fee(6); + ParaA::execute_with(|| { let message = Xcm(vec![ - WithdrawAsset((Here, AMOUNT).into()), - BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: WeightLimit::Unlimited }, + WithdrawAsset((Here, AMOUNT + message_fee).into()), + BuyExecution { + fees: (Here, message_fee).into(), + weight_limit: WeightLimit::Unlimited, + }, // Set the instructions that are executed when ExpectOrigin does not pass. // In this case, reporting back an error to the Parachain. SetErrorHandler(Xcm(vec![ReportError(QueryResponseInfo { @@ -76,13 +92,18 @@ mod tests { assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone(),)); }); + let instruction_index_that_errored = 4; + // Check that QueryResponse message with ExpectationFalse error was received. ParaA::execute_with(|| { assert_eq!( parachain::MsgQueue::received_dmp(), vec![Xcm(vec![QueryResponse { query_id: QUERY_ID, - response: Response::ExecutionResult(Some((4, XcmError::ExpectationFalse))), + response: Response::ExecutionResult(Some(( + instruction_index_that_errored, + XcmError::ExpectationFalse + ))), max_weight: Weight::from_all(0), querier: None, }])], @@ -98,10 +119,15 @@ mod tests { fn expect_pallet() { MockNet::reset(); + let message_fee = relay_chain::estimate_message_fee(5); + ParaA::execute_with(|| { let message = Xcm(vec![ - WithdrawAsset((Here, AMOUNT).into()), - BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: WeightLimit::Unlimited }, + WithdrawAsset((Here, message_fee).into()), + BuyExecution { + fees: (Here, message_fee).into(), + weight_limit: WeightLimit::Unlimited, + }, // Set the instructions that are executed when ExpectPallet does not pass. // In this case, reporting back an error to the Parachain. SetErrorHandler(Xcm(vec![ReportError(QueryResponseInfo { @@ -147,10 +173,15 @@ mod tests { fn expect_error() { MockNet::reset(); + let message_fee = relay_chain::estimate_message_fee(6); + ParaA::execute_with(|| { let message = Xcm(vec![ - WithdrawAsset((Here, AMOUNT).into()), - BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: WeightLimit::Unlimited }, + WithdrawAsset((Here, message_fee).into()), + BuyExecution { + fees: (Here, message_fee).into(), + weight_limit: WeightLimit::Unlimited, + }, // ReportError is only executed if the thrown error is the `VersionIncompatible` error. SetErrorHandler(Xcm(vec![ ExpectError(Some((1, XcmError::VersionIncompatible))), @@ -174,7 +205,7 @@ mod tests { // Does not receive a message as the incorrect error was thrown during execution. ParaA::execute_with(|| { - assert_eq!(parachain::MsgQueue::received_dmp(), vec![],); + assert_eq!(parachain::MsgQueue::received_dmp(), vec![]); }); } @@ -189,6 +220,7 @@ mod tests { #[test] fn expect_transact_status() { MockNet::reset(); + // Runtime call dispatched by the Transact instruction. // set_balance requires root origin. let call = relay_chain::RuntimeCall::Balances(pallet_balances::Call::< @@ -199,9 +231,15 @@ mod tests { new_reserved: 0, }); + let message_fee = relay_chain::estimate_message_fee(6); + let set_balance_weight_estimation = Weight::from_parts(1_000_000_000, 10_000); + let set_balance_fee_estimation = + relay_chain::estimate_fee_for_weight(set_balance_weight_estimation); + let fees = message_fee + set_balance_fee_estimation; + let message = Xcm(vec![ - WithdrawAsset((Here, AMOUNT).into()), - BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: WeightLimit::Unlimited }, + WithdrawAsset((Here, fees).into()), + BuyExecution { fees: (Here, fees).into(), weight_limit: WeightLimit::Unlimited }, SetErrorHandler(Xcm(vec![ReportTransactStatus(QueryResponseInfo { destination: Parachain(1).into(), query_id: QUERY_ID, @@ -209,14 +247,14 @@ mod tests { })])), Transact { origin_kind: OriginKind::SovereignAccount, - require_weight_at_most: Weight::from_parts(INITIAL_BALANCE as u64, 1024 * 1024), + require_weight_at_most: set_balance_weight_estimation, call: call.encode().into(), }, ExpectTransactStatus(MaybeErrorCode::Success), ]); ParaA::execute_with(|| { - assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone(),)); + assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone())); }); // The execution of set_balance does not succeed, and error is reported back to the parachain. diff --git a/examples/src/first_look.rs b/examples/src/first_look.rs index a846c12..31362bd 100644 --- a/examples/src/first_look.rs +++ b/examples/src/first_look.rs @@ -12,14 +12,16 @@ mod tests { ParaA::execute_with(|| { // Amount to transfer. - let amount: u128 = 10; + let amount: u128 = 10 * CENTS; // Check that the balance of Alice is equal to the `INITIAL_BALANCE`. assert_eq!(ParachainBalances::free_balance(&ALICE), INITIAL_BALANCE); + let fee = parachain::estimate_message_fee(3); + // The XCM used to transfer funds from Alice to Bob. let message = Xcm(vec![ - WithdrawAsset((Here, amount).into()), - BuyExecution { fees: (Here, amount).into(), weight_limit: WeightLimit::Unlimited }, + WithdrawAsset(vec![(Here, amount).into(), (Parent, fee).into()].into()), + BuyExecution { fees: (Parent, fee).into(), weight_limit: WeightLimit::Unlimited }, DepositAsset { assets: All.into(), beneficiary: MultiLocation { @@ -41,6 +43,7 @@ mod tests { // Check if the funds are subtracted from the account of Alice and added to the account of Bob. assert_eq!(ParachainBalances::free_balance(ALICE), INITIAL_BALANCE - amount); + assert_eq!(parachain::Assets::balance(0, ALICE), INITIAL_BALANCE - fee); assert_eq!(ParachainBalances::free_balance(BOB), amount); }); } diff --git a/examples/src/holding_modifiers/mod.rs b/examples/src/holding_modifiers/mod.rs index 264ca9a..1b05b76 100644 --- a/examples/src/holding_modifiers/mod.rs +++ b/examples/src/holding_modifiers/mod.rs @@ -13,8 +13,8 @@ mod tests { #[test] fn burn_assets() { let message = Xcm(vec![ + UnpaidExecution { weight_limit: WeightLimit::Unlimited, check_origin: None }, WithdrawAsset((Here, 10 * CENTS).into()), - BuyExecution { fees: (Here, CENTS).into(), weight_limit: WeightLimit::Unlimited }, BurnAsset((Here, 4 * CENTS).into()), ReportHolding { response_info: QueryResponseInfo { @@ -58,8 +58,8 @@ mod tests { parachain::set_exchange_assets(assets_in_exchange); let message = Xcm(vec![ + UnpaidExecution { weight_limit: WeightLimit::Unlimited, check_origin: None }, WithdrawAsset((Here, 10 * CENTS).into()), - BuyExecution { fees: (Here, CENTS).into(), weight_limit: WeightLimit::Unlimited }, // Maximal field set to true. ExchangeAsset { give: Definite((Here, 5 * CENTS).into()), @@ -103,8 +103,8 @@ mod tests { parachain::set_exchange_assets(assets_in_exchange); let message = Xcm(vec![ + UnpaidExecution { weight_limit: WeightLimit::Unlimited, check_origin: None }, WithdrawAsset((Here, 10 * CENTS).into()), - BuyExecution { fees: (Here, CENTS).into(), weight_limit: WeightLimit::Unlimited }, // Maximal field set to false. ExchangeAsset { give: Definite((Here, 5 * CENTS).into()), diff --git a/examples/src/locks/mod.rs b/examples/src/locks/mod.rs index 6d1d404..2c67555 100644 --- a/examples/src/locks/mod.rs +++ b/examples/src/locks/mod.rs @@ -1,29 +1,34 @@ #[cfg(test)] mod tests { use crate::simple_test_net::*; - use frame_support::{assert_ok, pallet_prelude::Weight}; + use frame_support::assert_ok; use pallet_balances::{BalanceLock, Reasons}; use xcm::latest::prelude::*; use xcm_simulator::TestExt; /// Scenario: - /// Parachain A locks 5 Cents of relay chain native assets of its Sovereign account on the relay chain and assigns Parachain B as unlocker. + /// ALICE from parachain A locks 5 cents of relay chain native assets of its Sovereign account on the relay chain and assigns Parachain B as unlocker. /// Parachain A then asks Parachain B to unlock the funds partly. Parachain B responds by sending an UnlockAssets instruction to the relay chain. + #[ignore] // TODO: Fix issue upstream #[test] fn remote_locking_on_relay() { MockNet::reset(); + let fee = relay_chain::estimate_message_fee(4); // Accounts for the `DescendOrigin` instruction added by `send_xcm` + ParaA::execute_with(|| { - let message = Xcm(vec![LockAsset { - asset: (Here, 5 * CENTS).into(), - unlocker: (Parachain(2)).into(), - }]); - assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone())); + let message = Xcm(vec![ + WithdrawAsset((Here, fee).into()), + BuyExecution { fees: (Here, fee).into(), weight_limit: WeightLimit::Unlimited }, + LockAsset { asset: (Here, 5 * CENTS).into(), unlocker: (Parachain(2)).into() }, + ]); + let interior = AccountId32 { id: ALICE.into(), network: None }; + assert_ok!(ParachainPalletXcm::send_xcm(interior, Parent, message.clone())); }); Relay::execute_with(|| { assert_eq!( - relay_chain::Balances::locks(¶chain_sovereign_account_id(1)), + relay_chain::Balances::locks(¶chain_account_sovereign_account_id(1, ALICE)), vec![BalanceLock { id: *b"py/xcmlk", amount: 5 * CENTS, reasons: Reasons::All }] ); }); @@ -32,24 +37,30 @@ mod tests { assert_eq!( parachain::MsgQueue::received_dmp(), vec![Xcm(vec![NoteUnlockable { - owner: (Parent, Parachain(1)).into(), + owner: (Parent, Parachain(1), AccountId32 { id: ALICE.into(), network: None }) + .into(), asset: (Parent, 5 * CENTS).into() }])] ); }); ParaA::execute_with(|| { - let message = Xcm(vec![RequestUnlock { - asset: (Parent, 3 * CENTS).into(), - locker: Parent.into(), - }]); - - assert_ok!(ParachainPalletXcm::send_xcm(Here, (Parent, Parachain(2)), message.clone())); + let message = Xcm(vec![ + WithdrawAsset((Parent, fee).into()), + BuyExecution { fees: (Parent, fee).into(), weight_limit: WeightLimit::Unlimited }, + RequestUnlock { asset: (Parent, 3 * CENTS).into(), locker: Parent.into() }, + ]); + let interior = AccountId32 { id: ALICE.into(), network: None }; + assert_ok!(ParachainPalletXcm::send_xcm( + interior, + (Parent, Parachain(2)), + message.clone() + )); }); Relay::execute_with(|| { assert_eq!( - relay_chain::Balances::locks(¶chain_sovereign_account_id(1)), + relay_chain::Balances::locks(¶chain_account_sovereign_account_id(1, ALICE)), vec![BalanceLock { id: *b"py/xcmlk", amount: 2 * CENTS, reasons: Reasons::All }] ); }); @@ -69,17 +80,23 @@ mod tests { /// Unlockers: B, C; Funds registered in pallet-xcm: 2, 5. /// Lock set in pallet-balances: 5. /// + #[ignore] // TODO: Fix issue upstream #[test] fn locking_overlap() { MockNet::reset(); + let fee = relay_chain::estimate_message_fee(4); + // 1) ParaA::execute_with(|| { let message = Xcm(vec![ + WithdrawAsset((Here, fee).into()), + BuyExecution { fees: (Here, fee).into(), weight_limit: WeightLimit::Unlimited }, LockAsset { asset: (Here, 10 * CENTS).into(), unlocker: (Parachain(2)).into() }, LockAsset { asset: (Here, 5 * CENTS).into(), unlocker: (Parachain(3)).into() }, ]); - assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone())); + let interior = AccountId32 { id: ALICE.into(), network: None }; + assert_ok!(ParachainPalletXcm::send_xcm(interior, Parent, message.clone())); }); Relay::execute_with(|| { @@ -94,7 +111,8 @@ mod tests { assert_eq!( parachain::MsgQueue::received_dmp(), vec![Xcm(vec![NoteUnlockable { - owner: (Parent, Parachain(1)).into(), + owner: (Parent, Parachain(1), AccountId32 { id: ALICE.into(), network: None }) + .into(), asset: (Parent, 10 * CENTS).into() }])] ); @@ -104,20 +122,31 @@ mod tests { assert_eq!( parachain::MsgQueue::received_dmp(), vec![Xcm(vec![NoteUnlockable { - owner: (Parent, Parachain(1)).into(), + owner: (Parent, Parachain(1), AccountId32 { id: ALICE.into(), network: None }) + .into(), asset: (Parent, 5 * CENTS).into() }])] ); }); + let new_fee = parachain::estimate_message_fee(3); + // 3) ParaA::execute_with(|| { - let message = Xcm(vec![RequestUnlock { - asset: (Parent, 8 * CENTS).into(), - locker: Parent.into(), - }]); - - assert_ok!(ParachainPalletXcm::send_xcm(Here, (Parent, Parachain(2)), message.clone())); + let message = Xcm(vec![ + WithdrawAsset((Parent, new_fee).into()), + BuyExecution { + fees: (Parent, new_fee).into(), + weight_limit: WeightLimit::Unlimited, + }, + RequestUnlock { asset: (Parent, 8 * CENTS).into(), locker: Parent.into() }, + ]); + let interior = AccountId32 { id: ALICE.into(), network: None }; + assert_ok!(ParachainPalletXcm::send_xcm( + interior, + (Parent, Parachain(2)), + message.clone() + )); }); // 4) diff --git a/examples/src/queries/mod.rs b/examples/src/queries/mod.rs index 96004df..d99626f 100644 --- a/examples/src/queries/mod.rs +++ b/examples/src/queries/mod.rs @@ -7,7 +7,7 @@ mod tests { use xcm::latest::prelude::*; use xcm_simulator::TestExt; - const AMOUNT: u128 = 1 * CENTS; + const AMOUNT: u128 = 50 * CENTS; /// Arbitrary query id const QUERY_ID: u64 = 1234; @@ -20,14 +20,16 @@ mod tests { fn query_holding() { MockNet::reset(); - // Send a message which fully succeeds to the relay chain. + let fee_in_relay = relay_chain::estimate_message_fee(4); + + // Send a message which succeeds to the relay chain. // And then report the status of the holding register back to ParaA ParaA::execute_with(|| { let message = Xcm(vec![ WithdrawAsset((Here, AMOUNT).into()), - BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: Unlimited }, + BuyExecution { fees: (Here, fee_in_relay).into(), weight_limit: Unlimited }, DepositAsset { - assets: Definite((Here, AMOUNT - 5).into()), + assets: Definite((Here, AMOUNT - (5 * CENTS)).into()), beneficiary: Parachain(2).into(), }, ReportHolding { @@ -53,7 +55,7 @@ mod tests { // Deposit executed assert_eq!( relay_chain::Balances::free_balance(parachain_sovereign_account_id(2)), - INITIAL_BALANCE + AMOUNT - 5 + INITIAL_BALANCE + (AMOUNT - 5 * CENTS) ); }); @@ -63,7 +65,9 @@ mod tests { parachain::MsgQueue::received_dmp(), vec![Xcm(vec![QueryResponse { query_id: QUERY_ID, - response: Response::Assets((Parent, AMOUNT - (AMOUNT - 5)).into()), + response: Response::Assets( + (Parent, AMOUNT - (AMOUNT - 5 * CENTS) - fee_in_relay).into() + ), max_weight: Weight::from_all(0), querier: Some(Here.into()), }])], @@ -72,7 +76,7 @@ mod tests { } /// Scenario: - /// Parachain A wants to query the `PalletInfo` of the balances pallet in the relay chain. + /// Parachain A wants to query for information on the balances pallet in the relay chain. /// It sends a `QueryPallet` instruction to the relay chain. /// The relay chain responds with a `QueryResponse` instruction containing the `PalletInfo`. /// @@ -81,15 +85,24 @@ mod tests { fn query_pallet() { MockNet::reset(); + let fee_in_relay = relay_chain::estimate_message_fee(3); + ParaA::execute_with(|| { - let message = Xcm(vec![QueryPallet { - module_name: "pallet_balances".into(), - response_info: QueryResponseInfo { - destination: Parachain(1).into(), - query_id: QUERY_ID, - max_weight: Weight::from_all(0), + let message = Xcm(vec![ + WithdrawAsset((Here, fee_in_relay).into()), + BuyExecution { + fees: (Here, fee_in_relay).into(), + weight_limit: WeightLimit::Unlimited, }, - }]); + QueryPallet { + module_name: "pallet_balances".into(), + response_info: QueryResponseInfo { + destination: Parachain(1).into(), + query_id: QUERY_ID, + max_weight: Weight::from_all(0), + }, + }, + ]); assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone(),)); print_para_events(); @@ -121,26 +134,36 @@ mod tests { fn report_error() { MockNet::reset(); + let fee_in_relay = + relay_chain::estimate_message_fee(4) + relay_chain::estimate_message_fee(1); + let message = Xcm(vec![ + WithdrawAsset((Here, fee_in_relay).into()), + BuyExecution { + fees: (Here, fee_in_relay).into(), + weight_limit: WeightLimit::Unlimited, + }, // Set the Error Handler to report back status of Error register. SetErrorHandler(Xcm(vec![ReportError(QueryResponseInfo { destination: Parachain(1).into(), query_id: QUERY_ID, max_weight: Weight::from_all(0), })])), - Trap(1u64), + Trap(1u64), // Error is thrown on index 3 ]); ParaA::execute_with(|| { assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone())); }); + let index_of_error = 3; + ParaA::execute_with(|| { assert_eq!( parachain::MsgQueue::received_dmp(), vec![Xcm(vec![QueryResponse { query_id: QUERY_ID, - response: Response::ExecutionResult(Some((1, XcmError::Trap(1)))), + response: Response::ExecutionResult(Some((index_of_error, XcmError::Trap(1)))), max_weight: Weight::from_all(0), querier: Some(Here.into()), }])], @@ -162,10 +185,19 @@ mod tests { }, ); + let message_fee = relay_chain::estimate_message_fee(5); + let remark_weight_estimation = Weight::from_parts(20_000_000, 100_000); // We overestimate the weight taken by this extrinsic + let remark_fee_estimation = relay_chain::estimate_fee_for_weight(remark_weight_estimation); + let message = Xcm(vec![ + WithdrawAsset((Here, message_fee + remark_fee_estimation).into()), + BuyExecution { + fees: (Here, message_fee + remark_fee_estimation).into(), + weight_limit: WeightLimit::Unlimited, + }, Transact { origin_kind: OriginKind::SovereignAccount, - require_weight_at_most: Weight::from_parts(INITIAL_BALANCE as u64, 1024 * 1024), + require_weight_at_most: remark_weight_estimation, call: call.encode().into(), }, ReportTransactStatus(QueryResponseInfo { diff --git a/examples/src/simple_test_net/mod.rs b/examples/src/simple_test_net/mod.rs index 3046066..dae9f0c 100644 --- a/examples/src/simple_test_net/mod.rs +++ b/examples/src/simple_test_net/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2021 Parity Technologies (UK) Ltd. +// Copyright Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify @@ -19,10 +19,10 @@ pub mod relay_chain; use core::{borrow::Borrow, marker::PhantomData}; -use frame_support::{sp_tracing, traits::GenesisBuild}; +use frame_support::{ensure, pallet_prelude::Weight, sp_tracing, traits::GenesisBuild}; use sp_core::blake2_256; use xcm::prelude::*; -use xcm_executor::traits::Convert; +use xcm_executor::traits::{Convert, ShouldExecute}; use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain}; // Accounts @@ -34,7 +34,7 @@ pub const BOB: sp_runtime::AccountId32 = sp_runtime::AccountId32::new([2u8; 32]) pub type Balance = u128; pub const UNITS: Balance = 10_000_000_000; pub const CENTS: Balance = UNITS / 100; // 100_000_000 -pub const INITIAL_BALANCE: u128 = 1 * UNITS; +pub const INITIAL_BALANCE: u128 = 10 * UNITS; decl_test_parachain! { pub struct ParaA { @@ -126,18 +126,25 @@ pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - let other_para_id: u32 = match para_id { - 1 => 2, - 2 => 1, + let other_para_ids = match para_id { + 1 => [2, 3], + 2 => [1, 3], + 3 => [1, 2], _ => panic!("No parachain exists with para_id = {para_id}"), }; pallet_balances::GenesisConfig:: { - balances: vec![ - (ALICE, INITIAL_BALANCE), - (relay_sovereign_account_id(), INITIAL_BALANCE), - (sibling_account_sovereign_account_id(other_para_id, ALICE), INITIAL_BALANCE), - ], + balances: vec![(ALICE, INITIAL_BALANCE), (relay_sovereign_account_id(), INITIAL_BALANCE)] + .into_iter() + .chain(other_para_ids.iter().map( + // Initial balance of native token for ALICE on all sibling sovereign accounts + |¶_id| (sibling_account_sovereign_account_id(para_id, ALICE), INITIAL_BALANCE), + )) + .chain(other_para_ids.iter().map( + // Initial balance of native token all sibling sovereign accounts + |¶_id| (sibling_sovereign_account_id(para_id), INITIAL_BALANCE), + )) + .collect(), } .assimilate_storage(&mut t) .unwrap(); @@ -145,13 +152,26 @@ pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { pallet_assets::GenesisConfig:: { assets: vec![ (0u128, ADMIN, false, 1u128), // Create derivative asset for relay's native token - (other_para_id as u128, ADMIN, false, 1u128), // Create derivative asset for the other parachain's native token - ], + ] + .into_iter() + .chain(other_para_ids.iter().map(|¶_id| (para_id as u128, ADMIN, false, 1u128))) // Derivative assets for the other parachains' native tokens + .collect(), metadata: Default::default(), accounts: vec![ (0u128, ALICE, INITIAL_BALANCE), - (other_para_id as u128, ALICE, INITIAL_BALANCE), - ], + (0u128, relay_sovereign_account_id(), INITIAL_BALANCE), + ] + .into_iter() + .chain(other_para_ids.iter().map(|¶_id| (para_id as u128, ALICE, INITIAL_BALANCE))) // Initial balance for derivatives of other parachains' tokens + .chain(other_para_ids.iter().map(|¶_id| { + (0u128, sibling_account_sovereign_account_id(para_id, ALICE), INITIAL_BALANCE) + })) // Initial balance for sovereign accounts (for fee payment) + .chain( + other_para_ids + .iter() + .map(|¶_id| (0u128, sibling_sovereign_account_id(para_id), INITIAL_BALANCE)), + ) // Initial balance for sovereign accounts (for fee payment) + .collect(), } .assimilate_storage(&mut t) .unwrap(); @@ -175,6 +195,10 @@ pub fn relay_ext() -> sp_io::TestExternalities { (ALICE, INITIAL_BALANCE), (parachain_sovereign_account_id(1), INITIAL_BALANCE), (parachain_sovereign_account_id(2), INITIAL_BALANCE), + (parachain_sovereign_account_id(3), INITIAL_BALANCE), + (parachain_account_sovereign_account_id(1, ALICE), INITIAL_BALANCE), + (parachain_account_sovereign_account_id(2, ALICE), INITIAL_BALANCE), + (parachain_account_sovereign_account_id(3, ALICE), INITIAL_BALANCE), ], } .assimilate_storage(&mut t) @@ -254,3 +278,36 @@ impl ForeignChainAliasAccount { (FOREIGN_CHAIN_PREFIX_RELAY, id, parents).using_encoded(blake2_256) } } + +// TODO: Is this vulnerable to DoS? It's how the instructions work +pub struct AllowNoteUnlockables; +impl ShouldExecute for AllowNoteUnlockables { + fn should_execute( + _origin: &MultiLocation, + instructions: &mut [Instruction], + _max_weight: Weight, + _weight_credit: &mut Weight, + ) -> Result<(), ()> { + ensure!(instructions.len() == 1, ()); + match instructions.first() { + Some(NoteUnlockable { .. }) => Ok(()), + _ => Err(()), + } + } +} + +pub struct AllowUnlocks; +impl ShouldExecute for AllowUnlocks { + fn should_execute( + _origin: &MultiLocation, + instructions: &mut [Instruction], + _max_weight: Weight, + _weight_credit: &mut Weight, + ) -> Result<(), ()> { + ensure!(instructions.len() == 1, ()); + match instructions.first() { + Some(UnlockAsset { .. }) => Ok(()), + _ => Err(()), + } + } +} diff --git a/examples/src/simple_test_net/parachain.rs b/examples/src/simple_test_net/parachain.rs index 3775dca..6f1c79f 100644 --- a/examples/src/simple_test_net/parachain.rs +++ b/examples/src/simple_test_net/parachain.rs @@ -1,4 +1,4 @@ -// Copyright 2021 Parity Technologies (UK) Ltd. +// Copyright Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify @@ -16,16 +16,19 @@ //! Parachain runtime mock. -use super::{Balance, ForeignChainAliasAccount}; +use super::{AllowNoteUnlockables, Balance, ForeignChainAliasAccount}; use codec::{Decode, Encode}; use core::marker::PhantomData; use frame_support::{ construct_runtime, ensure, parameter_types, traits::{ - AsEnsureOriginWithArg, ContainsPair, EnsureOrigin, EnsureOriginWithArg, Everything, - EverythingBut, Nothing, + AsEnsureOriginWithArg, Contains, ContainsPair, EnsureOrigin, EnsureOriginWithArg, + Everything, EverythingBut, Nothing, + }, + weights::{ + constants::{WEIGHT_PROOF_SIZE_PER_MB, WEIGHT_REF_TIME_PER_SECOND}, + Weight, }, - weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, }; use frame_system::{EnsureRoot, EnsureSigned}; use pallet_xcm::XcmPassthrough; @@ -43,11 +46,12 @@ use sp_runtime::{ use sp_std::{cell::RefCell, prelude::*}; use xcm::{latest::prelude::*, VersionedXcm}; use xcm_builder::{ - AccountId32Aliases, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, ConvertedConcreteId, - CurrencyAdapter as XcmCurrencyAdapter, EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, - FungiblesAdapter, IsConcrete, NativeAsset, NoChecking, NonFungiblesAdapter, ParentAsSuperuser, - ParentIsPreset, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, + AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowTopLevelPaidExecutionFrom, + AsPrefixedGeneralIndex, ConvertedConcreteId, CurrencyAdapter as XcmCurrencyAdapter, + EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, FungiblesAdapter, IsConcrete, + NativeAsset, NoChecking, NonFungiblesAdapter, ParentAsSuperuser, ParentIsPreset, + SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, WithComputedOrigin, }; use xcm_executor::{ traits::{AssetExchange, Convert, JustTry}, @@ -208,8 +212,8 @@ pub type XcmOriginToCallOrigin = ( ); parameter_types! { - pub const UnitWeightCost: Weight = Weight::from_parts(1, 1); - pub RatePerSecondPerByte: (AssetId, u128, u128) = (Concrete(Parent.into()), 1, 1); + pub const XcmInstructionWeight: Weight = Weight::from_parts(1_000, 1_000); + pub TokensPerSecondPerMegabyte: (AssetId, u128, u128) = (Concrete(Parent.into()), 1_000_000_000_000, 1024 * 1024); pub const MaxInstructions: u32 = 100; pub const MaxAssetsIntoHolding: u32 = 64; pub ForeignPrefix: MultiLocation = (Parent,).into(); @@ -218,6 +222,23 @@ parameter_types! { (Parent.into(), Wild(AllOf { id: Concrete(Parent.into()), fun: WildFungible })); } +pub fn estimate_message_fee(number_of_instructions: u64) -> u128 { + let weight = estimate_weight(number_of_instructions); + + estimate_fee_for_weight(weight) +} + +pub fn estimate_weight(number_of_instructions: u64) -> Weight { + XcmInstructionWeight::get().saturating_mul(number_of_instructions) +} + +pub fn estimate_fee_for_weight(weight: Weight) -> u128 { + let (_, units_per_second, units_per_mb) = TokensPerSecondPerMegabyte::get(); + + units_per_second * (weight.ref_time() as u128) / (WEIGHT_REF_TIME_PER_SECOND as u128) + + units_per_mb * (weight.proof_size() as u128) / (WEIGHT_PROOF_SIZE_PER_MB as u128) +} + pub type LocalBalancesTransactor = XcmCurrencyAdapter, SovereignAccountOf, AccountId, ()>; @@ -262,8 +283,23 @@ pub type ForeignUniquesTransactor = NonFungiblesAdapter< pub type AssetTransactors = (LocalBalancesTransactor, ForeignAssetsTransactor, ForeignUniquesTransactor); +pub struct ParentRelay; +impl Contains for ParentRelay { + fn contains(location: &MultiLocation) -> bool { + location.contains_parents_only(1) + } +} + pub type XcmRouter = super::ParachainXcmRouter; -pub type Barrier = AllowUnpaidExecutionFrom; +pub type Barrier = WithComputedOrigin< + ( + AllowNoteUnlockables, + AllowExplicitUnpaidExecutionFrom, + AllowTopLevelPaidExecutionFrom, + ), + UniversalLocation, + ConstU32<1>, +>; parameter_types! { pub NftCollectionOne: MultiAssetFilter @@ -321,8 +357,8 @@ impl Config for XcmConfig { type IsTeleporter = TrustedTeleporters; type UniversalLocation = UniversalLocation; type Barrier = Barrier; - type Weigher = FixedWeightBounds; - type Trader = FixedRateOfFungible; + type Weigher = FixedWeightBounds; + type Trader = FixedRateOfFungible; type ResponseHandler = (); type AssetTrap = PolkadotXcm; type AssetLocker = PolkadotXcm; @@ -509,7 +545,7 @@ impl pallet_xcm::Config for Runtime { type XcmExecutor = XcmExecutor; type XcmTeleportFilter = Nothing; type XcmReserveTransferFilter = Everything; - type Weigher = FixedWeightBounds; + type Weigher = FixedWeightBounds; type UniversalLocation = UniversalLocation; type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; diff --git a/examples/src/simple_test_net/relay_chain.rs b/examples/src/simple_test_net/relay_chain.rs index 84552c6..9814d6c 100644 --- a/examples/src/simple_test_net/relay_chain.rs +++ b/examples/src/simple_test_net/relay_chain.rs @@ -1,4 +1,4 @@ -// Copyright 2021 Parity Technologies (UK) Ltd. +// Copyright Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify @@ -18,8 +18,11 @@ use frame_support::{ construct_runtime, parameter_types, - traits::{AsEnsureOriginWithArg, Everything, Nothing}, - weights::Weight, + traits::{AsEnsureOriginWithArg, Contains, Everything, Nothing}, + weights::{ + constants::{WEIGHT_PROOF_SIZE_PER_MB, WEIGHT_REF_TIME_PER_SECOND}, + Weight, + }, }; use frame_system::EnsureRoot; @@ -30,15 +33,16 @@ use polkadot_parachain::primitives::Id as ParaId; use polkadot_runtime_parachains::{configuration, origin, shared, ump}; use xcm::latest::prelude::*; use xcm_builder::{ - AccountId32Aliases, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, ChildParachainAsNative, - ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, ConvertedConcreteId, - CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, FixedWeightBounds, IsConcrete, - NoChecking, NonFungiblesAdapter, SiblingParachainConvertsVia, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, + AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowTopLevelPaidExecutionFrom, + AsPrefixedGeneralIndex, ChildParachainAsNative, ChildParachainConvertsVia, + ChildSystemParachainAsSuperuser, ConvertedConcreteId, CurrencyAdapter as XcmCurrencyAdapter, + FixedRateOfFungible, FixedWeightBounds, IsConcrete, NoChecking, NonFungiblesAdapter, + SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, WithComputedOrigin, }; use xcm_executor::{traits::JustTry, Config, XcmExecutor}; -use super::Balance; +use super::{AllowNoteUnlockables, AllowUnlocks, Balance, ForeignChainAliasAccount}; pub type AccountId = AccountId32; @@ -126,6 +130,7 @@ parameter_types! { } pub type SovereignAccountOf = ( + ForeignChainAliasAccount, AccountId32Aliases, ChildParachainConvertsVia, SiblingParachainConvertsVia, @@ -153,15 +158,48 @@ type LocalOriginConverter = ( ); parameter_types! { - pub const BaseXcmWeight: Weight = Weight::from_parts(1_000, 1_000); - pub TokensPerSecondPerByte: (AssetId, u128, u128) = + pub const XcmInstructionWeight: Weight = Weight::from_parts(1_000, 1_000); + pub TokensPerSecondPerMegabyte: (AssetId, u128, u128) = (Concrete(TokenLocation::get()), 1_000_000_000_000, 1024 * 1024); pub const MaxInstructions: u32 = 100; pub const MaxAssetsIntoHolding: u32 = 64; } +pub fn estimate_message_fee(number_of_instructions: u64) -> u128 { + let weight = estimate_message_weight(number_of_instructions); + + estimate_fee_for_weight(weight) +} + +pub fn estimate_message_weight(number_of_instructions: u64) -> Weight { + XcmInstructionWeight::get().saturating_mul(number_of_instructions) +} + +pub fn estimate_fee_for_weight(weight: Weight) -> u128 { + let (_, units_per_second, units_per_mb) = TokensPerSecondPerMegabyte::get(); + + units_per_second * (weight.ref_time() as u128) / (WEIGHT_REF_TIME_PER_SECOND as u128) + + units_per_mb * (weight.proof_size() as u128) / (WEIGHT_PROOF_SIZE_PER_MB as u128) +} + +pub struct ChildrenParachains; +impl Contains for ChildrenParachains { + fn contains(location: &MultiLocation) -> bool { + matches!(location, MultiLocation { parents: 0, interior: X1(Parachain(_)) }) + } +} + pub type XcmRouter = super::RelayChainXcmRouter; -pub type Barrier = AllowUnpaidExecutionFrom; +pub type Barrier = WithComputedOrigin< + ( + AllowNoteUnlockables, + AllowUnlocks, + AllowExplicitUnpaidExecutionFrom, + AllowTopLevelPaidExecutionFrom, + ), + UniversalLocation, + ConstU32<1>, +>; pub struct XcmConfig; impl Config for XcmConfig { @@ -173,8 +211,8 @@ impl Config for XcmConfig { type IsTeleporter = (); type UniversalLocation = UniversalLocation; type Barrier = Barrier; - type Weigher = FixedWeightBounds; - type Trader = FixedRateOfFungible; + type Weigher = FixedWeightBounds; + type Trader = FixedRateOfFungible; type ResponseHandler = XcmPallet; type AssetTrap = XcmPallet; type AssetLocker = XcmPallet; @@ -207,7 +245,7 @@ impl pallet_xcm::Config for Runtime { type XcmExecutor = XcmExecutor; type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Everything; - type Weigher = FixedWeightBounds; + type Weigher = FixedWeightBounds; type UniversalLocation = UniversalLocation; type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; diff --git a/examples/src/transact/mod.rs b/examples/src/transact/mod.rs index b1b9841..0f17690 100644 --- a/examples/src/transact/mod.rs +++ b/examples/src/transact/mod.rs @@ -6,8 +6,6 @@ mod tests { use xcm::latest::prelude::*; use xcm_simulator::TestExt; - const AMOUNT: u128 = 1 * CENTS; - /// Scenario: /// Relay chain sets the balance of Alice on Parachain(1). /// The relay chain is able to do this, because Parachain(1) trusts the relay chain to execute runtime calls as root. @@ -19,17 +17,23 @@ mod tests { let call = parachain::RuntimeCall::Balances( pallet_balances::Call::::set_balance { who: ALICE, - new_free: 5 * AMOUNT, + new_free: 5 * CENTS, new_reserved: 0, }, ); + let message_fee = parachain::estimate_message_fee(3); + let set_balance_weight_estimation = Weight::from_parts(1_000_000_000, 10_000); + let set_balance_fee_estimation = + parachain::estimate_fee_for_weight(set_balance_weight_estimation); + let fees = message_fee + set_balance_fee_estimation; + let message = Xcm(vec![ - WithdrawAsset((Here, AMOUNT).into()), - BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: WeightLimit::Unlimited }, + WithdrawAsset((Parent, fees).into()), + BuyExecution { fees: (Parent, fees).into(), weight_limit: WeightLimit::Unlimited }, Transact { origin_kind: OriginKind::Superuser, - require_weight_at_most: Weight::from_parts(1_000_000_000, 1024 * 1024), + require_weight_at_most: set_balance_weight_estimation, call: call.encode().into(), }, ]); @@ -39,7 +43,7 @@ mod tests { }); ParaA::execute_with(|| { - assert_eq!(ParachainBalances::free_balance(ALICE), 5 * AMOUNT); + assert_eq!(ParachainBalances::free_balance(ALICE), 5 * CENTS); }) } @@ -50,6 +54,7 @@ mod tests { #[test] fn transact_mint_nft() { MockNet::reset(); + let create_collection = relay_chain::RuntimeCall::Uniques(pallet_uniques::Call::< relay_chain::Runtime, >::create { @@ -57,6 +62,15 @@ mod tests { admin: parachain_sovereign_account_id(1), }); + let message_fee = relay_chain::estimate_message_fee(4); + let create_collection_weight_estimation = Weight::from_parts(1_000_000_000, 10_000); + let create_collection_fee_estimation = + relay_chain::estimate_fee_for_weight(create_collection_weight_estimation); + let mint_nft_weight_estimation = Weight::from_parts(1_000_000_000, 10_000); + let mint_nft_fee_estimation = + relay_chain::estimate_fee_for_weight(mint_nft_weight_estimation); + let fees = message_fee + create_collection_fee_estimation + mint_nft_fee_estimation; + let mint = relay_chain::RuntimeCall::Uniques(pallet_uniques::Call::::mint { collection: 1u32, @@ -65,16 +79,16 @@ mod tests { }); let message = Xcm(vec![ - WithdrawAsset((Here, AMOUNT).into()), - BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: WeightLimit::Unlimited }, + WithdrawAsset((Here, fees).into()), + BuyExecution { fees: (Here, fees).into(), weight_limit: WeightLimit::Unlimited }, Transact { origin_kind: OriginKind::SovereignAccount, - require_weight_at_most: Weight::from_parts(INITIAL_BALANCE as u64, 1024 * 1024), + require_weight_at_most: create_collection_weight_estimation, call: create_collection.encode().into(), }, Transact { origin_kind: OriginKind::SovereignAccount, - require_weight_at_most: Weight::from_parts(INITIAL_BALANCE as u64, 1024 * 1024), + require_weight_at_most: mint_nft_weight_estimation, call: mint.encode().into(), }, ]); diff --git a/examples/src/transfers/reserve.rs b/examples/src/transfers/reserve.rs index 46b8417..2e986ab 100644 --- a/examples/src/transfers/reserve.rs +++ b/examples/src/transfers/reserve.rs @@ -6,36 +6,58 @@ mod tests { use xcm_simulator::TestExt; /// Scenario: - /// ALICE transfers her FDOT from parachain A to parachain B. + /// ALICE transfers relay native tokens from parachain A to parachain B. #[test] fn reserve_backed_transfer_para_to_para() { MockNet::reset(); let withdraw_amount = 50 * CENTS; + // Estimated from the number of instructions and knowledge of the config + let fee_in_source = parachain::estimate_message_fee(3); + let fee_in_relay = relay_chain::estimate_message_fee(4); + let fee_in_destination = parachain::estimate_message_fee(4); + + // In this case, we know exactly how much fees we need for each step of the process let message: Xcm = Xcm(vec![ - WithdrawAsset((Parent, withdraw_amount).into()), + WithdrawAsset((Parent, withdraw_amount).into()), // Fees are paid in the relay's token + BuyExecution { + fees: (Parent, fee_in_source).into(), + weight_limit: WeightLimit::Unlimited, + }, InitiateReserveWithdraw { assets: All.into(), reserve: Parent.into(), xcm: Xcm(vec![ BuyExecution { - fees: (Here, withdraw_amount).into(), + fees: (Here, fee_in_relay).into(), weight_limit: WeightLimit::Unlimited, }, DepositReserveAsset { assets: All.into(), dest: Parachain(2).into(), - xcm: Xcm(vec![DepositAsset { - assets: All.into(), - beneficiary: Junction::AccountId32 { id: ALICE.into(), network: None } + xcm: Xcm(vec![ + BuyExecution { + fees: (Parent, fee_in_destination).into(), + weight_limit: WeightLimit::Unlimited, + }, + DepositAsset { + assets: All.into(), + beneficiary: Junction::AccountId32 { + id: ALICE.into(), + network: None, + } .into(), - }]), + }, + ]), }, ]), }, ]); + let fee_until_relay = fee_in_source + fee_in_relay; + let fee_until_destination = fee_until_relay + fee_in_destination; + ParaA::execute_with(|| { assert_ok!(parachain::PolkadotXcm::execute( parachain::RuntimeOrigin::signed(ALICE), @@ -49,37 +71,51 @@ mod tests { Relay::execute_with(|| { assert_eq!( relay_chain::Balances::free_balance(¶chain_sovereign_account_id(2)), - INITIAL_BALANCE + withdraw_amount + INITIAL_BALANCE + withdraw_amount - fee_until_relay ); }); ParaB::execute_with(|| { - assert_eq!(parachain::Assets::balance(0, &ALICE), INITIAL_BALANCE + withdraw_amount); + assert_eq!( + parachain::Assets::balance(0, &ALICE), + INITIAL_BALANCE + withdraw_amount - fee_until_destination + ); }); } /// Scenario: - /// ALICE transfers her FDOT from relay to parachain B. + /// ALICE transfers relay native tokens from relay to parachain B. #[test] fn reserve_backed_transfer_relay_to_para() { MockNet::reset(); let withdraw_amount = 50 * CENTS; - let message: Xcm = Xcm(vec![TransferReserveAsset { - assets: (Here, withdraw_amount).into(), - dest: Parachain(2).into(), - xcm: Xcm(vec![ - BuyExecution { - fees: (Here, withdraw_amount).into(), - weight_limit: WeightLimit::Unlimited, - }, - DepositAsset { - assets: All.into(), - beneficiary: Junction::AccountId32 { id: ALICE.into(), network: None }.into(), - }, - ]), - }]); + let fee_in_source = relay_chain::estimate_message_fee(3); + let fee_in_destination = parachain::estimate_message_fee(4); + + let message: Xcm = Xcm(vec![ + WithdrawAsset((Here, fee_in_source).into()), + BuyExecution { + fees: (Here, fee_in_source).into(), + weight_limit: WeightLimit::Unlimited, + }, + TransferReserveAsset { + assets: (Here, withdraw_amount).into(), + dest: Parachain(2).into(), + xcm: Xcm(vec![ + BuyExecution { + fees: (Parent, fee_in_destination).into(), + weight_limit: WeightLimit::Unlimited, + }, + DepositAsset { + assets: All.into(), + beneficiary: Junction::AccountId32 { id: ALICE.into(), network: None } + .into(), + }, + ]), + }, + ]); Relay::execute_with(|| { assert_ok!(relay_chain::XcmPallet::execute( @@ -91,7 +127,7 @@ mod tests { // ALICE's balance in the relay chain decreases assert_eq!( relay_chain::Balances::free_balance(&ALICE), - INITIAL_BALANCE - withdraw_amount + INITIAL_BALANCE - withdraw_amount - fee_in_source ); // Parachain(2)'s sovereign account's balance increases @@ -102,7 +138,10 @@ mod tests { }); ParaB::execute_with(|| { - assert_eq!(parachain::Assets::balance(0, &ALICE), INITIAL_BALANCE + withdraw_amount); + assert_eq!( + parachain::Assets::balance(0, &ALICE), + INITIAL_BALANCE + (withdraw_amount - fee_in_destination) + ); }); } @@ -112,15 +151,29 @@ mod tests { let withdraw_amount = 50 * CENTS; + let fee_in_source = parachain::estimate_message_fee(3); + let fee_in_destination = relay_chain::estimate_message_fee(4); + let message: Xcm = Xcm(vec![ WithdrawAsset((Parent, withdraw_amount).into()), + BuyExecution { + fees: (Parent, fee_in_source).into(), + weight_limit: WeightLimit::Unlimited, + }, InitiateReserveWithdraw { assets: All.into(), reserve: Parent.into(), - xcm: Xcm(vec![DepositAsset { - assets: All.into(), - beneficiary: Junction::AccountId32 { id: ALICE.into(), network: None }.into(), - }]), + xcm: Xcm(vec![ + BuyExecution { + fees: (Here, fee_in_destination).into(), + weight_limit: WeightLimit::Unlimited, + }, + DepositAsset { + assets: All.into(), + beneficiary: Junction::AccountId32 { id: ALICE.into(), network: None } + .into(), + }, + ]), }, ]); @@ -139,13 +192,13 @@ mod tests { // Parachain(1)'s sovereign account balance decreases assert_eq!( relay_chain::Balances::free_balance(parachain_sovereign_account_id(1)), - INITIAL_BALANCE - withdraw_amount + INITIAL_BALANCE - (withdraw_amount - fee_in_source) ); // ALICE's balance in the relay chain increases assert_eq!( relay_chain::Balances::free_balance(&ALICE), - INITIAL_BALANCE + withdraw_amount + INITIAL_BALANCE + (withdraw_amount - fee_in_source - fee_in_destination) ); }); } diff --git a/examples/src/transfers/teleport.rs b/examples/src/transfers/teleport.rs index 719bff3..20b4aa5 100644 --- a/examples/src/transfers/teleport.rs +++ b/examples/src/transfers/teleport.rs @@ -11,16 +11,31 @@ mod tests { fn teleport_fungible() { MockNet::reset(); - let teleport_amount = 50 * CENTS; + let withdraw_amount = 50 * CENTS; + + let fee_in_source = relay_chain::estimate_message_fee(3); + let fee_in_destination = parachain::estimate_message_fee(4); + let message: Xcm = Xcm(vec![ - WithdrawAsset((Here, teleport_amount).into()), + WithdrawAsset((Here, withdraw_amount).into()), + BuyExecution { + fees: (Here, fee_in_source).into(), + weight_limit: WeightLimit::Unlimited, + }, InitiateTeleport { - assets: AllCounted(1).into(), + assets: All.into(), dest: Parachain(1).into(), - xcm: Xcm(vec![DepositAsset { - assets: AllCounted(1).into(), - beneficiary: Junction::AccountId32 { network: None, id: ALICE.into() }.into(), - }]), + xcm: Xcm(vec![ + BuyExecution { + fees: (Parent, fee_in_destination).into(), + weight_limit: WeightLimit::Unlimited, + }, + DepositAsset { + assets: All.into(), + beneficiary: Junction::AccountId32 { network: None, id: ALICE.into() } + .into(), + }, + ]), }, ]); @@ -33,93 +48,32 @@ mod tests { assert_eq!( relay_chain::Balances::free_balance(ALICE), - INITIAL_BALANCE - teleport_amount + INITIAL_BALANCE - withdraw_amount ); }); ParaA::execute_with(|| { let expected_message_received: Xcm = Xcm(vec![ - ReceiveTeleportedAsset(vec![(Parent, teleport_amount).into()].into()), + ReceiveTeleportedAsset( + vec![(Parent, withdraw_amount - fee_in_source).into()].into(), + ), ClearOrigin, + BuyExecution { + fees: (Parent, fee_in_destination).into(), + weight_limit: WeightLimit::Unlimited, + }, DepositAsset { - assets: AllCounted(1).into(), + assets: All.into(), beneficiary: Junction::AccountId32 { network: None, id: ALICE.into() }.into(), }, ]); assert_eq!(parachain::MsgQueue::received_dmp(), vec![expected_message_received]); - assert_eq!(parachain::Assets::balance(0, &ALICE), INITIAL_BALANCE + teleport_amount); - }); - } - - /// Scenario: - /// ALICE teleports her nft from the relay chain to parachain A - #[test] - fn teleport_nft() { - MockNet::reset(); - - Relay::execute_with(|| { - // Mint NFT for Alice on Relay chain - assert_ok!(relay_chain::Uniques::force_create( - relay_chain::RuntimeOrigin::root(), - 1, - ALICE, - true - )); - assert_ok!(relay_chain::Uniques::mint( - relay_chain::RuntimeOrigin::signed(ALICE), - 1, - 42, - ALICE - )); - - assert_eq!(relay_chain::Uniques::owner(1, 42), Some(ALICE)); - }); - - ParaA::execute_with(|| { - // Create NFT collection representing the relay chain one - assert_ok!(parachain::ForeignUniques::force_create( - parachain::RuntimeOrigin::root(), - 1u32, - ALICE, - false - )); - - // Alice is Collection Owner. - assert_eq!(parachain::ForeignUniques::collection_owner(1u32), Some(ALICE)); - // Alice does not own Collection Item 42 yet. - assert_eq!(parachain::ForeignUniques::owner(1u32, 42u32.into()), None); - assert_eq!(parachain::Balances::reserved_balance(&ALICE), 0); - }); - - let message: Xcm = Xcm(vec![ - WithdrawAsset((GeneralIndex(1), 42u64).into()), - InitiateTeleport { - assets: AllCounted(1).into(), - dest: Parachain(1).into(), - xcm: Xcm(vec![DepositAsset { - assets: AllCounted(1).into(), - beneficiary: Junction::AccountId32 { id: ALICE.into(), network: None }.into(), - }]), - }, - ]); - - Relay::execute_with(|| { - assert_ok!(relay_chain::XcmPallet::execute( - relay_chain::RuntimeOrigin::signed(ALICE), - Box::new(xcm::VersionedXcm::V3(message.into())), - (100_000_000_000, 100_000_000_000).into(), - )); - }); - - ParaA::execute_with(|| { - assert_eq!(parachain::ForeignUniques::owner(1u32, 42u32.into()), Some(ALICE)); - assert_eq!(parachain::Balances::reserved_balance(&ALICE), 1000); - }); - - Relay::execute_with(|| { - assert_eq!(relay_chain::Uniques::owner(1, 42), None); + assert_eq!( + parachain::Assets::balance(0, &ALICE), + INITIAL_BALANCE + (withdraw_amount - fee_in_source - fee_in_destination) + ); }); } } diff --git a/examples/src/trap_and_claim/mod.rs b/examples/src/trap_and_claim/mod.rs index 1651b00..e785f5f 100644 --- a/examples/src/trap_and_claim/mod.rs +++ b/examples/src/trap_and_claim/mod.rs @@ -14,9 +14,11 @@ mod tests { /// It then deposits the assets in the account of ALICE. #[test] fn trap_and_claim_assets() { + MockNet::reset(); + let message = Xcm(vec![ + UnpaidExecution { weight_limit: WeightLimit::Unlimited, check_origin: None }, WithdrawAsset((Here, 10 * CENTS).into()), - BuyExecution { fees: (Here, CENTS).into(), weight_limit: WeightLimit::Unlimited }, Trap(0), // <-- Errors DepositAsset { // <-- Not executed because of error. @@ -33,6 +35,7 @@ mod tests { }); let claim_message = Xcm(vec![ + UnpaidExecution { weight_limit: WeightLimit::Unlimited, check_origin: None }, ClaimAsset { assets: (Here, 10 * CENTS).into(), ticket: Here.into() }, ReportHolding { response_info: QueryResponseInfo { diff --git a/examples/src/version_subscription/mod.rs b/examples/src/version_subscription/mod.rs index c48e5ff..1d67fd6 100644 --- a/examples/src/version_subscription/mod.rs +++ b/examples/src/version_subscription/mod.rs @@ -14,11 +14,14 @@ mod tests { fn subscribe_and_unsubscribe_version() { MockNet::reset(); + let message_fee = relay_chain::estimate_message_fee(3); + let query_id_set = 1234; - let message = Xcm(vec![SubscribeVersion { - query_id: query_id_set, - max_response_weight: Weight::from_all(0), - }]); + let message = Xcm(vec![ + WithdrawAsset((Here, message_fee).into()), + BuyExecution { fees: (Here, message_fee).into(), weight_limit: WeightLimit::Unlimited }, + SubscribeVersion { query_id: query_id_set, max_response_weight: Weight::from_all(0) }, + ]); ParaA::execute_with(|| { assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone())); From 93c16dda82d8f39f2694db3e1974b25792ee1e2e Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Wed, 31 May 2023 10:56:58 -0300 Subject: [PATCH 3/3] Add a new transact example --- examples/src/transact/mod.rs | 47 ++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/examples/src/transact/mod.rs b/examples/src/transact/mod.rs index 0f17690..cfbc46a 100644 --- a/examples/src/transact/mod.rs +++ b/examples/src/transact/mod.rs @@ -106,4 +106,51 @@ mod tests { assert_eq!(relay_chain::Uniques::owner(1u32, 1u32), Some(ALICE)); }); } + + #[test] + fn transact_para_to_para() { + MockNet::reset(); + + // We use a simple call, just a transfer, could've been done with the TransferAsset instruction + let call = parachain::RuntimeCall::Balances( + pallet_balances::Call::::transfer { dest: BOB, value: 5 * CENTS }, + ); + + // We calculate and estimate fees + let message_fee = parachain::estimate_message_fee(3); + let estimated_call_weight = Weight::from_parts(100_000_000, 100_000); + let estimated_call_fee = parachain::estimate_fee(estimated_weight_call); + + // We withdraw enough assets for fee payment from ALICE's account on parachain A + // and call `Transact` on parachain B + let message = Xcm(vec![ + WithdrawAsset((Here, message_fee + estimated_call_fee).into()), + BuyExecution { fees: (Here, message_fee).into(), weight_limit: WeightLimit::Unlimited }, + Transact { + origin_kind: OriginKind::SovereignAccount, // We are calling transact from a sovereign account + require_weight_at_most: estimated_call_weight, + call: call.encode().into(), + }, + ]); + + // We want the origin to be ../Parachain(1)/ALICE + let interior = AccountId32 { id: ALICE.into(), network: None }; + + ParaA::execute_with(|| { + assert_ok!(parachain::PolkadotXcm::send_xcm( + interior, + (Parent, Parachain(2)), + message.clone() + )); + }); + + ParaB::execute_with(|| { + // The sovereign account of ../Parachain(1)/ALICE pays 5 cents to BOB's local account + assert_eq!( + parachain::Balances::free_balance(parachain_account_sovereign_account_id(1, ALICE)), + INITIAL_BALANCE - 5 * CENTS + ); + assert_eq!(parachain::Balances::free_balance(BOB), INITIAL_BALANCE + 5 * CENTS); + }); + } }