mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 19:51:02 +00:00
XCM WeightTrader: Swap Fee Asset for Native Asset (#1845)
Implements an XCM executor `WeightTrader`, facilitating fee payments in any asset that can be exchanged for a native asset. A few constraints need to be observed: - `buy_weight` and `refund` operations must be atomic, as another weight trader implementation might be attempted in case of failure. - swap credit must be utilized since there isn’t an account to which an asset of some class can be deposited with a guarantee to meet the existential deposit requirement. Also, operating with credits enhances the efficiency of the weight trader - https://github.com/paritytech/polkadot-sdk/pull/1677 related PRs: - (depends) https://github.com/paritytech/polkadot-sdk/pull/2031 - (depends) https://github.com/paritytech/polkadot-sdk/pull/1677 - (caused) https://github.com/paritytech/polkadot-sdk/pull/1847 - (caused) https://github.com/paritytech/polkadot-sdk/pull/1876 // DONE: impl `OnUnbalanced` for a `fungible/s` credit // DONE: make the trader free from a concept of a native currency and drop few fallible conversions. related issue - https://github.com/paritytech/polkadot-sdk/issues/1842 // DONE: tests --------- Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> Co-authored-by: Liam Aharon <liam.aharon@hotmail.com>
This commit is contained in:
-75
@@ -27,78 +27,3 @@ 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 =
|
||||
(X2(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,
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
+127
@@ -270,3 +270,130 @@ fn cannot_create_pool_from_pool_assets() {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pay_xcm_fee_with_some_asset_swapped_for_native() {
|
||||
let asset_native = asset_hub_rococo_runtime::xcm_config::TokenLocation::get();
|
||||
let asset_one = MultiLocation {
|
||||
parents: 0,
|
||||
interior: X2(PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())),
|
||||
};
|
||||
let penpal = AssetHubRococo::sovereign_account_id_of(AssetHubRococo::sibling_location_of(
|
||||
PenpalA::para_id(),
|
||||
));
|
||||
|
||||
AssetHubRococo::execute_with(|| {
|
||||
type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent;
|
||||
|
||||
// set up pool with ASSET_ID <> NATIVE pair
|
||||
assert_ok!(<AssetHubRococo as AssetHubRococoPallet>::Assets::create(
|
||||
<AssetHubRococo as Chain>::RuntimeOrigin::signed(AssetHubRococoSender::get()),
|
||||
ASSET_ID.into(),
|
||||
AssetHubRococoSender::get().into(),
|
||||
ASSET_MIN_BALANCE,
|
||||
));
|
||||
assert!(<AssetHubRococo as AssetHubRococoPallet>::Assets::asset_exists(ASSET_ID));
|
||||
|
||||
assert_ok!(<AssetHubRococo as AssetHubRococoPallet>::Assets::mint(
|
||||
<AssetHubRococo as Chain>::RuntimeOrigin::signed(AssetHubRococoSender::get()),
|
||||
ASSET_ID.into(),
|
||||
AssetHubRococoSender::get().into(),
|
||||
3_000_000_000_000,
|
||||
));
|
||||
|
||||
assert_ok!(<AssetHubRococo as AssetHubRococoPallet>::AssetConversion::create_pool(
|
||||
<AssetHubRococo as Chain>::RuntimeOrigin::signed(AssetHubRococoSender::get()),
|
||||
Box::new(asset_native),
|
||||
Box::new(asset_one),
|
||||
));
|
||||
|
||||
assert_expected_events!(
|
||||
AssetHubRococo,
|
||||
vec![
|
||||
RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {},
|
||||
]
|
||||
);
|
||||
|
||||
assert_ok!(<AssetHubRococo as AssetHubRococoPallet>::AssetConversion::add_liquidity(
|
||||
<AssetHubRococo as Chain>::RuntimeOrigin::signed(AssetHubRococoSender::get()),
|
||||
Box::new(asset_native),
|
||||
Box::new(asset_one),
|
||||
1_000_000_000_000,
|
||||
2_000_000_000_000,
|
||||
0,
|
||||
0,
|
||||
AssetHubRococoSender::get().into()
|
||||
));
|
||||
|
||||
assert_expected_events!(
|
||||
AssetHubRococo,
|
||||
vec![
|
||||
RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {lp_token_minted, .. }) => { lp_token_minted: *lp_token_minted == 1414213562273, },
|
||||
]
|
||||
);
|
||||
|
||||
// ensure `penpal` sovereign account has no native tokens and mint some `ASSET_ID`
|
||||
assert_eq!(
|
||||
<AssetHubRococo as AssetHubRococoPallet>::Balances::free_balance(penpal.clone()),
|
||||
0
|
||||
);
|
||||
|
||||
assert_ok!(<AssetHubRococo as AssetHubRococoPallet>::Assets::touch_other(
|
||||
<AssetHubRococo as Chain>::RuntimeOrigin::signed(AssetHubRococoSender::get()),
|
||||
ASSET_ID.into(),
|
||||
penpal.clone().into(),
|
||||
));
|
||||
|
||||
assert_ok!(<AssetHubRococo as AssetHubRococoPallet>::Assets::mint(
|
||||
<AssetHubRococo as Chain>::RuntimeOrigin::signed(AssetHubRococoSender::get()),
|
||||
ASSET_ID.into(),
|
||||
penpal.clone().into(),
|
||||
10_000_000_000_000,
|
||||
));
|
||||
});
|
||||
|
||||
PenpalA::execute_with(|| {
|
||||
// send xcm transact from `penpal` account which as only `ASSET_ID` tokens on
|
||||
// `AssetHubRococo`
|
||||
let call = AssetHubRococo::force_create_asset_call(
|
||||
ASSET_ID + 1000,
|
||||
penpal.clone(),
|
||||
true,
|
||||
ASSET_MIN_BALANCE,
|
||||
);
|
||||
|
||||
let penpal_root = <PenpalA as Chain>::RuntimeOrigin::root();
|
||||
let fee_amount = 4_000_000_000_000u128;
|
||||
let asset_one =
|
||||
(X2(PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())), fee_amount)
|
||||
.into();
|
||||
let asset_hub_location = PenpalA::sibling_location_of(AssetHubRococo::para_id()).into();
|
||||
let xcm = xcm_transact_paid_execution(
|
||||
call,
|
||||
OriginKind::SovereignAccount,
|
||||
asset_one,
|
||||
penpal.clone(),
|
||||
);
|
||||
|
||||
assert_ok!(<PenpalA as PenpalAPallet>::PolkadotXcm::send(
|
||||
penpal_root,
|
||||
bx!(asset_hub_location),
|
||||
bx!(xcm),
|
||||
));
|
||||
|
||||
PenpalA::assert_xcm_pallet_sent();
|
||||
});
|
||||
|
||||
AssetHubRococo::execute_with(|| {
|
||||
type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent;
|
||||
|
||||
AssetHubRococo::assert_xcmp_queue_success(None);
|
||||
assert_expected_events!(
|
||||
AssetHubRococo,
|
||||
vec![
|
||||
RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::SwapCreditExecuted { .. },) => {},
|
||||
RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true,.. }) => {},
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
-75
@@ -27,78 +27,3 @@ 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 =
|
||||
(X2(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,
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
+127
@@ -264,3 +264,130 @@ fn cannot_create_pool_from_pool_assets() {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pay_xcm_fee_with_some_asset_swapped_for_native() {
|
||||
let asset_native = asset_hub_westend_runtime::xcm_config::WestendLocation::get();
|
||||
let asset_one = MultiLocation {
|
||||
parents: 0,
|
||||
interior: X2(PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())),
|
||||
};
|
||||
let penpal = AssetHubWestend::sovereign_account_id_of(AssetHubWestend::sibling_location_of(
|
||||
PenpalB::para_id(),
|
||||
));
|
||||
|
||||
AssetHubWestend::execute_with(|| {
|
||||
type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
|
||||
|
||||
// set up pool with ASSET_ID <> NATIVE pair
|
||||
assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::Assets::create(
|
||||
<AssetHubWestend as Chain>::RuntimeOrigin::signed(AssetHubWestendSender::get()),
|
||||
ASSET_ID.into(),
|
||||
AssetHubWestendSender::get().into(),
|
||||
ASSET_MIN_BALANCE,
|
||||
));
|
||||
assert!(<AssetHubWestend as AssetHubWestendPallet>::Assets::asset_exists(ASSET_ID));
|
||||
|
||||
assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::Assets::mint(
|
||||
<AssetHubWestend as Chain>::RuntimeOrigin::signed(AssetHubWestendSender::get()),
|
||||
ASSET_ID.into(),
|
||||
AssetHubWestendSender::get().into(),
|
||||
3_000_000_000_000,
|
||||
));
|
||||
|
||||
assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::AssetConversion::create_pool(
|
||||
<AssetHubWestend as Chain>::RuntimeOrigin::signed(AssetHubWestendSender::get()),
|
||||
Box::new(asset_native),
|
||||
Box::new(asset_one),
|
||||
));
|
||||
|
||||
assert_expected_events!(
|
||||
AssetHubWestend,
|
||||
vec![
|
||||
RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {},
|
||||
]
|
||||
);
|
||||
|
||||
assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::AssetConversion::add_liquidity(
|
||||
<AssetHubWestend as Chain>::RuntimeOrigin::signed(AssetHubWestendSender::get()),
|
||||
Box::new(asset_native),
|
||||
Box::new(asset_one),
|
||||
1_000_000_000_000,
|
||||
2_000_000_000_000,
|
||||
0,
|
||||
0,
|
||||
AssetHubWestendSender::get().into()
|
||||
));
|
||||
|
||||
assert_expected_events!(
|
||||
AssetHubWestend,
|
||||
vec![
|
||||
RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {lp_token_minted, .. }) => { lp_token_minted: *lp_token_minted == 1414213562273, },
|
||||
]
|
||||
);
|
||||
|
||||
// ensure `penpal` sovereign account has no native tokens and mint some `ASSET_ID`
|
||||
assert_eq!(
|
||||
<AssetHubWestend as AssetHubWestendPallet>::Balances::free_balance(penpal.clone()),
|
||||
0
|
||||
);
|
||||
|
||||
assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::Assets::touch_other(
|
||||
<AssetHubWestend as Chain>::RuntimeOrigin::signed(AssetHubWestendSender::get()),
|
||||
ASSET_ID.into(),
|
||||
penpal.clone().into(),
|
||||
));
|
||||
|
||||
assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::Assets::mint(
|
||||
<AssetHubWestend as Chain>::RuntimeOrigin::signed(AssetHubWestendSender::get()),
|
||||
ASSET_ID.into(),
|
||||
penpal.clone().into(),
|
||||
10_000_000_000_000,
|
||||
));
|
||||
});
|
||||
|
||||
PenpalB::execute_with(|| {
|
||||
// send xcm transact from `penpal` account which as only `ASSET_ID` tokens on
|
||||
// `AssetHubWestend`
|
||||
let call = AssetHubWestend::force_create_asset_call(
|
||||
ASSET_ID + 1000,
|
||||
penpal.clone(),
|
||||
true,
|
||||
ASSET_MIN_BALANCE,
|
||||
);
|
||||
|
||||
let penpal_root = <PenpalB as Chain>::RuntimeOrigin::root();
|
||||
let fee_amount = 4_000_000_000_000u128;
|
||||
let asset_one =
|
||||
(X2(PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())), fee_amount)
|
||||
.into();
|
||||
let asset_hub_location = PenpalB::sibling_location_of(AssetHubWestend::para_id()).into();
|
||||
let xcm = xcm_transact_paid_execution(
|
||||
call,
|
||||
OriginKind::SovereignAccount,
|
||||
asset_one,
|
||||
penpal.clone(),
|
||||
);
|
||||
|
||||
assert_ok!(<PenpalB as PenpalBPallet>::PolkadotXcm::send(
|
||||
penpal_root,
|
||||
bx!(asset_hub_location),
|
||||
bx!(xcm),
|
||||
));
|
||||
|
||||
PenpalB::assert_xcm_pallet_sent();
|
||||
});
|
||||
|
||||
AssetHubWestend::execute_with(|| {
|
||||
type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
|
||||
|
||||
AssetHubWestend::assert_xcmp_queue_success(None);
|
||||
assert_expected_events!(
|
||||
AssetHubWestend,
|
||||
vec![
|
||||
RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::SwapCreditExecuted { .. },) => {},
|
||||
RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true,.. }) => {},
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
+1
@@ -20,6 +20,7 @@ hex-literal = "0.4.1"
|
||||
sp-core = { path = "../../../../../../../substrate/primitives/core", default-features = false }
|
||||
frame-support = { path = "../../../../../../../substrate/frame/support", default-features = false }
|
||||
pallet-assets = { path = "../../../../../../../substrate/frame/assets", default-features = false }
|
||||
pallet-asset-conversion = { path = "../../../../../../../substrate/frame/asset-conversion", default-features = false }
|
||||
pallet-balances = { path = "../../../../../../../substrate/frame/balances", default-features = false }
|
||||
pallet-message-queue = { path = "../../../../../../../substrate/frame/message-queue" }
|
||||
sp-runtime = { path = "../../../../../../../substrate/primitives/runtime", default-features = false }
|
||||
|
||||
+2
-1
@@ -61,7 +61,8 @@ pub use rococo_westend_system_emulated_network::{
|
||||
rococo_emulated_chain::{genesis::ED as ROCOCO_ED, RococoRelayPallet as RococoPallet},
|
||||
AssetHubRococoPara as AssetHubRococo, AssetHubRococoParaReceiver as AssetHubRococoReceiver,
|
||||
AssetHubRococoParaSender as AssetHubRococoSender, AssetHubWestendPara as AssetHubWestend,
|
||||
AssetHubWestendParaReceiver as AssetHubWestendReceiver, BridgeHubRococoPara as BridgeHubRococo,
|
||||
AssetHubWestendParaReceiver as AssetHubWestendReceiver,
|
||||
AssetHubWestendParaSender as AssetHubWestendSender, BridgeHubRococoPara as BridgeHubRococo,
|
||||
BridgeHubRococoParaSender as BridgeHubRococoSender, BridgeHubWestendPara as BridgeHubWestend,
|
||||
RococoRelay as Rococo, RococoRelayReceiver as RococoReceiver,
|
||||
RococoRelaySender as RococoSender,
|
||||
|
||||
+44
-1
@@ -49,6 +49,49 @@ fn send_rocs_from_asset_hub_rococo_to_asset_hub_westend() {
|
||||
AssetHubWestend::para_id(),
|
||||
);
|
||||
|
||||
AssetHubWestend::execute_with(|| {
|
||||
type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
|
||||
|
||||
// setup a pool to pay xcm fees with `roc_at_asset_hub_westend` tokens
|
||||
assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::ForeignAssets::mint(
|
||||
<AssetHubWestend as Chain>::RuntimeOrigin::signed(AssetHubWestendSender::get()),
|
||||
roc_at_asset_hub_westend.into(),
|
||||
AssetHubWestendSender::get().into(),
|
||||
3_000_000_000_000,
|
||||
));
|
||||
|
||||
assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::AssetConversion::create_pool(
|
||||
<AssetHubWestend as Chain>::RuntimeOrigin::signed(AssetHubWestendSender::get()),
|
||||
Box::new(Parent.into()),
|
||||
Box::new(roc_at_asset_hub_westend),
|
||||
));
|
||||
|
||||
assert_expected_events!(
|
||||
AssetHubWestend,
|
||||
vec![
|
||||
RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {},
|
||||
]
|
||||
);
|
||||
|
||||
assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::AssetConversion::add_liquidity(
|
||||
<AssetHubWestend as Chain>::RuntimeOrigin::signed(AssetHubWestendSender::get()),
|
||||
Box::new(Parent.into()),
|
||||
Box::new(roc_at_asset_hub_westend),
|
||||
1_000_000_000_000,
|
||||
2_000_000_000_000,
|
||||
1,
|
||||
1,
|
||||
AssetHubWestendSender::get().into()
|
||||
));
|
||||
|
||||
assert_expected_events!(
|
||||
AssetHubWestend,
|
||||
vec![
|
||||
RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {..}) => {},
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
let rocs_in_reserve_on_ahr_before =
|
||||
<AssetHubRococo as Chain>::account_data_of(sov_ahw_on_ahr.clone()).free;
|
||||
let sender_rocs_before =
|
||||
@@ -58,7 +101,7 @@ fn send_rocs_from_asset_hub_rococo_to_asset_hub_westend() {
|
||||
<Assets as Inspect<_>>::balance(roc_at_asset_hub_westend, &AssetHubWestendReceiver::get())
|
||||
});
|
||||
|
||||
let amount = ASSET_HUB_ROCOCO_ED * 1_000;
|
||||
let amount = ASSET_HUB_ROCOCO_ED * 1_000_000;
|
||||
send_asset_from_asset_hub_rococo_to_asset_hub_westend(roc_at_asset_hub_rococo, amount);
|
||||
AssetHubWestend::execute_with(|| {
|
||||
type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
|
||||
|
||||
+1
@@ -16,6 +16,7 @@ codec = { package = "parity-scale-codec", version = "3.4.0", default-features =
|
||||
# Substrate
|
||||
frame-support = { path = "../../../../../../../substrate/frame/support", default-features = false }
|
||||
pallet-assets = { path = "../../../../../../../substrate/frame/assets", default-features = false }
|
||||
pallet-asset-conversion = { path = "../../../../../../../substrate/frame/asset-conversion", default-features = false }
|
||||
pallet-balances = { path = "../../../../../../../substrate/frame/balances", default-features = false }
|
||||
pallet-message-queue = { path = "../../../../../../../substrate/frame/message-queue" }
|
||||
sp-runtime = { path = "../../../../../../../substrate/primitives/runtime", default-features = false }
|
||||
|
||||
+2
-1
@@ -55,7 +55,8 @@ pub use rococo_westend_system_emulated_network::{
|
||||
},
|
||||
westend_emulated_chain::WestendRelayPallet as WestendPallet,
|
||||
AssetHubRococoPara as AssetHubRococo, AssetHubRococoParaReceiver as AssetHubRococoReceiver,
|
||||
AssetHubWestendPara as AssetHubWestend, AssetHubWestendParaReceiver as AssetHubWestendReceiver,
|
||||
AssetHubRococoParaSender as AssetHubRococoSender, AssetHubWestendPara as AssetHubWestend,
|
||||
AssetHubWestendParaReceiver as AssetHubWestendReceiver,
|
||||
AssetHubWestendParaSender as AssetHubWestendSender, BridgeHubRococoPara as BridgeHubRococo,
|
||||
BridgeHubWestendPara as BridgeHubWestend, BridgeHubWestendParaSender as BridgeHubWestendSender,
|
||||
WestendRelay as Westend,
|
||||
|
||||
+43
@@ -48,6 +48,49 @@ fn send_wnds_from_asset_hub_westend_to_asset_hub_rococo() {
|
||||
AssetHubRococo::para_id(),
|
||||
);
|
||||
|
||||
AssetHubRococo::execute_with(|| {
|
||||
type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent;
|
||||
|
||||
// setup a pool to pay xcm fees with `wnd_at_asset_hub_rococo` tokens
|
||||
assert_ok!(<AssetHubRococo as AssetHubRococoPallet>::ForeignAssets::mint(
|
||||
<AssetHubRococo as Chain>::RuntimeOrigin::signed(AssetHubRococoSender::get()),
|
||||
wnd_at_asset_hub_rococo.into(),
|
||||
AssetHubRococoSender::get().into(),
|
||||
3_000_000_000_000,
|
||||
));
|
||||
|
||||
assert_ok!(<AssetHubRococo as AssetHubRococoPallet>::AssetConversion::create_pool(
|
||||
<AssetHubRococo as Chain>::RuntimeOrigin::signed(AssetHubRococoSender::get()),
|
||||
Box::new(Parent.into()),
|
||||
Box::new(wnd_at_asset_hub_rococo),
|
||||
));
|
||||
|
||||
assert_expected_events!(
|
||||
AssetHubRococo,
|
||||
vec![
|
||||
RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {},
|
||||
]
|
||||
);
|
||||
|
||||
assert_ok!(<AssetHubRococo as AssetHubRococoPallet>::AssetConversion::add_liquidity(
|
||||
<AssetHubRococo as Chain>::RuntimeOrigin::signed(AssetHubRococoSender::get()),
|
||||
Box::new(Parent.into()),
|
||||
Box::new(wnd_at_asset_hub_rococo),
|
||||
1_000_000_000_000,
|
||||
2_000_000_000_000,
|
||||
1,
|
||||
1,
|
||||
AssetHubRococoSender::get().into()
|
||||
));
|
||||
|
||||
assert_expected_events!(
|
||||
AssetHubRococo,
|
||||
vec![
|
||||
RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {..}) => {},
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
let wnds_in_reserve_on_ahw_before =
|
||||
<AssetHubWestend as Chain>::account_data_of(sov_ahr_on_ahw.clone()).free;
|
||||
let sender_wnds_before =
|
||||
|
||||
@@ -312,7 +312,7 @@ impl pallet_assets::Config<PoolAssetsInstance> for Runtime {
|
||||
type BenchmarkHelper = ();
|
||||
}
|
||||
|
||||
/// Union fungibles implementation for `Assets`` and `ForeignAssets`.
|
||||
/// Union fungibles implementation for `Assets` and `ForeignAssets`.
|
||||
pub type LocalAndForeignAssets = fungibles::UnionOf<
|
||||
Assets,
|
||||
ForeignAssets,
|
||||
@@ -324,18 +324,21 @@ pub type LocalAndForeignAssets = fungibles::UnionOf<
|
||||
AccountId,
|
||||
>;
|
||||
|
||||
/// Union fungibles implementation for [`LocalAndForeignAssets`] and `Balances`.
|
||||
pub type NativeAndAssets = fungible::UnionOf<
|
||||
Balances,
|
||||
LocalAndForeignAssets,
|
||||
TargetFromLeft<TokenLocation>,
|
||||
MultiLocation,
|
||||
AccountId,
|
||||
>;
|
||||
|
||||
impl pallet_asset_conversion::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Balance = Balance;
|
||||
type HigherPrecisionBalance = sp_core::U256;
|
||||
type AssetKind = MultiLocation;
|
||||
type Assets = fungible::UnionOf<
|
||||
Balances,
|
||||
LocalAndForeignAssets,
|
||||
TargetFromLeft<TokenLocation>,
|
||||
Self::AssetKind,
|
||||
Self::AccountId,
|
||||
>;
|
||||
type Assets = NativeAndAssets;
|
||||
type PoolId = (Self::AssetKind, Self::AssetKind);
|
||||
type PoolLocator =
|
||||
pallet_asset_conversion::WithFirstAsset<TokenLocation, AccountId, Self::AssetKind>;
|
||||
|
||||
@@ -15,17 +15,21 @@
|
||||
|
||||
use super::{
|
||||
AccountId, AllPalletsWithSystem, Assets, Authorship, Balance, Balances, BaseDeliveryFee,
|
||||
FeeAssetId, ForeignAssets, ForeignAssetsInstance, ParachainInfo, ParachainSystem, PolkadotXcm,
|
||||
PoolAssets, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, ToWestendXcmRouter,
|
||||
TransactionByteFee, TrustBackedAssetsInstance, WeightToFee, XcmpQueue,
|
||||
CollatorSelection, FeeAssetId, ForeignAssets, ForeignAssetsInstance, ParachainInfo,
|
||||
ParachainSystem, PolkadotXcm, PoolAssets, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin,
|
||||
ToWestendXcmRouter, TransactionByteFee, TrustBackedAssetsInstance, WeightToFee, XcmpQueue,
|
||||
};
|
||||
use assets_common::{
|
||||
local_and_foreign_assets::MatchesLocalAndForeignAssetsMultiLocation,
|
||||
matching::{FromNetwork, FromSiblingParachain, IsForeignConcreteAsset},
|
||||
TrustBackedAssetsAsMultiLocation,
|
||||
};
|
||||
use frame_support::{
|
||||
match_types, parameter_types,
|
||||
traits::{ConstU32, Contains, Equals, Everything, Nothing, PalletInfoAccess},
|
||||
traits::{
|
||||
tokens::imbalance::ResolveAssetTo, ConstU32, Contains, Equals, Everything, Nothing,
|
||||
PalletInfoAccess,
|
||||
},
|
||||
};
|
||||
use frame_system::EnsureRoot;
|
||||
use pallet_xcm::XcmPassthrough;
|
||||
@@ -73,6 +77,7 @@ parameter_types! {
|
||||
pub PoolAssetsPalletLocation: MultiLocation =
|
||||
PalletInstance(<PoolAssets as PalletInfoAccess>::index() as u8).into();
|
||||
pub CheckingAccount: AccountId = PolkadotXcm::check_account();
|
||||
pub StakingPot: AccountId = CollatorSelection::account_id();
|
||||
pub const GovernanceLocation: MultiLocation = MultiLocation::parent();
|
||||
pub TreasuryAccount: AccountId = TREASURY_PALLET_ID.into_account_truncating();
|
||||
pub RelayTreasuryLocation: MultiLocation = (Parent, PalletInstance(rococo_runtime_constants::TREASURY_PALLET_ID)).into();
|
||||
@@ -550,31 +555,17 @@ impl xcm_executor::Config for XcmConfig {
|
||||
>;
|
||||
type Trader = (
|
||||
UsingComponents<WeightToFee, TokenLocation, AccountId, Balances, ToStakingPot<Runtime>>,
|
||||
// This trader allows to pay with `is_sufficient=true` "Trust Backed" assets from dedicated
|
||||
// `pallet_assets` instance - `Assets`.
|
||||
cumulus_primitives_utility::TakeFirstAssetTrader<
|
||||
cumulus_primitives_utility::SwapFirstAssetTrader<
|
||||
TokenLocation,
|
||||
crate::AssetConversion,
|
||||
WeightToFee,
|
||||
crate::NativeAndAssets,
|
||||
(
|
||||
TrustBackedAssetsAsMultiLocation<TrustBackedAssetsPalletLocation, Balance>,
|
||||
ForeignAssetsConvertedConcreteId,
|
||||
),
|
||||
ResolveAssetTo<StakingPot, crate::NativeAndAssets>,
|
||||
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;
|
||||
|
||||
@@ -17,34 +17,38 @@
|
||||
|
||||
//! Tests for the Rococo Assets Hub chain.
|
||||
|
||||
use asset_hub_rococo_runtime::xcm_config::{
|
||||
AssetFeeAsExistentialDepositMultiplierFeeCharger, TokenLocation,
|
||||
TrustBackedAssetsPalletLocation,
|
||||
use asset_hub_rococo_runtime::{
|
||||
xcm_config,
|
||||
xcm_config::{bridging, ForeignCreatorsSovereignAccountOf, LocationToAccountId, TokenLocation},
|
||||
AllPalletsWithoutSystem, MetadataDepositBase, MetadataDepositPerByte, RuntimeCall,
|
||||
RuntimeEvent, ToWestendXcmRouterInstance, XcmpQueue,
|
||||
};
|
||||
pub use asset_hub_rococo_runtime::{
|
||||
xcm_config::{
|
||||
self, bridging, CheckingAccount, ForeignCreatorsSovereignAccountOf, LocationToAccountId,
|
||||
XcmConfig,
|
||||
},
|
||||
AllPalletsWithoutSystem, AssetDeposit, Assets, Balances, ExistentialDeposit, ForeignAssets,
|
||||
ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, ParachainSystem, Runtime,
|
||||
RuntimeCall, RuntimeEvent, SessionKeys, System, ToWestendXcmRouterInstance,
|
||||
TrustBackedAssetsInstance, XcmpQueue,
|
||||
xcm_config::{CheckingAccount, TrustBackedAssetsPalletLocation, XcmConfig},
|
||||
AssetConversion, AssetDeposit, Assets, Balances, CollatorSelection, ExistentialDeposit,
|
||||
ForeignAssets, ForeignAssetsInstance, ParachainSystem, Runtime, SessionKeys, System,
|
||||
TrustBackedAssetsInstance,
|
||||
};
|
||||
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_noop, assert_ok,
|
||||
traits::fungibles::InspectEnumerable,
|
||||
assert_ok,
|
||||
traits::{
|
||||
fungible::{Inspect, Mutate},
|
||||
fungibles::{
|
||||
Create, Inspect as FungiblesInspect, InspectEnumerable, Mutate as FungiblesMutate,
|
||||
},
|
||||
},
|
||||
weights::{Weight, WeightToFee as WeightToFeeT},
|
||||
};
|
||||
use parachains_common::{
|
||||
rococo::fee::WeightToFee, AccountId, AssetIdForTrustBackedAssets, AuraId, Balance,
|
||||
rococo::{currency::UNITS, fee::WeightToFee},
|
||||
AccountId, AssetIdForTrustBackedAssets, AuraId, Balance,
|
||||
};
|
||||
use sp_runtime::traits::MaybeEquivalence;
|
||||
use std::convert::Into;
|
||||
use xcm::latest::prelude::*;
|
||||
use xcm_executor::traits::{Identity, JustTry, WeightTrader};
|
||||
|
||||
@@ -69,7 +73,7 @@ fn collator_session_keys() -> CollatorSessionKeys<Runtime> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_asset_xcm_trader() {
|
||||
fn test_buy_and_refund_weight_in_native() {
|
||||
ExtBuilder::<Runtime>::default()
|
||||
.with_collators(vec![AccountId::from(ALICE)])
|
||||
.with_session_keys(vec![(
|
||||
@@ -79,77 +83,55 @@ fn test_asset_xcm_trader() {
|
||||
)])
|
||||
.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
|
||||
));
|
||||
let bob: AccountId = SOME_ASSET_ADMIN.into();
|
||||
let staking_pot = CollatorSelection::account_id();
|
||||
let native_location = TokenLocation::get();
|
||||
let initial_balance = 200 * UNITS;
|
||||
|
||||
// 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
|
||||
));
|
||||
assert_ok!(Balances::mint_into(&bob, initial_balance));
|
||||
assert_ok!(Balances::mint_into(&staking_pot, initial_balance));
|
||||
|
||||
// get asset id as multilocation
|
||||
let asset_multilocation =
|
||||
AssetIdForTrustBackedAssetsConvert::convert_back(&local_asset_id).unwrap();
|
||||
// keep initial total issuance to assert later.
|
||||
let total_issuance = Balances::total_issuance();
|
||||
|
||||
// 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: MultiAsset =
|
||||
(asset_multilocation, asset_amount_needed + asset_amount_extra).into();
|
||||
|
||||
let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
|
||||
// prepare input to buy weight.
|
||||
let weight = Weight::from_parts(4_000_000_000, 0);
|
||||
let fee = WeightToFee::weight_to_fee(&weight);
|
||||
let extra_amount = 100;
|
||||
let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
|
||||
let payment: MultiAsset = (native_location, fee + extra_amount).into();
|
||||
|
||||
// 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_multilocation, asset_amount_extra).into())
|
||||
);
|
||||
// init trader and buy weight.
|
||||
let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
|
||||
let unused_asset =
|
||||
trader.buy_weight(weight, payment.into(), &ctx).expect("Expected Ok");
|
||||
|
||||
// Drop trader
|
||||
// assert.
|
||||
let unused_amount =
|
||||
unused_asset.fungible.get(&native_location.into()).map_or(0, |a| *a);
|
||||
assert_eq!(unused_amount, extra_amount);
|
||||
assert_eq!(Balances::total_issuance(), total_issuance);
|
||||
|
||||
// prepare input to refund weight.
|
||||
let refund_weight = Weight::from_parts(1_000_000_000, 0);
|
||||
let refund = WeightToFee::weight_to_fee(&refund_weight);
|
||||
|
||||
// refund.
|
||||
let actual_refund = trader.refund_weight(refund_weight, &ctx).unwrap();
|
||||
assert_eq!(actual_refund, (native_location, refund).into());
|
||||
|
||||
// assert.
|
||||
assert_eq!(Balances::balance(&staking_pot), initial_balance);
|
||||
// only after `trader` is dropped we expect the fee to be resolved into the treasury
|
||||
// account.
|
||||
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
|
||||
);
|
||||
});
|
||||
assert_eq!(Balances::balance(&staking_pot), initial_balance + fee - refund);
|
||||
assert_eq!(Balances::total_issuance(), total_issuance + fee - refund);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_asset_xcm_trader_with_refund() {
|
||||
fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() {
|
||||
ExtBuilder::<Runtime>::default()
|
||||
.with_collators(vec![AccountId::from(ALICE)])
|
||||
.with_session_keys(vec![(
|
||||
@@ -159,249 +141,192 @@ fn test_asset_xcm_trader_with_refund() {
|
||||
)])
|
||||
.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 bob: AccountId = SOME_ASSET_ADMIN.into();
|
||||
let staking_pot = CollatorSelection::account_id();
|
||||
let asset_1: u32 = 1;
|
||||
let native_location = TokenLocation::get();
|
||||
let asset_1_location =
|
||||
AssetIdForTrustBackedAssetsConvert::convert_back(&asset_1).unwrap();
|
||||
// bob's initial balance for native and `asset1` assets.
|
||||
let initial_balance = 200 * UNITS;
|
||||
// liquidity for both arms of (native, asset1) pool.
|
||||
let pool_liquidity = 100 * UNITS;
|
||||
|
||||
// init asset, balances and pool.
|
||||
assert_ok!(<Assets as Create<_>>::create(asset_1, bob.clone(), true, 10));
|
||||
|
||||
assert_ok!(Assets::mint_into(asset_1, &bob, initial_balance));
|
||||
assert_ok!(Balances::mint_into(&bob, initial_balance));
|
||||
assert_ok!(Balances::mint_into(&staking_pot, initial_balance));
|
||||
|
||||
assert_ok!(AssetConversion::create_pool(
|
||||
RuntimeHelper::origin_of(bob.clone()),
|
||||
Box::new(native_location),
|
||||
Box::new(asset_1_location)
|
||||
));
|
||||
|
||||
// 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()
|
||||
assert_ok!(AssetConversion::add_liquidity(
|
||||
RuntimeHelper::origin_of(bob.clone()),
|
||||
Box::new(native_location),
|
||||
Box::new(asset_1_location),
|
||||
pool_liquidity,
|
||||
pool_liquidity,
|
||||
1,
|
||||
1,
|
||||
bob,
|
||||
));
|
||||
|
||||
let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
|
||||
// keep initial total issuance to assert later.
|
||||
let asset_total_issuance = Assets::total_issuance(asset_1);
|
||||
let native_total_issuance = Balances::total_issuance();
|
||||
|
||||
// prepare input to buy weight.
|
||||
let weight = Weight::from_parts(4_000_000_000, 0);
|
||||
let fee = WeightToFee::weight_to_fee(&weight);
|
||||
let asset_fee =
|
||||
AssetConversion::get_amount_in(&fee, &pool_liquidity, &pool_liquidity).unwrap();
|
||||
let extra_amount = 100;
|
||||
let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
|
||||
let payment: MultiAsset = (asset_1_location, asset_fee + extra_amount).into();
|
||||
|
||||
// Set Alice as block author, who will receive fees
|
||||
RuntimeHelper::run_to_block(2, AccountId::from(ALICE));
|
||||
// init trader and buy weight.
|
||||
let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
|
||||
let unused_asset =
|
||||
trader.buy_weight(weight, payment.into(), &ctx).expect("Expected Ok");
|
||||
|
||||
// We are going to buy 4e9 weight
|
||||
let bought = Weight::from_parts(4_000_000_000u64, 0);
|
||||
// assert.
|
||||
let unused_amount =
|
||||
unused_asset.fungible.get(&asset_1_location.into()).map_or(0, |a| *a);
|
||||
assert_eq!(unused_amount, extra_amount);
|
||||
assert_eq!(Assets::total_issuance(asset_1), asset_total_issuance + asset_fee);
|
||||
|
||||
let asset_multilocation = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap();
|
||||
// prepare input to refund weight.
|
||||
let refund_weight = Weight::from_parts(1_000_000_000, 0);
|
||||
let refund = WeightToFee::weight_to_fee(&refund_weight);
|
||||
let (reserve1, reserve2) =
|
||||
AssetConversion::get_reserves(native_location, asset_1_location).unwrap();
|
||||
let asset_refund =
|
||||
AssetConversion::get_amount_out(&refund, &reserve1, &reserve2).unwrap();
|
||||
|
||||
// lets calculate amount needed
|
||||
let amount_bought = WeightToFee::weight_to_fee(&bought);
|
||||
|
||||
let asset: MultiAsset = (asset_multilocation, 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));
|
||||
// refund.
|
||||
let actual_refund = trader.refund_weight(refund_weight, &ctx).unwrap();
|
||||
assert_eq!(actual_refund, (asset_1_location, asset_refund).into());
|
||||
|
||||
// assert.
|
||||
assert_eq!(Balances::balance(&staking_pot), initial_balance);
|
||||
// only after `trader` is dropped we expect the fee to be resolved into the treasury
|
||||
// account.
|
||||
drop(trader);
|
||||
assert_eq!(Balances::balance(&staking_pot), initial_balance + fee - refund);
|
||||
assert_eq!(
|
||||
trader.refund_weight(bought - weight_used, &ctx),
|
||||
Some((asset_multilocation, amount_refunded).into())
|
||||
Assets::total_issuance(asset_1),
|
||||
asset_total_issuance + asset_fee - asset_refund
|
||||
);
|
||||
assert_eq!(Balances::total_issuance(), native_total_issuance);
|
||||
})
|
||||
}
|
||||
|
||||
// Drop trader
|
||||
drop(trader);
|
||||
#[test]
|
||||
fn test_buy_and_refund_weight_with_swap_foreign_asset_xcm_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(|| {
|
||||
let bob: AccountId = SOME_ASSET_ADMIN.into();
|
||||
let staking_pot = CollatorSelection::account_id();
|
||||
let native_location = TokenLocation::get();
|
||||
let foreign_location =
|
||||
MultiLocation { parents: 1, interior: X2(Parachain(1234), GeneralIndex(12345)) };
|
||||
// bob's initial balance for native and `asset1` assets.
|
||||
let initial_balance = 200 * UNITS;
|
||||
// liquidity for both arms of (native, asset1) pool.
|
||||
let pool_liquidity = 100 * UNITS;
|
||||
|
||||
// We only should have paid for half of the bought weight
|
||||
let fees_paid = WeightToFee::weight_to_fee(&weight_used);
|
||||
// init asset, balances and pool.
|
||||
assert_ok!(<ForeignAssets as Create<_>>::create(
|
||||
foreign_location,
|
||||
bob.clone(),
|
||||
true,
|
||||
10
|
||||
));
|
||||
|
||||
assert_ok!(ForeignAssets::mint_into(foreign_location, &bob, initial_balance));
|
||||
assert_ok!(Balances::mint_into(&bob, initial_balance));
|
||||
assert_ok!(Balances::mint_into(&staking_pot, initial_balance));
|
||||
|
||||
assert_ok!(AssetConversion::create_pool(
|
||||
RuntimeHelper::origin_of(bob.clone()),
|
||||
Box::new(native_location),
|
||||
Box::new(foreign_location)
|
||||
));
|
||||
|
||||
assert_ok!(AssetConversion::add_liquidity(
|
||||
RuntimeHelper::origin_of(bob.clone()),
|
||||
Box::new(native_location),
|
||||
Box::new(foreign_location),
|
||||
pool_liquidity,
|
||||
pool_liquidity,
|
||||
1,
|
||||
1,
|
||||
bob,
|
||||
));
|
||||
|
||||
// keep initial total issuance to assert later.
|
||||
let asset_total_issuance = ForeignAssets::total_issuance(foreign_location);
|
||||
let native_total_issuance = Balances::total_issuance();
|
||||
|
||||
// prepare input to buy weight.
|
||||
let weight = Weight::from_parts(4_000_000_000, 0);
|
||||
let fee = WeightToFee::weight_to_fee(&weight);
|
||||
let asset_fee =
|
||||
AssetConversion::get_amount_in(&fee, &pool_liquidity, &pool_liquidity).unwrap();
|
||||
let extra_amount = 100;
|
||||
let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
|
||||
let payment: MultiAsset = (foreign_location, asset_fee + extra_amount).into();
|
||||
|
||||
// init trader and buy weight.
|
||||
let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
|
||||
let unused_asset =
|
||||
trader.buy_weight(weight, payment.into(), &ctx).expect("Expected Ok");
|
||||
|
||||
// assert.
|
||||
let unused_amount =
|
||||
unused_asset.fungible.get(&foreign_location.into()).map_or(0, |a| *a);
|
||||
assert_eq!(unused_amount, extra_amount);
|
||||
assert_eq!(
|
||||
Assets::balance(1, AccountId::from(ALICE)),
|
||||
ExistentialDeposit::get() + fees_paid
|
||||
ForeignAssets::total_issuance(foreign_location),
|
||||
asset_total_issuance + asset_fee
|
||||
);
|
||||
|
||||
// We also need to ensure the total supply increased
|
||||
assert_eq!(Assets::total_supply(1), ExistentialDeposit::get() + fees_paid);
|
||||
});
|
||||
}
|
||||
// prepare input to refund weight.
|
||||
let refund_weight = Weight::from_parts(1_000_000_000, 0);
|
||||
let refund = WeightToFee::weight_to_fee(&refund_weight);
|
||||
let (reserve1, reserve2) =
|
||||
AssetConversion::get_reserves(native_location, foreign_location).unwrap();
|
||||
let asset_refund =
|
||||
AssetConversion::get_amount_out(&refund, &reserve1, &reserve2).unwrap();
|
||||
|
||||
#[test]
|
||||
fn test_asset_xcm_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()
|
||||
));
|
||||
// refund.
|
||||
let actual_refund = trader.refund_weight(refund_weight, &ctx).unwrap();
|
||||
assert_eq!(actual_refund, (foreign_location, asset_refund).into());
|
||||
|
||||
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_multilocation = AssetIdForTrustBackedAssetsConvert::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: MultiAsset = (asset_multilocation, 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() {
|
||||
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_multilocation = AssetIdForTrustBackedAssetsConvert::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: MultiAsset = (asset_multilocation, amount_bought).into();
|
||||
assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive);
|
||||
|
||||
// Now lets buy ED at least
|
||||
let asset: MultiAsset = (asset_multilocation, 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
|
||||
// assert.
|
||||
assert_eq!(Balances::balance(&staking_pot), initial_balance);
|
||||
// only after `trader` is dropped we expect the fee to be resolved into the treasury
|
||||
// account.
|
||||
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_multilocation = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap();
|
||||
|
||||
let asset: MultiAsset = (asset_multilocation, 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);
|
||||
});
|
||||
assert_eq!(Balances::balance(&staking_pot), initial_balance + fee - refund);
|
||||
assert_eq!(
|
||||
ForeignAssets::total_issuance(foreign_location),
|
||||
asset_total_issuance + asset_fee - asset_refund
|
||||
);
|
||||
assert_eq!(Balances::total_issuance(), native_total_issuance);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -295,7 +295,7 @@ impl pallet_assets::Config<PoolAssetsInstance> for Runtime {
|
||||
type BenchmarkHelper = ();
|
||||
}
|
||||
|
||||
/// Union fungibles implementation for `Assets`` and `ForeignAssets`.
|
||||
/// Union fungibles implementation for `Assets` and `ForeignAssets`.
|
||||
pub type LocalAndForeignAssets = fungibles::UnionOf<
|
||||
Assets,
|
||||
ForeignAssets,
|
||||
@@ -307,18 +307,21 @@ pub type LocalAndForeignAssets = fungibles::UnionOf<
|
||||
AccountId,
|
||||
>;
|
||||
|
||||
/// Union fungibles implementation for [`LocalAndForeignAssets`] and `Balances`.
|
||||
pub type NativeAndAssets = fungible::UnionOf<
|
||||
Balances,
|
||||
LocalAndForeignAssets,
|
||||
TargetFromLeft<WestendLocation>,
|
||||
MultiLocation,
|
||||
AccountId,
|
||||
>;
|
||||
|
||||
impl pallet_asset_conversion::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Balance = Balance;
|
||||
type HigherPrecisionBalance = sp_core::U256;
|
||||
type AssetKind = MultiLocation;
|
||||
type Assets = fungible::UnionOf<
|
||||
Balances,
|
||||
LocalAndForeignAssets,
|
||||
TargetFromLeft<WestendLocation>,
|
||||
Self::AssetKind,
|
||||
Self::AccountId,
|
||||
>;
|
||||
type Assets = NativeAndAssets;
|
||||
type PoolId = (Self::AssetKind, Self::AssetKind);
|
||||
type PoolLocator =
|
||||
pallet_asset_conversion::WithFirstAsset<WestendLocation, AccountId, Self::AssetKind>;
|
||||
|
||||
@@ -15,17 +15,21 @@
|
||||
|
||||
use super::{
|
||||
AccountId, AllPalletsWithSystem, Assets, Authorship, Balance, Balances, BaseDeliveryFee,
|
||||
FeeAssetId, ForeignAssets, ForeignAssetsInstance, ParachainInfo, ParachainSystem, PolkadotXcm,
|
||||
PoolAssets, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, ToRococoXcmRouter,
|
||||
TransactionByteFee, TrustBackedAssetsInstance, WeightToFee, XcmpQueue,
|
||||
CollatorSelection, FeeAssetId, ForeignAssets, ForeignAssetsInstance, ParachainInfo,
|
||||
ParachainSystem, PolkadotXcm, PoolAssets, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin,
|
||||
ToRococoXcmRouter, TransactionByteFee, TrustBackedAssetsInstance, WeightToFee, XcmpQueue,
|
||||
};
|
||||
use assets_common::{
|
||||
local_and_foreign_assets::MatchesLocalAndForeignAssetsMultiLocation,
|
||||
matching::{FromSiblingParachain, IsForeignConcreteAsset},
|
||||
TrustBackedAssetsAsMultiLocation,
|
||||
};
|
||||
use frame_support::{
|
||||
match_types, parameter_types,
|
||||
traits::{ConstU32, Contains, Equals, Everything, Nothing, PalletInfoAccess},
|
||||
traits::{
|
||||
tokens::imbalance::ResolveAssetTo, ConstU32, Contains, Equals, Everything, Nothing,
|
||||
PalletInfoAccess,
|
||||
},
|
||||
};
|
||||
use frame_system::EnsureRoot;
|
||||
use pallet_xcm::XcmPassthrough;
|
||||
@@ -70,6 +74,7 @@ parameter_types! {
|
||||
pub PoolAssetsPalletLocation: MultiLocation =
|
||||
PalletInstance(<PoolAssets as PalletInfoAccess>::index() as u8).into();
|
||||
pub CheckingAccount: AccountId = PolkadotXcm::check_account();
|
||||
pub StakingPot: AccountId = CollatorSelection::account_id();
|
||||
pub TreasuryAccount: AccountId = TREASURY_PALLET_ID.into_account_truncating();
|
||||
pub RelayTreasuryLocation: MultiLocation = (Parent, PalletInstance(westend_runtime_constants::TREASURY_PALLET_ID)).into();
|
||||
}
|
||||
@@ -567,31 +572,17 @@ impl xcm_executor::Config for XcmConfig {
|
||||
>;
|
||||
type Trader = (
|
||||
UsingComponents<WeightToFee, WestendLocation, AccountId, Balances, ToStakingPot<Runtime>>,
|
||||
// This trader allows to pay with `is_sufficient=true` "Trust Backed" assets from dedicated
|
||||
// `pallet_assets` instance - `Assets`.
|
||||
cumulus_primitives_utility::TakeFirstAssetTrader<
|
||||
cumulus_primitives_utility::SwapFirstAssetTrader<
|
||||
WestendLocation,
|
||||
crate::AssetConversion,
|
||||
WeightToFee,
|
||||
crate::NativeAndAssets,
|
||||
(
|
||||
TrustBackedAssetsAsMultiLocation<TrustBackedAssetsPalletLocation, Balance>,
|
||||
ForeignAssetsConvertedConcreteId,
|
||||
),
|
||||
ResolveAssetTo<StakingPot, crate::NativeAndAssets>,
|
||||
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;
|
||||
|
||||
@@ -18,28 +18,36 @@
|
||||
//! Tests for the Westmint (Westend Assets Hub) chain.
|
||||
|
||||
use asset_hub_westend_runtime::{
|
||||
xcm_config,
|
||||
xcm_config::{
|
||||
self, bridging, AssetFeeAsExistentialDepositMultiplierFeeCharger, CheckingAccount,
|
||||
ForeignCreatorsSovereignAccountOf, LocationToAccountId, TrustBackedAssetsPalletLocation,
|
||||
WestendLocation, XcmConfig,
|
||||
bridging, ForeignCreatorsSovereignAccountOf, LocationToAccountId, WestendLocation,
|
||||
},
|
||||
AllPalletsWithoutSystem, AssetDeposit, Assets, Balances, ExistentialDeposit, ForeignAssets,
|
||||
ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, ParachainSystem,
|
||||
PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, SessionKeys,
|
||||
ToRococoXcmRouterInstance, TrustBackedAssetsInstance, XcmpQueue,
|
||||
AllPalletsWithoutSystem, MetadataDepositBase, MetadataDepositPerByte, PolkadotXcm, RuntimeCall,
|
||||
RuntimeEvent, RuntimeOrigin, ToRococoXcmRouterInstance, XcmpQueue,
|
||||
};
|
||||
pub use asset_hub_westend_runtime::{
|
||||
xcm_config::{CheckingAccount, TrustBackedAssetsPalletLocation, XcmConfig},
|
||||
AssetConversion, AssetDeposit, Assets, Balances, CollatorSelection, ExistentialDeposit,
|
||||
ForeignAssets, ForeignAssetsInstance, ParachainSystem, Runtime, SessionKeys, System,
|
||||
TrustBackedAssetsInstance,
|
||||
};
|
||||
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_noop, assert_ok,
|
||||
traits::fungibles::InspectEnumerable,
|
||||
assert_ok,
|
||||
traits::{
|
||||
fungible::{Inspect, Mutate},
|
||||
fungibles::{
|
||||
Create, Inspect as FungiblesInspect, InspectEnumerable, Mutate as FungiblesMutate,
|
||||
},
|
||||
},
|
||||
weights::{Weight, WeightToFee as WeightToFeeT},
|
||||
};
|
||||
use parachains_common::{
|
||||
westend::fee::WeightToFee, AccountId, AssetIdForTrustBackedAssets, AuraId, Balance,
|
||||
westend::{currency::UNITS, fee::WeightToFee},
|
||||
AccountId, AssetIdForTrustBackedAssets, AuraId, Balance,
|
||||
};
|
||||
use sp_runtime::traits::MaybeEquivalence;
|
||||
use std::convert::Into;
|
||||
@@ -67,7 +75,7 @@ fn collator_session_keys() -> CollatorSessionKeys<Runtime> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_asset_xcm_trader() {
|
||||
fn test_buy_and_refund_weight_in_native() {
|
||||
ExtBuilder::<Runtime>::default()
|
||||
.with_collators(vec![AccountId::from(ALICE)])
|
||||
.with_session_keys(vec![(
|
||||
@@ -77,77 +85,55 @@ fn test_asset_xcm_trader() {
|
||||
)])
|
||||
.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
|
||||
));
|
||||
let bob: AccountId = SOME_ASSET_ADMIN.into();
|
||||
let staking_pot = CollatorSelection::account_id();
|
||||
let native_location = WestendLocation::get();
|
||||
let initial_balance = 200 * UNITS;
|
||||
|
||||
// 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
|
||||
));
|
||||
assert_ok!(Balances::mint_into(&bob, initial_balance));
|
||||
assert_ok!(Balances::mint_into(&staking_pot, initial_balance));
|
||||
|
||||
// get asset id as multilocation
|
||||
let asset_multilocation =
|
||||
AssetIdForTrustBackedAssetsConvert::convert_back(&local_asset_id).unwrap();
|
||||
// keep initial total issuance to assert later.
|
||||
let total_issuance = Balances::total_issuance();
|
||||
|
||||
// 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: MultiAsset =
|
||||
(asset_multilocation, asset_amount_needed + asset_amount_extra).into();
|
||||
|
||||
let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
|
||||
// prepare input to buy weight.
|
||||
let weight = Weight::from_parts(4_000_000_000, 0);
|
||||
let fee = WeightToFee::weight_to_fee(&weight);
|
||||
let extra_amount = 100;
|
||||
let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
|
||||
let payment: MultiAsset = (native_location, fee + extra_amount).into();
|
||||
|
||||
// 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_multilocation, asset_amount_extra).into())
|
||||
);
|
||||
// init trader and buy weight.
|
||||
let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
|
||||
let unused_asset =
|
||||
trader.buy_weight(weight, payment.into(), &ctx).expect("Expected Ok");
|
||||
|
||||
// Drop trader
|
||||
// assert.
|
||||
let unused_amount =
|
||||
unused_asset.fungible.get(&native_location.into()).map_or(0, |a| *a);
|
||||
assert_eq!(unused_amount, extra_amount);
|
||||
assert_eq!(Balances::total_issuance(), total_issuance);
|
||||
|
||||
// prepare input to refund weight.
|
||||
let refund_weight = Weight::from_parts(1_000_000_000, 0);
|
||||
let refund = WeightToFee::weight_to_fee(&refund_weight);
|
||||
|
||||
// refund.
|
||||
let actual_refund = trader.refund_weight(refund_weight, &ctx).unwrap();
|
||||
assert_eq!(actual_refund, (native_location, refund).into());
|
||||
|
||||
// assert.
|
||||
assert_eq!(Balances::balance(&staking_pot), initial_balance);
|
||||
// only after `trader` is dropped we expect the fee to be resolved into the treasury
|
||||
// account.
|
||||
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
|
||||
);
|
||||
});
|
||||
assert_eq!(Balances::balance(&staking_pot), initial_balance + fee - refund);
|
||||
assert_eq!(Balances::total_issuance(), total_issuance + fee - refund);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_asset_xcm_trader_with_refund() {
|
||||
fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() {
|
||||
ExtBuilder::<Runtime>::default()
|
||||
.with_collators(vec![AccountId::from(ALICE)])
|
||||
.with_session_keys(vec![(
|
||||
@@ -157,247 +143,192 @@ fn test_asset_xcm_trader_with_refund() {
|
||||
)])
|
||||
.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 bob: AccountId = SOME_ASSET_ADMIN.into();
|
||||
let staking_pot = CollatorSelection::account_id();
|
||||
let asset_1: u32 = 1;
|
||||
let native_location = WestendLocation::get();
|
||||
let asset_1_location =
|
||||
AssetIdForTrustBackedAssetsConvert::convert_back(&asset_1).unwrap();
|
||||
// bob's initial balance for native and `asset1` assets.
|
||||
let initial_balance = 200 * UNITS;
|
||||
// liquidity for both arms of (native, asset1) pool.
|
||||
let pool_liquidity = 100 * UNITS;
|
||||
|
||||
// init asset, balances and pool.
|
||||
assert_ok!(<Assets as Create<_>>::create(asset_1, bob.clone(), true, 10));
|
||||
|
||||
assert_ok!(Assets::mint_into(asset_1, &bob, initial_balance));
|
||||
assert_ok!(Balances::mint_into(&bob, initial_balance));
|
||||
assert_ok!(Balances::mint_into(&staking_pot, initial_balance));
|
||||
|
||||
assert_ok!(AssetConversion::create_pool(
|
||||
RuntimeHelper::origin_of(bob.clone()),
|
||||
Box::new(native_location),
|
||||
Box::new(asset_1_location)
|
||||
));
|
||||
|
||||
// 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()
|
||||
assert_ok!(AssetConversion::add_liquidity(
|
||||
RuntimeHelper::origin_of(bob.clone()),
|
||||
Box::new(native_location),
|
||||
Box::new(asset_1_location),
|
||||
pool_liquidity,
|
||||
pool_liquidity,
|
||||
1,
|
||||
1,
|
||||
bob,
|
||||
));
|
||||
|
||||
let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
|
||||
// keep initial total issuance to assert later.
|
||||
let asset_total_issuance = Assets::total_issuance(asset_1);
|
||||
let native_total_issuance = Balances::total_issuance();
|
||||
|
||||
// prepare input to buy weight.
|
||||
let weight = Weight::from_parts(4_000_000_000, 0);
|
||||
let fee = WeightToFee::weight_to_fee(&weight);
|
||||
let asset_fee =
|
||||
AssetConversion::get_amount_in(&fee, &pool_liquidity, &pool_liquidity).unwrap();
|
||||
let extra_amount = 100;
|
||||
let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
|
||||
let payment: MultiAsset = (asset_1_location, asset_fee + extra_amount).into();
|
||||
|
||||
// Set Alice as block author, who will receive fees
|
||||
RuntimeHelper::run_to_block(2, AccountId::from(ALICE));
|
||||
// init trader and buy weight.
|
||||
let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
|
||||
let unused_asset =
|
||||
trader.buy_weight(weight, payment.into(), &ctx).expect("Expected Ok");
|
||||
|
||||
// We are going to buy 4e9 weight
|
||||
let bought = Weight::from_parts(4_000_000_000u64, 0);
|
||||
let asset_multilocation = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap();
|
||||
// assert.
|
||||
let unused_amount =
|
||||
unused_asset.fungible.get(&asset_1_location.into()).map_or(0, |a| *a);
|
||||
assert_eq!(unused_amount, extra_amount);
|
||||
assert_eq!(Assets::total_issuance(asset_1), asset_total_issuance + asset_fee);
|
||||
|
||||
// lets calculate amount needed
|
||||
let amount_bought = WeightToFee::weight_to_fee(&bought);
|
||||
// prepare input to refund weight.
|
||||
let refund_weight = Weight::from_parts(1_000_000_000, 0);
|
||||
let refund = WeightToFee::weight_to_fee(&refund_weight);
|
||||
let (reserve1, reserve2) =
|
||||
AssetConversion::get_reserves(native_location, asset_1_location).unwrap();
|
||||
let asset_refund =
|
||||
AssetConversion::get_amount_out(&refund, &reserve1, &reserve2).unwrap();
|
||||
|
||||
let asset: MultiAsset = (asset_multilocation, 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));
|
||||
// refund.
|
||||
let actual_refund = trader.refund_weight(refund_weight, &ctx).unwrap();
|
||||
assert_eq!(actual_refund, (asset_1_location, asset_refund).into());
|
||||
|
||||
// assert.
|
||||
assert_eq!(Balances::balance(&staking_pot), initial_balance);
|
||||
// only after `trader` is dropped we expect the fee to be resolved into the treasury
|
||||
// account.
|
||||
drop(trader);
|
||||
assert_eq!(Balances::balance(&staking_pot), initial_balance + fee - refund);
|
||||
assert_eq!(
|
||||
trader.refund_weight(bought - weight_used, &ctx),
|
||||
Some((asset_multilocation, amount_refunded).into())
|
||||
Assets::total_issuance(asset_1),
|
||||
asset_total_issuance + asset_fee - asset_refund
|
||||
);
|
||||
assert_eq!(Balances::total_issuance(), native_total_issuance);
|
||||
})
|
||||
}
|
||||
|
||||
// Drop trader
|
||||
drop(trader);
|
||||
#[test]
|
||||
fn test_buy_and_refund_weight_with_swap_foreign_asset_xcm_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(|| {
|
||||
let bob: AccountId = SOME_ASSET_ADMIN.into();
|
||||
let staking_pot = CollatorSelection::account_id();
|
||||
let native_location = WestendLocation::get();
|
||||
let foreign_location =
|
||||
MultiLocation { parents: 1, interior: X2(Parachain(1234), GeneralIndex(12345)) };
|
||||
// bob's initial balance for native and `asset1` assets.
|
||||
let initial_balance = 200 * UNITS;
|
||||
// liquidity for both arms of (native, asset1) pool.
|
||||
let pool_liquidity = 100 * UNITS;
|
||||
|
||||
// We only should have paid for half of the bought weight
|
||||
let fees_paid = WeightToFee::weight_to_fee(&weight_used);
|
||||
// init asset, balances and pool.
|
||||
assert_ok!(<ForeignAssets as Create<_>>::create(
|
||||
foreign_location,
|
||||
bob.clone(),
|
||||
true,
|
||||
10
|
||||
));
|
||||
|
||||
assert_ok!(ForeignAssets::mint_into(foreign_location, &bob, initial_balance));
|
||||
assert_ok!(Balances::mint_into(&bob, initial_balance));
|
||||
assert_ok!(Balances::mint_into(&staking_pot, initial_balance));
|
||||
|
||||
assert_ok!(AssetConversion::create_pool(
|
||||
RuntimeHelper::origin_of(bob.clone()),
|
||||
Box::new(native_location),
|
||||
Box::new(foreign_location)
|
||||
));
|
||||
|
||||
assert_ok!(AssetConversion::add_liquidity(
|
||||
RuntimeHelper::origin_of(bob.clone()),
|
||||
Box::new(native_location),
|
||||
Box::new(foreign_location),
|
||||
pool_liquidity,
|
||||
pool_liquidity,
|
||||
1,
|
||||
1,
|
||||
bob,
|
||||
));
|
||||
|
||||
// keep initial total issuance to assert later.
|
||||
let asset_total_issuance = ForeignAssets::total_issuance(foreign_location);
|
||||
let native_total_issuance = Balances::total_issuance();
|
||||
|
||||
// prepare input to buy weight.
|
||||
let weight = Weight::from_parts(4_000_000_000, 0);
|
||||
let fee = WeightToFee::weight_to_fee(&weight);
|
||||
let asset_fee =
|
||||
AssetConversion::get_amount_in(&fee, &pool_liquidity, &pool_liquidity).unwrap();
|
||||
let extra_amount = 100;
|
||||
let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
|
||||
let payment: MultiAsset = (foreign_location, asset_fee + extra_amount).into();
|
||||
|
||||
// init trader and buy weight.
|
||||
let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
|
||||
let unused_asset =
|
||||
trader.buy_weight(weight, payment.into(), &ctx).expect("Expected Ok");
|
||||
|
||||
// assert.
|
||||
let unused_amount =
|
||||
unused_asset.fungible.get(&foreign_location.into()).map_or(0, |a| *a);
|
||||
assert_eq!(unused_amount, extra_amount);
|
||||
assert_eq!(
|
||||
Assets::balance(1, AccountId::from(ALICE)),
|
||||
ExistentialDeposit::get() + fees_paid
|
||||
ForeignAssets::total_issuance(foreign_location),
|
||||
asset_total_issuance + asset_fee
|
||||
);
|
||||
|
||||
// We also need to ensure the total supply increased
|
||||
assert_eq!(Assets::total_supply(1), ExistentialDeposit::get() + fees_paid);
|
||||
});
|
||||
}
|
||||
// prepare input to refund weight.
|
||||
let refund_weight = Weight::from_parts(1_000_000_000, 0);
|
||||
let refund = WeightToFee::weight_to_fee(&refund_weight);
|
||||
let (reserve1, reserve2) =
|
||||
AssetConversion::get_reserves(native_location, foreign_location).unwrap();
|
||||
let asset_refund =
|
||||
AssetConversion::get_amount_out(&refund, &reserve1, &reserve2).unwrap();
|
||||
|
||||
#[test]
|
||||
fn test_asset_xcm_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()
|
||||
));
|
||||
// refund.
|
||||
let actual_refund = trader.refund_weight(refund_weight, &ctx).unwrap();
|
||||
assert_eq!(actual_refund, (foreign_location, asset_refund).into());
|
||||
|
||||
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_multilocation = AssetIdForTrustBackedAssetsConvert::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: MultiAsset = (asset_multilocation, 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() {
|
||||
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_multilocation = AssetIdForTrustBackedAssetsConvert::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: MultiAsset = (asset_multilocation, amount_bought).into();
|
||||
assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive);
|
||||
|
||||
// Now lets buy ED at least
|
||||
let asset: MultiAsset = (asset_multilocation, 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
|
||||
// assert.
|
||||
assert_eq!(Balances::balance(&staking_pot), initial_balance);
|
||||
// only after `trader` is dropped we expect the fee to be resolved into the treasury
|
||||
// account.
|
||||
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_multilocation = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap();
|
||||
|
||||
let asset: MultiAsset = (asset_multilocation, 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);
|
||||
});
|
||||
assert_eq!(Balances::balance(&staking_pot), initial_balance + fee - refund);
|
||||
assert_eq!(
|
||||
ForeignAssets::total_issuance(foreign_location),
|
||||
asset_total_issuance + asset_fee - asset_refund
|
||||
);
|
||||
assert_eq!(Balances::total_issuance(), native_total_issuance);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -47,6 +47,16 @@ pub type TrustBackedAssetsConvertedConcreteId<TrustBackedAssetsPalletLocation, B
|
||||
/// AssetId used for identifying assets by MultiLocation.
|
||||
pub type MultiLocationForAssetId = MultiLocation;
|
||||
|
||||
/// [`MatchedConvertedConcreteId`] converter dedicated for `TrustBackedAssets`
|
||||
pub type TrustBackedAssetsAsMultiLocation<TrustBackedAssetsPalletLocation, Balance> =
|
||||
MatchedConvertedConcreteId<
|
||||
MultiLocationForAssetId,
|
||||
Balance,
|
||||
StartsWith<TrustBackedAssetsPalletLocation>,
|
||||
Identity,
|
||||
JustTry,
|
||||
>;
|
||||
|
||||
/// [`MatchedConvertedConcreteId`] converter dedicated for storing `AssetId` as `MultiLocation`.
|
||||
pub type MultiLocationConvertedConcreteId<MultiLocationFilter, Balance> =
|
||||
MatchedConvertedConcreteId<
|
||||
|
||||
@@ -16,6 +16,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features =
|
||||
frame-support = { path = "../../../../../substrate/frame/support", default-features = false }
|
||||
frame-system = { path = "../../../../../substrate/frame/system", default-features = false }
|
||||
pallet-assets = { path = "../../../../../substrate/frame/assets", default-features = false }
|
||||
pallet-asset-conversion = { path = "../../../../../substrate/frame/asset-conversion", default-features = false }
|
||||
pallet-balances = { path = "../../../../../substrate/frame/balances", default-features = false }
|
||||
pallet-session = { path = "../../../../../substrate/frame/session", default-features = false }
|
||||
sp-consensus-aura = { path = "../../../../../substrate/primitives/consensus/aura", default-features = false }
|
||||
@@ -64,6 +65,7 @@ std = [
|
||||
"cumulus-test-relay-sproof-builder/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"pallet-asset-conversion/std",
|
||||
"pallet-assets/std",
|
||||
"pallet-balances/std",
|
||||
"pallet-collator-selection/std",
|
||||
|
||||
@@ -30,6 +30,7 @@ use parachains_runtimes_test_utils::{
|
||||
ValidatorIdOf, XcmReceivedFrom,
|
||||
};
|
||||
use sp_runtime::{traits::StaticLookup, Saturating};
|
||||
use sp_std::ops::Mul;
|
||||
use xcm::{latest::prelude::*, VersionedMultiAssets};
|
||||
use xcm_builder::{CreateMatcher, MatchXcm};
|
||||
use xcm_executor::{traits::ConvertLocation, XcmExecutor};
|
||||
@@ -336,12 +337,13 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works<
|
||||
+ pallet_collator_selection::Config
|
||||
+ cumulus_pallet_parachain_system::Config
|
||||
+ cumulus_pallet_xcmp_queue::Config
|
||||
+ pallet_assets::Config<ForeignAssetsPalletInstance>,
|
||||
+ pallet_assets::Config<ForeignAssetsPalletInstance>
|
||||
+ pallet_asset_conversion::Config,
|
||||
AllPalletsWithoutSystem:
|
||||
OnInitialize<BlockNumberFor<Runtime>> + OnFinalize<BlockNumberFor<Runtime>>,
|
||||
AccountIdOf<Runtime>: Into<[u8; 32]>,
|
||||
AccountIdOf<Runtime>: Into<[u8; 32]> + From<[u8; 32]>,
|
||||
ValidatorIdOf<Runtime>: From<AccountIdOf<Runtime>>,
|
||||
BalanceOf<Runtime>: From<Balance>,
|
||||
BalanceOf<Runtime>: From<Balance> + Into<Balance>,
|
||||
XcmConfig: xcm_executor::Config,
|
||||
LocationToAccountId: ConvertLocation<AccountIdOf<Runtime>>,
|
||||
<Runtime as pallet_assets::Config<ForeignAssetsPalletInstance>>::AssetId:
|
||||
@@ -354,6 +356,9 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works<
|
||||
+ Into<AccountId>,
|
||||
<<Runtime as frame_system::Config>::Lookup as StaticLookup>::Source:
|
||||
From<<Runtime as frame_system::Config>::AccountId>,
|
||||
<Runtime as pallet_asset_conversion::Config>::AssetKind:
|
||||
From<MultiLocation> + Into<MultiLocation>,
|
||||
<Runtime as pallet_asset_conversion::Config>::Balance: From<Balance>,
|
||||
ForeignAssetsPalletInstance: 'static,
|
||||
{
|
||||
ExtBuilder::<Runtime>::default()
|
||||
@@ -400,6 +405,43 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works<
|
||||
)
|
||||
);
|
||||
|
||||
// setup a pool to pay fees with `foreign_asset_id_multilocation` tokens
|
||||
let pool_owner: AccountIdOf<Runtime> = [1u8; 32].into();
|
||||
let native_asset = MultiLocation::parent();
|
||||
let pool_liquidity: u128 =
|
||||
existential_deposit.into().max(foreign_asset_id_minimum_balance).mul(100_000);
|
||||
|
||||
let _ = <pallet_balances::Pallet<Runtime>>::deposit_creating(
|
||||
&pool_owner,
|
||||
(existential_deposit.into() + pool_liquidity).mul(2).into(),
|
||||
);
|
||||
|
||||
assert_ok!(<pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::mint(
|
||||
RuntimeHelper::<Runtime, AllPalletsWithoutSystem>::origin_of(
|
||||
sovereign_account_as_owner_of_foreign_asset
|
||||
),
|
||||
foreign_asset_id_multilocation.into(),
|
||||
pool_owner.clone().into(),
|
||||
(foreign_asset_id_minimum_balance + pool_liquidity).mul(2).into(),
|
||||
));
|
||||
|
||||
assert_ok!(<pallet_asset_conversion::Pallet<Runtime>>::create_pool(
|
||||
RuntimeHelper::<Runtime, AllPalletsWithoutSystem>::origin_of(pool_owner.clone()),
|
||||
Box::new(native_asset.into()),
|
||||
Box::new(foreign_asset_id_multilocation.into())
|
||||
));
|
||||
|
||||
assert_ok!(<pallet_asset_conversion::Pallet<Runtime>>::add_liquidity(
|
||||
RuntimeHelper::<Runtime, AllPalletsWithoutSystem>::origin_of(pool_owner.clone()),
|
||||
Box::new(native_asset.into()),
|
||||
Box::new(foreign_asset_id_multilocation.into()),
|
||||
pool_liquidity.into(),
|
||||
pool_liquidity.into(),
|
||||
1.into(),
|
||||
1.into(),
|
||||
pool_owner,
|
||||
));
|
||||
|
||||
// Balances before
|
||||
assert_eq!(
|
||||
<pallet_balances::Pallet<Runtime>>::free_balance(&target_account),
|
||||
@@ -485,14 +527,12 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works<
|
||||
);
|
||||
assert_ok!(outcome.ensure_complete());
|
||||
|
||||
// author actual balance after (received fees from Trader for ForeignAssets)
|
||||
let author_received_fees =
|
||||
<pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::balance(
|
||||
foreign_asset_id_multilocation.into(),
|
||||
&block_author_account,
|
||||
);
|
||||
|
||||
// Balances after (untouched)
|
||||
// Balances after
|
||||
// staking pot receives xcm fees in dot
|
||||
assert!(
|
||||
<pallet_balances::Pallet<Runtime>>::free_balance(&staking_pot) !=
|
||||
existential_deposit
|
||||
);
|
||||
assert_eq!(
|
||||
<pallet_balances::Pallet<Runtime>>::free_balance(&target_account),
|
||||
existential_deposit.clone()
|
||||
@@ -501,25 +541,13 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works<
|
||||
<pallet_balances::Pallet<Runtime>>::free_balance(&block_author_account),
|
||||
0.into()
|
||||
);
|
||||
assert_eq!(
|
||||
<pallet_balances::Pallet<Runtime>>::free_balance(&staking_pot),
|
||||
existential_deposit.clone()
|
||||
);
|
||||
|
||||
// ForeignAssets balances after
|
||||
assert_eq!(
|
||||
assert!(
|
||||
<pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::balance(
|
||||
foreign_asset_id_multilocation.into(),
|
||||
&target_account
|
||||
),
|
||||
(transfered_foreign_asset_id_amount - author_received_fees.into()).into()
|
||||
);
|
||||
assert_eq!(
|
||||
<pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::balance(
|
||||
foreign_asset_id_multilocation.into(),
|
||||
&block_author_account
|
||||
),
|
||||
author_received_fees
|
||||
) > 0.into()
|
||||
);
|
||||
assert_eq!(
|
||||
<pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::balance(
|
||||
@@ -528,6 +556,13 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works<
|
||||
),
|
||||
0.into()
|
||||
);
|
||||
assert_eq!(
|
||||
<pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::balance(
|
||||
foreign_asset_id_multilocation.into(),
|
||||
&block_author_account
|
||||
),
|
||||
0.into()
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user