Skip to content

Commit

Permalink
Merge branch 'main' into chore/polkadot-sdk-stable2409-3
Browse files Browse the repository at this point in the history
  • Loading branch information
pandres95 authored Jan 30, 2025
2 parents eb2873d + ffc67cb commit 89e927b
Show file tree
Hide file tree
Showing 9 changed files with 657 additions and 64 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

### Added

- Asset Hubs: added an AssetExchanger to be able to swap tokens using the xcm executor, even for delivery fees ([polkadot-fellows/runtimes#539](https://github.com/polkadot-fellows/runtimes/pull/539)).
- Location conversion tests for relays and parachains ([polkadot-fellows/runtimes#487](https://github.com/polkadot-fellows/runtimes/pull/487))
- Asset Hubs: XcmPaymentApi now returns all assets in a pool with the native token as acceptable as fee payment ([polkadot-fellows/runtimes#523](https://github.com/polkadot-fellows/runtimes/pull/523))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@

use super::reserve_transfer::*;
use crate::{
foreign_balance_on,
tests::teleport::do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt,
*,
};
use asset_hub_kusama_runtime::xcm_config::KsmLocation;
use emulated_integration_tests_common::USDT_ID;

fn para_to_para_assethub_hop_assertions(t: ParaToParaThroughAHTest) {
type RuntimeEvent = <AssetHubKusama as Chain>::RuntimeEvent;
Expand Down Expand Up @@ -788,3 +790,180 @@ fn transfer_native_asset_from_relay_to_para_through_asset_hub() {
// should be non-zero
assert!(receiver_assets_after < receiver_assets_before + amount_to_send);
}

// We transfer USDT from PenpalA to PenpalB through Asset Hub.
// The sender on PenpalA pays delivery fees in KSM.
// When the message arrives to Asset Hub, execution and delivery fees are paid in USDT
// swapping for KSM automatically.
// When it arrives to PenpalB, execution fees are paid with USDT by swapping for KSM.
#[test]
fn usdt_only_transfer_from_para_to_para_through_asset_hub() {
// Initialize necessary variables.
let amount_to_send = 1_000_000_000_000;
let sender = PenpalASender::get();
let destination = PenpalA::sibling_location_of(PenpalB::para_id());
let penpal_a_as_seen_by_ah = AssetHubKusama::sibling_location_of(PenpalA::para_id());
let sov_penpal_on_ah = AssetHubKusama::sovereign_account_id_of(penpal_a_as_seen_by_ah);
let receiver = PenpalBReceiver::get();
let fee_asset_item = 0;
let usdt_location: Location =
(Parent, Parachain(1000), PalletInstance(50), GeneralIndex(1984)).into();
let usdt_location_ah: Location = (PalletInstance(50), GeneralIndex(1984)).into();
let ksm_location = Location::parent();
let assets: Vec<Asset> = vec![(usdt_location.clone(), amount_to_send).into()];

// Sender needs some ksm to pay for delivery fees.
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(PenpalAssetOwner::get()),
ksm_location.clone(),
sender.clone(),
10_000_000_000_000,
);

// The sovereign account of PenpalA in AssetHubKusama needs to have the same amount of USDT
// since it's the reserve.
AssetHubKusama::mint_asset(
<AssetHubKusama as Chain>::RuntimeOrigin::signed(AssetHubKusamaAssetOwner::get()),
USDT_ID,
sov_penpal_on_ah,
10_000_000_000_000,
);

// Mint USDT to sender to be able to transfer.
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(PenpalAssetOwner::get()),
usdt_location.clone(),
sender.clone(),
10_000_000_000_000,
);

// AssetHubKusama has a pool between USDT and ksm so fees can be paid with USDT by automatically
// swapping them for ksm.
create_pool_with_ksm_on!(
AssetHubKusama,
usdt_location_ah,
false,
AssetHubKusamaAssetOwner::get()
);

// PenpalB has a pool between USDT and ksm so fees can be paid with USDT by automatically
// swapping them for ksm.
create_pool_with_ksm_on!(PenpalB, usdt_location.clone(), true, PenpalAssetOwner::get());

// Sender starts with a lot of USDT.
let sender_balance_before = foreign_balance_on!(PenpalA, usdt_location.clone(), &sender);
assert_eq!(sender_balance_before, 10_000_000_000_000);

// Receiver has no USDT.
let receiver_balance_before = foreign_balance_on!(PenpalB, usdt_location.clone(), &receiver);
assert_eq!(receiver_balance_before, 0);

let test_args = TestContext {
sender: sender.clone(),
receiver: receiver.clone(),
args: TestArgs::new_para(
destination.clone(),
receiver.clone(),
amount_to_send,
assets.into(),
None,
fee_asset_item,
),
};
let mut test = ParaToParaThroughAHTest::new(test_args);

// Assertions executed on the sender, PenpalA.
fn sender_assertions(_: ParaToParaThroughAHTest) {
type Event = <PenpalA as Chain>::RuntimeEvent;

let transfer_amount = 1_000_000_000_000;
let usdt_location: Location =
(Parent, Parachain(1000), PalletInstance(50), GeneralIndex(1984)).into();

assert_expected_events!(
PenpalA,
vec![
Event::ForeignAssets(
pallet_assets::Event::Burned { asset_id, balance, .. }
) => {
asset_id: *asset_id == usdt_location.clone(),
balance: *balance == transfer_amount,
},
]
);
}

// Assertions executed on the intermediate hop, AssetHubKusama.
fn ah_assertions(_: ParaToParaThroughAHTest) {
type Event = <AssetHubKusama as Chain>::RuntimeEvent;

let transfer_amount = 1_000_000_000_000;
let penpal_a_as_seen_by_ah = AssetHubKusama::sibling_location_of(PenpalA::para_id());
let sov_penpal_on_ah = AssetHubKusama::sovereign_account_id_of(penpal_a_as_seen_by_ah);

assert_expected_events!(
AssetHubKusama,
vec![
// USDT is burned from sovereign account of PenpalA.
Event::Assets(
pallet_assets::Event::Burned { asset_id, owner, balance }
) => {
asset_id: *asset_id == 1984,
owner: *owner == sov_penpal_on_ah,
balance: *balance == transfer_amount,
},
// Credit is swapped.
Event::AssetConversion(
pallet_asset_conversion::Event::SwapCreditExecuted { .. }
) => {},
// Message from PenpalA was processed.
Event::MessageQueue(
pallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
}

// Assertions executed on the receiver, PenpalB.
fn receiver_assertions(_: ParaToParaThroughAHTest) {
type Event = <PenpalB as Chain>::RuntimeEvent;

let usdt_location: Location =
(Parent, Parachain(1000), PalletInstance(50), GeneralIndex(1984)).into();
let receiver = PenpalBReceiver::get();
let final_amount = 990_665_188_940;

assert_expected_events!(
PenpalB,
vec![
// Final amount gets deposited to receiver.
Event::ForeignAssets(
pallet_assets::Event::Issued { asset_id, owner, amount }
) => {
asset_id: *asset_id == usdt_location,
owner: *owner == receiver,
amount: *amount == final_amount,
},
// Swap was made to pay fees with USDT.
Event::AssetConversion(
pallet_asset_conversion::Event::SwapCreditExecuted { .. }
) => {},
]
);
}

// Run test and assert.
test.set_assertion::<PenpalA>(sender_assertions);
test.set_assertion::<AssetHubKusama>(ah_assertions);
test.set_assertion::<PenpalB>(receiver_assertions);
test.set_dispatchable::<PenpalA>(para_to_para_transfer_assets_through_ah);
test.assert();

// Sender has less USDT after the transfer.
let sender_balance_after = foreign_balance_on!(PenpalA, usdt_location.clone(), &sender);
assert_eq!(sender_balance_after, 9_000_000_000_000);

// Receiver gets `transfer_amount` minus fees.
let receiver_balance_after = foreign_balance_on!(PenpalB, usdt_location.clone(), &receiver);
assert_eq!(receiver_balance_after, 990_665_188_940);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,79 @@ mod swap;
mod teleport;
mod treasury;
mod xcm_fee_estimation;

#[macro_export]
macro_rules! foreign_balance_on {
( $chain:ident, $id:expr, $who:expr ) => {
emulated_integration_tests_common::impls::paste::paste! {
<$chain>::execute_with(|| {
type ForeignAssets = <$chain as [<$chain Pallet>]>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance($id, $who)
})
}
};
}

#[macro_export]
macro_rules! create_pool_with_ksm_on {
( $chain:ident, $asset_id:expr, $is_foreign:expr, $asset_owner:expr ) => {
emulated_integration_tests_common::impls::paste::paste! {
<$chain>::execute_with(|| {
type RuntimeEvent = <$chain as Chain>::RuntimeEvent;
let owner = $asset_owner;
let signed_owner = <$chain as Chain>::RuntimeOrigin::signed(owner.clone());
let ksm_location: Location = Parent.into();
if $is_foreign {
assert_ok!(<$chain as [<$chain Pallet>]>::ForeignAssets::mint(
signed_owner.clone(),
$asset_id.clone().into(),
owner.clone().into(),
10_000_000_000_000, // For it to have more than enough.
));
} else {
let asset_id = match $asset_id.interior.last() {
Some(GeneralIndex(id)) => *id as u32,
_ => unreachable!(),
};
assert_ok!(<$chain as [<$chain Pallet>]>::Assets::mint(
signed_owner.clone(),
asset_id.into(),
owner.clone().into(),
10_000_000_000_000, // For it to have more than enough.
));
}

assert_ok!(<$chain as [<$chain Pallet>]>::AssetConversion::create_pool(
signed_owner.clone(),
Box::new(ksm_location.clone()),
Box::new($asset_id.clone()),
));

assert_expected_events!(
$chain,
vec![
RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {},
]
);

assert_ok!(<$chain as [<$chain Pallet>]>::AssetConversion::add_liquidity(
signed_owner,
Box::new(ksm_location),
Box::new($asset_id),
1_000_000_000_000,
2_000_000_000_000, // $asset_id is worth half of ksm
0,
0,
owner.into()
));

assert_expected_events!(
$chain,
vec![
RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded { .. }) => {},
]
);
});
}
};
}
Loading

0 comments on commit 89e927b

Please sign in to comment.