Skip to content

Commit

Permalink
Backport xcmpayment-fee tests from polkadot-fellows repo (paritytech#…
Browse files Browse the repository at this point in the history
…5100)

# Description
This is continue of the work to backport
`emulated-integration-tests-common`, if you want to understand the full
context start with reading
[paritytech#4930](paritytech#4930)
  • Loading branch information
programskillforverification authored and TarekkMA committed Aug 2, 2024
1 parent a0ff84d commit 3f5076a
Show file tree
Hide file tree
Showing 8 changed files with 489 additions and 205 deletions.
103 changes: 52 additions & 51 deletions Cargo.lock

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,11 @@ pub fn non_fee_asset(assets: &Assets, fee_idx: usize) -> Option<(Location, u128)
};
Some((asset.id.0, asset_amount))
}

pub fn get_amount_from_versioned_assets(assets: VersionedAssets) -> u128 {
let latest_assets: Assets = assets.try_into().unwrap();
let Fungible(amount) = latest_assets.inner()[0].fun else {
unreachable!("asset is non-fungible");
};
amount
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pallet-utility = { workspace = true }
xcm = { workspace = true }
pallet-xcm = { workspace = true }
xcm-executor = { workspace = true }
xcm-runtime-apis = { workspace = true, default-features = true }
polkadot-runtime-common = { workspace = true, default-features = true }
rococo-runtime-constants = { workspace = true, default-features = true }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ mod imports {
assert_expected_events, bx, Chain, Parachain as Para, RelayChain as Relay, Test,
TestArgs, TestContext, TestExt,
},
xcm_helpers::{non_fee_asset, xcm_transact_paid_execution},
xcm_helpers::{
get_amount_from_versioned_assets, non_fee_asset, xcm_transact_paid_execution,
},
ASSETS_PALLET_ID, RESERVABLE_ASSET_ID, XCM_V3,
};
pub use parachains_common::Balance;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ mod set_xcm_versions;
mod swap;
mod teleport;
mod treasury;
mod xcm_fee_estimation;
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Tests for XCM fee estimation in the runtime.

use crate::imports::*;
use frame_support::{
dispatch::RawOrigin,
sp_runtime::{traits::Dispatchable, DispatchResult},
};
use xcm_runtime_apis::{
dry_run::runtime_decl_for_dry_run_api::DryRunApiV1,
fees::runtime_decl_for_xcm_payment_api::XcmPaymentApiV1,
};

fn sender_assertions(test: ParaToParaThroughAHTest) {
type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
PenpalA::assert_xcm_pallet_attempted_complete(None);

assert_expected_events!(
PenpalA,
vec![
RuntimeEvent::ForeignAssets(
pallet_assets::Event::Burned { asset_id, owner, balance }
) => {
asset_id: *asset_id == Location::new(1, []),
owner: *owner == test.sender.account_id,
balance: *balance == test.args.amount,
},
]
);
}

fn hop_assertions(test: ParaToParaThroughAHTest) {
type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent;
AssetHubRococo::assert_xcmp_queue_success(None);

assert_expected_events!(
AssetHubRococo,
vec![
RuntimeEvent::Balances(
pallet_balances::Event::Burned { amount, .. }
) => {
amount: *amount == test.args.amount,
},
]
);
}

fn receiver_assertions(test: ParaToParaThroughAHTest) {
type RuntimeEvent = <PenpalB as Chain>::RuntimeEvent;
PenpalB::assert_xcmp_queue_success(None);

assert_expected_events!(
PenpalB,
vec![
RuntimeEvent::ForeignAssets(
pallet_assets::Event::Issued { asset_id, owner, .. }
) => {
asset_id: *asset_id == Location::new(1, []),
owner: *owner == test.receiver.account_id,
},
]
);
}

fn transfer_assets_para_to_para_through_ah_dispatchable(
test: ParaToParaThroughAHTest,
) -> DispatchResult {
let call = transfer_assets_para_to_para_through_ah_call(test.clone());
match call.dispatch(test.signed_origin) {
Ok(_) => Ok(()),
Err(error_with_post_info) => Err(error_with_post_info.error),
}
}

