Refactoring Checkpoint: (WIP)
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
mod mock;
|
||||
|
||||
use pezframe_support::{
|
||||
assert_err, assert_ok,
|
||||
pezsp_runtime::{
|
||||
testing::H256,
|
||||
traits::{IdentifyAccount, Verify},
|
||||
AccountId32, MultiSignature,
|
||||
},
|
||||
};
|
||||
use mock::*;
|
||||
use pezsp_api::ProvideRuntimeApi;
|
||||
use xcm::prelude::*;
|
||||
use xcm_runtime_pezapis::conversions::{
|
||||
Error as LocationToAccountApiError, LocationToAccountApi, LocationToAccountHelper,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn convert_location_to_account_works() {
|
||||
pezsp_io::TestExternalities::default().execute_with(|| {
|
||||
let client = TestClient {};
|
||||
let runtime_api = client.runtime_api();
|
||||
|
||||
// Test unknown conversion for `Here` location
|
||||
assert_err!(
|
||||
runtime_api
|
||||
.convert_location(H256::zero(), VersionedLocation::from(Location::here()))
|
||||
.unwrap(),
|
||||
LocationToAccountApiError::Unsupported
|
||||
);
|
||||
|
||||
// Test known conversion for sibling teyrchain location
|
||||
assert_ok!(
|
||||
runtime_api
|
||||
.convert_location(H256::zero(), VersionedLocation::from((Parent, Teyrchain(1000))))
|
||||
.unwrap(),
|
||||
1000_u64
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn location_to_account_helper_with_multi_signature_works() {
|
||||
type Signature = MultiSignature;
|
||||
type AccountIdForConversions = <<Signature as Verify>::Signer as IdentifyAccount>::AccountId;
|
||||
// We alias only `Location::parent()`
|
||||
pub type LocationToAccountIdForConversions =
|
||||
(xcm_builder::ParentIsPreset<AccountIdForConversions>,);
|
||||
|
||||
// Test unknown conversion for `Here` location
|
||||
assert_err!(
|
||||
LocationToAccountHelper::<
|
||||
AccountIdForConversions,
|
||||
LocationToAccountIdForConversions,
|
||||
>::convert_location(Location::here().into_versioned()),
|
||||
LocationToAccountApiError::Unsupported
|
||||
);
|
||||
|
||||
// Test known conversion for `Parent` location
|
||||
assert_ok!(
|
||||
LocationToAccountHelper::<
|
||||
AccountIdForConversions,
|
||||
LocationToAccountIdForConversions,
|
||||
>::convert_location(Location::parent().into_versioned()),
|
||||
AccountId32::from(hex_literal::hex!("506172656e740000000000000000000000000000000000000000000000000000"))
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,571 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Tests for using both the XCM fee payment API and the dry-run API.
|
||||
|
||||
use pezframe_support::pezsp_runtime::testing::H256;
|
||||
use pezframe_system::RawOrigin;
|
||||
use pezsp_api::ProvideRuntimeApi;
|
||||
use xcm::prelude::*;
|
||||
use xcm_runtime_pezapis::{
|
||||
dry_run::{CallDryRunEffects, DryRunApi},
|
||||
fees::XcmPaymentApi,
|
||||
};
|
||||
|
||||
mod mock;
|
||||
use mock::{
|
||||
new_test_ext_with_balances, new_test_ext_with_balances_and_assets, DeliveryFees,
|
||||
ExistentialDeposit, HereLocation, OriginCaller, RuntimeCall, RuntimeEvent, TestClient,
|
||||
ASSET_HUB_ASSETS_PALLET_INSTANCE, ASSET_HUB_PARA_ID, USDT_ID,
|
||||
};
|
||||
use xcm_pez_simulator::fake_message_hash;
|
||||
|
||||
// Scenario: User `1` in the local chain (id 2000) wants to transfer assets to account `[0u8; 32]`
|
||||
// on "AssetHub". He wants to make sure he has enough for fees, so before he calls the
|
||||
// `transfer_asset` extrinsic to do the transfer, he decides to use the `XcmDryRunApi` and
|
||||
// `XcmPaymentApi` runtime APIs to estimate fees. This uses a teleport because we're dealing with
|
||||
// the native token of the chain, which is registered on "AssetHub". The fees are sent as a reserve
|
||||
// asset transfer, since they're paid in the relay token.
|
||||
//
|
||||
// Teleport Teyrchain(2000) Token
|
||||
// Reserve Asset Transfer Relay Token for fees
|
||||
// Teyrchain(2000) -------------------------------------------> Teyrchain(1000)
|
||||
#[test]
|
||||
fn fee_estimation_for_teleport() {
|
||||
pezsp_tracing::init_for_tests();
|
||||
let who = 1; // AccountId = u64.
|
||||
let balances = vec![(who, 100 + DeliveryFees::get() + ExistentialDeposit::get())];
|
||||
let assets = vec![(1, who, 50)];
|
||||
new_test_ext_with_balances_and_assets(balances, assets).execute_with(|| {
|
||||
let client = TestClient;
|
||||
let runtime_api = client.runtime_api();
|
||||
let call = RuntimeCall::XcmPallet(pezpallet_xcm::Call::transfer_assets {
|
||||
dest: Box::new(VersionedLocation::from((Parent, Teyrchain(1000)))),
|
||||
beneficiary: Box::new(VersionedLocation::from(AccountId32 {
|
||||
id: [0u8; 32],
|
||||
network: None,
|
||||
})),
|
||||
assets: Box::new(VersionedAssets::from(vec![
|
||||
(Here, 100u128).into(),
|
||||
(Parent, 20u128).into(),
|
||||
])),
|
||||
fee_asset_id: Box::new(Parent.into()), // Fees are paid with the RelayToken
|
||||
weight_limit: Unlimited,
|
||||
});
|
||||
let origin = OriginCaller::system(RawOrigin::Signed(who));
|
||||
let dry_run_effects = runtime_api
|
||||
.dry_run_call(H256::zero(), origin, call, XCM_VERSION)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
dry_run_effects.local_xcm,
|
||||
Some(VersionedXcm::from(
|
||||
Xcm::builder_unsafe()
|
||||
.withdraw_asset((Parent, 20u128))
|
||||
.burn_asset((Parent, 20u128))
|
||||
.withdraw_asset((Here, 100u128))
|
||||
.burn_asset((Here, 100u128))
|
||||
.build()
|
||||
)),
|
||||
);
|
||||
let send_destination = Location::new(1, [Teyrchain(1000)]);
|
||||
let send_message = Xcm::<()>::builder_unsafe()
|
||||
.withdraw_asset((Parent, 20u128))
|
||||
.buy_execution((Parent, 20u128), Unlimited)
|
||||
.receive_teleported_asset(((Parent, Teyrchain(2000)), 100u128))
|
||||
.clear_origin()
|
||||
.deposit_asset(AllCounted(2), [0u8; 32])
|
||||
.build();
|
||||
assert_eq!(
|
||||
dry_run_effects.forwarded_xcms,
|
||||
vec![(
|
||||
VersionedLocation::from(send_destination.clone()),
|
||||
vec![VersionedXcm::from(send_message.clone())],
|
||||
),],
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
dry_run_effects.emitted_events,
|
||||
vec![
|
||||
RuntimeEvent::System(pezframe_system::Event::NewAccount {
|
||||
account: 8660274132218572653 // TODO: Why is this not `1`?
|
||||
}),
|
||||
RuntimeEvent::Balances(pezpallet_balances::Event::Endowed {
|
||||
account: 8660274132218572653,
|
||||
free_balance: 100
|
||||
}),
|
||||
RuntimeEvent::Balances(pezpallet_balances::Event::Minted {
|
||||
who: 8660274132218572653,
|
||||
amount: 100
|
||||
}),
|
||||
RuntimeEvent::AssetsPallet(pezpallet_assets::Event::Burned {
|
||||
asset_id: 1,
|
||||
owner: 1,
|
||||
balance: 20
|
||||
}),
|
||||
RuntimeEvent::Balances(pezpallet_balances::Event::Burned { who: 1, amount: 100 }),
|
||||
RuntimeEvent::XcmPallet(pezpallet_xcm::Event::Attempted {
|
||||
outcome: Outcome::Complete { used: Weight::from_parts(400, 40) },
|
||||
}),
|
||||
RuntimeEvent::Balances(pezpallet_balances::Event::Burned { who: 1, amount: 20 }),
|
||||
RuntimeEvent::XcmPallet(pezpallet_xcm::Event::FeesPaid {
|
||||
paying: AccountIndex64 { index: 1, network: None }.into(),
|
||||
fees: (Here, 20u128).into(),
|
||||
}),
|
||||
RuntimeEvent::XcmPallet(pezpallet_xcm::Event::Sent {
|
||||
origin: AccountIndex64 { index: 1, network: None }.into(),
|
||||
destination: (Parent, Teyrchain(1000)).into(),
|
||||
message: send_message.clone(),
|
||||
message_id: fake_message_hash(&send_message),
|
||||
}),
|
||||
]
|
||||
);
|
||||
|
||||
// Weighing the local program is not relevant for extrinsics that already
|
||||
// take this weight into account.
|
||||
// In this case, we really only care about delivery fees.
|
||||
let local_xcm = dry_run_effects.local_xcm.unwrap();
|
||||
|
||||
// We get a double result since the actual call returns a result and the runtime api returns
|
||||
// results.
|
||||
let weight =
|
||||
runtime_api.query_xcm_weight(H256::zero(), local_xcm.clone()).unwrap().unwrap();
|
||||
let asset_id = VersionedAssetId::from(AssetId(HereLocation::get()));
|
||||
assert_eq!(weight, Weight::from_parts(400, 40));
|
||||
let execution_fees = runtime_api
|
||||
.query_weight_to_asset_fee(H256::zero(), weight, asset_id.clone())
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(execution_fees, 440);
|
||||
|
||||
let mut forwarded_xcms_iter = dry_run_effects.forwarded_xcms.into_iter();
|
||||
|
||||
let (destination, remote_messages) = forwarded_xcms_iter.next().unwrap();
|
||||
let remote_message = &remote_messages[0];
|
||||
|
||||
let delivery_fees = runtime_api
|
||||
.query_delivery_fees(
|
||||
H256::zero(),
|
||||
destination.clone(),
|
||||
remote_message.clone(),
|
||||
asset_id,
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(delivery_fees, VersionedAssets::from((Here, 20u128)));
|
||||
|
||||
// This would have to be the runtime API of the destination,
|
||||
// which we have the location for.
|
||||
// If I had a mock runtime configured for "AssetHub" then I would use the
|
||||
// runtime APIs from that.
|
||||
let remote_execution_weight = runtime_api
|
||||
.query_xcm_weight(H256::zero(), remote_message.clone())
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let remote_execution_fees = runtime_api
|
||||
.query_weight_to_asset_fee(
|
||||
H256::zero(),
|
||||
remote_execution_weight,
|
||||
VersionedAssetId::from(AssetId(HereLocation::get())),
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(remote_execution_fees, 550);
|
||||
|
||||
// Now we know that locally we need to use `execution_fees` and
|
||||
// `delivery_fees`.
|
||||
// On the message we forward to the destination, we need to
|
||||
// put `remote_execution_fees` in `BuyExecution`.
|
||||
// For the `transfer_assets` extrinsic, it just means passing the correct amount
|
||||
// of fees in the parameters.
|
||||
});
|
||||
}
|
||||
|
||||
// Same scenario as in `fee_estimation_for_teleport`, but the user in teyrchain 2000 wants
|
||||
// to send relay tokens over to teyrchain 1000.
|
||||
//
|
||||
// Reserve Asset Transfer Relay Token
|
||||
// Reserve Asset Transfer Relay Token for fees
|
||||
// Teyrchain(2000) -------------------------------------------> Teyrchain(1000)
|
||||
fn dry_run_reserve_asset_transfer_common(
|
||||
input_xcm_version: XcmVersion,
|
||||
expected_result_xcms_version: XcmVersion,
|
||||
dry_run_call: impl FnOnce(&TestClient, OriginCaller, RuntimeCall) -> CallDryRunEffects<RuntimeEvent>,
|
||||
) {
|
||||
pezsp_tracing::init_for_tests();
|
||||
let who = 1; // AccountId = u64.
|
||||
// Native token used for fees.
|
||||
let balances = vec![(who, DeliveryFees::get() + ExistentialDeposit::get())];
|
||||
// Relay token is the one we want to transfer.
|
||||
let assets = vec![(1, who, 100)]; // id, account_id, balance.
|
||||
new_test_ext_with_balances_and_assets(balances, assets).execute_with(|| {
|
||||
let call = RuntimeCall::XcmPallet(pezpallet_xcm::Call::transfer_assets {
|
||||
dest: Box::new(
|
||||
VersionedLocation::from((Parent, Teyrchain(1000)))
|
||||
.into_version(input_xcm_version)
|
||||
.unwrap(),
|
||||
),
|
||||
beneficiary: Box::new(
|
||||
VersionedLocation::from(AccountId32 { id: [0u8; 32], network: None })
|
||||
.into_version(input_xcm_version)
|
||||
.unwrap(),
|
||||
),
|
||||
assets: Box::new(
|
||||
VersionedAssets::from((Parent, 100u128))
|
||||
.into_version(input_xcm_version)
|
||||
.unwrap(),
|
||||
),
|
||||
fee_asset_id: Box::new(Parent.into()),
|
||||
weight_limit: Unlimited,
|
||||
});
|
||||
let origin = OriginCaller::system(RawOrigin::Signed(who));
|
||||
let dry_run_effects = dry_run_call(&TestClient, origin, call);
|
||||
|
||||
assert_eq!(
|
||||
dry_run_effects.local_xcm,
|
||||
Some(
|
||||
VersionedXcm::from(
|
||||
Xcm::builder_unsafe()
|
||||
.withdraw_asset((Parent, 100u128))
|
||||
.burn_asset((Parent, 100u128))
|
||||
.build()
|
||||
)
|
||||
.into_version(expected_result_xcms_version)
|
||||
.unwrap()
|
||||
),
|
||||
);
|
||||
|
||||
// In this case, the transfer type is `DestinationReserve`, so the remote xcm just withdraws
|
||||
// the assets.
|
||||
let send_destination = Location::new(1, Teyrchain(1000));
|
||||
let send_message = Xcm::<()>::builder_unsafe()
|
||||
.withdraw_asset((Parent, 100u128))
|
||||
.clear_origin()
|
||||
.buy_execution((Parent, 100u128), Unlimited)
|
||||
.deposit_asset(AllCounted(1), [0u8; 32])
|
||||
.build();
|
||||
assert_eq!(
|
||||
dry_run_effects.forwarded_xcms,
|
||||
vec![(
|
||||
VersionedLocation::from(send_destination.clone())
|
||||
.into_version(expected_result_xcms_version)
|
||||
.unwrap(),
|
||||
vec![VersionedXcm::from(send_message.clone())
|
||||
.into_version(expected_result_xcms_version)
|
||||
.unwrap()],
|
||||
),],
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
dry_run_effects.emitted_events,
|
||||
vec![
|
||||
RuntimeEvent::AssetsPallet(pezpallet_assets::Event::Burned {
|
||||
asset_id: 1,
|
||||
owner: 1,
|
||||
balance: 100
|
||||
}),
|
||||
RuntimeEvent::XcmPallet(pezpallet_xcm::Event::Attempted {
|
||||
outcome: Outcome::Complete { used: Weight::from_parts(200, 20) }
|
||||
}),
|
||||
RuntimeEvent::Balances(pezpallet_balances::Event::Burned { who: 1, amount: 20 }),
|
||||
RuntimeEvent::XcmPallet(pezpallet_xcm::Event::FeesPaid {
|
||||
paying: AccountIndex64 { index: 1, network: None }.into(),
|
||||
fees: (Here, 20u128).into()
|
||||
}),
|
||||
RuntimeEvent::XcmPallet(pezpallet_xcm::Event::Sent {
|
||||
origin: AccountIndex64 { index: 1, network: None }.into(),
|
||||
destination: send_destination.clone(),
|
||||
message: send_message.clone(),
|
||||
message_id: fake_message_hash(&send_message),
|
||||
}),
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dry_run_reserve_asset_transfer_xcm_versions() {
|
||||
let tested_versions = MIN_XCM_VERSION..=XCM_VERSION;
|
||||
|
||||
for version in tested_versions {
|
||||
let input_version = version;
|
||||
let expected_result_xcms_version = version;
|
||||
dry_run_reserve_asset_transfer_common(
|
||||
input_version,
|
||||
expected_result_xcms_version,
|
||||
|client, origin, call| {
|
||||
client
|
||||
.runtime_api()
|
||||
.dry_run_call(H256::zero(), origin, call, expected_result_xcms_version)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dry_run_before_api_v2_reserve_asset_transfer() {
|
||||
let tested_versions = MIN_XCM_VERSION..=XCM_VERSION;
|
||||
|
||||
for version in tested_versions {
|
||||
let input_version = version;
|
||||
let expected_result_xcms_version = XCM_VERSION;
|
||||
dry_run_reserve_asset_transfer_common(
|
||||
input_version,
|
||||
expected_result_xcms_version,
|
||||
|client, origin, call| {
|
||||
#[allow(deprecated)]
|
||||
client
|
||||
.runtime_api()
|
||||
.dry_run_call_before_version_2(H256::zero(), origin, call)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn dry_run_xcm_common(xcm_version: XcmVersion) {
|
||||
pezsp_tracing::init_for_tests();
|
||||
let who = 1; // AccountId = u64.
|
||||
let transfer_amount = 100u128;
|
||||
// We need to build the XCM to weigh it and then build the real XCM that can pay for fees.
|
||||
let inner_xcm = Xcm::<()>::builder_unsafe()
|
||||
.buy_execution((Here, 1u128), Unlimited) // We'd need to query the destination chain for fees.
|
||||
.deposit_asset(AllCounted(1), [0u8; 32])
|
||||
.build();
|
||||
let xcm_to_weigh = Xcm::<RuntimeCall>::builder_unsafe()
|
||||
.withdraw_asset((Here, transfer_amount))
|
||||
.clear_origin()
|
||||
.buy_execution((Here, transfer_amount), Unlimited)
|
||||
.deposit_reserve_asset(AllCounted(1), (Parent, Teyrchain(2100)), inner_xcm.clone())
|
||||
.build();
|
||||
let client = TestClient;
|
||||
let runtime_api = client.runtime_api();
|
||||
let xcm_weight = runtime_api
|
||||
.query_xcm_weight(
|
||||
H256::zero(),
|
||||
VersionedXcm::from(xcm_to_weigh.clone().into())
|
||||
.into_version(xcm_version)
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let execution_fees = runtime_api
|
||||
.query_weight_to_asset_fee(
|
||||
H256::zero(),
|
||||
xcm_weight,
|
||||
VersionedAssetId::from(AssetId(Here.into())).into_version(xcm_version).unwrap(),
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let xcm = Xcm::<RuntimeCall>::builder_unsafe()
|
||||
.withdraw_asset((Here, transfer_amount + execution_fees))
|
||||
.clear_origin()
|
||||
.buy_execution((Here, execution_fees), Unlimited)
|
||||
.deposit_reserve_asset(AllCounted(1), (Parent, Teyrchain(2100)), inner_xcm.clone())
|
||||
.build();
|
||||
let expected_msg_id = fake_message_hash(&xcm);
|
||||
let balances = vec![(
|
||||
who,
|
||||
transfer_amount + execution_fees + DeliveryFees::get() + ExistentialDeposit::get(),
|
||||
)];
|
||||
new_test_ext_with_balances(balances).execute_with(|| {
|
||||
let dry_run_effects = runtime_api
|
||||
.dry_run_xcm(
|
||||
H256::zero(),
|
||||
VersionedLocation::from([AccountIndex64 { index: 1, network: None }])
|
||||
.into_version(xcm_version)
|
||||
.unwrap(),
|
||||
VersionedXcm::from(xcm).into_version(xcm_version).unwrap(),
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let expected_xcms = Xcm::<()>::builder_unsafe()
|
||||
.reserve_asset_deposited((
|
||||
(Parent, Teyrchain(2000)),
|
||||
transfer_amount + execution_fees - DeliveryFees::get(),
|
||||
))
|
||||
.clear_origin()
|
||||
.buy_execution((Here, 1u128), Unlimited)
|
||||
.deposit_asset(AllCounted(1), [0u8; 32])
|
||||
.set_topic(expected_msg_id)
|
||||
.build();
|
||||
assert_eq!(
|
||||
dry_run_effects.forwarded_xcms,
|
||||
vec![(
|
||||
VersionedLocation::from((Parent, Teyrchain(2100)))
|
||||
.into_version(xcm_version)
|
||||
.unwrap(),
|
||||
vec![VersionedXcm::from(expected_xcms).into_version(xcm_version).unwrap()],
|
||||
),]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
dry_run_effects.emitted_events,
|
||||
vec![
|
||||
RuntimeEvent::Balances(pezpallet_balances::Event::Burned { who: 1, amount: 540 }),
|
||||
RuntimeEvent::System(pezframe_system::Event::NewAccount { account: 2100 }),
|
||||
RuntimeEvent::Balances(pezpallet_balances::Event::Endowed {
|
||||
account: 2100,
|
||||
free_balance: 520
|
||||
}),
|
||||
RuntimeEvent::Balances(pezpallet_balances::Event::Minted { who: 2100, amount: 520 }),
|
||||
RuntimeEvent::XcmPallet(pezpallet_xcm::Event::Sent {
|
||||
origin: (who,).into(),
|
||||
destination: (Parent, Teyrchain(2100)).into(),
|
||||
message: Xcm::default(),
|
||||
message_id: expected_msg_id,
|
||||
})
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dry_run_xcm_versions() {
|
||||
let tested_versions = [XCM_VERSION, 5, 4, 3];
|
||||
|
||||
for version in tested_versions {
|
||||
dry_run_xcm_common(version);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn calling_payment_api_with_a_lower_version_works() {
|
||||
let transfer_amount = 100u128;
|
||||
let xcm_to_weigh = Xcm::<RuntimeCall>::builder_unsafe()
|
||||
.withdraw_asset((Here, transfer_amount))
|
||||
.buy_execution((Here, transfer_amount), Unlimited)
|
||||
.deposit_asset(AllCounted(1), [1u8; 32])
|
||||
.build();
|
||||
let versioned_xcm_to_weigh = VersionedXcm::from(xcm_to_weigh.clone().into());
|
||||
let lower_version_xcm_to_weigh = versioned_xcm_to_weigh.into_version(XCM_VERSION - 1).unwrap();
|
||||
let client = TestClient;
|
||||
let runtime_api = client.runtime_api();
|
||||
let xcm_weight =
|
||||
runtime_api.query_xcm_weight(H256::zero(), lower_version_xcm_to_weigh).unwrap();
|
||||
assert!(xcm_weight.is_ok());
|
||||
let native_token = VersionedAssetId::from(AssetId(Here.into()));
|
||||
let lower_version_native_token = native_token.into_version(XCM_VERSION - 1).unwrap();
|
||||
let execution_fees = runtime_api
|
||||
.query_weight_to_asset_fee(H256::zero(), xcm_weight.unwrap(), lower_version_native_token)
|
||||
.unwrap();
|
||||
assert!(execution_fees.is_ok());
|
||||
}
|
||||
|
||||
// Test fee estimation for USDT reserve transfer with delivery fees in USDT.
|
||||
// In this scenario, we're sending USDT from teyrchain 2000 to teyrchain 1000 (AssetHub).
|
||||
// Since USDT is native to AssetHub, this will be a destination reserve transfer.
|
||||
// We request the delivery fees to be quoted in USDT using the asset_id parameter in
|
||||
// `query_delivery_fees`.
|
||||
//
|
||||
// Reserve Asset Transfer USDT
|
||||
// Delivery fees in USDT
|
||||
// Teyrchain(2000) -------------------------------------------> Teyrchain(1000)
|
||||
#[test]
|
||||
fn fee_estimation_for_usdt_reserve_transfer_in_usdt() {
|
||||
pezsp_tracing::init_for_tests();
|
||||
let who = 1; // AccountId = u64.
|
||||
let balances = vec![(who, DeliveryFees::get() + ExistentialDeposit::get())]; // Just enough for fees
|
||||
let assets = vec![
|
||||
(1984, who, 1000), // USDT (asset ID 1984) - amount to transfer plus fees
|
||||
];
|
||||
new_test_ext_with_balances_and_assets(balances, assets).execute_with(|| {
|
||||
let client = TestClient;
|
||||
let runtime_api = client.runtime_api();
|
||||
|
||||
// USDT location from our teyrchain (2000) perspective
|
||||
let usdt_location = Location::new(
|
||||
1,
|
||||
[
|
||||
Teyrchain(ASSET_HUB_PARA_ID),
|
||||
PalletInstance(ASSET_HUB_ASSETS_PALLET_INSTANCE),
|
||||
GeneralIndex(USDT_ID.into()),
|
||||
],
|
||||
);
|
||||
// USDT location from Asset Hub's perspective
|
||||
let usdt_location_ah_pov = Location::new(
|
||||
0,
|
||||
[PalletInstance(ASSET_HUB_ASSETS_PALLET_INSTANCE), GeneralIndex(USDT_ID.into())],
|
||||
);
|
||||
|
||||
let call = RuntimeCall::XcmPallet(pezpallet_xcm::Call::transfer_assets {
|
||||
dest: Box::new(VersionedLocation::from((Parent, Teyrchain(1000)))),
|
||||
beneficiary: Box::new(VersionedLocation::from(AccountId32 {
|
||||
id: [0u8; 32],
|
||||
network: None,
|
||||
})),
|
||||
assets: Box::new(VersionedAssets::from(vec![
|
||||
(usdt_location.clone(), 100u128).into(), // Send 100 USDT
|
||||
])),
|
||||
fee_asset_id: Box::new(VersionedAssetId::from(AssetId(usdt_location.clone()))),
|
||||
weight_limit: Unlimited,
|
||||
});
|
||||
let origin = OriginCaller::system(RawOrigin::Signed(who));
|
||||
let dry_run_effects = runtime_api
|
||||
.dry_run_call(H256::zero(), origin, call, XCM_VERSION)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
// For destination reserve transfer, we burn the assets locally
|
||||
assert_eq!(
|
||||
dry_run_effects.local_xcm,
|
||||
Some(VersionedXcm::from(
|
||||
Xcm::builder_unsafe()
|
||||
.withdraw_asset((usdt_location.clone(), 100u128))
|
||||
.burn_asset((usdt_location.clone(), 100u128))
|
||||
.build()
|
||||
)),
|
||||
);
|
||||
|
||||
let send_destination = Location::new(1, [Teyrchain(1000)]);
|
||||
// For destination reserve, the remote message withdraws the assets from the sovereign
|
||||
// account
|
||||
let send_message = Xcm::<()>::builder_unsafe()
|
||||
.withdraw_asset((usdt_location_ah_pov.clone(), 100u128))
|
||||
.clear_origin()
|
||||
.buy_execution((usdt_location_ah_pov.clone(), 100u128), Unlimited)
|
||||
.deposit_asset(AllCounted(1), [0u8; 32])
|
||||
.build();
|
||||
assert_eq!(
|
||||
dry_run_effects.forwarded_xcms,
|
||||
vec![(
|
||||
VersionedLocation::from(send_destination.clone()),
|
||||
vec![VersionedXcm::from(send_message.clone())],
|
||||
),],
|
||||
);
|
||||
|
||||
let mut forwarded_xcms_iter = dry_run_effects.forwarded_xcms.into_iter();
|
||||
let (destination, remote_messages) = forwarded_xcms_iter.next().unwrap();
|
||||
let remote_message = &remote_messages[0];
|
||||
|
||||
// Query delivery fees in USDT asset using the `asset_id` parameter.
|
||||
let usdt_asset_id = VersionedAssetId::from(AssetId(usdt_location.clone()));
|
||||
|
||||
let delivery_fees_usdt = runtime_api
|
||||
.query_delivery_fees(H256::zero(), destination, remote_message.clone(), usdt_asset_id)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
// With our MockAssetExchanger, we expect fees in USDT at a 1:2 conversion rate.
|
||||
// Native delivery fee is `DeliveryFees::get()`, so USDT fee should be twice that.
|
||||
let expected_usdt_fees = VersionedAssets::from((usdt_location, DeliveryFees::get() * 2));
|
||||
assert_eq!(delivery_fees_usdt, expected_usdt_fees);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,615 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Mock runtime for tests.
|
||||
//! Implements both runtime APIs for fee estimation and getting the messages for transfers.
|
||||
|
||||
use core::{cell::RefCell, marker::PhantomData};
|
||||
use pezframe_support::{
|
||||
construct_runtime, derive_impl, parameter_types, pezsp_runtime,
|
||||
pezsp_runtime::{
|
||||
traits::{Get, IdentityLookup, MaybeEquivalence, TryConvert},
|
||||
BuildStorage, SaturatedConversion,
|
||||
},
|
||||
traits::{
|
||||
AsEnsureOriginWithArg, ConstU128, ConstU32, Contains, ContainsPair, Disabled, Everything,
|
||||
Nothing, OriginTrait,
|
||||
},
|
||||
weights::WeightToFee as WeightToFeeT,
|
||||
};
|
||||
use pezframe_system::{EnsureRoot, RawOrigin as SystemRawOrigin};
|
||||
use pezpallet_xcm::TestWeightInfo;
|
||||
use xcm::{prelude::*, Version as XcmVersion};
|
||||
use xcm_builder::{
|
||||
AllowTopLevelPaidExecutionFrom, ConvertedConcreteId, EnsureXcmOrigin, FixedRateOfFungible,
|
||||
FixedWeightBounds, FungibleAdapter, FungiblesAdapter, InspectMessageQueues, IsConcrete,
|
||||
MintLocation, NoChecking, TakeWeightCredit,
|
||||
};
|
||||
use xcm_executor::{
|
||||
traits::{ConvertLocation, JustTry},
|
||||
XcmExecutor,
|
||||
};
|
||||
|
||||
use xcm_runtime_pezapis::{
|
||||
conversions::{Error as LocationToAccountApiError, LocationToAccountApi},
|
||||
dry_run::{CallDryRunEffects, DryRunApi, Error as XcmDryRunApiError, XcmDryRunEffects},
|
||||
fees::{Error as XcmPaymentApiError, XcmPaymentApi},
|
||||
trusted_query::{Error as TrustedQueryApiError, TrustedQueryApi},
|
||||
};
|
||||
use xcm_pez_simulator::helpers::derive_topic_id;
|
||||
|
||||
construct_runtime! {
|
||||
pub enum TestRuntime {
|
||||
System: pezframe_system,
|
||||
Balances: pezpallet_balances,
|
||||
AssetsPallet: pezpallet_assets,
|
||||
XcmPallet: pezpallet_xcm,
|
||||
}
|
||||
}
|
||||
|
||||
pub type TxExtension =
|
||||
(pezframe_system::CheckWeight<TestRuntime>, pezframe_system::WeightReclaim<TestRuntime>);
|
||||
|
||||
// we only use the hash type from this, so using the mock should be fine.
|
||||
pub(crate) type Extrinsic = pezsp_runtime::generic::UncheckedExtrinsic<
|
||||
u64,
|
||||
RuntimeCall,
|
||||
pezsp_runtime::testing::UintAuthorityId,
|
||||
TxExtension,
|
||||
>;
|
||||
type Block = pezsp_runtime::testing::Block<Extrinsic>;
|
||||
type Balance = u128;
|
||||
type AssetIdForAssetsPallet = u32;
|
||||
type AccountId = u64;
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for TestRuntime {
|
||||
type Block = Block;
|
||||
type AccountId = AccountId;
|
||||
type AccountData = pezpallet_balances::AccountData<Balance>;
|
||||
type Lookup = IdentityLookup<AccountId>;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_balances::Config for TestRuntime {
|
||||
type AccountStore = System;
|
||||
type Balance = Balance;
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
}
|
||||
|
||||
// Assets instance
|
||||
#[derive_impl(pezpallet_assets::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_assets::Config for TestRuntime {
|
||||
type AssetId = AssetIdForAssetsPallet;
|
||||
type Balance = Balance;
|
||||
type Currency = Balances;
|
||||
type CreateOrigin = AsEnsureOriginWithArg<pezframe_system::EnsureSigned<AccountId>>;
|
||||
type ForceOrigin = pezframe_system::EnsureRoot<AccountId>;
|
||||
type Holder = ();
|
||||
type Freezer = ();
|
||||
type AssetDeposit = ConstU128<1>;
|
||||
type AssetAccountDeposit = ConstU128<10>;
|
||||
type MetadataDepositBase = ConstU128<1>;
|
||||
type MetadataDepositPerByte = ConstU128<1>;
|
||||
type ApprovalDeposit = ConstU128<1>;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type BenchmarkHelper = ();
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
pub static SENT_XCM: RefCell<Vec<(Location, Xcm<()>)>> = const { RefCell::new(Vec::new()) };
|
||||
}
|
||||
|
||||
pub struct TestXcmSender;
|
||||
impl SendXcm for TestXcmSender {
|
||||
type Ticket = (Location, Xcm<()>);
|
||||
fn validate(
|
||||
dest: &mut Option<Location>,
|
||||
msg: &mut Option<Xcm<()>>,
|
||||
) -> SendResult<Self::Ticket> {
|
||||
let ticket = (dest.take().unwrap(), msg.take().unwrap());
|
||||
let fees: Assets = (HereLocation::get(), DeliveryFees::get()).into();
|
||||
Ok((ticket, fees))
|
||||
}
|
||||
fn deliver(ticket: Self::Ticket) -> Result<XcmHash, SendError> {
|
||||
let hash = derive_topic_id(&ticket.1);
|
||||
SENT_XCM.with(|q| q.borrow_mut().push(ticket));
|
||||
Ok(hash)
|
||||
}
|
||||
}
|
||||
impl InspectMessageQueues for TestXcmSender {
|
||||
fn clear_messages() {
|
||||
SENT_XCM.with(|q| q.borrow_mut().clear());
|
||||
}
|
||||
|
||||
fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> {
|
||||
SENT_XCM.with(|q| {
|
||||
(*q.borrow())
|
||||
.clone()
|
||||
.iter()
|
||||
.map(|(location, message)| {
|
||||
(
|
||||
VersionedLocation::from(location.clone()),
|
||||
vec![VersionedXcm::from(message.clone())],
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub type XcmRouter = TestXcmSender;
|
||||
|
||||
parameter_types! {
|
||||
pub const DeliveryFees: u128 = 20; // Arbitrary value.
|
||||
pub const ExistentialDeposit: u128 = 1; // Arbitrary value.
|
||||
pub const BaseXcmWeight: Weight = Weight::from_parts(100, 10); // Arbitrary value.
|
||||
pub const MaxInstructions: u32 = 100;
|
||||
pub const NativeTokenPerSecondPerByte: (AssetId, u128, u128) = (AssetId(HereLocation::get()), 1, 1);
|
||||
pub UniversalLocation: InteriorLocation = [GlobalConsensus(NetworkId::ByGenesis([0; 32])), Teyrchain(2000)].into();
|
||||
pub const HereLocation: Location = Location::here();
|
||||
pub const RelayLocation: Location = Location::parent();
|
||||
pub const MaxAssetsIntoHolding: u32 = 64;
|
||||
pub CheckAccount: AccountId = XcmPallet::check_account();
|
||||
pub LocalCheckAccount: (AccountId, MintLocation) = (CheckAccount::get(), MintLocation::Local);
|
||||
pub const AnyNetwork: Option<NetworkId> = None;
|
||||
}
|
||||
|
||||
/// Simple `WeightToFee` implementation that adds the ref_time by the proof_size.
|
||||
pub struct WeightToFee;
|
||||
impl WeightToFeeT for WeightToFee {
|
||||
type Balance = Balance;
|
||||
fn weight_to_fee(weight: &Weight) -> Self::Balance {
|
||||
Self::Balance::saturated_from(weight.ref_time())
|
||||
.saturating_add(Self::Balance::saturated_from(weight.proof_size()))
|
||||
}
|
||||
}
|
||||
|
||||
type Weigher = FixedWeightBounds<BaseXcmWeight, RuntimeCall, MaxInstructions>;
|
||||
|
||||
/// Matches the pair (NativeToken, AssetHub).
|
||||
/// This is used in the `IsTeleporter` configuration item, meaning we accept our native token
|
||||
/// coming from AssetHub as a teleport.
|
||||
pub struct NativeTokenToAssetHub;
|
||||
impl ContainsPair<Asset, Location> for NativeTokenToAssetHub {
|
||||
fn contains(asset: &Asset, origin: &Location) -> bool {
|
||||
matches!(asset.id.0.unpack(), (0, [])) &&
|
||||
matches!(origin.unpack(), (1, [Teyrchain(ASSET_HUB_PARA_ID)]))
|
||||
}
|
||||
}
|
||||
|
||||
/// Teyrchain id of Asset Hub.
|
||||
pub const ASSET_HUB_PARA_ID: u32 = 1000;
|
||||
/// The instance index of the trust-backed assets pallet in Asset Hub.
|
||||
pub const ASSET_HUB_ASSETS_PALLET_INSTANCE: u8 = 50;
|
||||
/// Id of USDT in Asset Hub.
|
||||
pub const USDT_ID: u32 = 1984;
|
||||
|
||||
/// Matches the pairs (RelayToken, AssetHub) and (UsdtToken, AssetHub).
|
||||
/// This is used in the `IsReserve` configuration item, meaning we accept the relay token
|
||||
/// coming from AssetHub as a reserve asset transfer.
|
||||
pub struct RelayTokenAndUsdtToAssetHub;
|
||||
impl ContainsPair<Asset, Location> for RelayTokenAndUsdtToAssetHub {
|
||||
fn contains(asset: &Asset, origin: &Location) -> bool {
|
||||
let is_asset_hub = matches!(origin.unpack(), (1, [Teyrchain(ASSET_HUB_PARA_ID)]));
|
||||
let is_relay_token = matches!(asset.id.0.unpack(), (1, []));
|
||||
let is_usdt = matches!(asset.id.0.unpack(), (1, [Teyrchain(ASSET_HUB_PARA_ID), PalletInstance(ASSET_HUB_ASSETS_PALLET_INSTANCE), GeneralIndex(asset_id)]) if *asset_id == USDT_ID.into());
|
||||
|
||||
is_asset_hub && (is_relay_token || is_usdt)
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts locations that are only the `AccountIndex64` junction into local u64 accounts.
|
||||
pub struct AccountIndex64Aliases<Network, AccountId>(PhantomData<(Network, AccountId)>);
|
||||
impl<Network: Get<Option<NetworkId>>, AccountId: From<u64>> ConvertLocation<AccountId>
|
||||
for AccountIndex64Aliases<Network, AccountId>
|
||||
{
|
||||
fn convert_location(location: &Location) -> Option<AccountId> {
|
||||
let index = match location.unpack() {
|
||||
(0, [AccountIndex64 { index, network: None }]) => index,
|
||||
(0, [AccountIndex64 { index, network }]) if *network == Network::get() => index,
|
||||
_ => return None,
|
||||
};
|
||||
Some((*index).into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Custom location converter to turn sibling chains into u64 accounts.
|
||||
pub struct SiblingChainToIndex64;
|
||||
impl ConvertLocation<AccountId> for SiblingChainToIndex64 {
|
||||
fn convert_location(location: &Location) -> Option<AccountId> {
|
||||
let index = match location.unpack() {
|
||||
(1, [Teyrchain(id)]) => id,
|
||||
_ => return None,
|
||||
};
|
||||
Some((*index).into())
|
||||
}
|
||||
}
|
||||
|
||||
/// We alias local account locations to actual local accounts.
|
||||
/// We also allow sovereign accounts for other sibling chains.
|
||||
pub type LocationToAccountId = (AccountIndex64Aliases<AnyNetwork, u64>, SiblingChainToIndex64);
|
||||
|
||||
pub type NativeTokenTransactor = FungibleAdapter<
|
||||
// We use pezpallet-balances for handling this fungible asset.
|
||||
Balances,
|
||||
// The fungible asset handled by this transactor is the native token of the chain.
|
||||
IsConcrete<HereLocation>,
|
||||
// How we convert locations to accounts.
|
||||
LocationToAccountId,
|
||||
// We need to specify the AccountId type.
|
||||
AccountId,
|
||||
// We mint the native tokens locally, so we track how many we've sent away via teleports.
|
||||
LocalCheckAccount,
|
||||
>;
|
||||
|
||||
/// Converter from Location to local asset id and viceversa.
|
||||
pub struct LocationToAssetIdForAssetsPallet;
|
||||
impl MaybeEquivalence<Location, AssetIdForAssetsPallet> for LocationToAssetIdForAssetsPallet {
|
||||
fn convert(location: &Location) -> Option<AssetIdForAssetsPallet> {
|
||||
match location.unpack() {
|
||||
(1, []) => Some(1 as AssetIdForAssetsPallet),
|
||||
(
|
||||
1,
|
||||
[Teyrchain(ASSET_HUB_PARA_ID), PalletInstance(ASSET_HUB_ASSETS_PALLET_INSTANCE), GeneralIndex(asset_id)],
|
||||
) if *asset_id == USDT_ID.into() => Some(USDT_ID as AssetIdForAssetsPallet),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_back(id: &AssetIdForAssetsPallet) -> Option<Location> {
|
||||
match id {
|
||||
1 => Some(Location::new(1, [])),
|
||||
asset_id if *asset_id == USDT_ID => Some(Location::new(
|
||||
1,
|
||||
[
|
||||
Teyrchain(ASSET_HUB_PARA_ID),
|
||||
PalletInstance(ASSET_HUB_ASSETS_PALLET_INSTANCE),
|
||||
GeneralIndex(USDT_ID.into()),
|
||||
],
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// AssetTransactor for handling assets other than the native one.
|
||||
pub type AssetsTransactor = FungiblesAdapter<
|
||||
// We use pezpallet-assets for handling the relay token.
|
||||
AssetsPallet,
|
||||
// Matches the relay token.
|
||||
ConvertedConcreteId<AssetIdForAssetsPallet, Balance, LocationToAssetIdForAssetsPallet, JustTry>,
|
||||
// How we convert locations to accounts.
|
||||
LocationToAccountId,
|
||||
// We need to specify the AccountId type.
|
||||
AccountId,
|
||||
// We don't track teleports.
|
||||
NoChecking,
|
||||
(),
|
||||
>;
|
||||
|
||||
pub type AssetTransactors = (NativeTokenTransactor, AssetsTransactor);
|
||||
|
||||
pub struct HereAndInnerLocations;
|
||||
impl Contains<Location> for HereAndInnerLocations {
|
||||
fn contains(location: &Location) -> bool {
|
||||
matches!(location.unpack(), (0, []) | (0, _))
|
||||
}
|
||||
}
|
||||
|
||||
pub type Barrier = (
|
||||
TakeWeightCredit, // We need this for pezpallet-xcm's extrinsics to work.
|
||||
AllowTopLevelPaidExecutionFrom<HereAndInnerLocations>, /* TODO: Technically, we should allow
|
||||
* messages from "AssetHub". */
|
||||
);
|
||||
|
||||
pub type Trader = FixedRateOfFungible<NativeTokenPerSecondPerByte, ()>;
|
||||
|
||||
pub struct XcmConfig;
|
||||
impl xcm_executor::Config for XcmConfig {
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type XcmSender = XcmRouter;
|
||||
type XcmEventEmitter = XcmPallet;
|
||||
type AssetTransactor = AssetTransactors;
|
||||
type OriginConverter = ();
|
||||
type IsReserve = RelayTokenAndUsdtToAssetHub;
|
||||
type IsTeleporter = NativeTokenToAssetHub;
|
||||
type UniversalLocation = UniversalLocation;
|
||||
type Barrier = Barrier;
|
||||
type Weigher = Weigher;
|
||||
type Trader = Trader;
|
||||
type ResponseHandler = ();
|
||||
type AssetTrap = ();
|
||||
type AssetLocker = ();
|
||||
type AssetExchanger = MockAssetExchanger;
|
||||
type AssetClaims = ();
|
||||
type SubscriptionService = ();
|
||||
type PalletInstancesInfo = AllPalletsWithSystem;
|
||||
type MaxAssetsIntoHolding = MaxAssetsIntoHolding;
|
||||
type FeeManager = ();
|
||||
type MessageExporter = ();
|
||||
type UniversalAliases = ();
|
||||
type CallDispatcher = RuntimeCall;
|
||||
type SafeCallFilter = Nothing;
|
||||
type Aliasers = Nothing;
|
||||
type TransactionalProcessor = ();
|
||||
type HrmpNewChannelOpenRequestHandler = ();
|
||||
type HrmpChannelAcceptedHandler = ();
|
||||
type HrmpChannelClosingHandler = ();
|
||||
type XcmRecorder = XcmPallet;
|
||||
}
|
||||
|
||||
/// Mock AssetExchanger that recognizes USDT and provides a 1:2 exchange rate
|
||||
/// (1 native token = 2 USDT tokens)
|
||||
pub struct MockAssetExchanger;
|
||||
impl xcm_executor::traits::AssetExchange for MockAssetExchanger {
|
||||
fn exchange_asset(
|
||||
_origin: Option<&Location>,
|
||||
give: xcm_executor::AssetsInHolding,
|
||||
want: &Assets,
|
||||
_maximal: bool,
|
||||
) -> Result<xcm_executor::AssetsInHolding, xcm_executor::AssetsInHolding> {
|
||||
let usdt_location = Location::new(
|
||||
1,
|
||||
[
|
||||
Teyrchain(ASSET_HUB_PARA_ID),
|
||||
PalletInstance(ASSET_HUB_ASSETS_PALLET_INSTANCE),
|
||||
GeneralIndex(USDT_ID.into()),
|
||||
],
|
||||
);
|
||||
|
||||
// Check if we're trying to exchange native asset for USDT
|
||||
if let Some(give_asset) = give.fungible.get(&AssetId(HereLocation::get())) {
|
||||
if let Some(want_asset) = want.get(0) {
|
||||
if want_asset.id.0 == usdt_location {
|
||||
// Convert native asset to USDT at 1:2 rate
|
||||
let usdt_amount = give_asset.saturating_mul(2);
|
||||
let mut result = xcm_executor::AssetsInHolding::new();
|
||||
result.subsume((AssetId(usdt_location), usdt_amount).into());
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we can't handle the exchange, return the original assets
|
||||
Err(give)
|
||||
}
|
||||
|
||||
fn quote_exchange_price(give: &Assets, want: &Assets, _maximal: bool) -> Option<Assets> {
|
||||
let usdt_location = Location::new(
|
||||
1,
|
||||
[
|
||||
Teyrchain(ASSET_HUB_PARA_ID),
|
||||
PalletInstance(ASSET_HUB_ASSETS_PALLET_INSTANCE),
|
||||
GeneralIndex(USDT_ID.into()),
|
||||
],
|
||||
);
|
||||
|
||||
// Check if we're trying to quote native asset for USDT
|
||||
if let Some(give_asset) = give.get(0) {
|
||||
if let Some(want_asset) = want.get(0) {
|
||||
if give_asset.id.0 == HereLocation::get() && want_asset.id.0 == usdt_location {
|
||||
if let Fungible(amount) = give_asset.fun {
|
||||
// Return the USDT amount we'd get at 1:2 rate
|
||||
let usdt_amount = amount.saturating_mul(2);
|
||||
return Some((AssetId(usdt_location), usdt_amount).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a signed origin of a u64 account into a location with only the `AccountIndex64`
|
||||
/// junction.
|
||||
pub struct SignedToAccountIndex64<RuntimeOrigin, AccountId>(
|
||||
PhantomData<(RuntimeOrigin, AccountId)>,
|
||||
);
|
||||
impl<RuntimeOrigin: OriginTrait + Clone, AccountId: Into<u64>> TryConvert<RuntimeOrigin, Location>
|
||||
for SignedToAccountIndex64<RuntimeOrigin, AccountId>
|
||||
where
|
||||
RuntimeOrigin::PalletsOrigin: From<SystemRawOrigin<AccountId>>
|
||||
+ TryInto<SystemRawOrigin<AccountId>, Error = RuntimeOrigin::PalletsOrigin>,
|
||||
{
|
||||
fn try_convert(origin: RuntimeOrigin) -> Result<Location, RuntimeOrigin> {
|
||||
origin.try_with_caller(|caller| match caller.try_into() {
|
||||
Ok(SystemRawOrigin::Signed(who)) =>
|
||||
Ok(Junction::AccountIndex64 { network: None, index: who.into() }.into()),
|
||||
Ok(other) => Err(other.into()),
|
||||
Err(other) => Err(other),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a local signed origin into an XCM location. Forms the basis for local origins
|
||||
/// sending/executing XCMs.
|
||||
pub type LocalOriginToLocation = SignedToAccountIndex64<RuntimeOrigin, AccountId>;
|
||||
|
||||
impl pezpallet_xcm::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type SendXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, ()>;
|
||||
type XcmRouter = XcmRouter;
|
||||
type ExecuteXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
|
||||
type XcmExecuteFilter = Nothing;
|
||||
type XcmExecutor = XcmExecutor<XcmConfig>;
|
||||
type XcmTeleportFilter = Everything; // Put everything instead of something more restricted.
|
||||
type XcmReserveTransferFilter = Everything; // Same.
|
||||
type Weigher = Weigher;
|
||||
type UniversalLocation = UniversalLocation;
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
|
||||
type AdvertisedXcmVersion = pezpallet_xcm::CurrentXcmVersion;
|
||||
type AdminOrigin = EnsureRoot<AccountId>;
|
||||
type TrustedLockers = ();
|
||||
type SovereignAccountOf = ();
|
||||
type Currency = Balances;
|
||||
type CurrencyMatcher = IsConcrete<HereLocation>;
|
||||
type MaxLockers = ConstU32<0>;
|
||||
type MaxRemoteLockConsumers = ConstU32<0>;
|
||||
type RemoteLockConsumerIdentifier = ();
|
||||
type WeightInfo = TestWeightInfo;
|
||||
type AuthorizedAliasConsideration = Disabled;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn new_test_ext_with_balances(balances: Vec<(AccountId, Balance)>) -> pezsp_io::TestExternalities {
|
||||
let mut t = pezframe_system::GenesisConfig::<TestRuntime>::default().build_storage().unwrap();
|
||||
|
||||
pezpallet_balances::GenesisConfig::<TestRuntime> { balances, ..Default::default() }
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
let mut ext = pezsp_io::TestExternalities::new(t);
|
||||
ext.execute_with(|| System::set_block_number(1));
|
||||
ext
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn new_test_ext_with_balances_and_assets(
|
||||
balances: Vec<(AccountId, Balance)>,
|
||||
assets: Vec<(AssetIdForAssetsPallet, AccountId, Balance)>,
|
||||
) -> pezsp_io::TestExternalities {
|
||||
let mut t = pezframe_system::GenesisConfig::<TestRuntime>::default().build_storage().unwrap();
|
||||
|
||||
pezpallet_balances::GenesisConfig::<TestRuntime> { balances, ..Default::default() }
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
pezpallet_assets::GenesisConfig::<TestRuntime> {
|
||||
assets: vec![
|
||||
// id, owner, is_sufficient, min_balance.
|
||||
// We don't actually need this to be sufficient, since we use the native assets in
|
||||
// tests for the existential deposit.
|
||||
(1, 0, true, 1),
|
||||
(1984, 0, true, 1),
|
||||
],
|
||||
metadata: vec![
|
||||
// id, name, symbol, decimals.
|
||||
(1, "Relay Token".into(), "RLY".into(), 12),
|
||||
(1984, "Tether".into(), "USDT".into(), 6),
|
||||
],
|
||||
accounts: assets,
|
||||
next_asset_id: None,
|
||||
reserves: vec![],
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
let mut ext = pezsp_io::TestExternalities::new(t);
|
||||
ext.execute_with(|| System::set_block_number(1));
|
||||
ext
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct TestClient;
|
||||
|
||||
pub(crate) struct RuntimeApi {
|
||||
_inner: TestClient,
|
||||
}
|
||||
|
||||
impl pezsp_api::ProvideRuntimeApi<Block> for TestClient {
|
||||
type Api = RuntimeApi;
|
||||
fn runtime_api(&self) -> pezsp_api::ApiRef<'_, Self::Api> {
|
||||
RuntimeApi { _inner: self.clone() }.into()
|
||||
}
|
||||
}
|
||||
|
||||
pezsp_api::mock_impl_runtime_apis! {
|
||||
impl TrustedQueryApi<Block> for RuntimeApi {
|
||||
fn is_trusted_reserve(asset: VersionedAsset, location: VersionedLocation) -> Result<bool, TrustedQueryApiError> {
|
||||
XcmPallet::is_trusted_reserve(asset, location)
|
||||
}
|
||||
|
||||
fn is_trusted_teleporter(asset: VersionedAsset, location: VersionedLocation) -> Result<bool, TrustedQueryApiError> {
|
||||
XcmPallet::is_trusted_teleporter(asset, location)
|
||||
}
|
||||
}
|
||||
|
||||
impl LocationToAccountApi<Block, AccountId> for RuntimeApi {
|
||||
fn convert_location(location: VersionedLocation) -> Result<AccountId, LocationToAccountApiError> {
|
||||
let location = location.try_into().map_err(|_| LocationToAccountApiError::VersionedConversionFailed)?;
|
||||
LocationToAccountId::convert_location(&location)
|
||||
.ok_or(LocationToAccountApiError::Unsupported)
|
||||
}
|
||||
}
|
||||
|
||||
impl XcmPaymentApi<Block> for RuntimeApi {
|
||||
fn query_acceptable_payment_assets(xcm_version: XcmVersion) -> Result<Vec<VersionedAssetId>, XcmPaymentApiError> {
|
||||
Ok(vec![
|
||||
VersionedAssetId::from(AssetId(HereLocation::get()))
|
||||
.into_version(xcm_version)
|
||||
.map_err(|_| XcmPaymentApiError::VersionedConversionFailed)?
|
||||
])
|
||||
}
|
||||
|
||||
fn query_xcm_weight(message: VersionedXcm<()>) -> Result<Weight, XcmPaymentApiError> {
|
||||
XcmPallet::query_xcm_weight(message)
|
||||
}
|
||||
|
||||
fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result<u128, XcmPaymentApiError> {
|
||||
let latest_asset_id: Result<AssetId, ()> = asset.clone().try_into();
|
||||
match latest_asset_id {
|
||||
Ok(asset_id) if asset_id.0 == HereLocation::get() => {
|
||||
Ok(WeightToFee::weight_to_fee(&weight))
|
||||
},
|
||||
Ok(asset_id) => {
|
||||
tracing::trace!(
|
||||
target: "xcm::XcmPaymentApi::query_weight_to_asset_fee",
|
||||
?asset_id,
|
||||
"query_weight_to_asset_fee - unhandled!"
|
||||
);
|
||||
Err(XcmPaymentApiError::AssetNotFound)
|
||||
},
|
||||
Err(_) => {
|
||||
tracing::trace!(
|
||||
target: "xcm::XcmPaymentApi::query_weight_to_asset_fee",
|
||||
?asset,
|
||||
"query_weight_to_asset_fee - failed to convert!"
|
||||
);
|
||||
Err(XcmPaymentApiError::VersionedConversionFailed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn query_delivery_fees(destination: VersionedLocation, message: VersionedXcm<()>, asset_id: VersionedAssetId) -> Result<VersionedAssets, XcmPaymentApiError> {
|
||||
XcmPallet::query_delivery_fees::<<XcmConfig as xcm_executor::Config>::AssetExchanger>(destination, message, asset_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl DryRunApi<Block, RuntimeCall, RuntimeEvent, OriginCaller> for RuntimeApi {
|
||||
fn dry_run_call(
|
||||
origin: OriginCaller,
|
||||
call: RuntimeCall,
|
||||
result_xcms_version: XcmVersion,
|
||||
) -> Result<CallDryRunEffects<RuntimeEvent>, XcmDryRunApiError> {
|
||||
pezpallet_xcm::Pallet::<TestRuntime>::dry_run_call::<TestRuntime, XcmRouter, OriginCaller, RuntimeCall>(origin, call, result_xcms_version)
|
||||
}
|
||||
|
||||
fn dry_run_call_before_version_2(
|
||||
origin: OriginCaller,
|
||||
call: RuntimeCall,
|
||||
) -> Result<CallDryRunEffects<RuntimeEvent>, XcmDryRunApiError> {
|
||||
pezpallet_xcm::Pallet::<TestRuntime>::dry_run_call::<TestRuntime, XcmRouter, OriginCaller, RuntimeCall>(origin, call, xcm::latest::VERSION)
|
||||
}
|
||||
|
||||
fn dry_run_xcm(origin_location: VersionedLocation, xcm: VersionedXcm<RuntimeCall>) -> Result<XcmDryRunEffects<RuntimeEvent>, XcmDryRunApiError> {
|
||||
pezpallet_xcm::Pallet::<TestRuntime>::dry_run_xcm::<XcmRouter>(origin_location, xcm)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
mod mock;
|
||||
|
||||
use pezframe_support::pezsp_runtime::testing::H256;
|
||||
use mock::*;
|
||||
use pezsp_api::ProvideRuntimeApi;
|
||||
use xcm::{prelude::*, v3};
|
||||
use xcm_runtime_pezapis::trusted_query::{Error, TrustedQueryApi};
|
||||
|
||||
#[test]
|
||||
fn query_trusted_reserve() {
|
||||
#[derive(Debug)]
|
||||
struct TestCase {
|
||||
name: &'static str,
|
||||
asset: VersionedAsset,
|
||||
location: VersionedLocation,
|
||||
expected: Result<bool, Error>,
|
||||
}
|
||||
|
||||
pezsp_io::TestExternalities::default().execute_with(|| {
|
||||
let client = TestClient {};
|
||||
let runtime_api = client.runtime_api();
|
||||
|
||||
let test_cases: Vec<TestCase> = vec![
|
||||
TestCase {
|
||||
// matches!(asset.id.0.unpack(), (1, [])) && matches!(origin.unpack(), (1,
|
||||
// [Teyrchain(1000)]))
|
||||
name: "Valid asset and location",
|
||||
asset: Asset { id: AssetId(Location::parent()), fun: Fungible(123) }.into(),
|
||||
location: (Parent, Teyrchain(1000)).into(),
|
||||
expected: Ok(true),
|
||||
},
|
||||
TestCase {
|
||||
name: "Invalid location and valid asset",
|
||||
asset: Asset { id: AssetId(Location::parent()), fun: Fungible(100) }.into(),
|
||||
location: (Parent, Teyrchain(1002)).into(),
|
||||
expected: Ok(false),
|
||||
},
|
||||
TestCase {
|
||||
name: "Valid location and invalid asset",
|
||||
asset: Asset { id: AssetId(Location::new(0, [])), fun: Fungible(100) }.into(),
|
||||
location: (Parent, Teyrchain(1000)).into(),
|
||||
expected: Ok(false),
|
||||
},
|
||||
TestCase {
|
||||
name: "Invalid asset conversion",
|
||||
asset: VersionedAsset::V3(v3::MultiAsset {
|
||||
id: v3::AssetId::Abstract([1; 32]),
|
||||
fun: v3::Fungibility::Fungible(1),
|
||||
}),
|
||||
location: (Parent, Teyrchain(1000)).into(),
|
||||
expected: Err(Error::VersionedAssetConversionFailed),
|
||||
},
|
||||
];
|
||||
|
||||
for tc in test_cases {
|
||||
let res =
|
||||
runtime_api.is_trusted_reserve(H256::zero(), tc.asset.clone(), tc.location.clone());
|
||||
let inner_res = res.unwrap_or_else(|e| {
|
||||
panic!("Test case '{}' failed with ApiError: {:?}", tc.name, e);
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
tc.expected, inner_res,
|
||||
"Test case '{}' failed: expected {:?}, got {:?}",
|
||||
tc.name, tc.expected, inner_res
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn query_trusted_teleporter() {
|
||||
#[derive(Debug)]
|
||||
struct TestCase {
|
||||
name: &'static str,
|
||||
asset: VersionedAsset,
|
||||
location: VersionedLocation,
|
||||
expected: Result<bool, Error>,
|
||||
}
|
||||
|
||||
pezsp_io::TestExternalities::default().execute_with(|| {
|
||||
let client = TestClient {};
|
||||
let runtime_api = client.runtime_api();
|
||||
|
||||
let test_cases: Vec<TestCase> = vec![
|
||||
TestCase {
|
||||
// matches!(asset.id.0.unpack(), (0, [])) && matches!(origin.unpack(), (1,
|
||||
// [Teyrchain(1000)]))
|
||||
name: "Valid asset and location",
|
||||
asset: Asset { id: AssetId(Location::new(0, [])), fun: Fungible(100) }.into(),
|
||||
location: (Parent, Teyrchain(1000)).into(),
|
||||
expected: Ok(true),
|
||||
},
|
||||
TestCase {
|
||||
name: "Invalid location and valid asset",
|
||||
asset: Asset { id: AssetId(Location::new(0, [])), fun: Fungible(100) }.into(),
|
||||
location: (Parent, Teyrchain(1002)).into(),
|
||||
expected: Ok(false),
|
||||
},
|
||||
TestCase {
|
||||
name: "Valid location and invalid asset",
|
||||
asset: Asset { id: AssetId(Location::new(1, [])), fun: Fungible(100) }.into(),
|
||||
location: (Parent, Teyrchain(1002)).into(),
|
||||
expected: Ok(false),
|
||||
},
|
||||
TestCase {
|
||||
name: "Invalid asset conversion",
|
||||
asset: VersionedAsset::V3(v3::MultiAsset {
|
||||
id: v3::AssetId::Abstract([1; 32]),
|
||||
fun: v3::Fungibility::Fungible(1),
|
||||
}),
|
||||
location: (Parent, Teyrchain(1000)).into(),
|
||||
expected: Err(Error::VersionedAssetConversionFailed),
|
||||
},
|
||||
];
|
||||
|
||||
for tc in test_cases {
|
||||
let res = runtime_api.is_trusted_teleporter(
|
||||
H256::zero(),
|
||||
tc.asset.clone(),
|
||||
tc.location.clone(),
|
||||
);
|
||||
let inner_res = res.unwrap_or_else(|e| {
|
||||
panic!("Test case '{}' failed with ApiError: {:?}", tc.name, e);
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
tc.expected, inner_res,
|
||||
"Test case '{}' failed: expected {:?}, got {:?}",
|
||||
tc.name, tc.expected, inner_res
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user