Improve Penpal runtime + emulated tests (#3543)

Issues addressed in this PR:
- Improve *Penpal* runtime:
- Properly handled received assets. Previously, it treated `(1, Here)`
as the local native currency, whereas it should be treated as a
`ForeignAsset`. This wasn't a great example of standard Parachain
behaviour, as no Parachain treats the system asset as the local
currency.
- Remove `AllowExplicitUnpaidExecutionFrom` the system. Again, this
wasn't a great example of standard Parachain behaviour.
- Move duplicated
`ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger` to
`assets_common` crate.
- Improve emulated tests:
  - Update *Penpal* tests to new runtime.
- To simplify tests, register the reserve transferred, teleported, and
system assets in *Penpal* and *AssetHub* genesis. This saves us from
having to create the assets repeatedly for each test
- Add missing test case:
`reserve_transfer_assets_from_para_to_system_para`.
  - Cleanup.
- Prevent integration tests crates imports from being re-exported, as
they were polluting the `polkadot-sdk` docs.

There is still a test case missing for reserve transfers:
- Reserve transfer of system asset from *Parachain* to *Parachain*
trough *AssetHub*.
- This is not yet possible with `pallet-xcm` due to the reasons
explained in https://github.com/paritytech/polkadot-sdk/pull/3339

---------

Co-authored-by: command-bot <>
This commit is contained in:
Ignacio Palacios
2024-03-14 11:29:24 +01:00
committed by GitHub
parent 606664e1bd
commit cfc4050d6b
47 changed files with 2581 additions and 1200 deletions
@@ -592,7 +592,7 @@ macro_rules! impl_assert_events_helpers_for_parachain {
}
#[macro_export]
macro_rules! impl_assets_helpers_for_parachain {
macro_rules! impl_assets_helpers_for_system_parachain {
( $chain:ident, $relay_chain:ident ) => {
$crate::impls::paste::paste! {
impl<N: $crate::impls::Network> $chain<N> {
@@ -630,38 +630,6 @@ macro_rules! impl_assets_helpers_for_parachain {
$crate::impls::xcm_transact_unpaid_execution(call, origin_kind)
}
/// Mint assets making use of the assets pallet
pub fn mint_asset(
signed_origin: <Self as $crate::impls::Chain>::RuntimeOrigin,
id: u32,
beneficiary: $crate::impls::AccountId,
amount_to_mint: u128,
) {
<Self as $crate::impls::TestExt>::execute_with(|| {
$crate::impls::assert_ok!(<Self as [<$chain ParaPallet>]>::Assets::mint(
signed_origin,
id.clone().into(),
beneficiary.clone().into(),
amount_to_mint
));
type RuntimeEvent<N> = <$chain<N> as $crate::impls::Chain>::RuntimeEvent;
$crate::impls::assert_expected_events!(
Self,
vec![
RuntimeEvent::<N>::Assets(
$crate::impls::pallet_assets::Event::Issued { asset_id, owner, amount }
) => {
asset_id: *asset_id == id,
owner: *owner == beneficiary.clone().into(),
amount: *amount == amount_to_mint,
},
]
);
});
}
/// Force create and mint assets making use of the assets pallet
pub fn force_create_and_mint_asset(
id: u32,
@@ -727,8 +695,8 @@ macro_rules! impl_assets_helpers_for_parachain {
}
#[macro_export]
macro_rules! impl_foreign_assets_helpers_for_parachain {
( $chain:ident, $relay_chain:ident ) => {
macro_rules! impl_assets_helpers_for_parachain {
( $chain:ident) => {
$crate::impls::paste::paste! {
impl<N: $crate::impls::Network> $chain<N> {
/// Create foreign assets using sudo `ForeignAssets::force_create()`
@@ -803,6 +771,118 @@ macro_rules! impl_foreign_assets_helpers_for_parachain {
);
});
}
/// Create assets using sudo `Assets::force_create()`
pub fn force_create_asset(
id: u32,
owner: $crate::impls::AccountId,
is_sufficient: bool,
min_balance: u128,
prefund_accounts: Vec<($crate::impls::AccountId, u128)>,
) {
use $crate::impls::Inspect;
let sudo_origin = <$chain<N> as $crate::impls::Chain>::RuntimeOrigin::root();
<Self as $crate::impls::TestExt>::execute_with(|| {
$crate::impls::assert_ok!(
<Self as [<$chain ParaPallet>]>::Assets::force_create(
sudo_origin,
id.clone().into(),
owner.clone().into(),
is_sufficient,
min_balance,
)
);
assert!(<Self as [<$chain ParaPallet>]>::Assets::asset_exists(id.clone()));
type RuntimeEvent<N> = <$chain<N> as $crate::impls::Chain>::RuntimeEvent;
$crate::impls::assert_expected_events!(
Self,
vec![
RuntimeEvent::<N>::Assets(
$crate::impls::pallet_assets::Event::ForceCreated {
asset_id,
..
}
) => { asset_id: *asset_id == id, },
]
);
});
for (beneficiary, amount) in prefund_accounts.into_iter() {
let signed_origin =
<$chain<N> as $crate::impls::Chain>::RuntimeOrigin::signed(owner.clone());
Self::mint_asset(signed_origin, id.clone(), beneficiary, amount);
}
}
/// Mint assets making use of the assets pallet
pub fn mint_asset(
signed_origin: <Self as $crate::impls::Chain>::RuntimeOrigin,
id: u32,
beneficiary: $crate::impls::AccountId,
amount_to_mint: u128,
) {
<Self as $crate::impls::TestExt>::execute_with(|| {
$crate::impls::assert_ok!(<Self as [<$chain ParaPallet>]>::Assets::mint(
signed_origin,
id.clone().into(),
beneficiary.clone().into(),
amount_to_mint
));
type RuntimeEvent<N> = <$chain<N> as $crate::impls::Chain>::RuntimeEvent;
$crate::impls::assert_expected_events!(
Self,
vec![
RuntimeEvent::<N>::Assets(
$crate::impls::pallet_assets::Event::Issued { asset_id, owner, amount }
) => {
asset_id: *asset_id == id,
owner: *owner == beneficiary.clone().into(),
amount: *amount == amount_to_mint,
},
]
);
});
}
/// Returns the encoded call for `create` from the assets pallet
pub fn create_asset_call(
asset_id: u32,
min_balance: $crate::impls::Balance,
admin: $crate::impls::AccountId,
) -> $crate::impls::DoubleEncoded<()> {
use $crate::impls::{Chain, Encode};
<Self as Chain>::RuntimeCall::Assets($crate::impls::pallet_assets::Call::<
<Self as Chain>::Runtime,
$crate::impls::pallet_assets::Instance1,
>::create {
id: asset_id.into(),
min_balance,
admin: admin.into(),
})
.encode()
.into()
}
/// Returns the encoded call for `create` from the foreign assets pallet
pub fn create_foreign_asset_call(
asset_id: $crate::impls::v3::Location,
min_balance: $crate::impls::Balance,
admin: $crate::impls::AccountId,
) -> $crate::impls::DoubleEncoded<()> {
use $crate::impls::{Chain, Encode};
<Self as Chain>::RuntimeCall::ForeignAssets($crate::impls::pallet_assets::Call::<
<Self as Chain>::Runtime,
$crate::impls::pallet_assets::Instance2,
>::create {
id: asset_id.into(),
min_balance,
admin: admin.into(),
})
.encode()
.into()
}
}
}
};
@@ -21,17 +21,19 @@ pub use xcm_emulator;
// Substrate
use beefy_primitives::ecdsa_crypto::AuthorityId as BeefyId;
use frame_support::parameter_types;
use grandpa::AuthorityId as GrandpaId;
use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
use sp_consensus_babe::AuthorityId as BabeId;
use sp_core::{sr25519, storage::Storage, Pair, Public};
use sp_runtime::{
traits::{IdentifyAccount, Verify},
traits::{AccountIdConversion, IdentifyAccount, Verify},
BuildStorage, MultiSignature,
};
// Polakdot
use parachains_common::BlockNumber;
use polkadot_parachain_primitives::primitives::Sibling;
use polkadot_runtime_parachains::configuration::HostConfiguration;
// Cumulus
@@ -49,6 +51,25 @@ pub const SAFE_XCM_VERSION: u32 = xcm::prelude::XCM_VERSION;
type AccountPublic = <MultiSignature as Verify>::Signer;
// This asset is added to AH as Asset and reserved transfer between Parachain and AH
pub const RESERVABLE_ASSET_ID: u32 = 1;
// This asset is added to AH as ForeignAsset and teleported between Penpal and AH
pub const TELEPORTABLE_ASSET_ID: u32 = 2;
pub const PENPAL_ID: u32 = 2000;
pub const ASSETS_PALLET_ID: u8 = 50;
parameter_types! {
pub PenpalTeleportableAssetLocation: xcm::v3::Location
= xcm::v3::Location::new(1, [
xcm::v3::Junction::Parachain(PENPAL_ID),
xcm::v3::Junction::PalletInstance(ASSETS_PALLET_ID),
xcm::v3::Junction::GeneralIndex(TELEPORTABLE_ASSET_ID.into()),
]
);
pub PenpalSiblingSovereigAccount: AccountId = Sibling::from(PENPAL_ID).into_account_truncating();
}
/// Helper function to generate a crypto pair from seed
pub fn get_from_seed<TPublic: Public>(seed: &str) -> <TPublic::Pair as Pair>::Public {
TPublic::Pair::from_string(&format!("//{}", seed), None)
@@ -16,16 +16,26 @@
pub use paste;
// Substrate
pub use frame_support::{pallet_prelude::Weight, weights::WeightToFee};
pub use pallet_assets;
pub use pallet_balances;
pub use pallet_message_queue;
pub use pallet_xcm;
// Polkadot
pub use xcm::prelude::{AccountId32, WeightLimit};
pub use xcm::{
prelude::{
AccountId32, All, Asset, AssetId, BuyExecution, DepositAsset, ExpectTransactStatus,
Fungible, Here, Location, MaybeErrorCode, OriginKind, RefundSurplus, Transact, Unlimited,
VersionedXcm, WeightLimit, WithdrawAsset, Xcm,
},
v3::Location as V3Location,
};
// Cumulus
pub use asset_test_utils;
pub use cumulus_pallet_xcmp_queue;
pub use parachains_common::AccountId;
pub use xcm_emulator::Chain;
#[macro_export]
@@ -120,102 +130,3 @@ macro_rules! test_parachain_is_trusted_teleporter {
}
};
}
#[macro_export]
macro_rules! include_penpal_create_foreign_asset_on_asset_hub {
( $penpal:ident, $asset_hub:ident, $relay_ed:expr, $weight_to_fee:expr) => {
$crate::impls::paste::paste! {
pub fn penpal_create_foreign_asset_on_asset_hub(
asset_id_on_penpal: u32,
foreign_asset_at_asset_hub: v3::Location,
ah_as_seen_by_penpal: Location,
is_sufficient: bool,
asset_owner: AccountId,
prefund_amount: u128,
) {
use frame_support::weights::WeightToFee;
let ah_check_account = $asset_hub::execute_with(|| {
<$asset_hub as [<$asset_hub Pallet>]>::PolkadotXcm::check_account()
});
let penpal_check_account =
$penpal::execute_with(|| <$penpal as [<$penpal Pallet>]>::PolkadotXcm::check_account());
let penpal_as_seen_by_ah = $asset_hub::sibling_location_of($penpal::para_id());
// prefund SA of Penpal on AssetHub with enough native tokens to pay for creating
// new foreign asset, also prefund CheckingAccount with ED, because teleported asset
// itself might not be sufficient and CheckingAccount cannot be created otherwise
let sov_penpal_on_ah = $asset_hub::sovereign_account_id_of(penpal_as_seen_by_ah.clone());
$asset_hub::fund_accounts(vec![
(sov_penpal_on_ah.clone().into(), $relay_ed * 100_000_000_000),
(ah_check_account.clone().into(), $relay_ed * 1000),
]);
// prefund SA of AssetHub on Penpal with native asset
let sov_ah_on_penpal = $penpal::sovereign_account_id_of(ah_as_seen_by_penpal.clone());
$penpal::fund_accounts(vec![
(sov_ah_on_penpal.into(), $relay_ed * 1_000_000_000),
(penpal_check_account.clone().into(), $relay_ed * 1000),
]);
// Force create asset on $penpal and prefund [<$penpal Sender>]
$penpal::force_create_and_mint_asset(
asset_id_on_penpal,
ASSET_MIN_BALANCE,
is_sufficient,
asset_owner,
None,
prefund_amount,
);
let require_weight_at_most = Weight::from_parts(1_100_000_000_000, 30_000);
// `OriginKind::Xcm` required by ForeignCreators pallet-assets origin filter
let origin_kind = OriginKind::Xcm;
let call_create_foreign_assets =
<$asset_hub as Chain>::RuntimeCall::ForeignAssets(pallet_assets::Call::<
<$asset_hub as Chain>::Runtime,
pallet_assets::Instance2,
>::create {
id: foreign_asset_at_asset_hub,
min_balance: ASSET_MIN_BALANCE,
admin: sov_penpal_on_ah.into(),
})
.encode();
let buy_execution_fee_amount = $weight_to_fee::weight_to_fee(
&Weight::from_parts(10_100_000_000_000, 300_000),
);
let buy_execution_fee = Asset {
id: AssetId(Location { parents: 1, interior: Here }),
fun: Fungible(buy_execution_fee_amount),
};
let xcm = VersionedXcm::from(Xcm(vec![
WithdrawAsset { 0: vec![buy_execution_fee.clone()].into() },
BuyExecution { fees: buy_execution_fee.clone(), weight_limit: Unlimited },
Transact { require_weight_at_most, origin_kind, call: call_create_foreign_assets.into() },
ExpectTransactStatus(MaybeErrorCode::Success),
RefundSurplus,
DepositAsset { assets: All.into(), beneficiary: penpal_as_seen_by_ah },
]));
// Send XCM message from penpal => asset_hub
let sudo_penpal_origin = <$penpal as Chain>::RuntimeOrigin::root();
$penpal::execute_with(|| {
assert_ok!(<$penpal as [<$penpal Pallet>]>::PolkadotXcm::send(
sudo_penpal_origin.clone(),
bx!(ah_as_seen_by_penpal.into()),
bx!(xcm),
));
type RuntimeEvent = <$penpal as Chain>::RuntimeEvent;
assert_expected_events!(
$penpal,
vec![
RuntimeEvent::PolkadotXcm(pallet_xcm::Event::Sent { .. }) => {},
]
);
});
$asset_hub::execute_with(|| {
type ForeignAssets = <$asset_hub as [<$asset_hub Pallet>]>::ForeignAssets;
assert!(ForeignAssets::asset_exists(foreign_asset_at_asset_hub));
});
}
}
};
}