Skip to content

Commit

Permalink
XCM Fee Payment Runtime API (paritytech#3607)
Browse files Browse the repository at this point in the history
The PR provides API for obtaining:
- the weight required to execute an XCM message,
- a list of acceptable `AssetId`s for message execution payment,
- the cost of the weight in the specified acceptable `AssetId`.

It is meant to address an issue where one has to guess how much fee to
pay for execution. Also, at the moment, a client has to guess which
assets are acceptable for fee execution payment.
See the related issue
paritytech#690.
With this API, a client is supposed to query the list of the supported
asset IDs (in the XCM version format the client understands), weigh the
XCM program the client wants to execute and convert the weight into one
of the acceptable assets. Note that the client is supposed to know what
program will be executed on what chains. However, having a small
companion JS library for the pallet-xcm and xtokens should be enough to
determine what XCM programs will be executed and where (since these
pallets compose a known small set of programs).
```Rust
pub trait XcmPaymentApi<Call>
	where
		Call: Codec,
	{
		/// Returns a list of acceptable payment assets.
		///
		/// # Arguments
		///
		/// * `xcm_version`: Version.
		fn query_acceptable_payment_assets(xcm_version: Version) -> Result<Vec<VersionedAssetId>, Error>;
		/// Returns a weight needed to execute a XCM.
		///
		/// # Arguments
		///
		/// * `message`: `VersionedXcm`.
		fn query_xcm_weight(message: VersionedXcm<Call>) -> Result<Weight, Error>;
		/// Converts a weight into a fee for the specified `AssetId`.
		///
		/// # Arguments
		///
		/// * `weight`: convertible `Weight`.
		/// * `asset`: `VersionedAssetId`.
		fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result<u128, Error>;
		/// Get delivery fees for sending a specific `message` to a `destination`.
		/// These always come in a specific asset, defined by the chain.
		///
		/// # Arguments
		/// * `message`: The message that'll be sent, necessary because most delivery fees are based on the
		///   size of the message.
		/// * `destination`: The destination to send the message to. Different destinations may use
		///   different senders that charge different fees.
		fn query_delivery_fees(destination: VersionedLocation, message: VersionedXcm<()>) -> Result<VersionedAssets, Error>;
	}
```
An
[example](https://gist.github.com/PraetorP/4bc323ff85401abe253897ba990ec29d)
of a client side code.

---------

Co-authored-by: Francisco Aguirre <[email protected]>
Co-authored-by: Adrian Catangiu <[email protected]>
Co-authored-by: Daniel Shiposha <[email protected]>
  • Loading branch information
4 people authored Mar 26, 2024
1 parent 0c15d88 commit 3c972fc
Show file tree
Hide file tree
Showing 15 changed files with 342 additions and 20 deletions.
19 changes: 19 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ members = [
"polkadot/xcm/xcm-builder",
"polkadot/xcm/xcm-executor",
"polkadot/xcm/xcm-executor/integration-tests",
"polkadot/xcm/xcm-fee-payment-runtime-api",
"polkadot/xcm/xcm-simulator",
"polkadot/xcm/xcm-simulator/example",
"polkadot/xcm/xcm-simulator/fuzzer",
Expand Down
3 changes: 3 additions & 0 deletions polkadot/node/service/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ polkadot-node-core-pvf-checker = { path = "../core/pvf-checker", optional = true
polkadot-node-core-runtime-api = { path = "../core/runtime-api", optional = true }
polkadot-statement-distribution = { path = "../network/statement-distribution", optional = true }

xcm = { package = "staging-xcm", path = "../../xcm" }
xcm-fee-payment-runtime-api = { path = "../../xcm/xcm-fee-payment-runtime-api" }

[dev-dependencies]
polkadot-test-client = { path = "../test/client" }
polkadot-node-subsystem-test-helpers = { path = "../subsystem-test-helpers" }
Expand Down
21 changes: 20 additions & 1 deletion polkadot/node/service/src/fake_runtime_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use polkadot_primitives::{
ScrapedOnChainVotes, SessionIndex, SessionInfo, ValidationCode, ValidationCodeHash,
ValidatorId, ValidatorIndex, ValidatorSignature,
};

use sp_core::OpaqueMetadata;
use sp_runtime::{
traits::Block as BlockT,
Expand All @@ -39,7 +40,7 @@ use sp_runtime::{
use sp_version::RuntimeVersion;
use sp_weights::Weight;
use std::collections::BTreeMap;

use xcm::{VersionedAssetId, VersionedAssets, VersionedLocation, VersionedXcm};
sp_api::decl_runtime_apis! {
/// This runtime API is only implemented for the test runtime!
pub trait GetLastTimestamp {
Expand Down Expand Up @@ -396,4 +397,22 @@ sp_api::impl_runtime_apis! {
unimplemented!()
}
}

impl xcm_fee_payment_runtime_api::XcmPaymentApi<Block> for Runtime {
fn query_acceptable_payment_assets(_: xcm::Version) -> Result<Vec<VersionedAssetId>, xcm_fee_payment_runtime_api::Error> {
unimplemented!()
}

fn query_weight_to_asset_fee(_: Weight, _: VersionedAssetId) -> Result<u128, xcm_fee_payment_runtime_api::Error> {
unimplemented!()
}

fn query_xcm_weight(_: VersionedXcm<()>) -> Result<Weight, xcm_fee_payment_runtime_api::Error> {
unimplemented!()
}

fn query_delivery_fees(_: VersionedLocation, _: VersionedXcm<()>) -> Result<VersionedAssets, xcm_fee_payment_runtime_api::Error> {
unimplemented!()
}
}
}
2 changes: 2 additions & 0 deletions polkadot/runtime/rococo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ polkadot-parachain-primitives = { path = "../../parachain", default-features = f
xcm = { package = "staging-xcm", path = "../../xcm", default-features = false }
xcm-executor = { package = "staging-xcm-executor", path = "../../xcm/xcm-executor", default-features = false }
xcm-builder = { package = "staging-xcm-builder", path = "../../xcm/xcm-builder", default-features = false }
xcm-fee-payment-runtime-api = { path = "../../xcm/xcm-fee-payment-runtime-api", default-features = false }

[dev-dependencies]
tiny-keccak = { version = "2.0.2", features = ["keccak"] }
Expand Down Expand Up @@ -208,6 +209,7 @@ std = [
"tx-pool-api/std",
"xcm-builder/std",
"xcm-executor/std",
"xcm-fee-payment-runtime-api/std",
"xcm/std",
]
runtime-benchmarks = [
Expand Down
53 changes: 44 additions & 9 deletions polkadot/runtime/rococo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ use frame_support::{
InstanceFilter, KeyOwnerProofSystem, LinearStoragePrice, PrivilegeCmp, ProcessMessage,
ProcessMessageError, StorageMapShim, WithdrawReasons,
},
weights::{ConstantMultiplier, WeightMeter},
weights::{ConstantMultiplier, WeightMeter, WeightToFee as _},
PalletId,
};
use frame_system::{EnsureRoot, EnsureSigned};
Expand All @@ -98,7 +98,10 @@ use sp_staking::SessionIndex;
#[cfg(any(feature = "std", test))]
use sp_version::NativeVersion;
use sp_version::RuntimeVersion;
use xcm::{latest::prelude::*, VersionedLocation};
use xcm::{
latest::prelude::*, IntoVersion, VersionedAssetId, VersionedAssets, VersionedLocation,
VersionedXcm,
};
use xcm_builder::PayOverXcm;

pub use frame_system::Call as SystemCall;
Expand All @@ -123,6 +126,7 @@ use governance::{
pallet_custom_origins, AuctionAdmin, Fellows, GeneralAdmin, LeaseAdmin, Treasurer,
TreasurySpender,
};
use xcm_fee_payment_runtime_api::Error as XcmPaymentApiError;

#[cfg(test)]
mod tests;
Expand Down Expand Up @@ -216,7 +220,7 @@ pub struct OriginPrivilegeCmp;
impl PrivilegeCmp<OriginCaller> for OriginPrivilegeCmp {
fn cmp_privilege(left: &OriginCaller, right: &OriginCaller) -> Option<Ordering> {
if left == right {
return Some(Ordering::Equal)
return Some(Ordering::Equal);
}

match (left, right) {
Expand Down Expand Up @@ -1493,11 +1497,11 @@ pub mod migrations {
let now = frame_system::Pallet::<Runtime>::block_number();
let lease = slots::Pallet::<Runtime>::lease(para);
if lease.is_empty() {
return None
return None;
}
// Lease not yet started, ignore:
if lease.iter().any(Option::is_none) {
return None
return None;
}
let (index, _) =
<slots::Pallet<Runtime> as Leaser<BlockNumber>>::lease_period_index(now)?;
Expand Down Expand Up @@ -1559,7 +1563,7 @@ pub mod migrations {
fn pre_upgrade() -> Result<sp_std::vec::Vec<u8>, sp_runtime::TryRuntimeError> {
if System::last_runtime_upgrade_spec_version() > UPGRADE_SESSION_KEYS_FROM_SPEC {
log::warn!(target: "runtime::session_keys", "Skipping session keys migration pre-upgrade check due to spec version (already applied?)");
return Ok(Vec::new())
return Ok(Vec::new());
}

log::info!(target: "runtime::session_keys", "Collecting pre-upgrade session keys state");
Expand Down Expand Up @@ -1588,7 +1592,7 @@ pub mod migrations {
fn on_runtime_upgrade() -> Weight {
if System::last_runtime_upgrade_spec_version() > UPGRADE_SESSION_KEYS_FROM_SPEC {
log::info!("Skipping session keys upgrade: already applied");
return <Runtime as frame_system::Config>::DbWeight::get().reads(1)
return <Runtime as frame_system::Config>::DbWeight::get().reads(1);
}
log::trace!("Upgrading session keys");
Session::upgrade_keys::<OldSessionKeys, _>(transform_session_keys);
Expand All @@ -1601,7 +1605,7 @@ pub mod migrations {
) -> Result<(), sp_runtime::TryRuntimeError> {
if System::last_runtime_upgrade_spec_version() > UPGRADE_SESSION_KEYS_FROM_SPEC {
log::warn!(target: "runtime::session_keys", "Skipping session keys migration post-upgrade check due to spec version (already applied?)");
return Ok(())
return Ok(());
}

let key_ids = SessionKeys::key_ids();
Expand Down Expand Up @@ -1785,6 +1789,37 @@ sp_api::impl_runtime_apis! {
}
}

impl xcm_fee_payment_runtime_api::XcmPaymentApi<Block> for Runtime {
fn query_acceptable_payment_assets(xcm_version: xcm::Version) -> Result<Vec<VersionedAssetId>, XcmPaymentApiError> {
if !matches!(xcm_version, 3 | 4) {
return Err(XcmPaymentApiError::UnhandledXcmVersion);
}
Ok([VersionedAssetId::V4(xcm_config::TokenLocation::get().into())]
.into_iter()
.filter_map(|asset| asset.into_version(xcm_version).ok())
.collect())
}

fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result<u128, XcmPaymentApiError> {
let local_asset = VersionedAssetId::V4(xcm_config::TokenLocation::get().into());
let asset = asset
.into_version(4)
.map_err(|_| XcmPaymentApiError::VersionedConversionFailed)?;

if asset != local_asset { return Err(XcmPaymentApiError::AssetNotFound); }

Ok(WeightToFee::weight_to_fee(&weight))
}

fn query_xcm_weight(message: VersionedXcm<()>) -> Result<Weight, XcmPaymentApiError> {
XcmPallet::query_xcm_weight(message)
}

fn query_delivery_fees(destination: VersionedLocation, message: VersionedXcm<()>) -> Result<VersionedAssets, XcmPaymentApiError> {
XcmPallet::query_delivery_fees(destination, message)
}
}

impl sp_api::Metadata<Block> for Runtime {
fn metadata() -> OpaqueMetadata {
OpaqueMetadata::new(Runtime::metadata().into())
Expand Down Expand Up @@ -2493,7 +2528,7 @@ mod remote_tests {
#[tokio::test]
async fn run_migrations() {
if var("RUN_MIGRATION_TESTS").is_err() {
return
return;
}

sp_tracing::try_init_simple();
Expand Down
2 changes: 2 additions & 0 deletions polkadot/runtime/westend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ runtime-parachains = { package = "polkadot-runtime-parachains", path = "../parac
xcm = { package = "staging-xcm", path = "../../xcm", default-features = false }
xcm-executor = { package = "staging-xcm-executor", path = "../../xcm/xcm-executor", default-features = false }
xcm-builder = { package = "staging-xcm-builder", path = "../../xcm/xcm-builder", default-features = false }
xcm-fee-payment-runtime-api = { path = "../../xcm/xcm-fee-payment-runtime-api", default-features = false }

[dev-dependencies]
hex-literal = "0.4.1"
Expand Down Expand Up @@ -227,6 +228,7 @@ std = [
"westend-runtime-constants/std",
"xcm-builder/std",
"xcm-executor/std",
"xcm-fee-payment-runtime-api/std",
"xcm/std",
]
runtime-benchmarks = [
Expand Down
51 changes: 42 additions & 9 deletions polkadot/runtime/westend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ use frame_support::{
InstanceFilter, KeyOwnerProofSystem, LinearStoragePrice, ProcessMessage,
ProcessMessageError, WithdrawReasons,
},
weights::{ConstantMultiplier, WeightMeter},
weights::{ConstantMultiplier, WeightMeter, WeightToFee as _},
PalletId,
};
use frame_system::{EnsureRoot, EnsureSigned};
Expand Down Expand Up @@ -101,11 +101,13 @@ use sp_std::{collections::btree_map::BTreeMap, prelude::*};
use sp_version::NativeVersion;
use sp_version::RuntimeVersion;
use xcm::{
latest::{InteriorLocation, Junction, Junction::PalletInstance},
VersionedLocation,
latest::prelude::*, IntoVersion, VersionedAssetId, VersionedAssets, VersionedLocation,
VersionedXcm,
};
use xcm_builder::PayOverXcm;

use xcm_fee_payment_runtime_api::Error as XcmPaymentApiError;

pub use frame_system::Call as SystemCall;
pub use pallet_balances::Call as BalancesCall;
pub use pallet_election_provider_multi_phase::{Call as EPMCall, GeometricDepositBase};
Expand Down Expand Up @@ -1667,11 +1669,11 @@ pub mod migrations {
let now = frame_system::Pallet::<Runtime>::block_number();
let lease = slots::Pallet::<Runtime>::lease(para);
if lease.is_empty() {
return None
return None;
}
// Lease not yet started, ignore:
if lease.iter().any(Option::is_none) {
return None
return None;
}
let (index, _) =
<slots::Pallet<Runtime> as Leaser<BlockNumber>>::lease_period_index(now)?;
Expand All @@ -1693,7 +1695,7 @@ pub mod migrations {
fn pre_upgrade() -> Result<sp_std::vec::Vec<u8>, sp_runtime::TryRuntimeError> {
if System::last_runtime_upgrade_spec_version() > UPGRADE_SESSION_KEYS_FROM_SPEC {
log::warn!(target: "runtime::session_keys", "Skipping session keys migration pre-upgrade check due to spec version (already applied?)");
return Ok(Vec::new())
return Ok(Vec::new());
}

log::info!(target: "runtime::session_keys", "Collecting pre-upgrade session keys state");
Expand Down Expand Up @@ -1722,7 +1724,7 @@ pub mod migrations {
fn on_runtime_upgrade() -> Weight {
if System::last_runtime_upgrade_spec_version() > UPGRADE_SESSION_KEYS_FROM_SPEC {
log::warn!("Skipping session keys upgrade: already applied");
return <Runtime as frame_system::Config>::DbWeight::get().reads(1)
return <Runtime as frame_system::Config>::DbWeight::get().reads(1);
}
log::info!("Upgrading session keys");
Session::upgrade_keys::<OldSessionKeys, _>(transform_session_keys);
Expand All @@ -1735,7 +1737,7 @@ pub mod migrations {
) -> Result<(), sp_runtime::TryRuntimeError> {
if System::last_runtime_upgrade_spec_version() > UPGRADE_SESSION_KEYS_FROM_SPEC {
log::warn!(target: "runtime::session_keys", "Skipping session keys migration post-upgrade check due to spec version (already applied?)");
return Ok(())
return Ok(());
}

let key_ids = SessionKeys::key_ids();
Expand Down Expand Up @@ -2332,6 +2334,37 @@ sp_api::impl_runtime_apis! {
}
}

impl xcm_fee_payment_runtime_api::XcmPaymentApi<Block> for Runtime {
fn query_acceptable_payment_assets(xcm_version: xcm::Version) -> Result<Vec<VersionedAssetId>, XcmPaymentApiError> {
if !matches!(xcm_version, 3 | 4) {
return Err(XcmPaymentApiError::UnhandledXcmVersion);
}
Ok([VersionedAssetId::V4(xcm_config::TokenLocation::get().into())]
.into_iter()
.filter_map(|asset| asset.into_version(xcm_version).ok())
.collect())
}

fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result<u128, XcmPaymentApiError> {
let local_asset = VersionedAssetId::V4(xcm_config::TokenLocation::get().into());
let asset = asset
.into_version(4)
.map_err(|_| XcmPaymentApiError::VersionedConversionFailed)?;

if asset != local_asset { return Err(XcmPaymentApiError::AssetNotFound); }

Ok(WeightToFee::weight_to_fee(&weight))
}

fn query_xcm_weight(message: VersionedXcm<()>) -> Result<Weight, XcmPaymentApiError> {
XcmPallet::query_xcm_weight(message)
}

fn query_delivery_fees(destination: VersionedLocation, message: VersionedXcm<()>) -> Result<VersionedAssets, XcmPaymentApiError> {
XcmPallet::query_delivery_fees(destination, message)
}
}

impl pallet_nomination_pools_runtime_api::NominationPoolsApi<
Block,
AccountId,
Expand Down Expand Up @@ -2650,7 +2683,7 @@ mod remote_tests {
#[tokio::test]
async fn run_migrations() {
if var("RUN_MIGRATION_TESTS").is_err() {
return
return;
}

sp_tracing::try_init_simple();
Expand Down
1 change: 0 additions & 1 deletion polkadot/runtime/westend/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ use std::collections::HashSet;
use crate::*;
use frame_support::traits::WhitelistedStorageKeys;
use sp_core::hexdisplay::HexDisplay;
use xcm::latest::prelude::*;

#[test]
fn remove_keys_weight_is_sensible() {
Expand Down
Loading

0 comments on commit 3c972fc

Please sign in to comment.