fn transfer_assets_para_to_para_through_ah_call(
test: ParaToParaThroughAHTest,
) -> <PenpalA as Chain>::RuntimeCall {
type RuntimeCall = <PenpalA as Chain>::RuntimeCall;

let asset_hub_location: Location = PenpalB::sibling_location_of(AssetHubRococo::para_id());
let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset {
assets: Wild(AllCounted(test.args.assets.len() as u32)),
beneficiary: test.args.beneficiary,
}]);
RuntimeCall::PolkadotXcm(pallet_xcm::Call::transfer_assets_using_type_and_then {
dest: bx!(test.args.dest.into()),
assets: bx!(test.args.assets.clone().into()),
assets_transfer_type: bx!(TransferType::RemoteReserve(asset_hub_location.clone().into())),
remote_fees_id: bx!(VersionedAssetId::V4(AssetId(Location::new(1, [])))),
fees_transfer_type: bx!(TransferType::RemoteReserve(asset_hub_location.into())),
custom_xcm_on_dest: bx!(VersionedXcm::from(custom_xcm_on_dest)),
weight_limit: test.args.weight_limit,
})
}

/// We are able to dry-run and estimate the fees for a multi-hop XCM journey.
/// Scenario: Alice on PenpalA has some DOTs and wants to send them to PenpalB.
/// We want to know the fees using the `DryRunApi` and `XcmPaymentApi`.
#[test]
fn multi_hop_works() {
let destination = PenpalA::sibling_location_of(PenpalB::para_id());
let sender = PenpalASender::get();
let amount_to_send = 1_000_000_000_000;
let asset_owner = PenpalAssetOwner::get();
let assets: Assets = (Parent, amount_to_send).into();
let relay_native_asset_location = Location::parent();
let sender_as_seen_by_ah = AssetHubRococo::sibling_location_of(PenpalA::para_id());
let sov_of_sender_on_ah = AssetHubRococo::sovereign_account_id_of(sender_as_seen_by_ah.clone());

// fund Parachain's sender account
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(asset_owner.clone()),
relay_native_asset_location.clone(),
sender.clone(),
amount_to_send * 2,
);

// fund the Parachain Origin's SA on AssetHub with the native tokens held in reserve.
AssetHubRococo::fund_accounts(vec![(sov_of_sender_on_ah.clone(), amount_to_send * 2)]);

// Init values for Parachain Destination
let beneficiary_id = PenpalBReceiver::get();

let test_args = TestContext {
sender: PenpalASender::get(), // Bob in PenpalB.
receiver: PenpalBReceiver::get(), // Alice.
args: TestArgs::new_para(
destination,
beneficiary_id.clone(),
amount_to_send,
assets,
None,
0,
),
};
let mut test = ParaToParaThroughAHTest::new(test_args);

// We get them from the PenpalA closure.
let mut delivery_fees_amount = 0;
let mut remote_message = VersionedXcm::V4(Xcm(Vec::new()));
<PenpalA as TestExt>::execute_with(|| {
type Runtime = <PenpalA as Chain>::Runtime;
type OriginCaller = <PenpalA as Chain>::OriginCaller;

let call = transfer_assets_para_to_para_through_ah_call(test.clone());
let origin = OriginCaller::system(RawOrigin::Signed(sender.clone()));
let result = Runtime::dry_run_call(origin, call).unwrap();
// We filter the result to get only the messages we are interested in.
let (destination_to_query, messages_to_query) = &result
.forwarded_xcms
.iter()
.find(|(destination, _)| {
*destination == VersionedLocation::V4(Location::new(1, [Parachain(1000)]))
})
.unwrap();
assert_eq!(messages_to_query.len(), 1);
remote_message = messages_to_query[0].clone();
let delivery_fees =
Runtime::query_delivery_fees(destination_to_query.clone(), remote_message.clone())
.unwrap();
delivery_fees_amount = get_amount_from_versioned_assets(delivery_fees);
});

