Newer
Older
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// 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 frame_support::sp_runtime::testing::H256;
use frame_system::RawOrigin;
use sp_api::ProvideRuntimeApi;
use xcm::prelude::*;
use xcm_runtime_apis::{
dry_run::{CallDryRunEffects, DryRunApi},
fees::XcmPaymentApi,
};
mod mock;
use mock::{
fake_message_hash, new_test_ext_with_balances, new_test_ext_with_balances_and_assets,
DeliveryFees, ExistentialDeposit, HereLocation, OriginCaller, RuntimeCall, RuntimeEvent,
TestClient,
};
// 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 Parachain(2000) Token
// Reserve Asset Transfer Relay Token for fees
// Parachain(2000) -------------------------------------------> Parachain(1000)
#[test]
fn fee_estimation_for_teleport() {
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(pallet_xcm::Call::transfer_assets {
dest: Box::new(VersionedLocation::from((Parent, Parachain(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_item: 1, // 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,
Branislav Kontur
committed
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, [Parachain(1000)]);
let send_message = Xcm::<()>::builder_unsafe()
.withdraw_asset((Parent, 20u128))
.buy_execution((Parent, 20u128), Unlimited)
.receive_teleported_asset(((Parent, Parachain(2000)), 100u128))
.clear_origin()
.deposit_asset(AllCounted(2), [0u8; 32])
.build();
assert_eq!(
dry_run_effects.forwarded_xcms,
vec![(
Branislav Kontur
committed
VersionedLocation::from(send_destination.clone()),
vec![VersionedXcm::from(send_message.clone())],
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
),],
);
assert_eq!(
dry_run_effects.emitted_events,
vec![
RuntimeEvent::System(frame_system::Event::NewAccount {
account: 8660274132218572653 // TODO: Why is this not `1`?
}),
RuntimeEvent::Balances(pallet_balances::Event::Endowed {
account: 8660274132218572653,
free_balance: 100
}),
RuntimeEvent::Balances(pallet_balances::Event::Minted {
who: 8660274132218572653,
amount: 100
}),
RuntimeEvent::AssetsPallet(pallet_assets::Event::Burned {
asset_id: 1,
owner: 1,
balance: 20
}),
RuntimeEvent::Balances(pallet_balances::Event::Burned { who: 1, amount: 100 }),
RuntimeEvent::XcmPallet(pallet_xcm::Event::Attempted {
outcome: Outcome::Complete { used: Weight::from_parts(400, 40) },
}),
RuntimeEvent::Balances(pallet_balances::Event::Burned { who: 1, amount: 20 }),
RuntimeEvent::XcmPallet(pallet_xcm::Event::FeesPaid {
paying: AccountIndex64 { index: 1, network: None }.into(),
fees: (Here, 20u128).into(),
}),
RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent {
origin: AccountIndex64 { index: 1, network: None }.into(),
destination: (Parent, Parachain(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();
assert_eq!(weight, Weight::from_parts(400, 40));
let execution_fees = runtime_api
.query_weight_to_asset_fee(
H256::zero(),
weight,
Branislav Kontur
committed
VersionedAssetId::from(AssetId(HereLocation::get())),
)
.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())
.unwrap()
.unwrap();
Branislav Kontur
committed
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,
Branislav Kontur
committed
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 parachain 2000 wants
// to send relay tokens over to parachain 1000.
//
// Reserve Asset Transfer Relay Token
// Reserve Asset Transfer Relay Token for fees
// Parachain(2000) -------------------------------------------> Parachain(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>,
) {
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(pallet_xcm::Call::transfer_assets {
dest: Box::new(
VersionedLocation::from((Parent, Parachain(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_item: 0,
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, Parachain(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()],
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
),],
);
assert_eq!(
dry_run_effects.emitted_events,
vec![
RuntimeEvent::AssetsPallet(pallet_assets::Event::Burned {
asset_id: 1,
owner: 1,
balance: 100
}),
RuntimeEvent::XcmPallet(pallet_xcm::Event::Attempted {
outcome: Outcome::Complete { used: Weight::from_parts(200, 20) }
}),
RuntimeEvent::Balances(pallet_balances::Event::Burned { who: 1, amount: 20 }),
RuntimeEvent::XcmPallet(pallet_xcm::Event::FeesPaid {
paying: AccountIndex64 { index: 1, network: None }.into(),
fees: (Here, 20u128).into()
}),
RuntimeEvent::XcmPallet(pallet_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]
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
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) {
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, Parachain(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
Branislav Kontur
committed
.query_weight_to_asset_fee(
H256::zero(),
xcm_weight,
VersionedAssetId::from(AssetId(Here.into())).into_version(xcm_version).unwrap(),
Branislav Kontur
committed
)
.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, Parachain(2100)), inner_xcm.clone())
.build();
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, Parachain(2000)),
transfer_amount + execution_fees - DeliveryFees::get(),
))
.clear_origin()
.buy_execution((Here, 1u128), Unlimited)
.deposit_asset(AllCounted(1), [0u8; 32])
.build();
let expected_msg_id = fake_message_hash(&expected_xcms);
assert_eq!(
dry_run_effects.forwarded_xcms,
vec![(
VersionedLocation::from((Parent, Parachain(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(pallet_balances::Event::Burned { who: 1, amount: 540 }),
RuntimeEvent::System(frame_system::Event::NewAccount { account: 2100 }),
RuntimeEvent::Balances(pallet_balances::Event::Endowed {
account: 2100,
free_balance: 520
}),
RuntimeEvent::Balances(pallet_balances::Event::Minted { who: 2100, amount: 520 }),
RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent {
origin: (who,).into(),
destination: (Parent, Parachain(2100)).into(),
message: Xcm::default(),
message_id: expected_msg_id,
})
]
);
});
}
Francisco Aguirre
committed
#[test]
fn dry_run_xcm_versions() {
let tested_versions = [XCM_VERSION, 5, 4, 3];
for version in tested_versions {
dry_run_xcm_common(version);
}
}
Francisco Aguirre
committed
#[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());
}
use frame_support::{
dispatch::DispatchErrorWithPostInfo,
sp_runtime::{DispatchError, ModuleError},
};
use sp_tracing::{capture_test_logs, tracing::Level};
let who = 1;
let balances = vec![(who, DeliveryFees::get() + ExistentialDeposit::get())];
let assets = vec![(1, who, 100)];
new_test_ext_with_balances_and_assets(balances, assets).execute_with(|| {
let client = TestClient;
let runtime_api = client.runtime_api();
let xcm_call = RuntimeCall::XcmPallet(pallet_xcm::Call::transfer_assets {
dest: VersionedLocation::from((Parent, Parachain(1000))).into(),
beneficiary: VersionedLocation::from(AccountId32 { id: [0u8; 32], network: None })
.into(),
assets: VersionedAssets::from((Parent, 1984u128)).into(),
fee_asset_item: 0,
weight_limit: Unlimited,
});
let origin = OriginCaller::system(RawOrigin::Signed(who));
let log_capture = capture_test_logs!(Level::DEBUG, true, {
let dry_run_effects = runtime_api
.dry_run_call(H256::zero(), origin, xcm_call, XCM_VERSION)
.unwrap()
.unwrap();
if let Err(DispatchErrorWithPostInfo {
error: DispatchError::Module(ModuleError { message: Some(err_msg), .. }),
..
}) = dry_run_effects.execution_result
{
assert_eq!(err_msg, "LocalExecutionIncomplete");
} else {
assert!(false, "Expected LocalExecutionIncomplete error");
}
println!("dry_run_effects.emitted_events={:?}", dry_run_effects.emitted_events);
assert!(dry_run_effects.emitted_events.is_empty());
});
assert!(log_capture.contains("xcm::pallet_xcm::execute_xcm_transfer: origin=Location"));
assert!(log_capture.contains("XCM execution failed with error with outcome: Incomplete"));
});
}