From 68a8dae8df20d0c239c62bf20a9ccd45eca3b19a Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Wed, 6 Sep 2023 16:34:08 +0300 Subject: [PATCH] asset-hubs runtimes: add reserve withdraw tests --- .../assets/asset-hub-kusama/tests/tests.rs | 30 +++ .../assets/asset-hub-polkadot/tests/tests.rs | 30 +++ .../assets/asset-hub-westend/tests/tests.rs | 30 +++ .../runtimes/assets/test-utils/src/lib.rs | 33 +++ .../assets/test-utils/src/test_cases.rs | 232 +++++++++++++++++- 5 files changed, 354 insertions(+), 1 deletion(-) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/tests/tests.rs b/cumulus/parachains/runtimes/assets/asset-hub-kusama/tests/tests.rs index 31004dfc62e1..88ec38b7ac96 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-kusama/tests/tests.rs @@ -662,3 +662,33 @@ fn reserve_transfer_native_asset_works() { WeightLimit::Unlimited, ); } + +#[test] +fn reserve_withdraw_foreign_asset_works() { + asset_test_utils::test_cases::reserve_withdraw_foreign_asset_works::< + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + ForeignAssetsInstance, + ParachainSystem, + XcmpQueue, + LocationToAccountId, + >( + collator_session_keys(), + ExistentialDeposit::get(), + AccountId::from(ALICE), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::PolkadotXcm(event)) => Some(event), + _ => None, + } + }), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::XcmpQueue(event)) => Some(event), + _ => None, + } + }), + WeightLimit::Unlimited, + ); +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/tests/tests.rs b/cumulus/parachains/runtimes/assets/asset-hub-polkadot/tests/tests.rs index 5dd3cbb80dec..a1f2e4bc1eac 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-polkadot/tests/tests.rs @@ -687,3 +687,33 @@ fn reserve_transfer_native_asset_works() { WeightLimit::Unlimited, ); } + +#[test] +fn reserve_withdraw_foreign_asset_works() { + asset_test_utils::test_cases::reserve_withdraw_foreign_asset_works::< + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + ForeignAssetsInstance, + ParachainSystem, + XcmpQueue, + LocationToAccountId, + >( + collator_session_keys(), + ExistentialDeposit::get(), + AccountId::from(ALICE), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::PolkadotXcm(event)) => Some(event), + _ => None, + } + }), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::XcmpQueue(event)) => Some(event), + _ => None, + } + }), + WeightLimit::Unlimited, + ); +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs index 4d8eba91b78a..1c3af637ea21 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs @@ -695,3 +695,33 @@ fn reserve_transfer_native_asset_works() { WeightLimit::Unlimited, ); } + +#[test] +fn reserve_withdraw_foreign_asset_works() { + asset_test_utils::test_cases::reserve_withdraw_foreign_asset_works::< + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + ForeignAssetsInstance, + ParachainSystem, + XcmpQueue, + LocationToAccountId, + >( + collator_session_keys(), + ExistentialDeposit::get(), + AccountId::from(ALICE), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::PolkadotXcm(event)) => Some(event), + _ => None, + } + }), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::XcmpQueue(event)) => Some(event), + _ => None, + } + }), + WeightLimit::Unlimited, + ); +} diff --git a/cumulus/parachains/runtimes/assets/test-utils/src/lib.rs b/cumulus/parachains/runtimes/assets/test-utils/src/lib.rs index f9ccb0cd61d1..d0121193a258 100644 --- a/cumulus/parachains/runtimes/assets/test-utils/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/test-utils/src/lib.rs @@ -60,3 +60,36 @@ pub(crate) fn assert_matches_reserve_asset_deposited_instructions( }) .expect("expected instruction DepositAsset"); } + +/// Helper function to verify `xcm` contains all relevant instructions expected on destination +/// chain as part of a reserve-asset-withdrawal. +pub(crate) fn assert_matches_reserve_assets_withdrawal_instructions( + xcm: &mut Xcm, + expected_reserve_assets: &MultiAssets, + expected_beneficiary: &MultiLocation, +) { + let _ = xcm + .0 + .matcher() + .match_next_inst(|instr| match instr { + WithdrawAsset(reserve_assets) if reserve_assets == expected_reserve_assets => Ok(()), + _ => Err(ProcessMessageError::BadFormat), + }) + .expect("expected instruction WithdrawAsset for reserve asset") + .match_next_inst(|instr| match instr { + ClearOrigin => Ok(()), + _ => Err(ProcessMessageError::BadFormat), + }) + .expect("expected instruction ClearOrigin") + .match_next_inst(|instr| match instr { + BuyExecution { .. } => Ok(()), + _ => Err(ProcessMessageError::BadFormat), + }) + .expect("expected instruction BuyExecution") + .match_next_inst(|instr| match instr { + DepositAsset { assets: _, beneficiary } if beneficiary == expected_beneficiary => + Ok(()), + _ => Err(ProcessMessageError::BadFormat), + }) + .expect("expected instruction DepositAsset"); +} diff --git a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs index 335a8a42be29..d831113c1560 100644 --- a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs +++ b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs @@ -15,7 +15,10 @@ //! Module contains predefined test-case scenarios for `Runtime` with various assets. -use crate::assert_matches_reserve_asset_deposited_instructions; +use crate::{ + assert_matches_reserve_asset_deposited_instructions, + assert_matches_reserve_assets_withdrawal_instructions, +}; use codec::Encode; use cumulus_primitives_core::XcmpMessageSource; use frame_support::{ @@ -1526,3 +1529,230 @@ pub fn reserve_transfer_native_asset_works< ); }) } + +/// Test-case makes sure that `Runtime` can initiate withdrawal of reserve-based foreign asset +pub fn reserve_withdraw_foreign_asset_works< + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + ForeignAssetsPalletInstance, + HrmpChannelOpener, + HrmpChannelSource, + LocationToAccountId, +>( + collator_session_keys: CollatorSessionKeys, + existential_deposit: BalanceOf, + alice_account: AccountIdOf, + unwrap_pallet_xcm_event: Box) -> Option>>, + unwrap_xcmp_queue_event: Box< + dyn Fn(Vec) -> Option>, + >, + weight_limit: WeightLimit, +) where + Runtime: frame_system::Config + + pallet_assets::Config + + pallet_balances::Config + + pallet_session::Config + + pallet_xcm::Config + + parachain_info::Config + + pallet_collator_selection::Config + + cumulus_pallet_parachain_system::Config + + cumulus_pallet_xcmp_queue::Config, + ForeignAssetsPalletInstance: 'static, + AllPalletsWithoutSystem: + OnInitialize> + OnFinalize>, + AccountIdOf: Into<[u8; 32]>, + ValidatorIdOf: From>, + BalanceOf: From, + ::Balance: From + Into, + XcmConfig: xcm_executor::Config, + LocationToAccountId: ConvertLocation>, + ::AccountId: + Into<<::RuntimeOrigin as OriginTrait>::AccountId>, + <::Lookup as StaticLookup>::Source: + From<::AccountId>, + ::AccountId: From, + >::AssetId: From, + >::AssetIdParameter: + From, + >::Balance: + From + Into, + HrmpChannelOpener: frame_support::inherent::ProvideInherent< + Call = cumulus_pallet_parachain_system::Call, + >, + HrmpChannelSource: XcmpMessageSource, +{ + let runtime_para_id = 1000; + ExtBuilder::::default() + .with_collators(collator_session_keys.collators()) + .with_session_keys(collator_session_keys.session_keys()) + .with_tracing() + .with_safe_xcm_version(3) + .with_para_id(runtime_para_id.into()) + .build() + .execute_with(|| { + let mut alice = [0u8; 32]; + alice[0] = 1; + let included_head = RuntimeHelper::::run_to_block( + 2, + AccountId::from(alice).into(), + ); + + // Let's assume remote parachain (1234) has and acts as a reserve for its own + // native asset (`BLA`). + // Local AssetHub user Alice has previously received `wBLA` as reserve-based foreign + // asset with its reserve on parachain 1234. + // Verify Alice can withdraw wBLA here to send BLAs to some beneficiary on para 1234. + + let other_para_id = 1234; + let foreign_asset_id = MultiLocation::new(1, X1(Parachain(other_para_id))); + let dest = MultiLocation::new(1, X1(Parachain(other_para_id))); + let dest_beneficiary = MultiLocation::new(1, X1(Parachain(other_para_id))) + .appended_with(AccountId32 { + network: None, + id: sp_runtime::AccountId32::new([3; 32]).into(), + }) + .unwrap(); + + let reserve_account = LocationToAccountId::convert_location(&dest) + .expect("Sovereign account for reserves"); + let balance_to_transfer = 1_000_000_000_000_u128; + + // open HRMP to other parachain + mock_open_hrmp_channel::( + runtime_para_id.into(), + other_para_id.into(), + included_head, + &alice, + ); + + // drip ED to alice account + let _ = >::deposit_creating( + &alice_account, + existential_deposit, + ); + // SA of target location needs to have at least ED, otherwise making reserve fails + let _ = >::deposit_creating( + &reserve_account, + existential_deposit, + ); + let existential_deposit: u128 = existential_deposit.into(); + + // create foreign assets with remote reserve (what we want to test withdraw for) + assert_ok!( + >::force_create( + RuntimeHelper::::root_origin(), + foreign_asset_id.into(), + reserve_account.clone().into(), + false, + existential_deposit.into() + ) + ); + // drip foreign asset to alice + assert_ok!(>::mint( + RuntimeHelper::::origin_of(reserve_account.clone()), + foreign_asset_id.into(), + alice_account.clone().into(), + (existential_deposit + balance_to_transfer).into() + )); + + assert_eq!( + >::free_balance(&alice_account), + existential_deposit.into() + ); + assert_eq!( + >::free_balance(&reserve_account), + existential_deposit.into() + ); + assert_eq!( + >::balance( + foreign_asset_id.into(), + alice_account.clone() + ), + (existential_deposit + balance_to_transfer).into() + ); + + // foreign asset to withdraw + let asset_to_withdraw = MultiAsset { + fun: Fungible(balance_to_transfer.into()), + id: Concrete(foreign_asset_id), + }; + + // pallet_xcm call reserve withdraw + assert_ok!(>::limited_reserve_withdraw_assets( + RuntimeHelper::::origin_of(alice_account.clone()), + Box::new(dest.into_versioned()), + Box::new(dest_beneficiary.into_versioned()), + Box::new(VersionedMultiAssets::from(MultiAssets::from(asset_to_withdraw))), + 0, + weight_limit, + )); + + // check alice account (balances not changed) + assert_eq!( + >::free_balance(&alice_account), + existential_deposit.into() + ); + // check reserve account (balances not changed) + assert_eq!( + >::free_balance(&reserve_account), + existential_deposit.into() + ); + // `ForeignAssets` for alice account is decreased + // TODO:check-parameter: change and assert in tests when (https://github.com/paritytech/polkadot/pull/7005) merged + assert_eq!( + >::balance( + foreign_asset_id.into(), + alice_account.clone() + ), + existential_deposit.into() + ); + + // check events + // check pallet_xcm attempted + RuntimeHelper::::assert_pallet_xcm_event_outcome( + &unwrap_pallet_xcm_event, + |outcome| { + assert_ok!(outcome.ensure_complete()); + }, + ); + + // check that xcm was sent + let xcm_sent_message_hash = >::events() + .into_iter() + .filter_map(|e| unwrap_xcmp_queue_event(e.event.encode())) + .find_map(|e| match e { + cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { message_hash } => + Some(message_hash), + _ => None, + }); + + // read xcm + let xcm_sent = RuntimeHelper::::take_xcm( + other_para_id.into(), + ) + .unwrap(); + + assert_eq!( + xcm_sent_message_hash, + Some(xcm_sent.using_encoded(sp_io::hashing::blake2_256)) + ); + let mut xcm_sent: Xcm<()> = xcm_sent.try_into().expect("versioned xcm"); + + // check sent XCM Program to other parachain + println!("reserve_withdraw_foreign_asset_works sent xcm: {:?}", xcm_sent); + + // The reserve assets (BLA) as seen by their native parachain. + // We expect the destination parachain to execute `WithdrawAsset()` on this. + let expected_reanchored_reserve_assets = MultiAssets::from(vec![MultiAsset { + id: Concrete(MultiLocation { parents: 0, interior: Here }), + fun: Fungible(1000000000000), + }]); + + assert_matches_reserve_assets_withdrawal_instructions( + &mut xcm_sent, + &expected_reanchored_reserve_assets, + &dest_beneficiary, + ); + }) +}