// These are set in the AssetHub closure.
let mut intermediate_execution_fees = 0;
let mut intermediate_delivery_fees_amount = 0;
let mut intermediate_remote_message = VersionedXcm::V4(Xcm::<()>(Vec::new()));
<AssetHubRococo as TestExt>::execute_with(|| {
type Runtime = <AssetHubRococo as Chain>::Runtime;
type RuntimeCall = <AssetHubRococo as Chain>::RuntimeCall;

// First we get the execution fees.
let weight = Runtime::query_xcm_weight(remote_message.clone()).unwrap();
intermediate_execution_fees = Runtime::query_weight_to_asset_fee(
weight,
VersionedAssetId::V4(Location::new(1, []).into()),
)
.unwrap();

// We have to do this to turn `VersionedXcm<()>` into `VersionedXcm<RuntimeCall>`.
let xcm_program =
VersionedXcm::V4(Xcm::<RuntimeCall>::from(remote_message.clone().try_into().unwrap()));

// Now we get the delivery fees to the final destination.
let result =
Runtime::dry_run_xcm(sender_as_seen_by_ah.clone().into(), xcm_program).unwrap();
let (destination_to_query, messages_to_query) = &result
.forwarded_xcms
.iter()
.find(|(destination, _)| {
*destination == VersionedLocation::V4(Location::new(1, [Parachain(2001)]))
})
.unwrap();
// There's actually two messages here.
// One created when the message we sent from PenpalA arrived and was executed.
// The second one when we dry-run the xcm.
// We could've gotten the message from the queue without having to dry-run, but
// offchain applications would have to dry-run, so we do it here as well.
intermediate_remote_message = messages_to_query[0].clone();
let delivery_fees = Runtime::query_delivery_fees(
destination_to_query.clone(),
intermediate_remote_message.clone(),
)
.unwrap();
intermediate_delivery_fees_amount = get_amount_from_versioned_assets(delivery_fees);
});

// Get the final execution fees in the destination.
let mut final_execution_fees = 0;
<PenpalB as TestExt>::execute_with(|| {
type Runtime = <PenpalA as Chain>::Runtime;

let weight = Runtime::query_xcm_weight(intermediate_remote_message.clone()).unwrap();
final_execution_fees =
Runtime::query_weight_to_asset_fee(weight, VersionedAssetId::V4(Parent.into()))
.unwrap();
});

// Dry-running is done.
PenpalA::reset_ext();
AssetHubRococo::reset_ext();
PenpalB::reset_ext();

// Fund accounts again.
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(asset_owner),
relay_native_asset_location.clone(),
sender.clone(),
amount_to_send * 2,
);
AssetHubRococo::fund_accounts(vec![(sov_of_sender_on_ah, amount_to_send * 2)]);

// Actually run the extrinsic.
let sender_assets_before = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.clone(), &sender)
});
let receiver_assets_before = PenpalB::execute_with(|| {
type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.clone(), &beneficiary_id)
});

test.set_assertion::<PenpalA>(sender_assertions);
test.set_assertion::<AssetHubRococo>(hop_assertions);
test.set_assertion::<PenpalB>(receiver_assertions);
test.set_dispatchable::<PenpalA>(transfer_assets_para_to_para_through_ah_dispatchable);
test.assert();

let sender_assets_after = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.clone(), &sender)
});
let receiver_assets_after = PenpalB::execute_with(|| {
type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location, &beneficiary_id)
});

// We know the exact fees on every hop.
assert_eq!(
sender_assets_after,
sender_assets_before - amount_to_send - delivery_fees_amount /* This is charged directly
* from the sender's
* account. */
);
assert_eq!(
receiver_assets_after,
receiver_assets_before + amount_to_send -
intermediate_execution_fees -
intermediate_delivery_fees_amount -
final_execution_fees
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ mod imports {
assert_expected_events, bx, Chain, Parachain as Para, RelayChain as Relay, Test,
TestArgs, TestContext, TestExt,
},
xcm_helpers::{non_fee_asset, xcm_transact_paid_execution},
xcm_helpers::{
get_amount_from_versioned_assets, non_fee_asset, xcm_transact_paid_execution,
},
ASSETS_PALLET_ID, RESERVABLE_ASSET_ID, XCM_V3,
};
pub use parachains_common::{AccountId, Balance};
Expand Down
Loading

0 comments on commit 3f5076a

Please sign in to comment.