mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 22:11:02 +00:00
Westend/Rococo Asset Hub: pay xcm fees with sufficient assets (#2978)
Set up the `TakeFirstAssetTrader` trader for Westend and Rococo Asset Hubs to cover XCM fees with sufficient assets. This PR reintroduces previously [removed](https://github.com/paritytech/polkadot-sdk/pull/1845) trader setups, as it was decided to keep both traders, `TakeFirstAssetTrader` and `SwapFirstAssetTrader`, during the transition period. --------- Co-authored-by: Svyatoslav Nikolsky <svyatonik@gmail.com>
This commit is contained in:
@@ -14,7 +14,7 @@ bridge-hub-westend-collator1: js-script ../helpers/best-finalized-header-at-brid
|
||||
|
||||
# step 4: send WND to //Alice on Rococo AH
|
||||
# (that's a required part of a sibling 0001-asset-transfer-works-westend-to-rococo.zndsl test)
|
||||
asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "reserve-transfer-assets-from-asset-hub-westend-local" within 60 seconds
|
||||
asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "reserve-transfer-assets-from-asset-hub-westend-local" within 120 seconds
|
||||
|
||||
# step 5: elsewhere Rococo has sent ROC to //Alice - let's wait for it
|
||||
asset-hub-westend-collator1: js-script ../helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,0,Rococo" within 600 seconds
|
||||
@@ -24,7 +24,7 @@ bridge-hub-westend-collator1: js-script ../helpers/relayer-rewards.js with "5FLS
|
||||
bridge-hub-westend-collator1: js-script ../helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x6268726F,ThisChain,0" within 300 seconds
|
||||
|
||||
# step 7: send wROC back to Alice at Rococo AH
|
||||
asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "withdraw-reserve-assets-from-asset-hub-westend-local" within 60 seconds
|
||||
asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "withdraw-reserve-assets-from-asset-hub-westend-local" within 120 seconds
|
||||
|
||||
# step 8: elsewhere Rococo has sent wWND to //Alice - let's wait for it
|
||||
# (we wait until //Alice account increases here - there are no other transactionc that may increase it)
|
||||
|
||||
@@ -14,7 +14,7 @@ bridge-hub-rococo-collator1: js-script ../helpers/best-finalized-header-at-bridg
|
||||
|
||||
# step 4: send ROC to //Alice on Westend AH
|
||||
# (that's a required part of a sibling 0001-asset-transfer-works-rococo-to-westend.zndsl test)
|
||||
asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "reserve-transfer-assets-from-asset-hub-rococo-local" within 60 seconds
|
||||
asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "reserve-transfer-assets-from-asset-hub-rococo-local" within 120 seconds
|
||||
|
||||
# step 5: elsewhere Westend has sent WND to //Alice - let's wait for it
|
||||
asset-hub-rococo-collator1: js-script ../helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,0,Westend" within 600 seconds
|
||||
@@ -24,7 +24,7 @@ bridge-hub-rococo-collator1: js-script ../helpers/relayer-rewards.js with "5FLSi
|
||||
bridge-hub-rococo-collator1: js-script ../helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x62687764,ThisChain,0" within 300 seconds
|
||||
|
||||
# step 7: send wWND back to Alice at Westend AH
|
||||
asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "withdraw-reserve-assets-from-asset-hub-rococo-local" within 60 seconds
|
||||
asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "withdraw-reserve-assets-from-asset-hub-rococo-local" within 120 seconds
|
||||
|
||||
# step 8: elsewhere Westend has sent wROC to //Alice - let's wait for it
|
||||
# (we wait until //Alice account increases here - there are no other transactionc that may increase it)
|
||||
|
||||
+75
@@ -27,3 +27,78 @@ fn send_transact_as_superuser_from_relay_to_system_para_works() {
|
||||
Some(Weight::from_parts(1_019_445_000, 200_000)),
|
||||
)
|
||||
}
|
||||
|
||||
/// Parachain should be able to send XCM paying its fee with sufficient asset
|
||||
/// in the System Parachain
|
||||
#[test]
|
||||
fn send_xcm_from_para_to_system_para_paying_fee_with_assets_works() {
|
||||
let para_sovereign_account = AssetHubRococo::sovereign_account_id_of(
|
||||
AssetHubRococo::sibling_location_of(PenpalA::para_id()),
|
||||
);
|
||||
|
||||
// Force create and mint assets for Parachain's sovereign account
|
||||
AssetHubRococo::force_create_and_mint_asset(
|
||||
ASSET_ID,
|
||||
ASSET_MIN_BALANCE,
|
||||
true,
|
||||
para_sovereign_account.clone(),
|
||||
Some(Weight::from_parts(1_019_445_000, 200_000)),
|
||||
ASSET_MIN_BALANCE * 1000000000,
|
||||
);
|
||||
|
||||
// We just need a call that can pass the `SafeCallFilter`
|
||||
// Call values are not relevant
|
||||
let call = AssetHubRococo::force_create_asset_call(
|
||||
ASSET_ID,
|
||||
para_sovereign_account.clone(),
|
||||
true,
|
||||
ASSET_MIN_BALANCE,
|
||||
);
|
||||
|
||||
let origin_kind = OriginKind::SovereignAccount;
|
||||
let fee_amount = ASSET_MIN_BALANCE * 1000000;
|
||||
let native_asset =
|
||||
([PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())], fee_amount).into();
|
||||
|
||||
let root_origin = <PenpalA as Chain>::RuntimeOrigin::root();
|
||||
let system_para_destination = PenpalA::sibling_location_of(AssetHubRococo::para_id()).into();
|
||||
let xcm = xcm_transact_paid_execution(
|
||||
call,
|
||||
origin_kind,
|
||||
native_asset,
|
||||
para_sovereign_account.clone(),
|
||||
);
|
||||
|
||||
PenpalA::execute_with(|| {
|
||||
assert_ok!(<PenpalA as PenpalAPallet>::PolkadotXcm::send(
|
||||
root_origin,
|
||||
bx!(system_para_destination),
|
||||
bx!(xcm),
|
||||
));
|
||||
|
||||
PenpalA::assert_xcm_pallet_sent();
|
||||
});
|
||||
|
||||
AssetHubRococo::execute_with(|| {
|
||||
type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent;
|
||||
|
||||
AssetHubRococo::assert_xcmp_queue_success(Some(Weight::from_parts(
|
||||
15_594_564_000,
|
||||
562_893,
|
||||
)));
|
||||
|
||||
assert_expected_events!(
|
||||
AssetHubRococo,
|
||||
vec![
|
||||
RuntimeEvent::Assets(pallet_assets::Event::Burned { asset_id, owner, balance }) => {
|
||||
asset_id: *asset_id == ASSET_ID,
|
||||
owner: *owner == para_sovereign_account,
|
||||
balance: *balance == fee_amount,
|
||||
},
|
||||
RuntimeEvent::Assets(pallet_assets::Event::Issued { asset_id, .. }) => {
|
||||
asset_id: *asset_id == ASSET_ID,
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
+75
@@ -27,3 +27,78 @@ fn send_transact_as_superuser_from_relay_to_system_para_works() {
|
||||
Some(Weight::from_parts(1_019_445_000, 200_000)),
|
||||
)
|
||||
}
|
||||
|
||||
/// Parachain should be able to send XCM paying its fee with sufficient asset
|
||||
/// in the System Parachain
|
||||
#[test]
|
||||
fn send_xcm_from_para_to_system_para_paying_fee_with_assets_works() {
|
||||
let para_sovereign_account = AssetHubWestend::sovereign_account_id_of(
|
||||
AssetHubWestend::sibling_location_of(PenpalB::para_id()),
|
||||
);
|
||||
|
||||
// Force create and mint assets for Parachain's sovereign account
|
||||
AssetHubWestend::force_create_and_mint_asset(
|
||||
ASSET_ID,
|
||||
ASSET_MIN_BALANCE,
|
||||
true,
|
||||
para_sovereign_account.clone(),
|
||||
Some(Weight::from_parts(1_019_445_000, 200_000)),
|
||||
ASSET_MIN_BALANCE * 1000000000,
|
||||
);
|
||||
|
||||
// We just need a call that can pass the `SafeCallFilter`
|
||||
// Call values are not relevant
|
||||
let call = AssetHubWestend::force_create_asset_call(
|
||||
ASSET_ID,
|
||||
para_sovereign_account.clone(),
|
||||
true,
|
||||
ASSET_MIN_BALANCE,
|
||||
);
|
||||
|
||||
let origin_kind = OriginKind::SovereignAccount;
|
||||
let fee_amount = ASSET_MIN_BALANCE * 1000000;
|
||||
let native_asset =
|
||||
([PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())], fee_amount).into();
|
||||
|
||||
let root_origin = <PenpalB as Chain>::RuntimeOrigin::root();
|
||||
let system_para_destination = PenpalB::sibling_location_of(AssetHubWestend::para_id()).into();
|
||||
let xcm = xcm_transact_paid_execution(
|
||||
call,
|
||||
origin_kind,
|
||||
native_asset,
|
||||
para_sovereign_account.clone(),
|
||||
);
|
||||
|
||||
PenpalB::execute_with(|| {
|
||||
assert_ok!(<PenpalB as PenpalBPallet>::PolkadotXcm::send(
|
||||
root_origin,
|
||||
bx!(system_para_destination),
|
||||
bx!(xcm),
|
||||
));
|
||||
|
||||
PenpalB::assert_xcm_pallet_sent();
|
||||
});
|
||||
|
||||
AssetHubWestend::execute_with(|| {
|
||||
type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
|
||||
|
||||
AssetHubWestend::assert_xcmp_queue_success(Some(Weight::from_parts(
|
||||
16_290_336_000,
|
||||
562_893,
|
||||
)));
|
||||
|
||||
assert_expected_events!(
|
||||
AssetHubWestend,
|
||||
vec![
|
||||
RuntimeEvent::Assets(pallet_assets::Event::Burned { asset_id, owner, balance }) => {
|
||||
asset_id: *asset_id == ASSET_ID,
|
||||
owner: *owner == para_sovereign_account,
|
||||
balance: *balance == fee_amount,
|
||||
},
|
||||
RuntimeEvent::Assets(pallet_assets::Event::Issued { asset_id, .. }) => {
|
||||
asset_id: *asset_id == ASSET_ID,
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -585,6 +585,32 @@ impl xcm_executor::Config for XcmConfig {
|
||||
ResolveAssetTo<StakingPot, crate::NativeAndAssets>,
|
||||
AccountId,
|
||||
>,
|
||||
// This trader allows to pay with `is_sufficient=true` "Trust Backed" assets from dedicated
|
||||
// `pallet_assets` instance - `Assets`.
|
||||
cumulus_primitives_utility::TakeFirstAssetTrader<
|
||||
AccountId,
|
||||
AssetFeeAsExistentialDepositMultiplierFeeCharger,
|
||||
TrustBackedAssetsConvertedConcreteId,
|
||||
Assets,
|
||||
cumulus_primitives_utility::XcmFeesTo32ByteAccount<
|
||||
FungiblesTransactor,
|
||||
AccountId,
|
||||
XcmAssetFeesReceiver,
|
||||
>,
|
||||
>,
|
||||
// This trader allows to pay with `is_sufficient=true` "Foreign" assets from dedicated
|
||||
// `pallet_assets` instance - `ForeignAssets`.
|
||||
cumulus_primitives_utility::TakeFirstAssetTrader<
|
||||
AccountId,
|
||||
ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger,
|
||||
ForeignAssetsConvertedConcreteId,
|
||||
ForeignAssets,
|
||||
cumulus_primitives_utility::XcmFeesTo32ByteAccount<
|
||||
ForeignFungiblesTransactor,
|
||||
AccountId,
|
||||
XcmAssetFeesReceiver,
|
||||
>,
|
||||
>,
|
||||
);
|
||||
type ResponseHandler = PolkadotXcm;
|
||||
type AssetTrap = PolkadotXcm;
|
||||
|
||||
@@ -20,27 +20,23 @@
|
||||
use asset_hub_rococo_runtime::{
|
||||
xcm_config,
|
||||
xcm_config::{
|
||||
bridging, ForeignCreatorsSovereignAccountOf, LocationToAccountId, TokenLocation,
|
||||
TokenLocationV3,
|
||||
bridging, AssetFeeAsExistentialDepositMultiplierFeeCharger, CheckingAccount,
|
||||
ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger, ForeignCreatorsSovereignAccountOf,
|
||||
LocationToAccountId, TokenLocation, TokenLocationV3, TrustBackedAssetsPalletLocation,
|
||||
TrustBackedAssetsPalletLocationV3, XcmConfig,
|
||||
},
|
||||
AllPalletsWithoutSystem, MetadataDepositBase, MetadataDepositPerByte, RuntimeCall,
|
||||
RuntimeEvent, ToWestendXcmRouterInstance, XcmpQueue,
|
||||
};
|
||||
pub use asset_hub_rococo_runtime::{
|
||||
xcm_config::{
|
||||
CheckingAccount, TrustBackedAssetsPalletLocation, TrustBackedAssetsPalletLocationV3,
|
||||
XcmConfig,
|
||||
},
|
||||
AssetConversion, AssetDeposit, Assets, Balances, CollatorSelection, ExistentialDeposit,
|
||||
ForeignAssets, ForeignAssetsInstance, ParachainSystem, Runtime, SessionKeys, System,
|
||||
TrustBackedAssetsInstance,
|
||||
AllPalletsWithoutSystem, AssetConversion, AssetDeposit, Assets, Balances, CollatorSelection,
|
||||
ExistentialDeposit, ForeignAssets, ForeignAssetsInstance, MetadataDepositBase,
|
||||
MetadataDepositPerByte, ParachainSystem, Runtime, RuntimeCall, RuntimeEvent, SessionKeys,
|
||||
ToWestendXcmRouterInstance, TrustBackedAssetsInstance, XcmpQueue,
|
||||
};
|
||||
use asset_test_utils::{
|
||||
test_cases_over_bridge::TestBridgingConfig, CollatorSessionKey, CollatorSessionKeys, ExtBuilder,
|
||||
};
|
||||
use codec::{Decode, Encode};
|
||||
use cumulus_primitives_utility::ChargeWeightInFungibles;
|
||||
use frame_support::{
|
||||
assert_ok,
|
||||
assert_noop, assert_ok,
|
||||
traits::{
|
||||
fungible::{Inspect, Mutate},
|
||||
fungibles::{
|
||||
@@ -353,6 +349,429 @@ fn test_buy_and_refund_weight_with_swap_foreign_asset_xcm_trader() {
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_asset_xcm_take_first_trader() {
|
||||
ExtBuilder::<Runtime>::default()
|
||||
.with_collators(vec![AccountId::from(ALICE)])
|
||||
.with_session_keys(vec![(
|
||||
AccountId::from(ALICE),
|
||||
AccountId::from(ALICE),
|
||||
SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) },
|
||||
)])
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
// We need root origin to create a sufficient asset
|
||||
let minimum_asset_balance = 3333333_u128;
|
||||
let local_asset_id = 1;
|
||||
assert_ok!(Assets::force_create(
|
||||
RuntimeHelper::root_origin(),
|
||||
local_asset_id.into(),
|
||||
AccountId::from(ALICE).into(),
|
||||
true,
|
||||
minimum_asset_balance
|
||||
));
|
||||
|
||||
// We first mint enough asset for the account to exist for assets
|
||||
assert_ok!(Assets::mint(
|
||||
RuntimeHelper::origin_of(AccountId::from(ALICE)),
|
||||
local_asset_id.into(),
|
||||
AccountId::from(ALICE).into(),
|
||||
minimum_asset_balance
|
||||
));
|
||||
|
||||
// get asset id as location
|
||||
let asset_location =
|
||||
AssetIdForTrustBackedAssetsConvertLatest::convert_back(&local_asset_id).unwrap();
|
||||
|
||||
// Set Alice as block author, who will receive fees
|
||||
RuntimeHelper::run_to_block(2, AccountId::from(ALICE));
|
||||
|
||||
// We are going to buy 4e9 weight
|
||||
let bought = Weight::from_parts(4_000_000_000u64, 0);
|
||||
|
||||
// Lets calculate amount needed
|
||||
let asset_amount_needed =
|
||||
AssetFeeAsExistentialDepositMultiplierFeeCharger::charge_weight_in_fungibles(
|
||||
local_asset_id,
|
||||
bought,
|
||||
)
|
||||
.expect("failed to compute");
|
||||
|
||||
// Lets pay with: asset_amount_needed + asset_amount_extra
|
||||
let asset_amount_extra = 100_u128;
|
||||
let asset: Asset =
|
||||
(asset_location.clone(), asset_amount_needed + asset_amount_extra).into();
|
||||
|
||||
let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
|
||||
let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
|
||||
|
||||
// Lets buy_weight and make sure buy_weight does not return an error
|
||||
let unused_assets = trader.buy_weight(bought, asset.into(), &ctx).expect("Expected Ok");
|
||||
// Check whether a correct amount of unused assets is returned
|
||||
assert_ok!(unused_assets.ensure_contains(&(asset_location, asset_amount_extra).into()));
|
||||
|
||||
// Drop trader
|
||||
drop(trader);
|
||||
|
||||
// Make sure author(Alice) has received the amount
|
||||
assert_eq!(
|
||||
Assets::balance(local_asset_id, AccountId::from(ALICE)),
|
||||
minimum_asset_balance + asset_amount_needed
|
||||
);
|
||||
|
||||
// We also need to ensure the total supply increased
|
||||
assert_eq!(
|
||||
Assets::total_supply(local_asset_id),
|
||||
minimum_asset_balance + asset_amount_needed
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_foreign_asset_xcm_take_first_trader() {
|
||||
ExtBuilder::<Runtime>::default()
|
||||
.with_collators(vec![AccountId::from(ALICE)])
|
||||
.with_session_keys(vec![(
|
||||
AccountId::from(ALICE),
|
||||
AccountId::from(ALICE),
|
||||
SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) },
|
||||
)])
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
// We need root origin to create a sufficient asset
|
||||
let minimum_asset_balance = 3333333_u128;
|
||||
let foreign_location = xcm::v3::Location {
|
||||
parents: 1,
|
||||
interior: (
|
||||
xcm::v3::Junction::Parachain(1234),
|
||||
xcm::v3::Junction::GeneralIndex(12345),
|
||||
)
|
||||
.into(),
|
||||
};
|
||||
assert_ok!(ForeignAssets::force_create(
|
||||
RuntimeHelper::root_origin(),
|
||||
foreign_location.into(),
|
||||
AccountId::from(ALICE).into(),
|
||||
true,
|
||||
minimum_asset_balance
|
||||
));
|
||||
|
||||
// We first mint enough asset for the account to exist for assets
|
||||
assert_ok!(ForeignAssets::mint(
|
||||
RuntimeHelper::origin_of(AccountId::from(ALICE)),
|
||||
foreign_location.into(),
|
||||
AccountId::from(ALICE).into(),
|
||||
minimum_asset_balance
|
||||
));
|
||||
|
||||
let asset_location_v4: Location = foreign_location.try_into().unwrap();
|
||||
|
||||
// Set Alice as block author, who will receive fees
|
||||
RuntimeHelper::run_to_block(2, AccountId::from(ALICE));
|
||||
|
||||
// We are going to buy 4e9 weight
|
||||
let bought = Weight::from_parts(4_000_000_000u64, 0);
|
||||
|
||||
// Lets calculate amount needed
|
||||
let asset_amount_needed =
|
||||
ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger::charge_weight_in_fungibles(
|
||||
foreign_location,
|
||||
bought,
|
||||
)
|
||||
.expect("failed to compute");
|
||||
|
||||
// Lets pay with: asset_amount_needed + asset_amount_extra
|
||||
let asset_amount_extra = 100_u128;
|
||||
let asset: Asset =
|
||||
(asset_location_v4.clone(), asset_amount_needed + asset_amount_extra).into();
|
||||
|
||||
let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
|
||||
let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
|
||||
|
||||
// Lets buy_weight and make sure buy_weight does not return an error
|
||||
let unused_assets = trader.buy_weight(bought, asset.into(), &ctx).expect("Expected Ok");
|
||||
// Check whether a correct amount of unused assets is returned
|
||||
assert_ok!(
|
||||
unused_assets.ensure_contains(&(asset_location_v4, asset_amount_extra).into())
|
||||
);
|
||||
|
||||
// Drop trader
|
||||
drop(trader);
|
||||
|
||||
// Make sure author(Alice) has received the amount
|
||||
assert_eq!(
|
||||
ForeignAssets::balance(foreign_location, AccountId::from(ALICE)),
|
||||
minimum_asset_balance + asset_amount_needed
|
||||
);
|
||||
|
||||
// We also need to ensure the total supply increased
|
||||
assert_eq!(
|
||||
ForeignAssets::total_supply(foreign_location),
|
||||
minimum_asset_balance + asset_amount_needed
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_asset_xcm_take_first_trader_with_refund() {
|
||||
ExtBuilder::<Runtime>::default()
|
||||
.with_collators(vec![AccountId::from(ALICE)])
|
||||
.with_session_keys(vec![(
|
||||
AccountId::from(ALICE),
|
||||
AccountId::from(ALICE),
|
||||
SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) },
|
||||
)])
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
// We need root origin to create a sufficient asset
|
||||
// We set existential deposit to be identical to the one for Balances first
|
||||
assert_ok!(Assets::force_create(
|
||||
RuntimeHelper::root_origin(),
|
||||
1.into(),
|
||||
AccountId::from(ALICE).into(),
|
||||
true,
|
||||
ExistentialDeposit::get()
|
||||
));
|
||||
|
||||
// We first mint enough asset for the account to exist for assets
|
||||
assert_ok!(Assets::mint(
|
||||
RuntimeHelper::origin_of(AccountId::from(ALICE)),
|
||||
1.into(),
|
||||
AccountId::from(ALICE).into(),
|
||||
ExistentialDeposit::get()
|
||||
));
|
||||
|
||||
let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
|
||||
let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
|
||||
|
||||
// Set Alice as block author, who will receive fees
|
||||
RuntimeHelper::run_to_block(2, AccountId::from(ALICE));
|
||||
|
||||
// We are going to buy 4e9 weight
|
||||
let bought = Weight::from_parts(4_000_000_000u64, 0);
|
||||
|
||||
let asset_location =
|
||||
AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap();
|
||||
|
||||
// lets calculate amount needed
|
||||
let amount_bought = WeightToFee::weight_to_fee(&bought);
|
||||
|
||||
let asset: Asset = (asset_location.clone(), amount_bought).into();
|
||||
|
||||
// Make sure buy_weight does not return an error
|
||||
assert_ok!(trader.buy_weight(bought, asset.clone().into(), &ctx));
|
||||
|
||||
// Make sure again buy_weight does return an error
|
||||
// This assert relies on the fact, that we use `TakeFirstAssetTrader` in `WeightTrader`
|
||||
// tuple chain, which cannot be called twice
|
||||
assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive);
|
||||
|
||||
// We actually use half of the weight
|
||||
let weight_used = bought / 2;
|
||||
|
||||
// Make sure refurnd works.
|
||||
let amount_refunded = WeightToFee::weight_to_fee(&(bought - weight_used));
|
||||
|
||||
assert_eq!(
|
||||
trader.refund_weight(bought - weight_used, &ctx),
|
||||
Some((asset_location, amount_refunded).into())
|
||||
);
|
||||
|
||||
// Drop trader
|
||||
drop(trader);
|
||||
|
||||
// We only should have paid for half of the bought weight
|
||||
let fees_paid = WeightToFee::weight_to_fee(&weight_used);
|
||||
|
||||
assert_eq!(
|
||||
Assets::balance(1, AccountId::from(ALICE)),
|
||||
ExistentialDeposit::get() + fees_paid
|
||||
);
|
||||
|
||||
// We also need to ensure the total supply increased
|
||||
assert_eq!(Assets::total_supply(1), ExistentialDeposit::get() + fees_paid);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_asset_xcm_take_first_trader_refund_not_possible_since_amount_less_than_ed() {
|
||||
ExtBuilder::<Runtime>::default()
|
||||
.with_collators(vec![AccountId::from(ALICE)])
|
||||
.with_session_keys(vec![(
|
||||
AccountId::from(ALICE),
|
||||
AccountId::from(ALICE),
|
||||
SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) },
|
||||
)])
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
// We need root origin to create a sufficient asset
|
||||
// We set existential deposit to be identical to the one for Balances first
|
||||
assert_ok!(Assets::force_create(
|
||||
RuntimeHelper::root_origin(),
|
||||
1.into(),
|
||||
AccountId::from(ALICE).into(),
|
||||
true,
|
||||
ExistentialDeposit::get()
|
||||
));
|
||||
|
||||
let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
|
||||
let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
|
||||
|
||||
// Set Alice as block author, who will receive fees
|
||||
RuntimeHelper::run_to_block(2, AccountId::from(ALICE));
|
||||
|
||||
// We are going to buy small amount
|
||||
let bought = Weight::from_parts(500_000_000u64, 0);
|
||||
|
||||
let asset_location =
|
||||
AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap();
|
||||
|
||||
let amount_bought = WeightToFee::weight_to_fee(&bought);
|
||||
|
||||
assert!(
|
||||
amount_bought < ExistentialDeposit::get(),
|
||||
"we are testing what happens when the amount does not exceed ED"
|
||||
);
|
||||
|
||||
let asset: Asset = (asset_location, amount_bought).into();
|
||||
|
||||
// Buy weight should return an error
|
||||
assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive);
|
||||
|
||||
// not credited since the ED is higher than this value
|
||||
assert_eq!(Assets::balance(1, AccountId::from(ALICE)), 0);
|
||||
|
||||
// We also need to ensure the total supply did not increase
|
||||
assert_eq!(Assets::total_supply(1), 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_that_buying_ed_refund_does_not_refund_for_take_first_trader() {
|
||||
ExtBuilder::<Runtime>::default()
|
||||
.with_collators(vec![AccountId::from(ALICE)])
|
||||
.with_session_keys(vec![(
|
||||
AccountId::from(ALICE),
|
||||
AccountId::from(ALICE),
|
||||
SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) },
|
||||
)])
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
// We need root origin to create a sufficient asset
|
||||
// We set existential deposit to be identical to the one for Balances first
|
||||
assert_ok!(Assets::force_create(
|
||||
RuntimeHelper::root_origin(),
|
||||
1.into(),
|
||||
AccountId::from(ALICE).into(),
|
||||
true,
|
||||
ExistentialDeposit::get()
|
||||
));
|
||||
|
||||
let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
|
||||
let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
|
||||
|
||||
// Set Alice as block author, who will receive fees
|
||||
RuntimeHelper::run_to_block(2, AccountId::from(ALICE));
|
||||
|
||||
// We are gonna buy ED
|
||||
let bought = Weight::from_parts(ExistentialDeposit::get().try_into().unwrap(), 0);
|
||||
|
||||
let asset_location =
|
||||
AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap();
|
||||
|
||||
let amount_bought = WeightToFee::weight_to_fee(&bought);
|
||||
|
||||
assert!(
|
||||
amount_bought < ExistentialDeposit::get(),
|
||||
"we are testing what happens when the amount does not exceed ED"
|
||||
);
|
||||
|
||||
// We know we will have to buy at least ED, so lets make sure first it will
|
||||
// fail with a payment of less than ED
|
||||
let asset: Asset = (asset_location.clone(), amount_bought).into();
|
||||
assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive);
|
||||
|
||||
// Now lets buy ED at least
|
||||
let asset: Asset = (asset_location, ExistentialDeposit::get()).into();
|
||||
|
||||
// Buy weight should work
|
||||
assert_ok!(trader.buy_weight(bought, asset.into(), &ctx));
|
||||
|
||||
// Should return None. We have a specific check making sure we dont go below ED for
|
||||
// drop payment
|
||||
assert_eq!(trader.refund_weight(bought, &ctx), None);
|
||||
|
||||
// Drop trader
|
||||
drop(trader);
|
||||
|
||||
// Make sure author(Alice) has received the amount
|
||||
assert_eq!(Assets::balance(1, AccountId::from(ALICE)), ExistentialDeposit::get());
|
||||
|
||||
// We also need to ensure the total supply increased
|
||||
assert_eq!(Assets::total_supply(1), ExistentialDeposit::get());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_asset_xcm_trader_not_possible_for_non_sufficient_assets() {
|
||||
ExtBuilder::<Runtime>::default()
|
||||
.with_collators(vec![AccountId::from(ALICE)])
|
||||
.with_session_keys(vec![(
|
||||
AccountId::from(ALICE),
|
||||
AccountId::from(ALICE),
|
||||
SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) },
|
||||
)])
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
// Create a non-sufficient asset with specific existential deposit
|
||||
let minimum_asset_balance = 1_000_000_u128;
|
||||
assert_ok!(Assets::force_create(
|
||||
RuntimeHelper::root_origin(),
|
||||
1.into(),
|
||||
AccountId::from(ALICE).into(),
|
||||
false,
|
||||
minimum_asset_balance
|
||||
));
|
||||
|
||||
// We first mint enough asset for the account to exist for assets
|
||||
assert_ok!(Assets::mint(
|
||||
RuntimeHelper::origin_of(AccountId::from(ALICE)),
|
||||
1.into(),
|
||||
AccountId::from(ALICE).into(),
|
||||
minimum_asset_balance
|
||||
));
|
||||
|
||||
let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
|
||||
let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
|
||||
|
||||
// Set Alice as block author, who will receive fees
|
||||
RuntimeHelper::run_to_block(2, AccountId::from(ALICE));
|
||||
|
||||
// We are going to buy 4e9 weight
|
||||
let bought = Weight::from_parts(4_000_000_000u64, 0);
|
||||
|
||||
// lets calculate amount needed
|
||||
let asset_amount_needed = WeightToFee::weight_to_fee(&bought);
|
||||
|
||||
let asset_location =
|
||||
AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap();
|
||||
|
||||
let asset: Asset = (asset_location, asset_amount_needed).into();
|
||||
|
||||
// Make sure again buy_weight does return an error
|
||||
assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive);
|
||||
|
||||
// Drop trader
|
||||
drop(trader);
|
||||
|
||||
// Make sure author(Alice) has NOT received the amount
|
||||
assert_eq!(Assets::balance(1, AccountId::from(ALICE)), minimum_asset_balance);
|
||||
|
||||
// We also need to ensure the total supply NOT increased
|
||||
assert_eq!(Assets::total_supply(1), minimum_asset_balance);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_assets_balances_api_works() {
|
||||
use assets_common::runtime_api::runtime_decl_for_fungibles_api::FungiblesApi;
|
||||
|
||||
@@ -609,6 +609,32 @@ impl xcm_executor::Config for XcmConfig {
|
||||
ResolveAssetTo<StakingPot, crate::NativeAndAssets>,
|
||||
AccountId,
|
||||
>,
|
||||
// This trader allows to pay with `is_sufficient=true` "Trust Backed" assets from dedicated
|
||||
// `pallet_assets` instance - `Assets`.
|
||||
cumulus_primitives_utility::TakeFirstAssetTrader<
|
||||
AccountId,
|
||||
AssetFeeAsExistentialDepositMultiplierFeeCharger,
|
||||
TrustBackedAssetsConvertedConcreteId,
|
||||
Assets,
|
||||
cumulus_primitives_utility::XcmFeesTo32ByteAccount<
|
||||
FungiblesTransactor,
|
||||
AccountId,
|
||||
XcmAssetFeesReceiver,
|
||||
>,
|
||||
>,
|
||||
// This trader allows to pay with `is_sufficient=true` "Foreign" assets from dedicated
|
||||
// `pallet_assets` instance - `ForeignAssets`.
|
||||
cumulus_primitives_utility::TakeFirstAssetTrader<
|
||||
AccountId,
|
||||
ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger,
|
||||
ForeignAssetsConvertedConcreteId,
|
||||
ForeignAssets,
|
||||
cumulus_primitives_utility::XcmFeesTo32ByteAccount<
|
||||
ForeignFungiblesTransactor,
|
||||
AccountId,
|
||||
XcmAssetFeesReceiver,
|
||||
>,
|
||||
>,
|
||||
);
|
||||
type ResponseHandler = PolkadotXcm;
|
||||
type AssetTrap = PolkadotXcm;
|
||||
|
||||
@@ -20,27 +20,24 @@
|
||||
use asset_hub_westend_runtime::{
|
||||
xcm_config,
|
||||
xcm_config::{
|
||||
bridging, ForeignCreatorsSovereignAccountOf, LocationToAccountId, WestendLocation,
|
||||
WestendLocationV3,
|
||||
bridging, AssetFeeAsExistentialDepositMultiplierFeeCharger, CheckingAccount,
|
||||
ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger, ForeignCreatorsSovereignAccountOf,
|
||||
LocationToAccountId, TrustBackedAssetsPalletLocation, TrustBackedAssetsPalletLocationV3,
|
||||
WestendLocation, WestendLocationV3, XcmConfig,
|
||||
},
|
||||
AllPalletsWithoutSystem, MetadataDepositBase, MetadataDepositPerByte, PolkadotXcm, RuntimeCall,
|
||||
RuntimeEvent, RuntimeOrigin, ToRococoXcmRouterInstance, XcmpQueue,
|
||||
};
|
||||
pub use asset_hub_westend_runtime::{
|
||||
xcm_config::{
|
||||
CheckingAccount, TrustBackedAssetsPalletLocation, TrustBackedAssetsPalletLocationV3,
|
||||
XcmConfig,
|
||||
},
|
||||
AssetConversion, AssetDeposit, Assets, Balances, CollatorSelection, ExistentialDeposit,
|
||||
ForeignAssets, ForeignAssetsInstance, ParachainSystem, Runtime, SessionKeys, System,
|
||||
TrustBackedAssetsInstance,
|
||||
AllPalletsWithoutSystem, Assets, Balances, ExistentialDeposit, ForeignAssets,
|
||||
ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, ParachainSystem,
|
||||
PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, SessionKeys,
|
||||
ToRococoXcmRouterInstance, TrustBackedAssetsInstance, XcmpQueue,
|
||||
};
|
||||
pub use asset_hub_westend_runtime::{AssetConversion, AssetDeposit, CollatorSelection, System};
|
||||
use asset_test_utils::{
|
||||
test_cases_over_bridge::TestBridgingConfig, CollatorSessionKey, CollatorSessionKeys, ExtBuilder,
|
||||
};
|
||||
use codec::{Decode, Encode};
|
||||
use cumulus_primitives_utility::ChargeWeightInFungibles;
|
||||
use frame_support::{
|
||||
assert_ok,
|
||||
assert_noop, assert_ok,
|
||||
traits::{
|
||||
fungible::{Inspect, Mutate},
|
||||
fungibles::{
|
||||
@@ -353,6 +350,427 @@ fn test_buy_and_refund_weight_with_swap_foreign_asset_xcm_trader() {
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_asset_xcm_take_first_trader() {
|
||||
ExtBuilder::<Runtime>::default()
|
||||
.with_collators(vec![AccountId::from(ALICE)])
|
||||
.with_session_keys(vec![(
|
||||
AccountId::from(ALICE),
|
||||
AccountId::from(ALICE),
|
||||
SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) },
|
||||
)])
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
// We need root origin to create a sufficient asset
|
||||
let minimum_asset_balance = 3333333_u128;
|
||||
let local_asset_id = 1;
|
||||
assert_ok!(Assets::force_create(
|
||||
RuntimeHelper::root_origin(),
|
||||
local_asset_id.into(),
|
||||
AccountId::from(ALICE).into(),
|
||||
true,
|
||||
minimum_asset_balance
|
||||
));
|
||||
|
||||
// We first mint enough asset for the account to exist for assets
|
||||
assert_ok!(Assets::mint(
|
||||
RuntimeHelper::origin_of(AccountId::from(ALICE)),
|
||||
local_asset_id.into(),
|
||||
AccountId::from(ALICE).into(),
|
||||
minimum_asset_balance
|
||||
));
|
||||
|
||||
// get asset id as location
|
||||
let asset_location =
|
||||
AssetIdForTrustBackedAssetsConvertLatest::convert_back(&local_asset_id).unwrap();
|
||||
|
||||
// Set Alice as block author, who will receive fees
|
||||
RuntimeHelper::run_to_block(2, AccountId::from(ALICE));
|
||||
|
||||
// We are going to buy 4e9 weight
|
||||
let bought = Weight::from_parts(4_000_000_000u64, 0);
|
||||
|
||||
// Lets calculate amount needed
|
||||
let asset_amount_needed =
|
||||
AssetFeeAsExistentialDepositMultiplierFeeCharger::charge_weight_in_fungibles(
|
||||
local_asset_id,
|
||||
bought,
|
||||
)
|
||||
.expect("failed to compute");
|
||||
|
||||
// Lets pay with: asset_amount_needed + asset_amount_extra
|
||||
let asset_amount_extra = 100_u128;
|
||||
let asset: Asset =
|
||||
(asset_location.clone(), asset_amount_needed + asset_amount_extra).into();
|
||||
|
||||
let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
|
||||
let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
|
||||
|
||||
// Lets buy_weight and make sure buy_weight does not return an error
|
||||
let unused_assets = trader.buy_weight(bought, asset.into(), &ctx).expect("Expected Ok");
|
||||
// Check whether a correct amount of unused assets is returned
|
||||
assert_ok!(unused_assets.ensure_contains(&(asset_location, asset_amount_extra).into()));
|
||||
|
||||
// Drop trader
|
||||
drop(trader);
|
||||
|
||||
// Make sure author(Alice) has received the amount
|
||||
assert_eq!(
|
||||
Assets::balance(local_asset_id, AccountId::from(ALICE)),
|
||||
minimum_asset_balance + asset_amount_needed
|
||||
);
|
||||
|
||||
// We also need to ensure the total supply increased
|
||||
assert_eq!(
|
||||
Assets::total_supply(local_asset_id),
|
||||
minimum_asset_balance + asset_amount_needed
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_foreign_asset_xcm_take_first_trader() {
|
||||
ExtBuilder::<Runtime>::default()
|
||||
.with_collators(vec![AccountId::from(ALICE)])
|
||||
.with_session_keys(vec![(
|
||||
AccountId::from(ALICE),
|
||||
AccountId::from(ALICE),
|
||||
SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) },
|
||||
)])
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
// We need root origin to create a sufficient asset
|
||||
let minimum_asset_balance = 3333333_u128;
|
||||
let foreign_location = xcm::v3::Location {
|
||||
parents: 1,
|
||||
interior: (
|
||||
xcm::v3::Junction::Parachain(1234),
|
||||
xcm::v3::Junction::GeneralIndex(12345),
|
||||
)
|
||||
.into(),
|
||||
};
|
||||
assert_ok!(ForeignAssets::force_create(
|
||||
RuntimeHelper::root_origin(),
|
||||
foreign_location.into(),
|
||||
AccountId::from(ALICE).into(),
|
||||
true,
|
||||
minimum_asset_balance
|
||||
));
|
||||
|
||||
// We first mint enough asset for the account to exist for assets
|
||||
assert_ok!(ForeignAssets::mint(
|
||||
RuntimeHelper::origin_of(AccountId::from(ALICE)),
|
||||
foreign_location.into(),
|
||||
AccountId::from(ALICE).into(),
|
||||
minimum_asset_balance
|
||||
));
|
||||
|
||||
let asset_location_v4: Location = foreign_location.try_into().unwrap();
|
||||
|
||||
// Set Alice as block author, who will receive fees
|
||||
RuntimeHelper::run_to_block(2, AccountId::from(ALICE));
|
||||
|
||||
// We are going to buy 4e9 weight
|
||||
let bought = Weight::from_parts(4_000_000_000u64, 0);
|
||||
|
||||
// Lets calculate amount needed
|
||||
let asset_amount_needed =
|
||||
ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger::charge_weight_in_fungibles(
|
||||
foreign_location,
|
||||
bought,
|
||||
)
|
||||
.expect("failed to compute");
|
||||
|
||||
// Lets pay with: asset_amount_needed + asset_amount_extra
|
||||
let asset_amount_extra = 100_u128;
|
||||
let asset: Asset =
|
||||
(asset_location_v4.clone(), asset_amount_needed + asset_amount_extra).into();
|
||||
|
||||
let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
|
||||
let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
|
||||
|
||||
// Lets buy_weight and make sure buy_weight does not return an error
|
||||
let unused_assets = trader.buy_weight(bought, asset.into(), &ctx).expect("Expected Ok");
|
||||
// Check whether a correct amount of unused assets is returned
|
||||
assert_ok!(
|
||||
unused_assets.ensure_contains(&(asset_location_v4, asset_amount_extra).into())
|
||||
);
|
||||
|
||||
// Drop trader
|
||||
drop(trader);
|
||||
|
||||
// Make sure author(Alice) has received the amount
|
||||
assert_eq!(
|
||||
ForeignAssets::balance(foreign_location, AccountId::from(ALICE)),
|
||||
minimum_asset_balance + asset_amount_needed
|
||||
);
|
||||
|
||||
// We also need to ensure the total supply increased
|
||||
assert_eq!(
|
||||
ForeignAssets::total_supply(foreign_location),
|
||||
minimum_asset_balance + asset_amount_needed
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_asset_xcm_take_first_trader_with_refund() {
|
||||
ExtBuilder::<Runtime>::default()
|
||||
.with_collators(vec![AccountId::from(ALICE)])
|
||||
.with_session_keys(vec![(
|
||||
AccountId::from(ALICE),
|
||||
AccountId::from(ALICE),
|
||||
SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) },
|
||||
)])
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
// We need root origin to create a sufficient asset
|
||||
// We set existential deposit to be identical to the one for Balances first
|
||||
assert_ok!(Assets::force_create(
|
||||
RuntimeHelper::root_origin(),
|
||||
1.into(),
|
||||
AccountId::from(ALICE).into(),
|
||||
true,
|
||||
ExistentialDeposit::get()
|
||||
));
|
||||
|
||||
// We first mint enough asset for the account to exist for assets
|
||||
assert_ok!(Assets::mint(
|
||||
RuntimeHelper::origin_of(AccountId::from(ALICE)),
|
||||
1.into(),
|
||||
AccountId::from(ALICE).into(),
|
||||
ExistentialDeposit::get()
|
||||
));
|
||||
|
||||
let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
|
||||
let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
|
||||
|
||||
// Set Alice as block author, who will receive fees
|
||||
RuntimeHelper::run_to_block(2, AccountId::from(ALICE));
|
||||
|
||||
// We are going to buy 4e9 weight
|
||||
let bought = Weight::from_parts(4_000_000_000u64, 0);
|
||||
let asset_location =
|
||||
AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap();
|
||||
|
||||
// lets calculate amount needed
|
||||
let amount_bought = WeightToFee::weight_to_fee(&bought);
|
||||
|
||||
let asset: Asset = (asset_location.clone(), amount_bought).into();
|
||||
|
||||
// Make sure buy_weight does not return an error
|
||||
assert_ok!(trader.buy_weight(bought, asset.clone().into(), &ctx));
|
||||
|
||||
// Make sure again buy_weight does return an error
|
||||
// This assert relies on the fact, that we use `TakeFirstAssetTrader` in `WeightTrader`
|
||||
// tuple chain, which cannot be called twice
|
||||
assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive);
|
||||
|
||||
// We actually use half of the weight
|
||||
let weight_used = bought / 2;
|
||||
|
||||
// Make sure refurnd works.
|
||||
let amount_refunded = WeightToFee::weight_to_fee(&(bought - weight_used));
|
||||
|
||||
assert_eq!(
|
||||
trader.refund_weight(bought - weight_used, &ctx),
|
||||
Some((asset_location, amount_refunded).into())
|
||||
);
|
||||
|
||||
// Drop trader
|
||||
drop(trader);
|
||||
|
||||
// We only should have paid for half of the bought weight
|
||||
let fees_paid = WeightToFee::weight_to_fee(&weight_used);
|
||||
|
||||
assert_eq!(
|
||||
Assets::balance(1, AccountId::from(ALICE)),
|
||||
ExistentialDeposit::get() + fees_paid
|
||||
);
|
||||
|
||||
// We also need to ensure the total supply increased
|
||||
assert_eq!(Assets::total_supply(1), ExistentialDeposit::get() + fees_paid);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_asset_xcm_take_first_trader_refund_not_possible_since_amount_less_than_ed() {
|
||||
ExtBuilder::<Runtime>::default()
|
||||
.with_collators(vec![AccountId::from(ALICE)])
|
||||
.with_session_keys(vec![(
|
||||
AccountId::from(ALICE),
|
||||
AccountId::from(ALICE),
|
||||
SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) },
|
||||
)])
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
// We need root origin to create a sufficient asset
|
||||
// We set existential deposit to be identical to the one for Balances first
|
||||
assert_ok!(Assets::force_create(
|
||||
RuntimeHelper::root_origin(),
|
||||
1.into(),
|
||||
AccountId::from(ALICE).into(),
|
||||
true,
|
||||
ExistentialDeposit::get()
|
||||
));
|
||||
|
||||
let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
|
||||
let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
|
||||
|
||||
// Set Alice as block author, who will receive fees
|
||||
RuntimeHelper::run_to_block(2, AccountId::from(ALICE));
|
||||
|
||||
// We are going to buy small amount
|
||||
let bought = Weight::from_parts(500_000_000u64, 0);
|
||||
|
||||
let asset_location =
|
||||
AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap();
|
||||
|
||||
let amount_bought = WeightToFee::weight_to_fee(&bought);
|
||||
|
||||
assert!(
|
||||
amount_bought < ExistentialDeposit::get(),
|
||||
"we are testing what happens when the amount does not exceed ED"
|
||||
);
|
||||
|
||||
let asset: Asset = (asset_location, amount_bought).into();
|
||||
|
||||
// Buy weight should return an error
|
||||
assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive);
|
||||
|
||||
// not credited since the ED is higher than this value
|
||||
assert_eq!(Assets::balance(1, AccountId::from(ALICE)), 0);
|
||||
|
||||
// We also need to ensure the total supply did not increase
|
||||
assert_eq!(Assets::total_supply(1), 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_that_buying_ed_refund_does_not_refund_for_take_first_trader() {
|
||||
ExtBuilder::<Runtime>::default()
|
||||
.with_collators(vec![AccountId::from(ALICE)])
|
||||
.with_session_keys(vec![(
|
||||
AccountId::from(ALICE),
|
||||
AccountId::from(ALICE),
|
||||
SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) },
|
||||
)])
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
// We need root origin to create a sufficient asset
|
||||
// We set existential deposit to be identical to the one for Balances first
|
||||
assert_ok!(Assets::force_create(
|
||||
RuntimeHelper::root_origin(),
|
||||
1.into(),
|
||||
AccountId::from(ALICE).into(),
|
||||
true,
|
||||
ExistentialDeposit::get()
|
||||
));
|
||||
|
||||
let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
|
||||
let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
|
||||
|
||||
// Set Alice as block author, who will receive fees
|
||||
RuntimeHelper::run_to_block(2, AccountId::from(ALICE));
|
||||
|
||||
let bought = Weight::from_parts(500_000_000u64, 0);
|
||||
|
||||
let asset_location =
|
||||
AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap();
|
||||
|
||||
let amount_bought = WeightToFee::weight_to_fee(&bought);
|
||||
|
||||
assert!(
|
||||
amount_bought < ExistentialDeposit::get(),
|
||||
"we are testing what happens when the amount does not exceed ED"
|
||||
);
|
||||
|
||||
// We know we will have to buy at least ED, so lets make sure first it will
|
||||
// fail with a payment of less than ED
|
||||
let asset: Asset = (asset_location.clone(), amount_bought).into();
|
||||
assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive);
|
||||
|
||||
// Now lets buy ED at least
|
||||
let asset: Asset = (asset_location.clone(), ExistentialDeposit::get()).into();
|
||||
|
||||
// Buy weight should work
|
||||
assert_ok!(trader.buy_weight(bought, asset.into(), &ctx));
|
||||
|
||||
// Should return None. We have a specific check making sure we dont go below ED for
|
||||
// drop payment
|
||||
assert_eq!(trader.refund_weight(bought, &ctx), None);
|
||||
|
||||
// Drop trader
|
||||
drop(trader);
|
||||
|
||||
// Make sure author(Alice) has received the amount
|
||||
assert_eq!(Assets::balance(1, AccountId::from(ALICE)), ExistentialDeposit::get());
|
||||
|
||||
// We also need to ensure the total supply increased
|
||||
assert_eq!(Assets::total_supply(1), ExistentialDeposit::get());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_asset_xcm_take_first_trader_not_possible_for_non_sufficient_assets() {
|
||||
ExtBuilder::<Runtime>::default()
|
||||
.with_collators(vec![AccountId::from(ALICE)])
|
||||
.with_session_keys(vec![(
|
||||
AccountId::from(ALICE),
|
||||
AccountId::from(ALICE),
|
||||
SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) },
|
||||
)])
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
// Create a non-sufficient asset with specific existential deposit
|
||||
let minimum_asset_balance = 1_000_000_u128;
|
||||
assert_ok!(Assets::force_create(
|
||||
RuntimeHelper::root_origin(),
|
||||
1.into(),
|
||||
AccountId::from(ALICE).into(),
|
||||
false,
|
||||
minimum_asset_balance
|
||||
));
|
||||
|
||||
// We first mint enough asset for the account to exist for assets
|
||||
assert_ok!(Assets::mint(
|
||||
RuntimeHelper::origin_of(AccountId::from(ALICE)),
|
||||
1.into(),
|
||||
AccountId::from(ALICE).into(),
|
||||
minimum_asset_balance
|
||||
));
|
||||
|
||||
let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
|
||||
let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
|
||||
|
||||
// Set Alice as block author, who will receive fees
|
||||
RuntimeHelper::run_to_block(2, AccountId::from(ALICE));
|
||||
|
||||
// We are going to buy 4e9 weight
|
||||
let bought = Weight::from_parts(4_000_000_000u64, 0);
|
||||
|
||||
// lets calculate amount needed
|
||||
let asset_amount_needed = WeightToFee::weight_to_fee(&bought);
|
||||
|
||||
let asset_location =
|
||||
AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap();
|
||||
|
||||
let asset: Asset = (asset_location, asset_amount_needed).into();
|
||||
|
||||
// Make sure again buy_weight does return an error
|
||||
assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive);
|
||||
|
||||
// Drop trader
|
||||
drop(trader);
|
||||
|
||||
// Make sure author(Alice) has NOT received the amount
|
||||
assert_eq!(Assets::balance(1, AccountId::from(ALICE)), minimum_asset_balance);
|
||||
|
||||
// We also need to ensure the total supply NOT increased
|
||||
assert_eq!(Assets::total_supply(1), minimum_asset_balance);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_assets_balances_api_works() {
|
||||
use assets_common::runtime_api::runtime_decl_for_fungibles_api::FungiblesApi;
|
||||
|
||||
Reference in New Issue
Block a user