mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 22:11: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:
Generated
+4
@@ -1066,6 +1066,7 @@ dependencies = [
|
|||||||
"frame-support",
|
"frame-support",
|
||||||
"frame-system",
|
"frame-system",
|
||||||
"hex-literal",
|
"hex-literal",
|
||||||
|
"pallet-asset-conversion",
|
||||||
"pallet-assets",
|
"pallet-assets",
|
||||||
"pallet-balances",
|
"pallet-balances",
|
||||||
"pallet-collator-selection",
|
"pallet-collator-selection",
|
||||||
@@ -1973,6 +1974,7 @@ dependencies = [
|
|||||||
"frame-support",
|
"frame-support",
|
||||||
"hex",
|
"hex",
|
||||||
"hex-literal",
|
"hex-literal",
|
||||||
|
"pallet-asset-conversion",
|
||||||
"pallet-assets",
|
"pallet-assets",
|
||||||
"pallet-balances",
|
"pallet-balances",
|
||||||
"pallet-bridge-messages",
|
"pallet-bridge-messages",
|
||||||
@@ -2165,6 +2167,7 @@ dependencies = [
|
|||||||
"cumulus-pallet-xcmp-queue",
|
"cumulus-pallet-xcmp-queue",
|
||||||
"emulated-integration-tests-common",
|
"emulated-integration-tests-common",
|
||||||
"frame-support",
|
"frame-support",
|
||||||
|
"pallet-asset-conversion",
|
||||||
"pallet-assets",
|
"pallet-assets",
|
||||||
"pallet-balances",
|
"pallet-balances",
|
||||||
"pallet-bridge-messages",
|
"pallet-bridge-messages",
|
||||||
@@ -4095,6 +4098,7 @@ dependencies = [
|
|||||||
"cumulus-primitives-core",
|
"cumulus-primitives-core",
|
||||||
"frame-support",
|
"frame-support",
|
||||||
"log",
|
"log",
|
||||||
|
"pallet-asset-conversion",
|
||||||
"pallet-xcm-benchmarks",
|
"pallet-xcm-benchmarks",
|
||||||
"parity-scale-codec",
|
"parity-scale-codec",
|
||||||
"polkadot-runtime-common",
|
"polkadot-runtime-common",
|
||||||
|
|||||||
-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)),
|
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)),
|
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 }
|
sp-core = { path = "../../../../../../../substrate/primitives/core", default-features = false }
|
||||||
frame-support = { path = "../../../../../../../substrate/frame/support", default-features = false }
|
frame-support = { path = "../../../../../../../substrate/frame/support", default-features = false }
|
||||||
pallet-assets = { path = "../../../../../../../substrate/frame/assets", 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-balances = { path = "../../../../../../../substrate/frame/balances", default-features = false }
|
||||||
pallet-message-queue = { path = "../../../../../../../substrate/frame/message-queue" }
|
pallet-message-queue = { path = "../../../../../../../substrate/frame/message-queue" }
|
||||||
sp-runtime = { path = "../../../../../../../substrate/primitives/runtime", default-features = false }
|
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},
|
rococo_emulated_chain::{genesis::ED as ROCOCO_ED, RococoRelayPallet as RococoPallet},
|
||||||
AssetHubRococoPara as AssetHubRococo, AssetHubRococoParaReceiver as AssetHubRococoReceiver,
|
AssetHubRococoPara as AssetHubRococo, AssetHubRococoParaReceiver as AssetHubRococoReceiver,
|
||||||
AssetHubRococoParaSender as AssetHubRococoSender, AssetHubWestendPara as AssetHubWestend,
|
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,
|
BridgeHubRococoParaSender as BridgeHubRococoSender, BridgeHubWestendPara as BridgeHubWestend,
|
||||||
RococoRelay as Rococo, RococoRelayReceiver as RococoReceiver,
|
RococoRelay as Rococo, RococoRelayReceiver as RococoReceiver,
|
||||||
RococoRelaySender as RococoSender,
|
RococoRelaySender as RococoSender,
|
||||||
|
|||||||
+44
-1
@@ -49,6 +49,49 @@ fn send_rocs_from_asset_hub_rococo_to_asset_hub_westend() {
|
|||||||
AssetHubWestend::para_id(),
|
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 =
|
let rocs_in_reserve_on_ahr_before =
|
||||||
<AssetHubRococo as Chain>::account_data_of(sov_ahw_on_ahr.clone()).free;
|
<AssetHubRococo as Chain>::account_data_of(sov_ahw_on_ahr.clone()).free;
|
||||||
let sender_rocs_before =
|
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())
|
<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);
|
send_asset_from_asset_hub_rococo_to_asset_hub_westend(roc_at_asset_hub_rococo, amount);
|
||||||
AssetHubWestend::execute_with(|| {
|
AssetHubWestend::execute_with(|| {
|
||||||
type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
|
type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
|
||||||
|
|||||||
+1
@@ -16,6 +16,7 @@ codec = { package = "parity-scale-codec", version = "3.4.0", default-features =
|
|||||||
# Substrate
|
# Substrate
|
||||||
frame-support = { path = "../../../../../../../substrate/frame/support", default-features = false }
|
frame-support = { path = "../../../../../../../substrate/frame/support", default-features = false }
|
||||||
pallet-assets = { path = "../../../../../../../substrate/frame/assets", 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-balances = { path = "../../../../../../../substrate/frame/balances", default-features = false }
|
||||||
pallet-message-queue = { path = "../../../../../../../substrate/frame/message-queue" }
|
pallet-message-queue = { path = "../../../../../../../substrate/frame/message-queue" }
|
||||||
sp-runtime = { path = "../../../../../../../substrate/primitives/runtime", default-features = false }
|
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,
|
westend_emulated_chain::WestendRelayPallet as WestendPallet,
|
||||||
AssetHubRococoPara as AssetHubRococo, AssetHubRococoParaReceiver as AssetHubRococoReceiver,
|
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,
|
AssetHubWestendParaSender as AssetHubWestendSender, BridgeHubRococoPara as BridgeHubRococo,
|
||||||
BridgeHubWestendPara as BridgeHubWestend, BridgeHubWestendParaSender as BridgeHubWestendSender,
|
BridgeHubWestendPara as BridgeHubWestend, BridgeHubWestendParaSender as BridgeHubWestendSender,
|
||||||
WestendRelay as Westend,
|
WestendRelay as Westend,
|
||||||
|
|||||||
+43
@@ -48,6 +48,49 @@ fn send_wnds_from_asset_hub_westend_to_asset_hub_rococo() {
|
|||||||
AssetHubRococo::para_id(),
|
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 =
|
let wnds_in_reserve_on_ahw_before =
|
||||||
<AssetHubWestend as Chain>::account_data_of(sov_ahr_on_ahw.clone()).free;
|
<AssetHubWestend as Chain>::account_data_of(sov_ahr_on_ahw.clone()).free;
|
||||||
let sender_wnds_before =
|
let sender_wnds_before =
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ impl pallet_assets::Config<PoolAssetsInstance> for Runtime {
|
|||||||
type BenchmarkHelper = ();
|
type BenchmarkHelper = ();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Union fungibles implementation for `Assets`` and `ForeignAssets`.
|
/// Union fungibles implementation for `Assets` and `ForeignAssets`.
|
||||||
pub type LocalAndForeignAssets = fungibles::UnionOf<
|
pub type LocalAndForeignAssets = fungibles::UnionOf<
|
||||||
Assets,
|
Assets,
|
||||||
ForeignAssets,
|
ForeignAssets,
|
||||||
@@ -324,18 +324,21 @@ pub type LocalAndForeignAssets = fungibles::UnionOf<
|
|||||||
AccountId,
|
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 {
|
impl pallet_asset_conversion::Config for Runtime {
|
||||||
type RuntimeEvent = RuntimeEvent;
|
type RuntimeEvent = RuntimeEvent;
|
||||||
type Balance = Balance;
|
type Balance = Balance;
|
||||||
type HigherPrecisionBalance = sp_core::U256;
|
type HigherPrecisionBalance = sp_core::U256;
|
||||||
type AssetKind = MultiLocation;
|
type AssetKind = MultiLocation;
|
||||||
type Assets = fungible::UnionOf<
|
type Assets = NativeAndAssets;
|
||||||
Balances,
|
|
||||||
LocalAndForeignAssets,
|
|
||||||
TargetFromLeft<TokenLocation>,
|
|
||||||
Self::AssetKind,
|
|
||||||
Self::AccountId,
|
|
||||||
>;
|
|
||||||
type PoolId = (Self::AssetKind, Self::AssetKind);
|
type PoolId = (Self::AssetKind, Self::AssetKind);
|
||||||
type PoolLocator =
|
type PoolLocator =
|
||||||
pallet_asset_conversion::WithFirstAsset<TokenLocation, AccountId, Self::AssetKind>;
|
pallet_asset_conversion::WithFirstAsset<TokenLocation, AccountId, Self::AssetKind>;
|
||||||
|
|||||||
@@ -15,17 +15,21 @@
|
|||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
AccountId, AllPalletsWithSystem, Assets, Authorship, Balance, Balances, BaseDeliveryFee,
|
AccountId, AllPalletsWithSystem, Assets, Authorship, Balance, Balances, BaseDeliveryFee,
|
||||||
FeeAssetId, ForeignAssets, ForeignAssetsInstance, ParachainInfo, ParachainSystem, PolkadotXcm,
|
CollatorSelection, FeeAssetId, ForeignAssets, ForeignAssetsInstance, ParachainInfo,
|
||||||
PoolAssets, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, ToWestendXcmRouter,
|
ParachainSystem, PolkadotXcm, PoolAssets, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin,
|
||||||
TransactionByteFee, TrustBackedAssetsInstance, WeightToFee, XcmpQueue,
|
ToWestendXcmRouter, TransactionByteFee, TrustBackedAssetsInstance, WeightToFee, XcmpQueue,
|
||||||
};
|
};
|
||||||
use assets_common::{
|
use assets_common::{
|
||||||
local_and_foreign_assets::MatchesLocalAndForeignAssetsMultiLocation,
|
local_and_foreign_assets::MatchesLocalAndForeignAssetsMultiLocation,
|
||||||
matching::{FromNetwork, FromSiblingParachain, IsForeignConcreteAsset},
|
matching::{FromNetwork, FromSiblingParachain, IsForeignConcreteAsset},
|
||||||
|
TrustBackedAssetsAsMultiLocation,
|
||||||
};
|
};
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
match_types, parameter_types,
|
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 frame_system::EnsureRoot;
|
||||||
use pallet_xcm::XcmPassthrough;
|
use pallet_xcm::XcmPassthrough;
|
||||||
@@ -73,6 +77,7 @@ parameter_types! {
|
|||||||
pub PoolAssetsPalletLocation: MultiLocation =
|
pub PoolAssetsPalletLocation: MultiLocation =
|
||||||
PalletInstance(<PoolAssets as PalletInfoAccess>::index() as u8).into();
|
PalletInstance(<PoolAssets as PalletInfoAccess>::index() as u8).into();
|
||||||
pub CheckingAccount: AccountId = PolkadotXcm::check_account();
|
pub CheckingAccount: AccountId = PolkadotXcm::check_account();
|
||||||
|
pub StakingPot: AccountId = CollatorSelection::account_id();
|
||||||
pub const GovernanceLocation: MultiLocation = MultiLocation::parent();
|
pub const GovernanceLocation: MultiLocation = MultiLocation::parent();
|
||||||
pub TreasuryAccount: AccountId = TREASURY_PALLET_ID.into_account_truncating();
|
pub TreasuryAccount: AccountId = TREASURY_PALLET_ID.into_account_truncating();
|
||||||
pub RelayTreasuryLocation: MultiLocation = (Parent, PalletInstance(rococo_runtime_constants::TREASURY_PALLET_ID)).into();
|
pub RelayTreasuryLocation: MultiLocation = (Parent, PalletInstance(rococo_runtime_constants::TREASURY_PALLET_ID)).into();
|
||||||
@@ -550,31 +555,17 @@ impl xcm_executor::Config for XcmConfig {
|
|||||||
>;
|
>;
|
||||||
type Trader = (
|
type Trader = (
|
||||||
UsingComponents<WeightToFee, TokenLocation, AccountId, Balances, ToStakingPot<Runtime>>,
|
UsingComponents<WeightToFee, TokenLocation, AccountId, Balances, ToStakingPot<Runtime>>,
|
||||||
// This trader allows to pay with `is_sufficient=true` "Trust Backed" assets from dedicated
|
cumulus_primitives_utility::SwapFirstAssetTrader<
|
||||||
// `pallet_assets` instance - `Assets`.
|
TokenLocation,
|
||||||
cumulus_primitives_utility::TakeFirstAssetTrader<
|
crate::AssetConversion,
|
||||||
|
WeightToFee,
|
||||||
|
crate::NativeAndAssets,
|
||||||
|
(
|
||||||
|
TrustBackedAssetsAsMultiLocation<TrustBackedAssetsPalletLocation, Balance>,
|
||||||
|
ForeignAssetsConvertedConcreteId,
|
||||||
|
),
|
||||||
|
ResolveAssetTo<StakingPot, crate::NativeAndAssets>,
|
||||||
AccountId,
|
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 ResponseHandler = PolkadotXcm;
|
||||||
|
|||||||
@@ -17,34 +17,38 @@
|
|||||||
|
|
||||||
//! Tests for the Rococo Assets Hub chain.
|
//! Tests for the Rococo Assets Hub chain.
|
||||||
|
|
||||||
use asset_hub_rococo_runtime::xcm_config::{
|
use asset_hub_rococo_runtime::{
|
||||||
AssetFeeAsExistentialDepositMultiplierFeeCharger, TokenLocation,
|
xcm_config,
|
||||||
TrustBackedAssetsPalletLocation,
|
xcm_config::{bridging, ForeignCreatorsSovereignAccountOf, LocationToAccountId, TokenLocation},
|
||||||
|
AllPalletsWithoutSystem, MetadataDepositBase, MetadataDepositPerByte, RuntimeCall,
|
||||||
|
RuntimeEvent, ToWestendXcmRouterInstance, XcmpQueue,
|
||||||
};
|
};
|
||||||
pub use asset_hub_rococo_runtime::{
|
pub use asset_hub_rococo_runtime::{
|
||||||
xcm_config::{
|
xcm_config::{CheckingAccount, TrustBackedAssetsPalletLocation, XcmConfig},
|
||||||
self, bridging, CheckingAccount, ForeignCreatorsSovereignAccountOf, LocationToAccountId,
|
AssetConversion, AssetDeposit, Assets, Balances, CollatorSelection, ExistentialDeposit,
|
||||||
XcmConfig,
|
ForeignAssets, ForeignAssetsInstance, ParachainSystem, Runtime, SessionKeys, System,
|
||||||
},
|
TrustBackedAssetsInstance,
|
||||||
AllPalletsWithoutSystem, AssetDeposit, Assets, Balances, ExistentialDeposit, ForeignAssets,
|
|
||||||
ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, ParachainSystem, Runtime,
|
|
||||||
RuntimeCall, RuntimeEvent, SessionKeys, System, ToWestendXcmRouterInstance,
|
|
||||||
TrustBackedAssetsInstance, XcmpQueue,
|
|
||||||
};
|
};
|
||||||
use asset_test_utils::{
|
use asset_test_utils::{
|
||||||
test_cases_over_bridge::TestBridgingConfig, CollatorSessionKey, CollatorSessionKeys, ExtBuilder,
|
test_cases_over_bridge::TestBridgingConfig, CollatorSessionKey, CollatorSessionKeys, ExtBuilder,
|
||||||
};
|
};
|
||||||
use codec::{Decode, Encode};
|
use codec::{Decode, Encode};
|
||||||
use cumulus_primitives_utility::ChargeWeightInFungibles;
|
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
assert_noop, assert_ok,
|
assert_ok,
|
||||||
traits::fungibles::InspectEnumerable,
|
traits::{
|
||||||
|
fungible::{Inspect, Mutate},
|
||||||
|
fungibles::{
|
||||||
|
Create, Inspect as FungiblesInspect, InspectEnumerable, Mutate as FungiblesMutate,
|
||||||
|
},
|
||||||
|
},
|
||||||
weights::{Weight, WeightToFee as WeightToFeeT},
|
weights::{Weight, WeightToFee as WeightToFeeT},
|
||||||
};
|
};
|
||||||
use parachains_common::{
|
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 sp_runtime::traits::MaybeEquivalence;
|
||||||
|
use std::convert::Into;
|
||||||
use xcm::latest::prelude::*;
|
use xcm::latest::prelude::*;
|
||||||
use xcm_executor::traits::{Identity, JustTry, WeightTrader};
|
use xcm_executor::traits::{Identity, JustTry, WeightTrader};
|
||||||
|
|
||||||
@@ -69,7 +73,7 @@ fn collator_session_keys() -> CollatorSessionKeys<Runtime> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_asset_xcm_trader() {
|
fn test_buy_and_refund_weight_in_native() {
|
||||||
ExtBuilder::<Runtime>::default()
|
ExtBuilder::<Runtime>::default()
|
||||||
.with_collators(vec![AccountId::from(ALICE)])
|
.with_collators(vec![AccountId::from(ALICE)])
|
||||||
.with_session_keys(vec![(
|
.with_session_keys(vec![(
|
||||||
@@ -79,77 +83,55 @@ fn test_asset_xcm_trader() {
|
|||||||
)])
|
)])
|
||||||
.build()
|
.build()
|
||||||
.execute_with(|| {
|
.execute_with(|| {
|
||||||
// We need root origin to create a sufficient asset
|
let bob: AccountId = SOME_ASSET_ADMIN.into();
|
||||||
let minimum_asset_balance = 3333333_u128;
|
let staking_pot = CollatorSelection::account_id();
|
||||||
let local_asset_id = 1;
|
let native_location = TokenLocation::get();
|
||||||
assert_ok!(Assets::force_create(
|
let initial_balance = 200 * UNITS;
|
||||||
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!(Balances::mint_into(&bob, initial_balance));
|
||||||
assert_ok!(Assets::mint(
|
assert_ok!(Balances::mint_into(&staking_pot, initial_balance));
|
||||||
RuntimeHelper::origin_of(AccountId::from(ALICE)),
|
|
||||||
local_asset_id.into(),
|
|
||||||
AccountId::from(ALICE).into(),
|
|
||||||
minimum_asset_balance
|
|
||||||
));
|
|
||||||
|
|
||||||
// get asset id as multilocation
|
// keep initial total issuance to assert later.
|
||||||
let asset_multilocation =
|
let total_issuance = Balances::total_issuance();
|
||||||
AssetIdForTrustBackedAssetsConvert::convert_back(&local_asset_id).unwrap();
|
|
||||||
|
|
||||||
// Set Alice as block author, who will receive fees
|
// prepare input to buy weight.
|
||||||
RuntimeHelper::run_to_block(2, AccountId::from(ALICE));
|
let weight = Weight::from_parts(4_000_000_000, 0);
|
||||||
|
let fee = WeightToFee::weight_to_fee(&weight);
|
||||||
// We are going to buy 4e9 weight
|
let extra_amount = 100;
|
||||||
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();
|
|
||||||
let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
|
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
|
// init trader and buy weight.
|
||||||
let unused_assets = trader.buy_weight(bought, asset.into(), &ctx).expect("Expected Ok");
|
let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
|
||||||
// Check whether a correct amount of unused assets is returned
|
let unused_asset =
|
||||||
assert_ok!(
|
trader.buy_weight(weight, payment.into(), &ctx).expect("Expected Ok");
|
||||||
unused_assets.ensure_contains(&(asset_multilocation, asset_amount_extra).into())
|
|
||||||
);
|
|
||||||
|
|
||||||
// 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);
|
drop(trader);
|
||||||
|
assert_eq!(Balances::balance(&staking_pot), initial_balance + fee - refund);
|
||||||
// Make sure author(Alice) has received the amount
|
assert_eq!(Balances::total_issuance(), total_issuance + fee - refund);
|
||||||
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]
|
#[test]
|
||||||
fn test_asset_xcm_trader_with_refund() {
|
fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() {
|
||||||
ExtBuilder::<Runtime>::default()
|
ExtBuilder::<Runtime>::default()
|
||||||
.with_collators(vec![AccountId::from(ALICE)])
|
.with_collators(vec![AccountId::from(ALICE)])
|
||||||
.with_session_keys(vec![(
|
.with_session_keys(vec![(
|
||||||
@@ -159,249 +141,192 @@ fn test_asset_xcm_trader_with_refund() {
|
|||||||
)])
|
)])
|
||||||
.build()
|
.build()
|
||||||
.execute_with(|| {
|
.execute_with(|| {
|
||||||
// We need root origin to create a sufficient asset
|
let bob: AccountId = SOME_ASSET_ADMIN.into();
|
||||||
// We set existential deposit to be identical to the one for Balances first
|
let staking_pot = CollatorSelection::account_id();
|
||||||
assert_ok!(Assets::force_create(
|
let asset_1: u32 = 1;
|
||||||
RuntimeHelper::root_origin(),
|
let native_location = TokenLocation::get();
|
||||||
1.into(),
|
let asset_1_location =
|
||||||
AccountId::from(ALICE).into(),
|
AssetIdForTrustBackedAssetsConvert::convert_back(&asset_1).unwrap();
|
||||||
true,
|
// bob's initial balance for native and `asset1` assets.
|
||||||
ExistentialDeposit::get()
|
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!(AssetConversion::add_liquidity(
|
||||||
assert_ok!(Assets::mint(
|
RuntimeHelper::origin_of(bob.clone()),
|
||||||
RuntimeHelper::origin_of(AccountId::from(ALICE)),
|
Box::new(native_location),
|
||||||
1.into(),
|
Box::new(asset_1_location),
|
||||||
AccountId::from(ALICE).into(),
|
pool_liquidity,
|
||||||
ExistentialDeposit::get()
|
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 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
|
// init trader and buy weight.
|
||||||
RuntimeHelper::run_to_block(2, AccountId::from(ALICE));
|
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
|
// assert.
|
||||||
let bought = Weight::from_parts(4_000_000_000u64, 0);
|
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
|
// refund.
|
||||||
let amount_bought = WeightToFee::weight_to_fee(&bought);
|
let actual_refund = trader.refund_weight(refund_weight, &ctx).unwrap();
|
||||||
|
assert_eq!(actual_refund, (asset_1_location, asset_refund).into());
|
||||||
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));
|
|
||||||
|
|
||||||
|
// 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!(
|
assert_eq!(
|
||||||
trader.refund_weight(bought - weight_used, &ctx),
|
Assets::total_issuance(asset_1),
|
||||||
Some((asset_multilocation, amount_refunded).into())
|
asset_total_issuance + asset_fee - asset_refund
|
||||||
);
|
);
|
||||||
|
assert_eq!(Balances::total_issuance(), native_total_issuance);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Drop trader
|
#[test]
|
||||||
drop(trader);
|
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
|
// init asset, balances and pool.
|
||||||
let fees_paid = WeightToFee::weight_to_fee(&weight_used);
|
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!(
|
assert_eq!(
|
||||||
Assets::balance(1, AccountId::from(ALICE)),
|
ForeignAssets::total_issuance(foreign_location),
|
||||||
ExistentialDeposit::get() + fees_paid
|
asset_total_issuance + asset_fee
|
||||||
);
|
);
|
||||||
|
|
||||||
// We also need to ensure the total supply increased
|
// prepare input to refund weight.
|
||||||
assert_eq!(Assets::total_supply(1), ExistentialDeposit::get() + fees_paid);
|
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]
|
// refund.
|
||||||
fn test_asset_xcm_trader_refund_not_possible_since_amount_less_than_ed() {
|
let actual_refund = trader.refund_weight(refund_weight, &ctx).unwrap();
|
||||||
ExtBuilder::<Runtime>::default()
|
assert_eq!(actual_refund, (foreign_location, asset_refund).into());
|
||||||
.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();
|
// assert.
|
||||||
let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
|
assert_eq!(Balances::balance(&staking_pot), initial_balance);
|
||||||
|
// only after `trader` is dropped we expect the fee to be resolved into the treasury
|
||||||
// Set Alice as block author, who will receive fees
|
// account.
|
||||||
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
|
|
||||||
drop(trader);
|
drop(trader);
|
||||||
|
assert_eq!(Balances::balance(&staking_pot), initial_balance + fee - refund);
|
||||||
// Make sure author(Alice) has received the amount
|
assert_eq!(
|
||||||
assert_eq!(Assets::balance(1, AccountId::from(ALICE)), ExistentialDeposit::get());
|
ForeignAssets::total_issuance(foreign_location),
|
||||||
|
asset_total_issuance + asset_fee - asset_refund
|
||||||
// We also need to ensure the total supply increased
|
);
|
||||||
assert_eq!(Assets::total_supply(1), ExistentialDeposit::get());
|
assert_eq!(Balances::total_issuance(), native_total_issuance);
|
||||||
});
|
})
|
||||||
}
|
|
||||||
|
|
||||||
#[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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ impl pallet_assets::Config<PoolAssetsInstance> for Runtime {
|
|||||||
type BenchmarkHelper = ();
|
type BenchmarkHelper = ();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Union fungibles implementation for `Assets`` and `ForeignAssets`.
|
/// Union fungibles implementation for `Assets` and `ForeignAssets`.
|
||||||
pub type LocalAndForeignAssets = fungibles::UnionOf<
|
pub type LocalAndForeignAssets = fungibles::UnionOf<
|
||||||
Assets,
|
Assets,
|
||||||
ForeignAssets,
|
ForeignAssets,
|
||||||
@@ -307,18 +307,21 @@ pub type LocalAndForeignAssets = fungibles::UnionOf<
|
|||||||
AccountId,
|
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 {
|
impl pallet_asset_conversion::Config for Runtime {
|
||||||
type RuntimeEvent = RuntimeEvent;
|
type RuntimeEvent = RuntimeEvent;
|
||||||
type Balance = Balance;
|
type Balance = Balance;
|
||||||
type HigherPrecisionBalance = sp_core::U256;
|
type HigherPrecisionBalance = sp_core::U256;
|
||||||
type AssetKind = MultiLocation;
|
type AssetKind = MultiLocation;
|
||||||
type Assets = fungible::UnionOf<
|
type Assets = NativeAndAssets;
|
||||||
Balances,
|
|
||||||
LocalAndForeignAssets,
|
|
||||||
TargetFromLeft<WestendLocation>,
|
|
||||||
Self::AssetKind,
|
|
||||||
Self::AccountId,
|
|
||||||
>;
|
|
||||||
type PoolId = (Self::AssetKind, Self::AssetKind);
|
type PoolId = (Self::AssetKind, Self::AssetKind);
|
||||||
type PoolLocator =
|
type PoolLocator =
|
||||||
pallet_asset_conversion::WithFirstAsset<WestendLocation, AccountId, Self::AssetKind>;
|
pallet_asset_conversion::WithFirstAsset<WestendLocation, AccountId, Self::AssetKind>;
|
||||||
|
|||||||
@@ -15,17 +15,21 @@
|
|||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
AccountId, AllPalletsWithSystem, Assets, Authorship, Balance, Balances, BaseDeliveryFee,
|
AccountId, AllPalletsWithSystem, Assets, Authorship, Balance, Balances, BaseDeliveryFee,
|
||||||
FeeAssetId, ForeignAssets, ForeignAssetsInstance, ParachainInfo, ParachainSystem, PolkadotXcm,
|
CollatorSelection, FeeAssetId, ForeignAssets, ForeignAssetsInstance, ParachainInfo,
|
||||||
PoolAssets, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, ToRococoXcmRouter,
|
ParachainSystem, PolkadotXcm, PoolAssets, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin,
|
||||||
TransactionByteFee, TrustBackedAssetsInstance, WeightToFee, XcmpQueue,
|
ToRococoXcmRouter, TransactionByteFee, TrustBackedAssetsInstance, WeightToFee, XcmpQueue,
|
||||||
};
|
};
|
||||||
use assets_common::{
|
use assets_common::{
|
||||||
local_and_foreign_assets::MatchesLocalAndForeignAssetsMultiLocation,
|
local_and_foreign_assets::MatchesLocalAndForeignAssetsMultiLocation,
|
||||||
matching::{FromSiblingParachain, IsForeignConcreteAsset},
|
matching::{FromSiblingParachain, IsForeignConcreteAsset},
|
||||||
|
TrustBackedAssetsAsMultiLocation,
|
||||||
};
|
};
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
match_types, parameter_types,
|
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 frame_system::EnsureRoot;
|
||||||
use pallet_xcm::XcmPassthrough;
|
use pallet_xcm::XcmPassthrough;
|
||||||
@@ -70,6 +74,7 @@ parameter_types! {
|
|||||||
pub PoolAssetsPalletLocation: MultiLocation =
|
pub PoolAssetsPalletLocation: MultiLocation =
|
||||||
PalletInstance(<PoolAssets as PalletInfoAccess>::index() as u8).into();
|
PalletInstance(<PoolAssets as PalletInfoAccess>::index() as u8).into();
|
||||||
pub CheckingAccount: AccountId = PolkadotXcm::check_account();
|
pub CheckingAccount: AccountId = PolkadotXcm::check_account();
|
||||||
|
pub StakingPot: AccountId = CollatorSelection::account_id();
|
||||||
pub TreasuryAccount: AccountId = TREASURY_PALLET_ID.into_account_truncating();
|
pub TreasuryAccount: AccountId = TREASURY_PALLET_ID.into_account_truncating();
|
||||||
pub RelayTreasuryLocation: MultiLocation = (Parent, PalletInstance(westend_runtime_constants::TREASURY_PALLET_ID)).into();
|
pub RelayTreasuryLocation: MultiLocation = (Parent, PalletInstance(westend_runtime_constants::TREASURY_PALLET_ID)).into();
|
||||||
}
|
}
|
||||||
@@ -567,31 +572,17 @@ impl xcm_executor::Config for XcmConfig {
|
|||||||
>;
|
>;
|
||||||
type Trader = (
|
type Trader = (
|
||||||
UsingComponents<WeightToFee, WestendLocation, AccountId, Balances, ToStakingPot<Runtime>>,
|
UsingComponents<WeightToFee, WestendLocation, AccountId, Balances, ToStakingPot<Runtime>>,
|
||||||
// This trader allows to pay with `is_sufficient=true` "Trust Backed" assets from dedicated
|
cumulus_primitives_utility::SwapFirstAssetTrader<
|
||||||
// `pallet_assets` instance - `Assets`.
|
WestendLocation,
|
||||||
cumulus_primitives_utility::TakeFirstAssetTrader<
|
crate::AssetConversion,
|
||||||
|
WeightToFee,
|
||||||
|
crate::NativeAndAssets,
|
||||||
|
(
|
||||||
|
TrustBackedAssetsAsMultiLocation<TrustBackedAssetsPalletLocation, Balance>,
|
||||||
|
ForeignAssetsConvertedConcreteId,
|
||||||
|
),
|
||||||
|
ResolveAssetTo<StakingPot, crate::NativeAndAssets>,
|
||||||
AccountId,
|
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 ResponseHandler = PolkadotXcm;
|
||||||
|
|||||||
@@ -18,28 +18,36 @@
|
|||||||
//! Tests for the Westmint (Westend Assets Hub) chain.
|
//! Tests for the Westmint (Westend Assets Hub) chain.
|
||||||
|
|
||||||
use asset_hub_westend_runtime::{
|
use asset_hub_westend_runtime::{
|
||||||
|
xcm_config,
|
||||||
xcm_config::{
|
xcm_config::{
|
||||||
self, bridging, AssetFeeAsExistentialDepositMultiplierFeeCharger, CheckingAccount,
|
bridging, ForeignCreatorsSovereignAccountOf, LocationToAccountId, WestendLocation,
|
||||||
ForeignCreatorsSovereignAccountOf, LocationToAccountId, TrustBackedAssetsPalletLocation,
|
|
||||||
WestendLocation, XcmConfig,
|
|
||||||
},
|
},
|
||||||
AllPalletsWithoutSystem, AssetDeposit, Assets, Balances, ExistentialDeposit, ForeignAssets,
|
AllPalletsWithoutSystem, MetadataDepositBase, MetadataDepositPerByte, PolkadotXcm, RuntimeCall,
|
||||||
ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, ParachainSystem,
|
RuntimeEvent, RuntimeOrigin, ToRococoXcmRouterInstance, XcmpQueue,
|
||||||
PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, SessionKeys,
|
};
|
||||||
ToRococoXcmRouterInstance, TrustBackedAssetsInstance, 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::{
|
use asset_test_utils::{
|
||||||
test_cases_over_bridge::TestBridgingConfig, CollatorSessionKey, CollatorSessionKeys, ExtBuilder,
|
test_cases_over_bridge::TestBridgingConfig, CollatorSessionKey, CollatorSessionKeys, ExtBuilder,
|
||||||
};
|
};
|
||||||
use codec::{Decode, Encode};
|
use codec::{Decode, Encode};
|
||||||
use cumulus_primitives_utility::ChargeWeightInFungibles;
|
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
assert_noop, assert_ok,
|
assert_ok,
|
||||||
traits::fungibles::InspectEnumerable,
|
traits::{
|
||||||
|
fungible::{Inspect, Mutate},
|
||||||
|
fungibles::{
|
||||||
|
Create, Inspect as FungiblesInspect, InspectEnumerable, Mutate as FungiblesMutate,
|
||||||
|
},
|
||||||
|
},
|
||||||
weights::{Weight, WeightToFee as WeightToFeeT},
|
weights::{Weight, WeightToFee as WeightToFeeT},
|
||||||
};
|
};
|
||||||
use parachains_common::{
|
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 sp_runtime::traits::MaybeEquivalence;
|
||||||
use std::convert::Into;
|
use std::convert::Into;
|
||||||
@@ -67,7 +75,7 @@ fn collator_session_keys() -> CollatorSessionKeys<Runtime> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_asset_xcm_trader() {
|
fn test_buy_and_refund_weight_in_native() {
|
||||||
ExtBuilder::<Runtime>::default()
|
ExtBuilder::<Runtime>::default()
|
||||||
.with_collators(vec![AccountId::from(ALICE)])
|
.with_collators(vec![AccountId::from(ALICE)])
|
||||||
.with_session_keys(vec![(
|
.with_session_keys(vec![(
|
||||||
@@ -77,77 +85,55 @@ fn test_asset_xcm_trader() {
|
|||||||
)])
|
)])
|
||||||
.build()
|
.build()
|
||||||
.execute_with(|| {
|
.execute_with(|| {
|
||||||
// We need root origin to create a sufficient asset
|
let bob: AccountId = SOME_ASSET_ADMIN.into();
|
||||||
let minimum_asset_balance = 3333333_u128;
|
let staking_pot = CollatorSelection::account_id();
|
||||||
let local_asset_id = 1;
|
let native_location = WestendLocation::get();
|
||||||
assert_ok!(Assets::force_create(
|
let initial_balance = 200 * UNITS;
|
||||||
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!(Balances::mint_into(&bob, initial_balance));
|
||||||
assert_ok!(Assets::mint(
|
assert_ok!(Balances::mint_into(&staking_pot, initial_balance));
|
||||||
RuntimeHelper::origin_of(AccountId::from(ALICE)),
|
|
||||||
local_asset_id.into(),
|
|
||||||
AccountId::from(ALICE).into(),
|
|
||||||
minimum_asset_balance
|
|
||||||
));
|
|
||||||
|
|
||||||
// get asset id as multilocation
|
// keep initial total issuance to assert later.
|
||||||
let asset_multilocation =
|
let total_issuance = Balances::total_issuance();
|
||||||
AssetIdForTrustBackedAssetsConvert::convert_back(&local_asset_id).unwrap();
|
|
||||||
|
|
||||||
// Set Alice as block author, who will receive fees
|
// prepare input to buy weight.
|
||||||
RuntimeHelper::run_to_block(2, AccountId::from(ALICE));
|
let weight = Weight::from_parts(4_000_000_000, 0);
|
||||||
|
let fee = WeightToFee::weight_to_fee(&weight);
|
||||||
// We are going to buy 4e9 weight
|
let extra_amount = 100;
|
||||||
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();
|
|
||||||
let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
|
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
|
// init trader and buy weight.
|
||||||
let unused_assets = trader.buy_weight(bought, asset.into(), &ctx).expect("Expected Ok");
|
let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
|
||||||
// Check whether a correct amount of unused assets is returned
|
let unused_asset =
|
||||||
assert_ok!(
|
trader.buy_weight(weight, payment.into(), &ctx).expect("Expected Ok");
|
||||||
unused_assets.ensure_contains(&(asset_multilocation, asset_amount_extra).into())
|
|
||||||
);
|
|
||||||
|
|
||||||
// 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);
|
drop(trader);
|
||||||
|
assert_eq!(Balances::balance(&staking_pot), initial_balance + fee - refund);
|
||||||
// Make sure author(Alice) has received the amount
|
assert_eq!(Balances::total_issuance(), total_issuance + fee - refund);
|
||||||
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]
|
#[test]
|
||||||
fn test_asset_xcm_trader_with_refund() {
|
fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() {
|
||||||
ExtBuilder::<Runtime>::default()
|
ExtBuilder::<Runtime>::default()
|
||||||
.with_collators(vec![AccountId::from(ALICE)])
|
.with_collators(vec![AccountId::from(ALICE)])
|
||||||
.with_session_keys(vec![(
|
.with_session_keys(vec![(
|
||||||
@@ -157,247 +143,192 @@ fn test_asset_xcm_trader_with_refund() {
|
|||||||
)])
|
)])
|
||||||
.build()
|
.build()
|
||||||
.execute_with(|| {
|
.execute_with(|| {
|
||||||
// We need root origin to create a sufficient asset
|
let bob: AccountId = SOME_ASSET_ADMIN.into();
|
||||||
// We set existential deposit to be identical to the one for Balances first
|
let staking_pot = CollatorSelection::account_id();
|
||||||
assert_ok!(Assets::force_create(
|
let asset_1: u32 = 1;
|
||||||
RuntimeHelper::root_origin(),
|
let native_location = WestendLocation::get();
|
||||||
1.into(),
|
let asset_1_location =
|
||||||
AccountId::from(ALICE).into(),
|
AssetIdForTrustBackedAssetsConvert::convert_back(&asset_1).unwrap();
|
||||||
true,
|
// bob's initial balance for native and `asset1` assets.
|
||||||
ExistentialDeposit::get()
|
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!(AssetConversion::add_liquidity(
|
||||||
assert_ok!(Assets::mint(
|
RuntimeHelper::origin_of(bob.clone()),
|
||||||
RuntimeHelper::origin_of(AccountId::from(ALICE)),
|
Box::new(native_location),
|
||||||
1.into(),
|
Box::new(asset_1_location),
|
||||||
AccountId::from(ALICE).into(),
|
pool_liquidity,
|
||||||
ExistentialDeposit::get()
|
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 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
|
// init trader and buy weight.
|
||||||
RuntimeHelper::run_to_block(2, AccountId::from(ALICE));
|
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
|
// assert.
|
||||||
let bought = Weight::from_parts(4_000_000_000u64, 0);
|
let unused_amount =
|
||||||
let asset_multilocation = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap();
|
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
|
// prepare input to refund weight.
|
||||||
let amount_bought = WeightToFee::weight_to_fee(&bought);
|
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();
|
// refund.
|
||||||
|
let actual_refund = trader.refund_weight(refund_weight, &ctx).unwrap();
|
||||||
// Make sure buy_weight does not return an error
|
assert_eq!(actual_refund, (asset_1_location, asset_refund).into());
|
||||||
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.
|
||||||
|
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!(
|
assert_eq!(
|
||||||
trader.refund_weight(bought - weight_used, &ctx),
|
Assets::total_issuance(asset_1),
|
||||||
Some((asset_multilocation, amount_refunded).into())
|
asset_total_issuance + asset_fee - asset_refund
|
||||||
);
|
);
|
||||||
|
assert_eq!(Balances::total_issuance(), native_total_issuance);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Drop trader
|
#[test]
|
||||||
drop(trader);
|
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
|
// init asset, balances and pool.
|
||||||
let fees_paid = WeightToFee::weight_to_fee(&weight_used);
|
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!(
|
assert_eq!(
|
||||||
Assets::balance(1, AccountId::from(ALICE)),
|
ForeignAssets::total_issuance(foreign_location),
|
||||||
ExistentialDeposit::get() + fees_paid
|
asset_total_issuance + asset_fee
|
||||||
);
|
);
|
||||||
|
|
||||||
// We also need to ensure the total supply increased
|
// prepare input to refund weight.
|
||||||
assert_eq!(Assets::total_supply(1), ExistentialDeposit::get() + fees_paid);
|
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]
|
// refund.
|
||||||
fn test_asset_xcm_trader_refund_not_possible_since_amount_less_than_ed() {
|
let actual_refund = trader.refund_weight(refund_weight, &ctx).unwrap();
|
||||||
ExtBuilder::<Runtime>::default()
|
assert_eq!(actual_refund, (foreign_location, asset_refund).into());
|
||||||
.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();
|
// assert.
|
||||||
let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
|
assert_eq!(Balances::balance(&staking_pot), initial_balance);
|
||||||
|
// only after `trader` is dropped we expect the fee to be resolved into the treasury
|
||||||
// Set Alice as block author, who will receive fees
|
// account.
|
||||||
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
|
|
||||||
drop(trader);
|
drop(trader);
|
||||||
|
assert_eq!(Balances::balance(&staking_pot), initial_balance + fee - refund);
|
||||||
// Make sure author(Alice) has received the amount
|
assert_eq!(
|
||||||
assert_eq!(Assets::balance(1, AccountId::from(ALICE)), ExistentialDeposit::get());
|
ForeignAssets::total_issuance(foreign_location),
|
||||||
|
asset_total_issuance + asset_fee - asset_refund
|
||||||
// We also need to ensure the total supply increased
|
);
|
||||||
assert_eq!(Assets::total_supply(1), ExistentialDeposit::get());
|
assert_eq!(Balances::total_issuance(), native_total_issuance);
|
||||||
});
|
})
|
||||||
}
|
|
||||||
|
|
||||||
#[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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -47,6 +47,16 @@ pub type TrustBackedAssetsConvertedConcreteId<TrustBackedAssetsPalletLocation, B
|
|||||||
/// AssetId used for identifying assets by MultiLocation.
|
/// AssetId used for identifying assets by MultiLocation.
|
||||||
pub type MultiLocationForAssetId = 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`.
|
/// [`MatchedConvertedConcreteId`] converter dedicated for storing `AssetId` as `MultiLocation`.
|
||||||
pub type MultiLocationConvertedConcreteId<MultiLocationFilter, Balance> =
|
pub type MultiLocationConvertedConcreteId<MultiLocationFilter, Balance> =
|
||||||
MatchedConvertedConcreteId<
|
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-support = { path = "../../../../../substrate/frame/support", default-features = false }
|
||||||
frame-system = { path = "../../../../../substrate/frame/system", default-features = false }
|
frame-system = { path = "../../../../../substrate/frame/system", default-features = false }
|
||||||
pallet-assets = { path = "../../../../../substrate/frame/assets", 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-balances = { path = "../../../../../substrate/frame/balances", default-features = false }
|
||||||
pallet-session = { path = "../../../../../substrate/frame/session", default-features = false }
|
pallet-session = { path = "../../../../../substrate/frame/session", default-features = false }
|
||||||
sp-consensus-aura = { path = "../../../../../substrate/primitives/consensus/aura", 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",
|
"cumulus-test-relay-sproof-builder/std",
|
||||||
"frame-support/std",
|
"frame-support/std",
|
||||||
"frame-system/std",
|
"frame-system/std",
|
||||||
|
"pallet-asset-conversion/std",
|
||||||
"pallet-assets/std",
|
"pallet-assets/std",
|
||||||
"pallet-balances/std",
|
"pallet-balances/std",
|
||||||
"pallet-collator-selection/std",
|
"pallet-collator-selection/std",
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ use parachains_runtimes_test_utils::{
|
|||||||
ValidatorIdOf, XcmReceivedFrom,
|
ValidatorIdOf, XcmReceivedFrom,
|
||||||
};
|
};
|
||||||
use sp_runtime::{traits::StaticLookup, Saturating};
|
use sp_runtime::{traits::StaticLookup, Saturating};
|
||||||
|
use sp_std::ops::Mul;
|
||||||
use xcm::{latest::prelude::*, VersionedMultiAssets};
|
use xcm::{latest::prelude::*, VersionedMultiAssets};
|
||||||
use xcm_builder::{CreateMatcher, MatchXcm};
|
use xcm_builder::{CreateMatcher, MatchXcm};
|
||||||
use xcm_executor::{traits::ConvertLocation, XcmExecutor};
|
use xcm_executor::{traits::ConvertLocation, XcmExecutor};
|
||||||
@@ -336,12 +337,13 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works<
|
|||||||
+ pallet_collator_selection::Config
|
+ pallet_collator_selection::Config
|
||||||
+ cumulus_pallet_parachain_system::Config
|
+ cumulus_pallet_parachain_system::Config
|
||||||
+ cumulus_pallet_xcmp_queue::Config
|
+ cumulus_pallet_xcmp_queue::Config
|
||||||
+ pallet_assets::Config<ForeignAssetsPalletInstance>,
|
+ pallet_assets::Config<ForeignAssetsPalletInstance>
|
||||||
|
+ pallet_asset_conversion::Config,
|
||||||
AllPalletsWithoutSystem:
|
AllPalletsWithoutSystem:
|
||||||
OnInitialize<BlockNumberFor<Runtime>> + OnFinalize<BlockNumberFor<Runtime>>,
|
OnInitialize<BlockNumberFor<Runtime>> + OnFinalize<BlockNumberFor<Runtime>>,
|
||||||
AccountIdOf<Runtime>: Into<[u8; 32]>,
|
AccountIdOf<Runtime>: Into<[u8; 32]> + From<[u8; 32]>,
|
||||||
ValidatorIdOf<Runtime>: From<AccountIdOf<Runtime>>,
|
ValidatorIdOf<Runtime>: From<AccountIdOf<Runtime>>,
|
||||||
BalanceOf<Runtime>: From<Balance>,
|
BalanceOf<Runtime>: From<Balance> + Into<Balance>,
|
||||||
XcmConfig: xcm_executor::Config,
|
XcmConfig: xcm_executor::Config,
|
||||||
LocationToAccountId: ConvertLocation<AccountIdOf<Runtime>>,
|
LocationToAccountId: ConvertLocation<AccountIdOf<Runtime>>,
|
||||||
<Runtime as pallet_assets::Config<ForeignAssetsPalletInstance>>::AssetId:
|
<Runtime as pallet_assets::Config<ForeignAssetsPalletInstance>>::AssetId:
|
||||||
@@ -354,6 +356,9 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works<
|
|||||||
+ Into<AccountId>,
|
+ Into<AccountId>,
|
||||||
<<Runtime as frame_system::Config>::Lookup as StaticLookup>::Source:
|
<<Runtime as frame_system::Config>::Lookup as StaticLookup>::Source:
|
||||||
From<<Runtime as frame_system::Config>::AccountId>,
|
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,
|
ForeignAssetsPalletInstance: 'static,
|
||||||
{
|
{
|
||||||
ExtBuilder::<Runtime>::default()
|
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
|
// Balances before
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
<pallet_balances::Pallet<Runtime>>::free_balance(&target_account),
|
<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());
|
assert_ok!(outcome.ensure_complete());
|
||||||
|
|
||||||
// author actual balance after (received fees from Trader for ForeignAssets)
|
// Balances after
|
||||||
let author_received_fees =
|
// staking pot receives xcm fees in dot
|
||||||
<pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::balance(
|
assert!(
|
||||||
foreign_asset_id_multilocation.into(),
|
<pallet_balances::Pallet<Runtime>>::free_balance(&staking_pot) !=
|
||||||
&block_author_account,
|
existential_deposit
|
||||||
);
|
);
|
||||||
|
|
||||||
// Balances after (untouched)
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
<pallet_balances::Pallet<Runtime>>::free_balance(&target_account),
|
<pallet_balances::Pallet<Runtime>>::free_balance(&target_account),
|
||||||
existential_deposit.clone()
|
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),
|
<pallet_balances::Pallet<Runtime>>::free_balance(&block_author_account),
|
||||||
0.into()
|
0.into()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
|
||||||
<pallet_balances::Pallet<Runtime>>::free_balance(&staking_pot),
|
|
||||||
existential_deposit.clone()
|
|
||||||
);
|
|
||||||
|
|
||||||
// ForeignAssets balances after
|
// ForeignAssets balances after
|
||||||
assert_eq!(
|
assert!(
|
||||||
<pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::balance(
|
<pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::balance(
|
||||||
foreign_asset_id_multilocation.into(),
|
foreign_asset_id_multilocation.into(),
|
||||||
&target_account
|
&target_account
|
||||||
),
|
) > 0.into()
|
||||||
(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
|
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
<pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::balance(
|
<pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::balance(
|
||||||
@@ -528,6 +556,13 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works<
|
|||||||
),
|
),
|
||||||
0.into()
|
0.into()
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
<pallet_assets::Pallet<Runtime, ForeignAssetsPalletInstance>>::balance(
|
||||||
|
foreign_asset_id_multilocation.into(),
|
||||||
|
&block_author_account
|
||||||
|
),
|
||||||
|
0.into()
|
||||||
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ frame-support = { path = "../../../substrate/frame/support", default-features =
|
|||||||
sp-io = { path = "../../../substrate/primitives/io", default-features = false }
|
sp-io = { path = "../../../substrate/primitives/io", default-features = false }
|
||||||
sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false }
|
sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false }
|
||||||
sp-std = { path = "../../../substrate/primitives/std", default-features = false }
|
sp-std = { path = "../../../substrate/primitives/std", default-features = false }
|
||||||
|
pallet-asset-conversion = { path = "../../../substrate/frame/asset-conversion", default-features = false }
|
||||||
|
|
||||||
# Polkadot
|
# Polkadot
|
||||||
polkadot-runtime-common = { path = "../../../polkadot/runtime/common", default-features = false }
|
polkadot-runtime-common = { path = "../../../polkadot/runtime/common", default-features = false }
|
||||||
@@ -37,6 +38,7 @@ std = [
|
|||||||
"cumulus-primitives-core/std",
|
"cumulus-primitives-core/std",
|
||||||
"frame-support/std",
|
"frame-support/std",
|
||||||
"log/std",
|
"log/std",
|
||||||
|
"pallet-asset-conversion/std",
|
||||||
"pallet-xcm-benchmarks/std",
|
"pallet-xcm-benchmarks/std",
|
||||||
"polkadot-runtime-common/std",
|
"polkadot-runtime-common/std",
|
||||||
"polkadot-runtime-parachains/std",
|
"polkadot-runtime-parachains/std",
|
||||||
@@ -51,6 +53,7 @@ std = [
|
|||||||
runtime-benchmarks = [
|
runtime-benchmarks = [
|
||||||
"cumulus-primitives-core/runtime-benchmarks",
|
"cumulus-primitives-core/runtime-benchmarks",
|
||||||
"frame-support/runtime-benchmarks",
|
"frame-support/runtime-benchmarks",
|
||||||
|
"pallet-asset-conversion/runtime-benchmarks",
|
||||||
"pallet-xcm-benchmarks/runtime-benchmarks",
|
"pallet-xcm-benchmarks/runtime-benchmarks",
|
||||||
"polkadot-runtime-common/runtime-benchmarks",
|
"polkadot-runtime-common/runtime-benchmarks",
|
||||||
"polkadot-runtime-parachains/runtime-benchmarks",
|
"polkadot-runtime-parachains/runtime-benchmarks",
|
||||||
|
|||||||
@@ -22,19 +22,24 @@
|
|||||||
use codec::Encode;
|
use codec::Encode;
|
||||||
use cumulus_primitives_core::{MessageSendError, UpwardMessageSender};
|
use cumulus_primitives_core::{MessageSendError, UpwardMessageSender};
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
traits::{
|
defensive,
|
||||||
tokens::{fungibles, fungibles::Inspect},
|
traits::{tokens::fungibles, Get, OnUnbalanced as OnUnbalancedT},
|
||||||
Get,
|
weights::{Weight, WeightToFee as WeightToFeeT},
|
||||||
},
|
|
||||||
weights::Weight,
|
|
||||||
};
|
};
|
||||||
|
use pallet_asset_conversion::SwapCredit as SwapCreditT;
|
||||||
use polkadot_runtime_common::xcm_sender::PriceForMessageDelivery;
|
use polkadot_runtime_common::xcm_sender::PriceForMessageDelivery;
|
||||||
use sp_runtime::{traits::Saturating, SaturatedConversion};
|
use sp_runtime::{
|
||||||
|
traits::{Saturating, Zero},
|
||||||
|
SaturatedConversion,
|
||||||
|
};
|
||||||
use sp_std::{marker::PhantomData, prelude::*};
|
use sp_std::{marker::PhantomData, prelude::*};
|
||||||
use xcm::{latest::prelude::*, WrapVersion};
|
use xcm::{latest::prelude::*, WrapVersion};
|
||||||
use xcm_builder::TakeRevenue;
|
use xcm_builder::TakeRevenue;
|
||||||
use xcm_executor::traits::{MatchesFungibles, TransactAsset, WeightTrader};
|
use xcm_executor::traits::{MatchesFungibles, TransactAsset, WeightTrader};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
/// Xcm router which recognises the `Parent` destination and handles it by sending the message into
|
/// Xcm router which recognises the `Parent` destination and handles it by sending the message into
|
||||||
/// the given UMP `UpwardMessageSender` implementation. Thus this essentially adapts an
|
/// the given UMP `UpwardMessageSender` implementation. Thus this essentially adapts an
|
||||||
/// `UpwardMessageSender` trait impl into a `SendXcm` trait impl.
|
/// `UpwardMessageSender` trait impl into a `SendXcm` trait impl.
|
||||||
@@ -286,23 +291,238 @@ impl<
|
|||||||
/// in such assetId for that amount of weight
|
/// in such assetId for that amount of weight
|
||||||
pub trait ChargeWeightInFungibles<AccountId, Assets: fungibles::Inspect<AccountId>> {
|
pub trait ChargeWeightInFungibles<AccountId, Assets: fungibles::Inspect<AccountId>> {
|
||||||
fn charge_weight_in_fungibles(
|
fn charge_weight_in_fungibles(
|
||||||
asset_id: <Assets as Inspect<AccountId>>::AssetId,
|
asset_id: <Assets as fungibles::Inspect<AccountId>>::AssetId,
|
||||||
weight: Weight,
|
weight: Weight,
|
||||||
) -> Result<<Assets as Inspect<AccountId>>::Balance, XcmError>;
|
) -> Result<<Assets as fungibles::Inspect<AccountId>>::Balance, XcmError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provides an implementation of [`WeightTrader`] to charge for weight using the first asset
|
||||||
|
/// specified in the `payment` argument.
|
||||||
|
///
|
||||||
|
/// The asset used to pay for the weight must differ from the `Target` asset and be exchangeable for
|
||||||
|
/// the same `Target` asset through `SwapCredit`.
|
||||||
|
///
|
||||||
|
/// ### Parameters:
|
||||||
|
/// - `Target`: the asset into which the user's payment will be exchanged using `SwapCredit`.
|
||||||
|
/// - `SwapCredit`: mechanism used for the exchange of the user's payment asset into the `Target`.
|
||||||
|
/// - `WeightToFee`: weight to the `Target` asset fee calculator.
|
||||||
|
/// - `Fungibles`: registry of fungible assets.
|
||||||
|
/// - `FungiblesAssetMatcher`: utility for mapping [`MultiAsset`] to `Fungibles::AssetId` and
|
||||||
|
/// `Fungibles::Balance`.
|
||||||
|
/// - `OnUnbalanced`: handler for the fee payment.
|
||||||
|
/// - `AccountId`: the account identifier type.
|
||||||
|
pub struct SwapFirstAssetTrader<
|
||||||
|
Target: Get<Fungibles::AssetId>,
|
||||||
|
SwapCredit: SwapCreditT<
|
||||||
|
AccountId,
|
||||||
|
Balance = Fungibles::Balance,
|
||||||
|
AssetKind = Fungibles::AssetId,
|
||||||
|
Credit = fungibles::Credit<AccountId, Fungibles>,
|
||||||
|
>,
|
||||||
|
WeightToFee: WeightToFeeT<Balance = Fungibles::Balance>,
|
||||||
|
Fungibles: fungibles::Balanced<AccountId>,
|
||||||
|
FungiblesAssetMatcher: MatchesFungibles<Fungibles::AssetId, Fungibles::Balance>,
|
||||||
|
OnUnbalanced: OnUnbalancedT<fungibles::Credit<AccountId, Fungibles>>,
|
||||||
|
AccountId,
|
||||||
|
> where
|
||||||
|
Fungibles::Balance: Into<u128>,
|
||||||
|
{
|
||||||
|
/// Accumulated fee paid for XCM execution.
|
||||||
|
total_fee: fungibles::Credit<AccountId, Fungibles>,
|
||||||
|
/// Last asset utilized by a client to settle a fee.
|
||||||
|
last_fee_asset: Option<AssetId>,
|
||||||
|
_phantom_data: PhantomData<(
|
||||||
|
Target,
|
||||||
|
SwapCredit,
|
||||||
|
WeightToFee,
|
||||||
|
Fungibles,
|
||||||
|
FungiblesAssetMatcher,
|
||||||
|
OnUnbalanced,
|
||||||
|
AccountId,
|
||||||
|
)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
Target: Get<Fungibles::AssetId>,
|
||||||
|
SwapCredit: SwapCreditT<
|
||||||
|
AccountId,
|
||||||
|
Balance = Fungibles::Balance,
|
||||||
|
AssetKind = Fungibles::AssetId,
|
||||||
|
Credit = fungibles::Credit<AccountId, Fungibles>,
|
||||||
|
>,
|
||||||
|
WeightToFee: WeightToFeeT<Balance = Fungibles::Balance>,
|
||||||
|
Fungibles: fungibles::Balanced<AccountId>,
|
||||||
|
FungiblesAssetMatcher: MatchesFungibles<Fungibles::AssetId, Fungibles::Balance>,
|
||||||
|
OnUnbalanced: OnUnbalancedT<fungibles::Credit<AccountId, Fungibles>>,
|
||||||
|
AccountId,
|
||||||
|
> WeightTrader
|
||||||
|
for SwapFirstAssetTrader<
|
||||||
|
Target,
|
||||||
|
SwapCredit,
|
||||||
|
WeightToFee,
|
||||||
|
Fungibles,
|
||||||
|
FungiblesAssetMatcher,
|
||||||
|
OnUnbalanced,
|
||||||
|
AccountId,
|
||||||
|
> where
|
||||||
|
Fungibles::Balance: Into<u128>,
|
||||||
|
{
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
total_fee: fungibles::Credit::<AccountId, Fungibles>::zero(Target::get()),
|
||||||
|
last_fee_asset: None,
|
||||||
|
_phantom_data: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn buy_weight(
|
||||||
|
&mut self,
|
||||||
|
weight: Weight,
|
||||||
|
mut payment: xcm_executor::Assets,
|
||||||
|
_context: &XcmContext,
|
||||||
|
) -> Result<xcm_executor::Assets, XcmError> {
|
||||||
|
log::trace!(
|
||||||
|
target: "xcm::weight",
|
||||||
|
"SwapFirstAssetTrader::buy_weight weight: {:?}, payment: {:?}",
|
||||||
|
weight,
|
||||||
|
payment,
|
||||||
|
);
|
||||||
|
let first_asset: MultiAsset =
|
||||||
|
payment.fungible.pop_first().ok_or(XcmError::AssetNotFound)?.into();
|
||||||
|
let (fungibles_asset, balance) = FungiblesAssetMatcher::matches_fungibles(&first_asset)
|
||||||
|
.map_err(|_| XcmError::AssetNotFound)?;
|
||||||
|
|
||||||
|
let swap_asset = fungibles_asset.clone().into();
|
||||||
|
if Target::get().eq(&swap_asset) {
|
||||||
|
// current trader is not applicable.
|
||||||
|
return Err(XcmError::FeesNotMet)
|
||||||
|
}
|
||||||
|
|
||||||
|
let credit_in = Fungibles::issue(fungibles_asset, balance);
|
||||||
|
let fee = WeightToFee::weight_to_fee(&weight);
|
||||||
|
|
||||||
|
// swap the user's asset for the `Target` asset.
|
||||||
|
let (credit_out, credit_change) = SwapCredit::swap_tokens_for_exact_tokens(
|
||||||
|
vec![swap_asset, Target::get()],
|
||||||
|
credit_in,
|
||||||
|
fee,
|
||||||
|
)
|
||||||
|
.map_err(|(credit_in, _)| {
|
||||||
|
drop(credit_in);
|
||||||
|
XcmError::FeesNotMet
|
||||||
|
})?;
|
||||||
|
|
||||||
|
match self.total_fee.subsume(credit_out) {
|
||||||
|
Err(credit_out) => {
|
||||||
|
// error may occur if `total_fee.asset` differs from `credit_out.asset`, which does
|
||||||
|
// not apply in this context.
|
||||||
|
defensive!(
|
||||||
|
"`total_fee.asset` must be equal to `credit_out.asset`",
|
||||||
|
(self.total_fee.asset(), credit_out.asset())
|
||||||
|
);
|
||||||
|
return Err(XcmError::FeesNotMet)
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
self.last_fee_asset = Some(first_asset.id);
|
||||||
|
|
||||||
|
payment.fungible.insert(first_asset.id, credit_change.peek().into());
|
||||||
|
drop(credit_change);
|
||||||
|
Ok(payment)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn refund_weight(&mut self, weight: Weight, _context: &XcmContext) -> Option<MultiAsset> {
|
||||||
|
log::trace!(
|
||||||
|
target: "xcm::weight",
|
||||||
|
"SwapFirstAssetTrader::refund_weight weight: {:?}, self.total_fee: {:?}",
|
||||||
|
weight,
|
||||||
|
self.total_fee,
|
||||||
|
);
|
||||||
|
if self.total_fee.peek().is_zero() {
|
||||||
|
// noting yet paid to refund.
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
let mut refund_asset = if let Some(asset) = &self.last_fee_asset {
|
||||||
|
// create an initial zero refund in the asset used in the last `buy_weight`.
|
||||||
|
(*asset, Fungible(0)).into()
|
||||||
|
} else {
|
||||||
|
return None
|
||||||
|
};
|
||||||
|
let refund_amount = WeightToFee::weight_to_fee(&weight);
|
||||||
|
if refund_amount >= self.total_fee.peek() {
|
||||||
|
// not enough was paid to refund the `weight`.
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
|
||||||
|
let refund_swap_asset = FungiblesAssetMatcher::matches_fungibles(&refund_asset)
|
||||||
|
.map(|(a, _)| a.into())
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
let refund = self.total_fee.extract(refund_amount);
|
||||||
|
let refund = match SwapCredit::swap_exact_tokens_for_tokens(
|
||||||
|
vec![Target::get(), refund_swap_asset],
|
||||||
|
refund,
|
||||||
|
None,
|
||||||
|
) {
|
||||||
|
Ok(refund_in_target) => refund_in_target,
|
||||||
|
Err((refund, _)) => {
|
||||||
|
// return an attempted refund back to the `total_fee`.
|
||||||
|
let _ = self.total_fee.subsume(refund).map_err(|refund| {
|
||||||
|
// error may occur if `total_fee.asset` differs from `refund.asset`, which does
|
||||||
|
// not apply in this context.
|
||||||
|
defensive!(
|
||||||
|
"`total_fee.asset` must be equal to `refund.asset`",
|
||||||
|
(self.total_fee.asset(), refund.asset())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return None
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
refund_asset.fun = refund.peek().into().into();
|
||||||
|
drop(refund);
|
||||||
|
Some(refund_asset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
Target: Get<Fungibles::AssetId>,
|
||||||
|
SwapCredit: SwapCreditT<
|
||||||
|
AccountId,
|
||||||
|
Balance = Fungibles::Balance,
|
||||||
|
AssetKind = Fungibles::AssetId,
|
||||||
|
Credit = fungibles::Credit<AccountId, Fungibles>,
|
||||||
|
>,
|
||||||
|
WeightToFee: WeightToFeeT<Balance = Fungibles::Balance>,
|
||||||
|
Fungibles: fungibles::Balanced<AccountId>,
|
||||||
|
FungiblesAssetMatcher: MatchesFungibles<Fungibles::AssetId, Fungibles::Balance>,
|
||||||
|
OnUnbalanced: OnUnbalancedT<fungibles::Credit<AccountId, Fungibles>>,
|
||||||
|
AccountId,
|
||||||
|
> Drop
|
||||||
|
for SwapFirstAssetTrader<
|
||||||
|
Target,
|
||||||
|
SwapCredit,
|
||||||
|
WeightToFee,
|
||||||
|
Fungibles,
|
||||||
|
FungiblesAssetMatcher,
|
||||||
|
OnUnbalanced,
|
||||||
|
AccountId,
|
||||||
|
> where
|
||||||
|
Fungibles::Balance: Into<u128>,
|
||||||
|
{
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if self.total_fee.peek().is_zero() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let total_fee = self.total_fee.extract(self.total_fee.peek());
|
||||||
|
OnUnbalanced::on_unbalanced(total_fee);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod test_xcm_router {
|
||||||
use super::*;
|
use super::*;
|
||||||
use cumulus_primitives_core::UpwardMessage;
|
use cumulus_primitives_core::UpwardMessage;
|
||||||
use frame_support::{
|
|
||||||
assert_ok,
|
|
||||||
traits::tokens::{
|
|
||||||
DepositConsequence, Fortitude, Preservation, Provenance, WithdrawConsequence,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use sp_runtime::DispatchError;
|
|
||||||
use xcm_executor::{traits::Error, Assets};
|
|
||||||
|
|
||||||
/// Validates [`validate`] for required Some(destination) and Some(message)
|
/// Validates [`validate`] for required Some(destination) and Some(message)
|
||||||
struct OkFixedXcmHashWithAssertingRequiredInputsSender;
|
struct OkFixedXcmHashWithAssertingRequiredInputsSender;
|
||||||
@@ -398,6 +618,18 @@ mod tests {
|
|||||||
)>(dest.into(), message)
|
)>(dest.into(), message)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_trader {
|
||||||
|
use super::*;
|
||||||
|
use frame_support::{
|
||||||
|
assert_ok,
|
||||||
|
traits::tokens::{
|
||||||
|
DepositConsequence, Fortitude, Preservation, Provenance, WithdrawConsequence,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use sp_runtime::DispatchError;
|
||||||
|
use xcm_executor::{traits::Error, Assets};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn take_first_asset_trader_buy_weight_called_twice_throws_error() {
|
fn take_first_asset_trader_buy_weight_called_twice_throws_error() {
|
||||||
@@ -491,9 +723,9 @@ mod tests {
|
|||||||
struct FeeChargerAssetsHandleRefund;
|
struct FeeChargerAssetsHandleRefund;
|
||||||
impl ChargeWeightInFungibles<TestAccountId, TestAssets> for FeeChargerAssetsHandleRefund {
|
impl ChargeWeightInFungibles<TestAccountId, TestAssets> for FeeChargerAssetsHandleRefund {
|
||||||
fn charge_weight_in_fungibles(
|
fn charge_weight_in_fungibles(
|
||||||
_: <TestAssets as Inspect<TestAccountId>>::AssetId,
|
_: <TestAssets as fungibles::Inspect<TestAccountId>>::AssetId,
|
||||||
_: Weight,
|
_: Weight,
|
||||||
) -> Result<<TestAssets as Inspect<TestAccountId>>::Balance, XcmError> {
|
) -> Result<<TestAssets as fungibles::Inspect<TestAccountId>>::Balance, XcmError> {
|
||||||
Ok(AMOUNT)
|
Ok(AMOUNT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Cumulus.
|
||||||
|
|
||||||
|
// Substrate is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Substrate is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Cumulus. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
mod swap_first;
|
||||||
@@ -0,0 +1,551 @@
|
|||||||
|
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Cumulus.
|
||||||
|
|
||||||
|
// Substrate is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Substrate is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Cumulus. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use crate::*;
|
||||||
|
use frame_support::{parameter_types, traits::fungibles::Inspect};
|
||||||
|
use mock::{setup_pool, AccountId, AssetId, Balance, Fungibles};
|
||||||
|
use xcm::latest::AssetId as XcmAssetId;
|
||||||
|
use xcm_executor::Assets as HoldingAsset;
|
||||||
|
|
||||||
|
fn create_holding_asset(asset_id: AssetId, amount: Balance) -> HoldingAsset {
|
||||||
|
create_asset(asset_id, amount).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_asset(asset_id: AssetId, amount: Balance) -> MultiAsset {
|
||||||
|
MultiAsset { id: create_asset_id(asset_id), fun: Fungible(amount) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_asset_id(asset_id: AssetId) -> XcmAssetId {
|
||||||
|
Concrete(MultiLocation::new(0, X1(GeneralIndex(asset_id.into()))))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn xcm_context() -> XcmContext {
|
||||||
|
XcmContext { origin: None, message_id: [0u8; 32], topic: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn weight_worth_of(fee: Balance) -> Weight {
|
||||||
|
Weight::from_parts(fee.try_into().unwrap(), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
const TARGET_ASSET: AssetId = 1;
|
||||||
|
const CLIENT_ASSET: AssetId = 2;
|
||||||
|
const CLIENT_ASSET_2: AssetId = 3;
|
||||||
|
|
||||||
|
parameter_types! {
|
||||||
|
pub const TargetAsset: AssetId = TARGET_ASSET;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Trader = SwapFirstAssetTrader<
|
||||||
|
TargetAsset,
|
||||||
|
mock::Swap,
|
||||||
|
mock::WeightToFee,
|
||||||
|
mock::Fungibles,
|
||||||
|
mock::FungiblesMatcher,
|
||||||
|
(),
|
||||||
|
AccountId,
|
||||||
|
>;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn holding_asset_swap_for_target() {
|
||||||
|
let client_asset_total = 15;
|
||||||
|
let fee = 5;
|
||||||
|
|
||||||
|
setup_pool(CLIENT_ASSET, 1000, TARGET_ASSET, 1000);
|
||||||
|
|
||||||
|
let holding_asset = create_holding_asset(CLIENT_ASSET, client_asset_total);
|
||||||
|
let holding_change = create_holding_asset(CLIENT_ASSET, client_asset_total - fee);
|
||||||
|
|
||||||
|
let target_total = Fungibles::total_issuance(TARGET_ASSET);
|
||||||
|
let client_total = Fungibles::total_issuance(CLIENT_ASSET);
|
||||||
|
|
||||||
|
let mut trader = Trader::new();
|
||||||
|
assert_eq!(
|
||||||
|
trader.buy_weight(weight_worth_of(fee), holding_asset, &xcm_context()).unwrap(),
|
||||||
|
holding_change
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(trader.total_fee.peek(), fee);
|
||||||
|
assert_eq!(trader.last_fee_asset, Some(create_asset_id(CLIENT_ASSET)));
|
||||||
|
|
||||||
|
assert_eq!(Fungibles::total_issuance(TARGET_ASSET), target_total);
|
||||||
|
assert_eq!(Fungibles::total_issuance(CLIENT_ASSET), client_total + fee);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn holding_asset_swap_for_target_twice() {
|
||||||
|
let client_asset_total = 20;
|
||||||
|
let fee1 = 5;
|
||||||
|
let fee2 = 6;
|
||||||
|
|
||||||
|
setup_pool(CLIENT_ASSET, 1000, TARGET_ASSET, 1000);
|
||||||
|
|
||||||
|
let holding_asset = create_holding_asset(CLIENT_ASSET, client_asset_total);
|
||||||
|
let holding_change1 = create_holding_asset(CLIENT_ASSET, client_asset_total - fee1);
|
||||||
|
let holding_change2 = create_holding_asset(CLIENT_ASSET, client_asset_total - fee1 - fee2);
|
||||||
|
|
||||||
|
let target_total = Fungibles::total_issuance(TARGET_ASSET);
|
||||||
|
let client_total = Fungibles::total_issuance(CLIENT_ASSET);
|
||||||
|
|
||||||
|
let mut trader = Trader::new();
|
||||||
|
assert_eq!(
|
||||||
|
trader.buy_weight(weight_worth_of(fee1), holding_asset, &xcm_context()).unwrap(),
|
||||||
|
holding_change1
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
trader
|
||||||
|
.buy_weight(weight_worth_of(fee2), holding_change1, &xcm_context())
|
||||||
|
.unwrap(),
|
||||||
|
holding_change2
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(trader.total_fee.peek(), fee1 + fee2);
|
||||||
|
assert_eq!(trader.last_fee_asset, Some(create_asset_id(CLIENT_ASSET)));
|
||||||
|
|
||||||
|
assert_eq!(Fungibles::total_issuance(TARGET_ASSET), target_total);
|
||||||
|
assert_eq!(Fungibles::total_issuance(CLIENT_ASSET), client_total + fee1 + fee2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn buy_and_refund_twice_for_target() {
|
||||||
|
let client_asset_total = 15;
|
||||||
|
let fee = 5;
|
||||||
|
let refund1 = 4;
|
||||||
|
let refund2 = 2;
|
||||||
|
|
||||||
|
setup_pool(CLIENT_ASSET, 1000, TARGET_ASSET, 1000);
|
||||||
|
// create pool for refund swap.
|
||||||
|
setup_pool(TARGET_ASSET, 1000, CLIENT_ASSET, 1000);
|
||||||
|
|
||||||
|
let holding_asset = create_holding_asset(CLIENT_ASSET, client_asset_total);
|
||||||
|
let holding_change = create_holding_asset(CLIENT_ASSET, client_asset_total - fee);
|
||||||
|
let refund_asset = create_asset(CLIENT_ASSET, refund1);
|
||||||
|
|
||||||
|
let target_total = Fungibles::total_issuance(TARGET_ASSET);
|
||||||
|
let client_total = Fungibles::total_issuance(CLIENT_ASSET);
|
||||||
|
|
||||||
|
let mut trader = Trader::new();
|
||||||
|
assert_eq!(
|
||||||
|
trader.buy_weight(weight_worth_of(fee), holding_asset, &xcm_context()).unwrap(),
|
||||||
|
holding_change
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(trader.total_fee.peek(), fee);
|
||||||
|
assert_eq!(trader.last_fee_asset, Some(create_asset_id(CLIENT_ASSET)));
|
||||||
|
|
||||||
|
assert_eq!(trader.refund_weight(weight_worth_of(refund1), &xcm_context()), Some(refund_asset));
|
||||||
|
|
||||||
|
assert_eq!(trader.total_fee.peek(), fee - refund1);
|
||||||
|
assert_eq!(trader.last_fee_asset, Some(create_asset_id(CLIENT_ASSET)));
|
||||||
|
|
||||||
|
assert_eq!(trader.refund_weight(weight_worth_of(refund2), &xcm_context()), None);
|
||||||
|
|
||||||
|
assert_eq!(trader.total_fee.peek(), fee - refund1);
|
||||||
|
assert_eq!(trader.last_fee_asset, Some(create_asset_id(CLIENT_ASSET)));
|
||||||
|
|
||||||
|
assert_eq!(Fungibles::total_issuance(TARGET_ASSET), target_total);
|
||||||
|
assert_eq!(Fungibles::total_issuance(CLIENT_ASSET), client_total + fee - refund1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn buy_with_various_assets_and_refund_for_target() {
|
||||||
|
let client_asset_total = 10;
|
||||||
|
let client_asset_2_total = 15;
|
||||||
|
let fee1 = 5;
|
||||||
|
let fee2 = 6;
|
||||||
|
let refund1 = 6;
|
||||||
|
let refund2 = 4;
|
||||||
|
|
||||||
|
setup_pool(CLIENT_ASSET, 1000, TARGET_ASSET, 1000);
|
||||||
|
setup_pool(CLIENT_ASSET_2, 1000, TARGET_ASSET, 1000);
|
||||||
|
// create pool for refund swap.
|
||||||
|
setup_pool(TARGET_ASSET, 1000, CLIENT_ASSET_2, 1000);
|
||||||
|
|
||||||
|
let holding_asset = create_holding_asset(CLIENT_ASSET, client_asset_total);
|
||||||
|
let holding_asset_2 = create_holding_asset(CLIENT_ASSET_2, client_asset_2_total);
|
||||||
|
let holding_change = create_holding_asset(CLIENT_ASSET, client_asset_total - fee1);
|
||||||
|
let holding_change_2 = create_holding_asset(CLIENT_ASSET_2, client_asset_2_total - fee2);
|
||||||
|
// both refunds in the latest buy asset (`CLIENT_ASSET_2`).
|
||||||
|
let refund_asset = create_asset(CLIENT_ASSET_2, refund1);
|
||||||
|
let refund_asset_2 = create_asset(CLIENT_ASSET_2, refund2);
|
||||||
|
|
||||||
|
let target_total = Fungibles::total_issuance(TARGET_ASSET);
|
||||||
|
let client_total = Fungibles::total_issuance(CLIENT_ASSET);
|
||||||
|
let client_total_2 = Fungibles::total_issuance(CLIENT_ASSET_2);
|
||||||
|
|
||||||
|
let mut trader = Trader::new();
|
||||||
|
// first purchase with `CLIENT_ASSET`.
|
||||||
|
assert_eq!(
|
||||||
|
trader.buy_weight(weight_worth_of(fee1), holding_asset, &xcm_context()).unwrap(),
|
||||||
|
holding_change
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(trader.total_fee.peek(), fee1);
|
||||||
|
assert_eq!(trader.last_fee_asset, Some(create_asset_id(CLIENT_ASSET)));
|
||||||
|
|
||||||
|
// second purchase with `CLIENT_ASSET_2`.
|
||||||
|
assert_eq!(
|
||||||
|
trader
|
||||||
|
.buy_weight(weight_worth_of(fee2), holding_asset_2, &xcm_context())
|
||||||
|
.unwrap(),
|
||||||
|
holding_change_2
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(trader.total_fee.peek(), fee1 + fee2);
|
||||||
|
assert_eq!(trader.last_fee_asset, Some(create_asset_id(CLIENT_ASSET_2)));
|
||||||
|
|
||||||
|
// first refund in the last asset used with `buy_weight`.
|
||||||
|
assert_eq!(trader.refund_weight(weight_worth_of(refund1), &xcm_context()), Some(refund_asset));
|
||||||
|
|
||||||
|
assert_eq!(trader.total_fee.peek(), fee1 + fee2 - refund1);
|
||||||
|
assert_eq!(trader.last_fee_asset, Some(create_asset_id(CLIENT_ASSET_2)));
|
||||||
|
|
||||||
|
// second refund in the last asset used with `buy_weight`.
|
||||||
|
assert_eq!(
|
||||||
|
trader.refund_weight(weight_worth_of(refund2), &xcm_context()),
|
||||||
|
Some(refund_asset_2)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(trader.total_fee.peek(), fee1 + fee2 - refund1 - refund2);
|
||||||
|
assert_eq!(trader.last_fee_asset, Some(create_asset_id(CLIENT_ASSET_2)));
|
||||||
|
|
||||||
|
assert_eq!(Fungibles::total_issuance(TARGET_ASSET), target_total);
|
||||||
|
assert_eq!(Fungibles::total_issuance(CLIENT_ASSET), client_total + fee1);
|
||||||
|
assert_eq!(
|
||||||
|
Fungibles::total_issuance(CLIENT_ASSET_2),
|
||||||
|
client_total_2 + fee2 - refund1 - refund2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn not_enough_to_refund() {
|
||||||
|
let client_asset_total = 15;
|
||||||
|
let fee = 5;
|
||||||
|
let refund = 6;
|
||||||
|
|
||||||
|
setup_pool(CLIENT_ASSET, 1000, TARGET_ASSET, 1000);
|
||||||
|
|
||||||
|
let holding_asset = create_holding_asset(CLIENT_ASSET, client_asset_total);
|
||||||
|
let holding_change = create_holding_asset(CLIENT_ASSET, client_asset_total - fee);
|
||||||
|
|
||||||
|
let target_total = Fungibles::total_issuance(TARGET_ASSET);
|
||||||
|
let client_total = Fungibles::total_issuance(CLIENT_ASSET);
|
||||||
|
|
||||||
|
let mut trader = Trader::new();
|
||||||
|
assert_eq!(
|
||||||
|
trader.buy_weight(weight_worth_of(fee), holding_asset, &xcm_context()).unwrap(),
|
||||||
|
holding_change
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(trader.total_fee.peek(), fee);
|
||||||
|
assert_eq!(trader.last_fee_asset, Some(create_asset_id(CLIENT_ASSET)));
|
||||||
|
|
||||||
|
assert_eq!(trader.refund_weight(weight_worth_of(refund), &xcm_context()), None);
|
||||||
|
|
||||||
|
assert_eq!(Fungibles::total_issuance(TARGET_ASSET), target_total);
|
||||||
|
assert_eq!(Fungibles::total_issuance(CLIENT_ASSET), client_total + fee);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn not_exchangeable_to_refund() {
|
||||||
|
let client_asset_total = 15;
|
||||||
|
let fee = 5;
|
||||||
|
let refund = 1;
|
||||||
|
|
||||||
|
setup_pool(CLIENT_ASSET, 1000, TARGET_ASSET, 1000);
|
||||||
|
|
||||||
|
let holding_asset = create_holding_asset(CLIENT_ASSET, client_asset_total);
|
||||||
|
let holding_change = create_holding_asset(CLIENT_ASSET, client_asset_total - fee);
|
||||||
|
|
||||||
|
let target_total = Fungibles::total_issuance(TARGET_ASSET);
|
||||||
|
let client_total = Fungibles::total_issuance(CLIENT_ASSET);
|
||||||
|
|
||||||
|
let mut trader = Trader::new();
|
||||||
|
assert_eq!(
|
||||||
|
trader.buy_weight(weight_worth_of(fee), holding_asset, &xcm_context()).unwrap(),
|
||||||
|
holding_change
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(trader.total_fee.peek(), fee);
|
||||||
|
assert_eq!(trader.last_fee_asset, Some(create_asset_id(CLIENT_ASSET)));
|
||||||
|
|
||||||
|
assert_eq!(trader.refund_weight(weight_worth_of(refund), &xcm_context()), None);
|
||||||
|
|
||||||
|
assert_eq!(Fungibles::total_issuance(TARGET_ASSET), target_total);
|
||||||
|
assert_eq!(Fungibles::total_issuance(CLIENT_ASSET), client_total + fee);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nothing_to_refund() {
|
||||||
|
let fee = 5;
|
||||||
|
|
||||||
|
let mut trader = Trader::new();
|
||||||
|
assert_eq!(trader.refund_weight(weight_worth_of(fee), &xcm_context()), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn holding_asset_not_exchangeable_for_target() {
|
||||||
|
let holding_asset = create_holding_asset(CLIENT_ASSET, 10);
|
||||||
|
|
||||||
|
let target_total = Fungibles::total_issuance(TARGET_ASSET);
|
||||||
|
let client_total = Fungibles::total_issuance(CLIENT_ASSET);
|
||||||
|
|
||||||
|
let mut trader = Trader::new();
|
||||||
|
assert_eq!(
|
||||||
|
trader
|
||||||
|
.buy_weight(Weight::from_all(10), holding_asset, &xcm_context())
|
||||||
|
.unwrap_err(),
|
||||||
|
XcmError::FeesNotMet
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(Fungibles::total_issuance(TARGET_ASSET), target_total);
|
||||||
|
assert_eq!(Fungibles::total_issuance(CLIENT_ASSET), client_total);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_holding_asset() {
|
||||||
|
let mut trader = Trader::new();
|
||||||
|
assert_eq!(
|
||||||
|
trader
|
||||||
|
.buy_weight(Weight::from_all(10), HoldingAsset::new(), &xcm_context())
|
||||||
|
.unwrap_err(),
|
||||||
|
XcmError::AssetNotFound
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fails_to_match_holding_asset() {
|
||||||
|
let mut trader = Trader::new();
|
||||||
|
let holding_asset =
|
||||||
|
MultiAsset { id: Concrete(MultiLocation::new(1, X1(Parachain(1)))), fun: Fungible(10) };
|
||||||
|
assert_eq!(
|
||||||
|
trader
|
||||||
|
.buy_weight(Weight::from_all(10), holding_asset.into(), &xcm_context())
|
||||||
|
.unwrap_err(),
|
||||||
|
XcmError::AssetNotFound
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn holding_asset_equal_to_target_asset() {
|
||||||
|
let mut trader = Trader::new();
|
||||||
|
let holding_asset = create_holding_asset(TargetAsset::get(), 10);
|
||||||
|
assert_eq!(
|
||||||
|
trader
|
||||||
|
.buy_weight(Weight::from_all(10), holding_asset, &xcm_context())
|
||||||
|
.unwrap_err(),
|
||||||
|
XcmError::FeesNotMet
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod mock {
|
||||||
|
use crate::*;
|
||||||
|
use core::cell::RefCell;
|
||||||
|
use frame_support::{
|
||||||
|
ensure,
|
||||||
|
traits::{
|
||||||
|
fungibles::{Balanced, DecreaseIssuance, Dust, IncreaseIssuance, Inspect, Unbalanced},
|
||||||
|
tokens::{
|
||||||
|
DepositConsequence, Fortitude, Fortitude::Polite, Precision::Exact, Preservation,
|
||||||
|
Preservation::Preserve, Provenance, WithdrawConsequence,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use sp_runtime::{traits::One, DispatchError};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use xcm::latest::Junction;
|
||||||
|
|
||||||
|
pub type AccountId = u64;
|
||||||
|
pub type AssetId = u32;
|
||||||
|
pub type Balance = u128;
|
||||||
|
pub type Credit = fungibles::Credit<AccountId, Fungibles>;
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
pub static TOTAL_ISSUANCE: RefCell<HashMap<AssetId, Balance>> = RefCell::new(HashMap::new());
|
||||||
|
pub static ACCOUNT: RefCell<HashMap<(AssetId, AccountId), Balance>> = RefCell::new(HashMap::new());
|
||||||
|
pub static SWAP: RefCell<HashMap<(AssetId, AssetId), AccountId>> = RefCell::new(HashMap::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Swap {}
|
||||||
|
impl SwapCreditT<AccountId> for Swap {
|
||||||
|
type Balance = Balance;
|
||||||
|
type AssetKind = AssetId;
|
||||||
|
type Credit = Credit;
|
||||||
|
fn max_path_len() -> u32 {
|
||||||
|
2
|
||||||
|
}
|
||||||
|
fn swap_exact_tokens_for_tokens(
|
||||||
|
path: Vec<Self::AssetKind>,
|
||||||
|
credit_in: Self::Credit,
|
||||||
|
amount_out_min: Option<Self::Balance>,
|
||||||
|
) -> Result<Self::Credit, (Self::Credit, DispatchError)> {
|
||||||
|
ensure!(2 == path.len(), (credit_in, DispatchError::Unavailable));
|
||||||
|
ensure!(
|
||||||
|
credit_in.peek() >= amount_out_min.unwrap_or(Self::Balance::zero()),
|
||||||
|
(credit_in, DispatchError::Unavailable)
|
||||||
|
);
|
||||||
|
let swap_res = SWAP.with(|b| b.borrow().get(&(path[0], path[1])).map(|v| *v));
|
||||||
|
let pool_account = match swap_res {
|
||||||
|
Some(a) => a,
|
||||||
|
None => return Err((credit_in, DispatchError::Unavailable)),
|
||||||
|
};
|
||||||
|
let credit_out = match Fungibles::withdraw(
|
||||||
|
path[1],
|
||||||
|
&pool_account,
|
||||||
|
credit_in.peek(),
|
||||||
|
Exact,
|
||||||
|
Preserve,
|
||||||
|
Polite,
|
||||||
|
) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(_) => return Err((credit_in, DispatchError::Unavailable)),
|
||||||
|
};
|
||||||
|
let _ = Fungibles::resolve(&pool_account, credit_in)
|
||||||
|
.map_err(|c| (c, DispatchError::Unavailable))?;
|
||||||
|
Ok(credit_out)
|
||||||
|
}
|
||||||
|
fn swap_tokens_for_exact_tokens(
|
||||||
|
path: Vec<Self::AssetKind>,
|
||||||
|
credit_in: Self::Credit,
|
||||||
|
amount_out: Self::Balance,
|
||||||
|
) -> Result<(Self::Credit, Self::Credit), (Self::Credit, DispatchError)> {
|
||||||
|
ensure!(2 == path.len(), (credit_in, DispatchError::Unavailable));
|
||||||
|
ensure!(credit_in.peek() >= amount_out, (credit_in, DispatchError::Unavailable));
|
||||||
|
let swap_res = SWAP.with(|b| b.borrow().get(&(path[0], path[1])).map(|v| *v));
|
||||||
|
let pool_account = match swap_res {
|
||||||
|
Some(a) => a,
|
||||||
|
None => return Err((credit_in, DispatchError::Unavailable)),
|
||||||
|
};
|
||||||
|
let credit_out = match Fungibles::withdraw(
|
||||||
|
path[1],
|
||||||
|
&pool_account,
|
||||||
|
amount_out,
|
||||||
|
Exact,
|
||||||
|
Preserve,
|
||||||
|
Polite,
|
||||||
|
) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(_) => return Err((credit_in, DispatchError::Unavailable)),
|
||||||
|
};
|
||||||
|
let (credit_in, change) = credit_in.split(amount_out);
|
||||||
|
let _ = Fungibles::resolve(&pool_account, credit_in)
|
||||||
|
.map_err(|c| (c, DispatchError::Unavailable))?;
|
||||||
|
Ok((credit_out, change))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pool_account(asset1: AssetId, asset2: AssetId) -> AccountId {
|
||||||
|
(1000 + asset1 * 10 + asset2 * 100).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setup_pool(asset1: AssetId, liquidity1: Balance, asset2: AssetId, liquidity2: Balance) {
|
||||||
|
let account = pool_account(asset1, asset2);
|
||||||
|
SWAP.with(|b| b.borrow_mut().insert((asset1, asset2), account));
|
||||||
|
let debt1 = Fungibles::deposit(asset1, &account, liquidity1, Exact);
|
||||||
|
let debt2 = Fungibles::deposit(asset2, &account, liquidity2, Exact);
|
||||||
|
drop(debt1);
|
||||||
|
drop(debt2);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WeightToFee;
|
||||||
|
impl WeightToFeeT for WeightToFee {
|
||||||
|
type Balance = Balance;
|
||||||
|
fn weight_to_fee(weight: &Weight) -> Self::Balance {
|
||||||
|
(weight.ref_time() + weight.proof_size()).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Fungibles {}
|
||||||
|
impl Inspect<AccountId> for Fungibles {
|
||||||
|
type AssetId = AssetId;
|
||||||
|
type Balance = Balance;
|
||||||
|
fn total_issuance(asset: Self::AssetId) -> Self::Balance {
|
||||||
|
TOTAL_ISSUANCE.with(|b| b.borrow().get(&asset).map_or(Self::Balance::zero(), |b| *b))
|
||||||
|
}
|
||||||
|
fn minimum_balance(_: Self::AssetId) -> Self::Balance {
|
||||||
|
Self::Balance::one()
|
||||||
|
}
|
||||||
|
fn total_balance(asset: Self::AssetId, who: &AccountId) -> Self::Balance {
|
||||||
|
ACCOUNT.with(|b| b.borrow().get(&(asset, *who)).map_or(Self::Balance::zero(), |b| *b))
|
||||||
|
}
|
||||||
|
fn balance(asset: Self::AssetId, who: &AccountId) -> Self::Balance {
|
||||||
|
ACCOUNT.with(|b| b.borrow().get(&(asset, *who)).map_or(Self::Balance::zero(), |b| *b))
|
||||||
|
}
|
||||||
|
fn reducible_balance(
|
||||||
|
asset: Self::AssetId,
|
||||||
|
who: &AccountId,
|
||||||
|
_: Preservation,
|
||||||
|
_: Fortitude,
|
||||||
|
) -> Self::Balance {
|
||||||
|
ACCOUNT.with(|b| b.borrow().get(&(asset, *who)).map_or(Self::Balance::zero(), |b| *b))
|
||||||
|
}
|
||||||
|
fn can_deposit(
|
||||||
|
_: Self::AssetId,
|
||||||
|
_: &AccountId,
|
||||||
|
_: Self::Balance,
|
||||||
|
_: Provenance,
|
||||||
|
) -> DepositConsequence {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
fn can_withdraw(
|
||||||
|
_: Self::AssetId,
|
||||||
|
_: &AccountId,
|
||||||
|
_: Self::Balance,
|
||||||
|
) -> WithdrawConsequence<Self::Balance> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
fn asset_exists(_: Self::AssetId) -> bool {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Unbalanced<AccountId> for Fungibles {
|
||||||
|
fn set_total_issuance(asset: Self::AssetId, amount: Self::Balance) {
|
||||||
|
TOTAL_ISSUANCE.with(|b| b.borrow_mut().insert(asset, amount));
|
||||||
|
}
|
||||||
|
fn handle_dust(_: Dust<AccountId, Self>) {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
fn write_balance(
|
||||||
|
asset: Self::AssetId,
|
||||||
|
who: &AccountId,
|
||||||
|
amount: Self::Balance,
|
||||||
|
) -> Result<Option<Self::Balance>, DispatchError> {
|
||||||
|
let _ = ACCOUNT.with(|b| b.borrow_mut().insert((asset, *who), amount));
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Balanced<AccountId> for Fungibles {
|
||||||
|
type OnDropCredit = DecreaseIssuance<AccountId, Self>;
|
||||||
|
type OnDropDebt = IncreaseIssuance<AccountId, Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FungiblesMatcher;
|
||||||
|
impl MatchesFungibles<AssetId, Balance> for FungiblesMatcher {
|
||||||
|
fn matches_fungibles(
|
||||||
|
a: &MultiAsset,
|
||||||
|
) -> core::result::Result<(AssetId, Balance), xcm_executor::traits::Error> {
|
||||||
|
match a {
|
||||||
|
MultiAsset {
|
||||||
|
fun: Fungible(amount),
|
||||||
|
id:
|
||||||
|
Concrete(MultiLocation { parents: 0, interior: X1(Junction::GeneralIndex(id)) }),
|
||||||
|
} => Ok(((*id).try_into().unwrap(), *amount)),
|
||||||
|
_ => Err(xcm_executor::traits::Error::AssetNotHandled),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
|
||||||
|
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
|
||||||
|
|
||||||
|
title: "XCM WeightTrader: Swap Fee Asset for Native Asset"
|
||||||
|
|
||||||
|
doc:
|
||||||
|
- audience: Runtime Dev
|
||||||
|
description: |
|
||||||
|
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.
|
||||||
|
|
||||||
|
crates:
|
||||||
|
- name: cumulus-primitives-utility
|
||||||
Reference in New Issue
Block a user