feat: initialize Kurdistan SDK - independent fork of Polkadot SDK

This commit is contained in:
2025-12-13 15:44:15 +03:00
commit 286de54384
6841 changed files with 1848356 additions and 0 deletions
+58
View File
@@ -0,0 +1,58 @@
[package]
name = "staging-xcm"
description = "The basic XCM datastructures."
version = "7.0.1"
authors.workspace = true
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
[lints]
workspace = true
[dependencies]
array-bytes = { workspace = true, default-features = true }
bounded-collections = { features = ["serde"], workspace = true }
codec = { features = ["derive", "max-encoded-len"], workspace = true }
derive-where = { workspace = true }
environmental = { workspace = true }
frame-support = { workspace = true }
hex-literal = { workspace = true, default-features = true }
impl-trait-for-tuples = { workspace = true }
scale-info = { features = ["derive", "serde"], workspace = true }
schemars = { default-features = true, optional = true, workspace = true }
serde = { features = ["alloc", "derive", "rc"], workspace = true }
sp-runtime = { workspace = true }
sp-weights = { features = ["serde"], workspace = true }
tracing = { workspace = true }
xcm-procedural = { workspace = true, default-features = true }
[dev-dependencies]
sp-io = { workspace = true, default-features = true }
[features]
default = ["std"]
wasm-api = []
std = [
"bounded-collections/std",
"codec/std",
"environmental/std",
"frame-support/std",
"scale-info/std",
"serde/std",
"sp-runtime/std",
"sp-weights/std",
"tracing/std",
]
json-schema = [
"bounded-collections/json-schema",
"dep:schemars",
"sp-weights/json-schema",
]
runtime-benchmarks = [
"frame-support/runtime-benchmarks",
"sp-io/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
"xcm-procedural/runtime-benchmarks",
]
+51
View File
@@ -0,0 +1,51 @@
[package]
name = "xcm-docs"
description = "Documentation and guides for XCM"
version = "0.1.0"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
repository.workspace = true
authors.workspace = true
edition.workspace = true
publish = false
[dependencies]
# For XCM stuff
pallet-xcm = { workspace = true, default-features = true }
xcm = { workspace = true, default-features = true }
xcm-builder = { workspace = true, default-features = true }
xcm-executor = { workspace = true, default-features = true }
xcm-simulator = { workspace = true, default-features = true }
# For building FRAME runtimes
codec = { workspace = true, default-features = true }
frame = { features = ["runtime"], workspace = true, default-features = true }
pezkuwi-runtime-teyrchains = { workspace = true, default-features = true }
scale-info = { workspace = true }
sp-io = { workspace = true, default-features = true }
sp-runtime = { workspace = true, default-features = true }
# Some pallets
pallet-balances = { workspace = true, default-features = true }
pallet-message-queue = { workspace = true, default-features = true }
# For building docs
docify = { workspace = true }
simple-mermaid = { workspace = true }
[dev-dependencies]
test-log = { workspace = true }
[features]
runtime-benchmarks = [
"frame/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"pallet-message-queue/runtime-benchmarks",
"pallet-xcm/runtime-benchmarks",
"pezkuwi-runtime-teyrchains/runtime-benchmarks",
"sp-io/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
"xcm-builder/runtime-benchmarks",
"xcm-executor/runtime-benchmarks",
"xcm-simulator/runtime-benchmarks",
"xcm/runtime-benchmarks",
]
@@ -0,0 +1,9 @@
flowchart
relay[Relaychain] --> paraA["Parachain(1000)"]
relay --> paraB["Parachain(2000)"]
paraA --> pallet[Pallet]
pallet --> indexA[Index 1]
pallet --> indexB[Index 2]
paraA --> account[Account]
+4
View File
@@ -0,0 +1,4 @@
flowchart
docs[xcm_docs] --> fundamentals
docs --> guides
docs --> cookbook
@@ -0,0 +1,6 @@
flowchart
relay[Relaychain] --"DMP"--> paraA["Parachain(2000)"]
relay --"DMP"--> paraB["Parachain(2001)"]
paraA --"UMP"--> relay
paraB --"UMP"--> relay
@@ -0,0 +1,3 @@
flowchart
universe[Universal Location] --> polkadot[Polkadot]
universe --> ethereum[Ethereum]
@@ -0,0 +1,6 @@
flowchart
relay[Polkadot] --> assetHub["Asset Hub"]
relay --> anotherPara["Another parachain"]
assetHub --> assetsPallet["Assets Pallet"]
assetsPallet --> usdt[1984]
+28
View File
@@ -0,0 +1,28 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! # XCM Cookbook
//!
//! A collection of XCM recipes.
//!
//! Each recipe is tested and explains all the code necessary to run it -- they're not just snippets
//! to copy and paste.
/// Configuring a teyrchain that only uses the Relay Chain native token.
/// In the case of Pezkuwi, this recipe will show you how to launch a teyrchain with no native
/// token -- dealing only on HEZ.
pub mod relay_token_transactor;
@@ -0,0 +1,52 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! # Relay Asset Transactor
//!
//! This example shows how to configure a teyrchain to only deal with the Relay Chain token.
//!
//! The first step is using the [`xcm_builder::FungibleAdapter`] to create an `AssetTransactor` that
//! can handle the relay chain token.
#![doc = docify::embed!("src/cookbook/relay_token_transactor/teyrchain/xcm_config.rs", asset_transactor)]
//!
//! The second step is to configure `IsReserve` to recognize the relay chain as a reserve for its
//! own asset.
//! With this, you'll be able to easily mint a derivative asset, backed one-to-one from the Relay
//! Chain, by using the xcm pallet's `transfer_assets` extrinsic.
//!
//! The `IsReserve` type takes a type that implements `ContainsPair<MultiAsset, MultiLocation>`.
//! In this case, we want a type that contains the pair `(relay_chain_native_token, relay_chain)`.
#![doc = docify::embed!("src/cookbook/relay_token_transactor/teyrchain/xcm_config.rs", is_reserve)]
//!
//! With this setup, we are able to do a reserve asset transfer to and from the teyrchain and relay
//! chain.
#![doc = docify::embed!("src/cookbook/relay_token_transactor/tests.rs", reserve_asset_transfers_work)]
//!
//! For the rest of the code, be sure to check the contents of this module.
/// The teyrchain runtime for this example
pub mod teyrchain;
/// The relay chain runtime for this example.
pub mod relay_chain;
/// The network for this example.
pub mod network;
/// Tests for this example.
#[cfg(test)]
pub mod tests;
@@ -0,0 +1,94 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Mock network
use frame::deps::{
frame_system,
sp_io::TestExternalities,
sp_runtime::{AccountId32, BuildStorage},
};
use xcm_simulator::{decl_test_network, decl_test_relay_chain, decl_test_teyrchain, TestExt};
use super::{relay_chain, teyrchain};
pub const ALICE: AccountId32 = AccountId32::new([0u8; 32]);
pub const BOB: AccountId32 = AccountId32::new([1u8; 32]);
pub const UNITS: u64 = 10_000_000_000;
pub const CENTS: u64 = 100_000_000;
pub const INITIAL_BALANCE: u64 = UNITS;
decl_test_teyrchain! {
pub struct ParaA {
Runtime = teyrchain::Runtime,
XcmpMessageHandler = teyrchain::MessageQueue,
DmpMessageHandler = teyrchain::MessageQueue,
new_ext = para_ext(),
}
}
decl_test_relay_chain! {
pub struct Relay {
Runtime = relay_chain::Runtime,
RuntimeCall = relay_chain::RuntimeCall,
RuntimeEvent = relay_chain::RuntimeEvent,
XcmConfig = relay_chain::XcmConfig,
MessageQueue = relay_chain::MessageQueue,
System = relay_chain::System,
new_ext = relay_ext(),
}
}
decl_test_network! {
pub struct MockNet {
relay_chain = Relay,
teyrchains = vec![
(2222, ParaA),
],
}
}
pub fn para_ext() -> TestExternalities {
use teyrchain::{MessageQueue, Runtime, System};
let t = frame_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
let mut ext = frame::deps::sp_io::TestExternalities::new(t);
ext.execute_with(|| {
System::set_block_number(1);
MessageQueue::set_para_id(2222.into());
});
ext
}
pub fn relay_ext() -> TestExternalities {
use relay_chain::{Runtime, System};
let mut t = frame_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
pallet_balances::GenesisConfig::<Runtime> {
balances: vec![(ALICE, INITIAL_BALANCE)],
..Default::default()
}
.assimilate_storage(&mut t)
.unwrap();
let mut ext = TestExternalities::new(t);
ext.execute_with(|| {
System::set_block_number(1);
});
ext
}
@@ -0,0 +1,104 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Relay chain runtime mock.
use frame::{
deps::{frame_support::weights::WeightMeter, sp_runtime::AccountId32},
prelude::*,
runtime::prelude::*,
traits::{IdentityLookup, ProcessMessage, ProcessMessageError},
};
use pezkuwi_runtime_teyrchains::inclusion::{AggregateMessageOrigin, UmpQueueId};
use xcm::latest::prelude::*;
mod xcm_config;
pub use xcm_config::LocationToAccountId;
use xcm_config::XcmConfig;
pub type AccountId = AccountId32;
pub type Balance = u64;
parameter_types! {
pub const BlockHashCount: u64 = 250;
}
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Runtime {
type AccountId = AccountId;
type Lookup = IdentityLookup<Self::AccountId>;
type Block = Block;
type AccountData = pallet_balances::AccountData<Balance>;
}
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
impl pallet_balances::Config for Runtime {
type AccountStore = System;
}
type Block = frame_system::mocking::MockBlock<Runtime>;
parameter_types! {
/// Amount of weight that can be spent per block to service messages.
pub MessageQueueServiceWeight: Weight = Weight::from_parts(1_000_000_000, 1_000_000);
pub const MessageQueueHeapSize: u32 = 65_536;
pub const MessageQueueMaxStale: u32 = 16;
}
/// Message processor to handle any messages that were enqueued into the `MessageQueue` pallet.
pub struct MessageProcessor;
impl ProcessMessage for MessageProcessor {
type Origin = AggregateMessageOrigin;
fn process_message(
message: &[u8],
origin: Self::Origin,
meter: &mut WeightMeter,
id: &mut [u8; 32],
) -> Result<bool, ProcessMessageError> {
let para = match origin {
AggregateMessageOrigin::Ump(UmpQueueId::Para(para)) => para,
};
xcm_builder::ProcessXcmMessage::<
Junction,
xcm_executor::XcmExecutor<XcmConfig>,
RuntimeCall,
>::process_message(message, Junction::Teyrchain(para.into()), meter, id)
}
}
impl pallet_message_queue::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type Size = u32;
type HeapSize = MessageQueueHeapSize;
type MaxStale = MessageQueueMaxStale;
type ServiceWeight = MessageQueueServiceWeight;
type MessageProcessor = MessageProcessor;
type QueueChangeHandler = ();
type QueuePausedQuery = ();
type WeightInfo = ();
type IdleMaxServiceWeight = MessageQueueServiceWeight;
}
construct_runtime! {
pub struct Runtime {
System: frame_system,
Balances: pallet_balances,
MessageQueue: pallet_message_queue,
XcmPallet: pallet_xcm,
}
}
@@ -0,0 +1,169 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Relay chain XCM configuration
use frame::{
deps::frame_system,
runtime::prelude::*,
traits::{Disabled, Everything, Nothing},
};
use xcm::latest::prelude::*;
use xcm_builder::{
AccountId32Aliases, DescribeAllTerminal, DescribeFamily, EnsureXcmOrigin,
FrameTransactionalProcessor, FungibleAdapter, HashedDescription, IsConcrete,
SignedToAccountId32,
};
use xcm_executor::XcmExecutor;
use super::{AccountId, Balances, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin};
parameter_types! {
pub HereLocation: Location = Location::here();
pub ThisNetwork: NetworkId = NetworkId::Pezkuwi;
}
/// Converter from XCM Locations to accounts.
/// This generates sovereign accounts for Locations and converts
/// local AccountId32 junctions to local accounts.
pub type LocationToAccountId = (
HashedDescription<AccountId, DescribeFamily<DescribeAllTerminal>>,
AccountId32Aliases<ThisNetwork, AccountId>,
);
mod asset_transactor {
use super::*;
/// AssetTransactor for handling the Relay Chain token.
pub type FungibleTransactor = FungibleAdapter<
// Use this `fungible` implementation.
Balances,
// This transactor handles the native token.
IsConcrete<HereLocation>,
// How to convert an XCM Location into a local account id.
// Whenever assets are handled, the location is turned into an account.
// This account is the one where balances are withdrawn/deposited.
LocationToAccountId,
// The account id type, needed because `fungible` is generic over it.
AccountId,
// Not tracking teleports.
(),
>;
/// All asset transactors, in this case only one
pub type AssetTransactor = FungibleTransactor;
}
mod weigher {
use super::*;
use xcm_builder::FixedWeightBounds;
parameter_types! {
pub const WeightPerInstruction: Weight = Weight::from_parts(1, 1);
pub const MaxInstructions: u32 = 100;
}
pub type Weigher = FixedWeightBounds<WeightPerInstruction, RuntimeCall, MaxInstructions>;
}
parameter_types! {
pub UniversalLocation: InteriorLocation = [GlobalConsensus(NetworkId::Pezkuwi)].into();
}
pub struct XcmConfig;
impl xcm_executor::Config for XcmConfig {
type RuntimeCall = RuntimeCall;
type XcmSender = ();
type XcmEventEmitter = ();
type AssetTransactor = asset_transactor::AssetTransactor;
type OriginConverter = ();
// We don't need to recognize anyone as a reserve
type IsReserve = ();
type IsTeleporter = ();
type UniversalLocation = UniversalLocation;
// This is not safe, you should use `xcm_builder::AllowTopLevelPaidExecutionFrom<T>` in a
// production chain
type Barrier = xcm_builder::AllowUnpaidExecutionFrom<Everything>;
type Weigher = weigher::Weigher;
type Trader = ();
type ResponseHandler = ();
type AssetTrap = ();
type AssetLocker = ();
type AssetExchanger = ();
type AssetClaims = ();
type SubscriptionService = ();
type PalletInstancesInfo = ();
type FeeManager = ();
type MaxAssetsIntoHolding = frame::traits::ConstU32<1>;
type MessageExporter = ();
type UniversalAliases = Nothing;
type CallDispatcher = RuntimeCall;
type SafeCallFilter = Everything;
type Aliasers = Nothing;
type TransactionalProcessor = FrameTransactionalProcessor;
type HrmpNewChannelOpenRequestHandler = ();
type HrmpChannelAcceptedHandler = ();
type HrmpChannelClosingHandler = ();
type XcmRecorder = ();
}
/// Converts a local signed origin into an XCM location. Forms the basis for local origins
/// sending/executing XCMs.
pub type LocalOriginToLocation = SignedToAccountId32<RuntimeOrigin, AccountId, ThisNetwork>;
impl pallet_xcm::Config for Runtime {
// No one can call `send`
type SendXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, ()>;
type XcmRouter = super::super::network::RelayChainXcmRouter; // Provided by xcm-simulator
// Anyone can execute XCM programs
type ExecuteXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
// We execute any type of program
type XcmExecuteFilter = Everything;
// How we execute programs
type XcmExecutor = XcmExecutor<XcmConfig>;
// We don't allow teleports
type XcmTeleportFilter = Nothing;
// We allow all reserve transfers.
// This is so it can act as a reserve for its native token.
type XcmReserveTransferFilter = Everything;
// Same weigher executor uses to weigh XCM programs
type Weigher = weigher::Weigher;
// Same universal location
type UniversalLocation = UniversalLocation;
// No version discovery needed
const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 0;
type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion;
type AdminOrigin = frame_system::EnsureRoot<AccountId>;
// No locking
type TrustedLockers = ();
type MaxLockers = frame::traits::ConstU32<0>;
type MaxRemoteLockConsumers = frame::traits::ConstU32<0>;
type RemoteLockConsumerIdentifier = ();
// How to turn locations into accounts
type SovereignAccountOf = LocationToAccountId;
// A currency to pay for things and its matcher, we are using the relay token
type Currency = Balances;
type CurrencyMatcher = IsConcrete<HereLocation>;
// Pallet benchmarks, no need for this example
type WeightInfo = pallet_xcm::TestWeightInfo;
// Runtime types
type RuntimeOrigin = RuntimeOrigin;
type RuntimeCall = RuntimeCall;
type RuntimeEvent = RuntimeEvent;
// Aliasing is disabled: xcm_executor::Config::Aliasers is set to `Nothing`.
type AuthorizedAliasConsideration = Disabled;
}
@@ -0,0 +1,137 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
use frame::testing_prelude::*;
use test_log::test;
use xcm::prelude::*;
use xcm_executor::traits::{ConvertLocation, TransferType};
use xcm_simulator::TestExt;
use super::{
network::{MockNet, ParaA, Relay, ALICE, BOB, CENTS, INITIAL_BALANCE},
relay_chain, teyrchain,
};
#[docify::export]
#[test]
fn reserve_asset_transfers_work() {
// Scenario:
// ALICE on the relay chain holds some of Relay Chain's native tokens.
// She transfers them to BOB's account on the teyrchain using a reserve transfer.
// BOB receives Relay Chain native token derivatives on the teyrchain,
// which are backed one-to-one with the real tokens on the Relay Chain.
//
// NOTE: We could've used ALICE on both chains because it's a different account,
// but using ALICE and BOB makes it clearer.
// We restart the mock network.
MockNet::reset();
// ALICE starts with INITIAL_BALANCE on the relay chain
Relay::execute_with(|| {
assert_eq!(relay_chain::Balances::free_balance(&ALICE), INITIAL_BALANCE);
});
// BOB starts with 0 on the teyrchain
ParaA::execute_with(|| {
assert_eq!(teyrchain::Balances::free_balance(&BOB), 0);
});
// ALICE on the Relay Chain sends some Relay Chain native tokens to BOB on the teyrchain.
// The transfer is done with the `transfer_assets` extrinsic in the XCM pallet.
// The extrinsic figures out it should do a reserve asset transfer
// with the local chain as reserve.
Relay::execute_with(|| {
// The teyrchain id is specified in the network.rs file in this recipe.
let destination: Location = Teyrchain(2222).into();
let beneficiary: Location =
AccountId32 { id: BOB.clone().into(), network: Some(NetworkId::Pezkuwi) }.into();
// We need to use `u128` here for the conversion to work properly.
// If we don't specify anything, it will be a `u64`, which the conversion
// will turn into a non-fungible token instead of a fungible one.
let assets: Assets = (Here, 50u128 * CENTS as u128).into();
assert_ok!(relay_chain::XcmPallet::transfer_assets_using_type_and_then(
relay_chain::RuntimeOrigin::signed(ALICE),
Box::new(VersionedLocation::from(destination.clone())),
Box::new(VersionedAssets::from(assets)),
Box::new(TransferType::LocalReserve),
Box::new(VersionedAssetId::from(AssetId(Location::here()))),
Box::new(TransferType::LocalReserve),
Box::new(VersionedXcm::from(
Xcm::<()>::builder_unsafe().deposit_asset(AllCounted(1), beneficiary).build()
)),
WeightLimit::Unlimited,
));
// ALICE now has less Relay Chain tokens.
assert_eq!(relay_chain::Balances::free_balance(&ALICE), INITIAL_BALANCE - 50 * CENTS);
// The funds of the sovereign account of the teyrchain increase by 50 cents,
// the ones transferred over to BOB.
// The funds in this sovereign account represent how many Relay Chain tokens
// have been sent to this teyrchain.
// If the teyrchain wants to send those assets somewhere else they have to go
// via the reserve, and this balance is updated accordingly.
// This is why the derivatives are backed one-to-one.
let teyrchains_sovereign_account =
relay_chain::LocationToAccountId::convert_location(&destination).unwrap();
assert_eq!(relay_chain::Balances::free_balance(teyrchains_sovereign_account), 50 * CENTS);
});
ParaA::execute_with(|| {
// On the teyrchain, BOB has received the derivative tokens
assert_eq!(teyrchain::Balances::free_balance(&BOB), 50 * CENTS);
// BOB gives back half to ALICE in the relay chain
let destination: Location = Parent.into();
let beneficiary: Location =
AccountId32 { id: ALICE.clone().into(), network: Some(NetworkId::Pezkuwi) }.into();
// We specify `Parent` because we are referencing the Relay Chain token.
// This chain doesn't have a token of its own, so we always refer to this token,
// and we do so by the Location of the Relay Chain.
let assets: Assets = (Parent, 25u128 * CENTS as u128).into();
assert_ok!(teyrchain::XcmPallet::transfer_assets_using_type_and_then(
teyrchain::RuntimeOrigin::signed(BOB),
Box::new(VersionedLocation::from(destination)),
Box::new(VersionedAssets::from(assets)),
Box::new(TransferType::DestinationReserve),
Box::new(VersionedAssetId::from(AssetId(Location::parent()))),
Box::new(TransferType::DestinationReserve),
Box::new(VersionedXcm::from(
Xcm::<()>::builder_unsafe().deposit_asset(AllCounted(1), beneficiary).build()
)),
WeightLimit::Unlimited,
));
// BOB's balance decreased
assert_eq!(teyrchain::Balances::free_balance(&BOB), 25 * CENTS);
});
Relay::execute_with(|| {
// ALICE's balance increases
assert_eq!(
relay_chain::Balances::free_balance(&ALICE),
INITIAL_BALANCE - 50 * CENTS + 25 * CENTS
);
// The funds in the teyrchain's sovereign account decrease.
let teyrchain: Location = Teyrchain(2222).into();
let teyrchains_sovereign_account =
relay_chain::LocationToAccountId::convert_location(&teyrchain).unwrap();
assert_eq!(relay_chain::Balances::free_balance(teyrchains_sovereign_account), 25 * CENTS);
});
}
@@ -0,0 +1,56 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! # Runtime
use frame::{deps::frame_system, runtime::prelude::*, traits::IdentityLookup};
use xcm_executor::XcmExecutor;
use xcm_simulator::mock_message_queue;
mod xcm_config;
use xcm_config::XcmConfig;
pub type Block = frame_system::mocking::MockBlock<Runtime>;
pub type AccountId = frame::deps::sp_runtime::AccountId32;
pub type Balance = u64;
construct_runtime! {
pub struct Runtime {
System: frame_system,
MessageQueue: mock_message_queue,
Balances: pallet_balances,
XcmPallet: pallet_xcm,
}
}
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Runtime {
type Block = Block;
type AccountId = AccountId;
type Lookup = IdentityLookup<AccountId>;
type AccountData = pallet_balances::AccountData<Balance>;
}
impl mock_message_queue::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type XcmExecutor = XcmExecutor<XcmConfig>;
}
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
impl pallet_balances::Config for Runtime {
type AccountStore = System;
}
@@ -0,0 +1,195 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! # XCM Configuration
use frame::{
deps::frame_system,
runtime::prelude::*,
traits::{Disabled, Everything, Nothing},
};
use xcm::latest::prelude::*;
use xcm_builder::{
AccountId32Aliases, DescribeAllTerminal, DescribeFamily, EnsureXcmOrigin,
FrameTransactionalProcessor, FungibleAdapter, HashedDescription, IsConcrete,
SignedToAccountId32,
};
use xcm_executor::XcmExecutor;
use super::{AccountId, Balances, MessageQueue, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin};
parameter_types! {
pub RelayLocation: Location = Location::parent();
pub ThisNetwork: NetworkId = NetworkId::Pezkuwi;
}
pub type LocationToAccountId = (
HashedDescription<AccountId, DescribeFamily<DescribeAllTerminal>>,
AccountId32Aliases<ThisNetwork, AccountId>,
);
/// Configuration related to asset transactors
#[docify::export]
mod asset_transactor {
use super::*;
parameter_types! {
pub ParentRelayLocation: Location = Location::parent();
}
/// AssetTransactor for handling the relay chain token
pub type FungibleTransactor = FungibleAdapter<
// Use this implementation of the `fungible::*` traits.
// `Balances` is the name given to the balances pallet in this particular recipe.
// Any implementation of the traits would suffice.
Balances,
// This transactor deals with the native token of the Relay Chain.
// This token is referenced by the Location of the Relay Chain relative to this chain
// -- Location::parent().
IsConcrete<ParentRelayLocation>,
// How to convert an XCM Location into a local account id.
// This is also something that's configured in the XCM executor.
LocationToAccountId,
// The type for account ids, only needed because `fungible` is generic over it.
AccountId,
// Not tracking teleports.
// This recipe only uses reserve asset transfers to handle the Relay Chain token.
(),
>;
/// Actual configuration item that'll be set in the XCM config.
/// A tuple could be used here to have multiple transactors, each (potentially) handling
/// different assets.
/// In this recipe, we only have one.
pub type AssetTransactor = FungibleTransactor;
}
/// Configuration related to token reserves
#[docify::export]
mod is_reserve {
use super::*;
parameter_types! {
/// Reserves are specified using a pair `(AssetFilter, Location)`.
/// Each pair means that the specified Location is a reserve for all the assets in AssetsFilter.
/// Here, we are specifying that the Relay Chain is the reserve location for its native token.
pub RelayTokenForRelay: (AssetFilter, Location) =
(Wild(AllOf { id: AssetId(Parent.into()), fun: WildFungible }), Parent.into());
}
/// The wrapper type xcm_builder::Case is needed in order to use this in the configuration.
pub type IsReserve = xcm_builder::Case<RelayTokenForRelay>;
}
mod weigher {
use super::*;
use xcm_builder::FixedWeightBounds;
parameter_types! {
pub const WeightPerInstruction: Weight = Weight::from_parts(1, 1);
pub const MaxInstructions: u32 = 100;
}
pub type Weigher = FixedWeightBounds<WeightPerInstruction, RuntimeCall, MaxInstructions>;
}
parameter_types! {
pub UniversalLocation: InteriorLocation = [GlobalConsensus(NetworkId::Pezkuwi), Teyrchain(2222)].into();
}
pub struct XcmConfig;
impl xcm_executor::Config for XcmConfig {
type RuntimeCall = RuntimeCall;
type XcmSender = ();
type XcmEventEmitter = ();
type AssetTransactor = asset_transactor::AssetTransactor;
type OriginConverter = ();
// The declaration of which Locations are reserves for which Assets.
type IsReserve = is_reserve::IsReserve;
type IsTeleporter = ();
type UniversalLocation = UniversalLocation;
// This is not safe, you should use `xcm_builder::AllowTopLevelPaidExecutionFrom<T>` in a
// production chain
type Barrier = xcm_builder::AllowUnpaidExecutionFrom<Everything>;
type Weigher = weigher::Weigher;
type Trader = ();
type ResponseHandler = ();
type AssetTrap = ();
type AssetLocker = ();
type AssetExchanger = ();
type AssetClaims = ();
type SubscriptionService = ();
type PalletInstancesInfo = ();
type FeeManager = ();
type MaxAssetsIntoHolding = frame::traits::ConstU32<1>;
type MessageExporter = ();
type UniversalAliases = Nothing;
type CallDispatcher = RuntimeCall;
type SafeCallFilter = Everything;
type Aliasers = Nothing;
type TransactionalProcessor = FrameTransactionalProcessor;
type HrmpNewChannelOpenRequestHandler = ();
type HrmpChannelAcceptedHandler = ();
type HrmpChannelClosingHandler = ();
type XcmRecorder = ();
}
/// Converts a local signed origin into an XCM location. Forms the basis for local origins
/// sending/executing XCMs.
pub type LocalOriginToLocation = SignedToAccountId32<RuntimeOrigin, AccountId, ThisNetwork>;
impl pallet_xcm::Config for Runtime {
// We turn off sending for these tests
type SendXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, ()>;
type XcmRouter = super::super::network::TeyrchainXcmRouter<MessageQueue>; // Provided by xcm-simulator
// Anyone can execute XCM programs
type ExecuteXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
// We execute any type of program
type XcmExecuteFilter = Everything;
// How we execute programs
type XcmExecutor = XcmExecutor<XcmConfig>;
// We don't allow teleports
type XcmTeleportFilter = Nothing;
// We allow all reserve transfers
type XcmReserveTransferFilter = Everything;
// Same weigher executor uses to weigh XCM programs
type Weigher = weigher::Weigher;
// Same universal location
type UniversalLocation = UniversalLocation;
// No version discovery needed
const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 0;
type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion;
type AdminOrigin = frame_system::EnsureRoot<AccountId>;
// No locking
type TrustedLockers = ();
type MaxLockers = frame::traits::ConstU32<0>;
type MaxRemoteLockConsumers = frame::traits::ConstU32<0>;
type RemoteLockConsumerIdentifier = ();
// How to turn locations into accounts
type SovereignAccountOf = LocationToAccountId;
// A currency to pay for things and its matcher, we are using the relay token
type Currency = Balances;
type CurrencyMatcher = IsConcrete<RelayLocation>;
// Pallet benchmarks, no need for this recipe
type WeightInfo = pallet_xcm::TestWeightInfo;
// Runtime types
type RuntimeOrigin = RuntimeOrigin;
type RuntimeCall = RuntimeCall;
type RuntimeEvent = RuntimeEvent;
// Aliasing is disabled: xcm_executor::Config::Aliasers is set to `Nothing`.
type AuthorizedAliasConsideration = Disabled;
}
+178
View File
@@ -0,0 +1,178 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! # XCM Fundamentals
//!
//! XCM standardizes usual actions users take in consensus systems, for example
//! dealing with assets locally, on other chains, and locking them.
//! XCM programs can both be executed locally or sent to a different consensus system.
//! Examples of consensus systems are blockchains and smart contracts.
//!
//! The goal of XCM is to allow multi-chain ecosystems to thrive via specialization.
//! Very specific functionalities can be abstracted away and standardized in this common language.
//! Then, every member of the ecosystem can implement the subset of the language that makes sense
//! for them.
//!
//! The language evolves over time to accomodate the needs of the community
//! via the [RFC process](https://github.com/paritytech/xcm-format/blob/master/proposals/0032-process.md).
//!
//! XCM is the language, it deals with interpreting and executing programs.
//! It does not deal with actually **sending** these programs from one consensus system to another.
//! This responsibility falls to a transport protocol.
//! XCM can even be interpreted on the local system, with no need of a transport protocol.
//! However, automatic and composable workflows can be achieved via the use of one.
//!
//! At the core of XCM lies the XCVM, the Cross-Consensus Virtual Machine.
//! It's the virtual machine that executes XCM programs.
//! It is a specification that comes with the language.
//!
//! For these docs, we'll use a Rust implementation of XCM and the XCVM, consisting of the following
//! parts:
//! - [`XCM`](xcm): Holds the definition of an XCM program, the instructions and main concepts.
//! - [`Executor`](xcm_executor): Implements the XCVM, capable of executing XCMs. Highly
//! configurable.
//! - [`Builder`](xcm_builder): A collection of types used to configure the executor.
//! - [`XCM Pallet`](pallet_xcm): A FRAME pallet for interacting with the executor.
//! - [`Simulator`](xcm_simulator): A playground to tinker with different XCM programs and executor
//! configurations.
//!
//! XCM programs are composed of Instructions, which reference Locations and Assets.
//!
//! ## Locations
//!
//! Locations are XCM's vocabulary of places we want to talk about in our XCM programs.
//! They are used to reference things like 32-byte accounts, governance bodies, smart contracts,
//! blockchains and more.
//!
//! Locations are hierarchical.
//! This means some places in consensus are wholly encapsulated in other places.
//! Say we have two systems A and B.
//! If any change in A's state implies a change in B's state, then we say A is interior to B.
#![doc = simple_mermaid::mermaid!("../mermaid/location_hierarchy.mmd")]
//!
//! Teyrchains are interior to their Relay Chain, since a change in their state implies a change in
//! the Relay Chain's state.
//!
//! Because of this hierarchy, the way we represent locations is with both a number of **parents**,
//! times we move __up__ the hierarchy, and a sequence of **junctions**, the steps we take __down__
//! the hierarchy after going up the specified number of parents.
//!
//! In Rust, this is specified with the following datatype:
//! ```ignore
//! pub struct Location {
//! parents: u8,
//! interior: Junctions,
//! }
//! ```
//!
//! Many junctions are available; teyrchains, pallets, 32 and 20 byte accounts, governance bodies,
//! and arbitrary indices are the most common.
//! A full list of available junctions can be found in the [format](https://github.com/paritytech/xcm-format#interior-locations--junctions)
//! and [Junction enum](xcm::v4::prelude::Junction).
//!
//! We'll use a file system notation to represent locations, and start with relative locations.
//! In the diagram, the location of teyrchain 1000 as seen from all other locations is as follows:
//! - From the relaychain: `Teyrchain(1000)`
//! - From teyrchain 1000 itself: `Here`
//! - From teyrchain 2000: `../Teyrchain(1000)`
//!
//! Relative locations are interpreted by the system that is executing an XCM program, which is the
//! receiver of a message in the case where it's sent.
//!
//! Locations can also be absolute.
//! Keeping in line with our filesystem analogy, we can imagine the root of our filesystem to exist.
//! This would be a location with no parents, that is also the parent of all systems that derive
//! their own consensus, say Pezkuwi or Ethereum or Bitcoin.
//! Such a location does not exist concretely, but we can still use this definition for it.
//! This is the **universal location**.
//! We need the universal location to be able to describe locations in an absolute way.
#![doc = simple_mermaid::mermaid!("../mermaid/universal_location.mmd")]
//!
//! Here, the absolute location of teyrchain 1000 would be
//! `GlobalConsensus(Pezkuwi)/Teyrchain(1000)`.
//!
//! ## Assets
//!
//! We want to be able to reference assets in our XCM programs, if only to be able to pay for fees.
//! Assets are represented using locations.
//!
//! The native asset of a chain is represented by the location of that chain.
//! For example, HEZ is represented by the location of the Pezkuwi relaychain.
//! If the interpreting chain has its own asset, it would be represented by `Here`.
//!
//! How do we represent other assets?
//! The asset hub system teyrchain in Pezkuwi, for example, holds a lot of assets.
//! To represent each of them, it uses the indices we mentioned, and it makes them interior to the
//! assets pallet instance it uses.
//! USDT, an example asset that lives on asset hub, is identified by the location
//! `Teyrchain(1000)/PalletInstance(53)/GeneralIndex(1984)`, when seen from the Pezkuwi relaychain.
#![doc = simple_mermaid::mermaid!("../mermaid/usdt_location.mmd")]
//!
//! Asset Hub also has another type of assets called `ForeignAssets`.
//! These assets are identified by the XCM Location to their origin.
//! Two such assets are a Teyrchain asset, like Moonbeam's GLMR, and KSM, from the cousin Kusama
//! network. These are represented as `../Teyrchain(2004)/PalletInstance(10)` and
//! `../../GlobalConsensus(Kusama)` respectively.
//!
//! The whole type can be seen in the [format](https://github.com/paritytech/xcm-format#6-universal-asset-identifiers)
//! and [rust docs](xcm::v4::prelude::Asset).
//!
//! ## Instructions
//!
//! Given the vocabulary to talk about both locations -- chains and accounts -- and assets, we now
//! need a way to express what we want the consensus system to do when executing our programs.
//! We need a way of writing our programs.
//!
//! XCM programs are composed of a sequence of instructions.
//!
//! All available instructions can be seen in the [format](https://github.com/paritytech/xcm-format#5-the-xcvm-instruction-set)
//! and the [Instruction enum](xcm::v4::prelude::Instruction).
//!
//! A very simple example is the following:
//!
//! ```ignore
//! let message = Xcm(vec![
//! TransferAsset { assets, beneficiary },
//! ]);
//! ```
//!
//! This instruction is enough to transfer `assets` from the account of the **origin** of a message
//! to the `beneficiary` account. However, because of XCM's generality, fees need to be paid
//! explicitly. This next example sheds more light on this:
//!
//! ```ignore
//! let message = Xcm(vec![
//! WithdrawAsset(assets),
//! BuyExecution { fees: assets, weight_limit },
//! DepositAsset { assets: AssetFilter(Wild(All)), beneficiary },
//! ]);
//! ```
//!
//! Here we see the process of transferring assets was broken down into smaller instructions, and we
//! add the explicit fee payment step in the middle.
//! `WithdrawAsset` withdraws assets from the account of the **origin** of the message for usage
//! inside this message's execution. `BuyExecution` explicitly buys execution for this program using
//! the assets specified in `fees`, with a sanity check of `weight_limit`. `DepositAsset` uses a
//! wildcard, specifying all remaining `assets` after subtracting the fees and a `beneficiary`
//! account.
//!
//! ## Next steps
//!
//! Continue with the [guides](crate::guides) for step-by-step tutorials on XCM,
//! or jump to the [cookbook](crate::cookbook) to see examples.
//!
//! The [glossary](crate::glossary) can be useful if some of the terms are confusing.
+124
View File
@@ -0,0 +1,124 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! # Glossary
//!
//! ## XCM (Cross-Consensus Messaging)
//!
//! A messaging format meant to communicate intentions between consensus systems.
//! XCM could also refer to a single message.
//!
//! ## Instructions
//!
//! XCMs are composed of a sequence of instructions.
//! Each instruction aims to convey a particular intention.
//! There are instructions for transferring and locking assets, handling fees, calling arbitrary
//! blobs, and more.
//!
//! ## Consensus system
//!
//! A system that can reach any kind of consensus.
//! For example, relay chains, teyrchains, smart contracts.
//! Most messaging between consensus systems has to be done asynchronously, for this, XCM is used.
//! Between two smart contracts on the same teyrchain, however, communication can be done
//! synchronously.
//!
//! ## [`Location`](xcm::v4::prelude::Location)
//!
//! A way of addressing consensus systems.
//! These could be relative or absolute.
//!
//! ## [`Junction`](xcm::v4::prelude::Junction)
//!
//! The different ways of descending down a [`Location`](xcm::v4::prelude::Location) hierarchy.
//! A junction can be a Teyrchain, an Account, or more.
//!
//! ## [`Asset`](xcm::v4::prelude::Asset)
//!
//! A way of identifying assets in the same or another consensus system, by using a
//! [`Location`](xcm::v4::prelude::Location).
//!
//! ## Sovereign account
//!
//! An account in a consensus system that is controlled by an account in another consensus system.
//!
//! Runtimes use a converter between a [`Location`](xcm::v4::prelude::Location) and an account.
//! These converters implement the [`ConvertLocation`](xcm_executor::traits::ConvertLocation) trait.
//!
//! ## Teleport
//!
//! A way of transferring assets between two consensus systems without the need of a third party.
//! It consists of the sender system burning the asset that wants to be sent over and the recipient
//! minting an equivalent amount of that asset. It requires a lot of trust between the two systems,
//! since failure to mint or burn will reduce or increase the total issuance of the token.
//!
//! ## Reserve asset transfer
//!
//! A way of transferring assets between two consensus systems that don't trust each other, by using
//! a third system they both trust, called the reserve. The real asset only exists on the reserve,
//! both sender and recipient only deal with derivatives. It consists of the sender burning a
//! certain amount of derivatives, telling the reserve to move real assets from its sovereign
//! account to the destination's sovereign account, and then telling the recipient to mint the right
//! amount of derivatives.
//! In practice, the reserve chain can also be one of the source or destination.
//!
//! ## XCVM
//!
//! The virtual machine behind XCM.
//! Every XCM is an XCVM programme.
//! Holds state in registers.
//!
//! An implementation of the virtual machine is the [`xcm-executor`](xcm_executor::XcmExecutor).
//!
//! ## Holding register
//!
//! An XCVM register used to hold arbitrary `Asset`s during the execution of an XCVM programme.
//!
//! ## Barrier
//!
//! An XCM executor configuration item that works as a firewall for incoming XCMs.
//! All XCMs have to pass the barrier to be executed, else they are dropped.
//! It can be used for whitelisting only certain types or messages or messages from certain senders.
//!
//! Lots of barrier definitions exist in [`xcm-builder`](xcm_builder).
//!
//! ## VMP (Vertical Message Passing)
//!
//! Umbrella term for both UMP (Upward Message Passing) and DMP (Downward Message Passing).
//!
//! The following diagram shows the uses of both protocols:
#![doc = simple_mermaid::mermaid!("../mermaid/transport_protocols.mmd")]
//!
//! ## UMP (Upward Message Passing)
//!
//! Transport-layer protocol that allows teyrchains to send messages upwards to their relay chain.
//!
//! ## DMP (Downward Message Passing)
//!
//! Transport-layer protocol that allows the relay chain to send messages downwards to one of their
//! teyrchains.
//!
//! ## XCMP (Cross-Consensus Message Passing)
//!
//! Transport-layer protocol that allows teyrchains to send messages between themselves, without
//! going through the relay chain.
//!
//! ## HRMP (Horizontal Message Passing)
//!
//! Transport-layer protocol that allows a teyrchain to send messages to a sibling teyrchain going
//! through the relay chain. It's a precursor to XCMP, also known as XCMP-lite.
//! It uses a mixture of UMP and DMP.
+26
View File
@@ -0,0 +1,26 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! # XCM Guides
//!
//! These guides aim to get you up and running with XCM.
//!
//! Coming soon.
//!
//! ## Next steps
//!
//! Jump to the [cookbook](crate::cookbook) for different examples.
+66
View File
@@ -0,0 +1,66 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! # XCM Docs
//!
//! Documentation and guides for XCM
//!
//! Welcome to the Cross-Consensus Messaging documentation!
//!
//! XCM is a **language** for communicating **intentions** between **consensus systems**.
//! Whether you're a developer, a blockchain enthusiast, or just interested in Pezkuwi, this guide
//! aims to provide you with an easy-to-understand and comprehensive introduction to XCM.
//!
//! ## Getting started
//!
//! Head over to the [fundamentals](fundamentals) section.
//! Then, go to the [guides](guides), to learn about how to do things with XCM.
//!
//! ## Cookbook
//!
//! There's also the [cookbook](cookbook) for useful recipes for XCM.
//!
//! ## Glossary
//!
//! There's a [glossary](glossary) with common terms used throughout the docs.
//!
//! ## Contribute
//!
//! To contribute to the format, check out the [RFC process](https://github.com/paritytech/xcm-format/blob/master/proposals/0032-process.md).
//! To contribute to these docs, [make a PR](https://github.com/pezkuwichain/pezkuwi-sdk).
//!
//! ## Why Rust Docs?
//!
//! Rust Docs allow docs to be as close to the source as possible.
//! They're also available offline automatically for anyone who has the `pezkuwi-sdk` repo locally.
//!
//! ## Docs structure
#![doc = simple_mermaid::mermaid!("../mermaid/structure.mmd")]
// Frame macros reference features which this crate does not have
#![allow(unexpected_cfgs)]
/// Fundamentals of the XCM language. The virtual machine, instructions, locations and assets.
pub mod fundamentals;
/// Step-by-step guides to set up an XCM environment and start hacking.
pub mod guides;
/// Useful recipes for programs and configurations.
pub mod cookbook;
/// Glossary
pub mod glossary;
@@ -0,0 +1,57 @@
[package]
name = "pallet-xcm-benchmarks"
authors.workspace = true
edition.workspace = true
license.workspace = true
version = "7.0.0"
description = "Benchmarks for the XCM pallet"
homepage.workspace = true
repository.workspace = true
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { workspace = true }
frame-benchmarking = { workspace = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
scale-info = { features = ["derive"], workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
xcm = { workspace = true }
xcm-builder = { workspace = true }
xcm-executor = { workspace = true }
[dev-dependencies]
pallet-balances = { workspace = true, default-features = true }
sp-tracing = { workspace = true, default-features = true }
xcm = { workspace = true, default-features = true }
[features]
default = ["std"]
std = [
"codec/std",
"frame-benchmarking/std",
"frame-support/std",
"frame-system/std",
"scale-info/std",
"sp-io/std",
"sp-runtime/std",
"xcm-builder/std",
"xcm-executor/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"sp-io/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
"xcm-builder/runtime-benchmarks",
"xcm-executor/runtime-benchmarks",
"xcm/runtime-benchmarks",
]
@@ -0,0 +1,339 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
use super::*;
use crate::{account_and_location, new_executor, AssetTransactorOf, EnsureDelivery, XcmCallOf};
use alloc::{vec, vec::Vec};
use frame_benchmarking::{benchmarks_instance_pallet, BenchmarkError, BenchmarkResult};
use frame_support::{
pallet_prelude::Get,
traits::fungible::{Inspect, Mutate},
weights::Weight,
BoundedVec,
};
use sp_runtime::traits::Bounded;
use xcm::latest::{prelude::*, AssetTransferFilter, MAX_ITEMS_IN_ASSETS};
use xcm_executor::traits::{ConvertLocation, FeeReason, TransactAsset};
benchmarks_instance_pallet! {
where_clause { where
<
<
T::TransactAsset
as
Inspect<T::AccountId>
>::Balance
as
TryInto<u128>
>::Error: core::fmt::Debug,
}
withdraw_asset {
let (sender_account, sender_location) = account_and_location::<T>(1);
let worst_case_holding = T::worst_case_holding(0);
let asset = T::get_asset();
<AssetTransactorOf<T>>::deposit_asset(&asset, &sender_location, None).unwrap();
let mut executor = new_executor::<T>(sender_location);
executor.set_holding(worst_case_holding.into());
let instruction = Instruction::<XcmCallOf<T>>::WithdrawAsset(vec![asset.clone()].into());
let xcm = Xcm(vec![instruction]);
}: {
executor.bench_process(xcm)?;
} verify {
assert!(executor.holding().ensure_contains(&vec![asset].into()).is_ok());
}
transfer_asset {
let (sender_account, sender_location) = account_and_location::<T>(1);
let asset = T::get_asset();
let assets: Assets = vec![asset.clone()].into();
// this xcm doesn't use holding
let dest_location = T::valid_destination()?;
let dest_account = T::AccountIdConverter::convert_location(&dest_location).unwrap();
<AssetTransactorOf<T>>::deposit_asset(&asset, &sender_location, None).unwrap();
// We deposit the asset twice so we have enough for ED after transferring
<AssetTransactorOf<T>>::deposit_asset(&asset, &sender_location, None).unwrap();
let mut executor = new_executor::<T>(sender_location);
let instruction = Instruction::TransferAsset { assets, beneficiary: dest_location };
let xcm = Xcm(vec![instruction]);
}: {
executor.bench_process(xcm)?;
} verify {
}
transfer_reserve_asset {
let (sender_account, sender_location) = account_and_location::<T>(1);
let dest_location = T::valid_destination()?;
let dest_account = T::AccountIdConverter::convert_location(&dest_location).unwrap();
let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery(
&sender_location,
&dest_location,
FeeReason::TransferReserveAsset
);
let asset = T::get_asset();
<AssetTransactorOf<T>>::deposit_asset(&asset, &sender_location, None).unwrap();
// We deposit the asset twice so we have enough for ED after transferring
<AssetTransactorOf<T>>::deposit_asset(&asset, &sender_location, None).unwrap();
let assets: Assets = vec![asset].into();
let mut executor = new_executor::<T>(sender_location);
if let Some(expected_fees_mode) = expected_fees_mode {
executor.set_fees_mode(expected_fees_mode);
}
if let Some(expected_assets_in_holding) = expected_assets_in_holding {
executor.set_holding(expected_assets_in_holding.into());
}
let instruction = Instruction::TransferReserveAsset {
assets,
dest: dest_location,
xcm: Xcm::new()
};
let xcm = Xcm(vec![instruction]);
}: {
executor.bench_process(xcm)?;
} verify {
// TODO: Check sender queue is not empty. #4426
}
reserve_asset_deposited {
let (trusted_reserve, transferable_reserve_asset) = T::TrustedReserve::get()
.ok_or(BenchmarkError::Override(
BenchmarkResult::from_weight(Weight::MAX)
))?;
let assets: Assets = vec![ transferable_reserve_asset ].into();
let mut executor = new_executor::<T>(trusted_reserve);
let instruction = Instruction::ReserveAssetDeposited(assets.clone());
let xcm = Xcm(vec![instruction]);
}: {
executor.bench_process(xcm)?;
} verify {
assert!(executor.holding().ensure_contains(&assets).is_ok());
}
initiate_reserve_withdraw {
let (sender_account, sender_location) = account_and_location::<T>(1);
let reserve = T::valid_destination().map_err(|_| BenchmarkError::Skip)?;
let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery(
&sender_location,
&reserve,
FeeReason::InitiateReserveWithdraw,
);
let sender_account_balance_before = T::TransactAsset::balance(&sender_account);
// generate holding and add possible required fees
let holding = if let Some(expected_assets_in_holding) = expected_assets_in_holding {
let mut holding = T::worst_case_holding(1 + expected_assets_in_holding.len() as u32);
for a in expected_assets_in_holding.into_inner() {
holding.push(a);
}
holding
} else {
T::worst_case_holding(1)
};
let mut executor = new_executor::<T>(sender_location);
executor.set_holding(holding.clone().into());
if let Some(expected_fees_mode) = expected_fees_mode {
executor.set_fees_mode(expected_fees_mode);
}
let instruction = Instruction::InitiateReserveWithdraw {
// Worst case is looking through all holdings for every asset explicitly - respecting the limit `MAX_ITEMS_IN_ASSETS`.
assets: Definite(holding.into_inner().into_iter().take(MAX_ITEMS_IN_ASSETS).collect::<Vec<_>>().into()),
reserve,
xcm: Xcm(vec![])
};
let xcm = Xcm(vec![instruction]);
}: {
executor.bench_process(xcm)?;
} verify {
// Check we charged the delivery fees
assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before);
// The execute completing successfully is as good as we can check.
// TODO: Potentially add new trait to XcmSender to detect a queued outgoing message. #4426
}
receive_teleported_asset {
// If there is no trusted teleporter, then we skip this benchmark.
let (trusted_teleporter, teleportable_asset) = T::TrustedTeleporter::get()
.ok_or(BenchmarkError::Skip)?;
if let Some((checked_account, _)) = T::CheckedAccount::get() {
T::TransactAsset::mint_into(
&checked_account,
<
T::TransactAsset
as
Inspect<T::AccountId>
>::Balance::max_value() / 2u32.into(),
)?;
}
let assets: Assets = vec![ teleportable_asset ].into();
let mut executor = new_executor::<T>(trusted_teleporter);
let instruction = Instruction::ReceiveTeleportedAsset(assets.clone());
let xcm = Xcm(vec![instruction]);
}: {
executor.bench_process(xcm).map_err(|_| {
BenchmarkError::Override(
BenchmarkResult::from_weight(Weight::MAX)
)
})?;
} verify {
assert!(executor.holding().ensure_contains(&assets).is_ok());
}
deposit_asset {
let asset = T::get_asset();
let mut holding = T::worst_case_holding(1);
// Add our asset to the holding.
holding.push(asset.clone());
// our dest must have no balance initially.
let dest_location = T::valid_destination()?;
let dest_account = T::AccountIdConverter::convert_location(&dest_location).unwrap();
// Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...)
let (_, _) = T::DeliveryHelper::ensure_successful_delivery(
&Default::default(),
&dest_location,
FeeReason::ChargeFees,
);
let mut executor = new_executor::<T>(Default::default());
executor.set_holding(holding.into());
let instruction = Instruction::<XcmCallOf<T>>::DepositAsset {
assets: asset.into(),
beneficiary: dest_location,
};
let xcm = Xcm(vec![instruction]);
}: {
executor.bench_process(xcm)?;
} verify {
}
deposit_reserve_asset {
let asset = T::get_asset();
let mut holding = T::worst_case_holding(1);
// Add our asset to the holding.
holding.push(asset.clone());
// our dest must have no balance initially.
let dest_location = T::valid_destination()?;
let dest_account = T::AccountIdConverter::convert_location(&dest_location).unwrap();
// Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...)
let (_, _) = T::DeliveryHelper::ensure_successful_delivery(
&Default::default(),
&dest_location,
FeeReason::ChargeFees,
);
let mut executor = new_executor::<T>(Default::default());
executor.set_holding(holding.into());
let instruction = Instruction::<XcmCallOf<T>>::DepositReserveAsset {
assets: asset.into(),
dest: dest_location,
xcm: Xcm::new(),
};
let xcm = Xcm(vec![instruction]);
}: {
executor.bench_process(xcm)?;
} verify {
}
initiate_teleport {
let asset = T::get_asset();
let mut holding = T::worst_case_holding(0);
// Add our asset to the holding.
holding.push(asset.clone());
let dest_location = T::valid_destination()?;
// Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...)
let (_, _) = T::DeliveryHelper::ensure_successful_delivery(
&Default::default(),
&dest_location,
FeeReason::ChargeFees,
);
let mut executor = new_executor::<T>(Default::default());
executor.set_holding(holding.into());
let instruction = Instruction::<XcmCallOf<T>>::InitiateTeleport {
assets: asset.into(),
dest: dest_location,
xcm: Xcm::new(),
};
let xcm = Xcm(vec![instruction]);
}: {
executor.bench_process(xcm)?;
} verify {
}
initiate_transfer {
let (sender_account, sender_location) = account_and_location::<T>(1);
let asset = T::get_asset();
let mut holding = T::worst_case_holding(1);
let dest_location = T::valid_destination()?;
// Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...)
let (_, _) = T::DeliveryHelper::ensure_successful_delivery(
&sender_location,
&dest_location,
FeeReason::ChargeFees,
);
// Add our asset to the holding.
holding.push(asset.clone());
let mut executor = new_executor::<T>(sender_location);
executor.set_holding(holding.into());
let instruction = Instruction::<XcmCallOf<T>>::InitiateTransfer {
destination: dest_location,
// ReserveDeposit is the most expensive filter.
remote_fees: Some(AssetTransferFilter::ReserveDeposit(asset.clone().into())),
// It's more expensive if we reanchor the origin.
preserve_origin: true,
assets: BoundedVec::truncate_from(vec![AssetTransferFilter::ReserveDeposit(asset.into())]),
remote_xcm: Xcm::new(),
};
let xcm = Xcm(vec![instruction]);
}: {
executor.bench_process(xcm)?;
} verify {
}
impl_benchmark_test_suite!(
Pallet,
crate::fungible::mock::new_test_ext(),
crate::fungible::mock::Test
);
}
@@ -0,0 +1,181 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! A mock runtime for XCM benchmarking.
use crate::{fungible as xcm_balances_benchmark, generate_holding_assets, mock::*};
use frame_benchmarking::BenchmarkError;
use frame_support::{
derive_impl, parameter_types,
traits::{Everything, Nothing},
};
use xcm::latest::prelude::*;
use xcm_builder::{
AllowUnpaidExecutionFrom, EnsureDecodableXcm, FrameTransactionalProcessor, MintLocation,
};
type Block = frame_system::mocking::MockBlock<Test>;
// For testing the pallet, we construct a mock runtime.
frame_support::construct_runtime!(
pub enum Test
{
System: frame_system,
Balances: pallet_balances,
XcmBalancesBenchmark: xcm_balances_benchmark,
}
);
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Test {
type Block = Block;
type AccountData = pallet_balances::AccountData<u64>;
}
parameter_types! {
pub const ExistentialDeposit: u64 = 7;
}
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
impl pallet_balances::Config for Test {
type ReserveIdentifier = [u8; 8];
type AccountStore = System;
}
parameter_types! {
pub const AssetDeposit: u64 = 100 * ExistentialDeposit::get();
pub const ApprovalDeposit: u64 = 1 * ExistentialDeposit::get();
pub const StringLimit: u32 = 50;
pub const MetadataDepositBase: u64 = 10 * ExistentialDeposit::get();
pub const MetadataDepositPerByte: u64 = 1 * ExistentialDeposit::get();
}
pub struct MatchAnyFungible;
impl xcm_executor::traits::MatchesFungible<u64> for MatchAnyFungible {
fn matches_fungible(m: &Asset) -> Option<u64> {
use sp_runtime::traits::SaturatedConversion;
match m {
Asset { fun: Fungible(amount), .. } => Some((*amount).saturated_into::<u64>()),
_ => None,
}
}
}
// Use balances as the asset transactor.
pub type AssetTransactor = xcm_builder::FungibleAdapter<
Balances,
MatchAnyFungible,
AccountIdConverter,
u64,
CheckingAccount,
>;
parameter_types! {
/// Maximum number of instructions in a single XCM fragment. A sanity check against weight
/// calculations getting too crazy.
pub const MaxInstructions: u32 = 100;
pub const MaxAssetsIntoHolding: u32 = 64;
}
pub struct XcmConfig;
impl xcm_executor::Config for XcmConfig {
type RuntimeCall = RuntimeCall;
type XcmSender = EnsureDecodableXcm<DevNull>;
type XcmEventEmitter = ();
type AssetTransactor = AssetTransactor;
type OriginConverter = ();
type IsReserve = TrustedReserves;
type IsTeleporter = TrustedTeleporters;
type UniversalLocation = UniversalLocation;
type Barrier = AllowUnpaidExecutionFrom<Everything>;
type Weigher = xcm_builder::FixedWeightBounds<UnitWeightCost, RuntimeCall, MaxInstructions>;
type Trader = xcm_builder::FixedRateOfFungible<WeightPrice, ()>;
type ResponseHandler = DevNull;
type AssetTrap = ();
type AssetLocker = ();
type AssetExchanger = ();
type AssetClaims = ();
type SubscriptionService = ();
type PalletInstancesInfo = AllPalletsWithSystem;
type MaxAssetsIntoHolding = MaxAssetsIntoHolding;
type FeeManager = ();
type MessageExporter = ();
type UniversalAliases = Nothing;
type CallDispatcher = RuntimeCall;
type SafeCallFilter = Everything;
type Aliasers = Nothing;
type TransactionalProcessor = FrameTransactionalProcessor;
type HrmpNewChannelOpenRequestHandler = ();
type HrmpChannelAcceptedHandler = ();
type HrmpChannelClosingHandler = ();
type XcmRecorder = ();
}
impl crate::Config for Test {
type XcmConfig = XcmConfig;
type AccountIdConverter = AccountIdConverter;
type DeliveryHelper = ();
fn valid_destination() -> Result<Location, BenchmarkError> {
let valid_destination: Location = [AccountId32 { network: None, id: [0u8; 32] }].into();
Ok(valid_destination)
}
fn worst_case_holding(depositable_count: u32) -> Assets {
generate_holding_assets(
<XcmConfig as xcm_executor::Config>::MaxAssetsIntoHolding::get() - depositable_count,
)
}
}
pub type TrustedTeleporters = xcm_builder::Case<TeleportConcreteFungible>;
pub type TrustedReserves = xcm_builder::Case<ReserveConcreteFungible>;
parameter_types! {
pub const CheckingAccount: Option<(u64, MintLocation)> = Some((100, MintLocation::Local));
pub ChildTeleporter: Location = Teyrchain(1000).into_location();
pub TrustedTeleporter: Option<(Location, Asset)> = Some((
ChildTeleporter::get(),
Asset { id: AssetId(Here.into_location()), fun: Fungible(100) },
));
pub TrustedReserve: Option<(Location, Asset)> = Some((
ChildTeleporter::get(),
Asset { id: AssetId(Here.into_location()), fun: Fungible(100) },
));
pub TeleportConcreteFungible: (AssetFilter, Location) =
(Wild(AllOf { fun: WildFungible, id: AssetId(Here.into_location()) }), ChildTeleporter::get());
pub ReserveConcreteFungible: (AssetFilter, Location) =
(Wild(AllOf { fun: WildFungible, id: AssetId(Here.into_location()) }), ChildTeleporter::get());
}
impl xcm_balances_benchmark::Config for Test {
type TransactAsset = Balances;
type CheckedAccount = CheckingAccount;
type TrustedTeleporter = TrustedTeleporter;
type TrustedReserve = TrustedReserve;
fn get_asset() -> Asset {
let amount = 1_000_000_000_000;
Asset { id: AssetId(Here.into()), fun: Fungible(amount) }
}
}
#[cfg(feature = "runtime-benchmarks")]
pub fn new_test_ext() -> sp_io::TestExternalities {
use sp_runtime::BuildStorage;
let t = RuntimeGenesisConfig { ..Default::default() }.build_storage().unwrap();
sp_tracing::try_init_simple();
t.into()
}
@@ -0,0 +1,52 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
// Benchmarking for the `AssetTransactor` trait via `Fungible`.
pub use pallet::*;
#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking;
#[cfg(test)]
mod mock;
#[frame_support::pallet]
pub mod pallet {
use frame_support::pallet_prelude::Get;
#[pallet::config]
pub trait Config<I: 'static = ()>: frame_system::Config + crate::Config {
/// The type of `fungible` that is being used under the hood.
///
/// This is useful for testing and checking.
type TransactAsset: frame_support::traits::fungible::Mutate<Self::AccountId>;
/// The account used to check assets being teleported.
type CheckedAccount: Get<Option<(Self::AccountId, xcm_builder::MintLocation)>>;
/// A trusted location which we allow teleports from, and the asset we allow to teleport.
type TrustedTeleporter: Get<Option<(xcm::latest::Location, xcm::latest::Asset)>>;
/// A trusted location where reserve assets are stored, and the asset we allow to be
/// reserves.
type TrustedReserve: Get<Option<(xcm::latest::Location, xcm::latest::Asset)>>;
/// Give me a fungible asset that your asset transactor is going to accept.
fn get_asset() -> xcm::latest::Asset;
}
#[pallet::pallet]
pub struct Pallet<T, I = ()>(_);
}
@@ -0,0 +1,969 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
#![cfg(feature = "runtime-benchmarks")]
use super::*;
use crate::{account_and_location, new_executor, EnsureDelivery, XcmCallOf};
use alloc::{vec, vec::Vec};
use codec::Encode;
use frame_benchmarking::v2::*;
use frame_support::{traits::fungible::Inspect, BoundedVec};
use xcm::{
latest::{prelude::*, MaxDispatchErrorLen, MaybeErrorCode, Weight, MAX_ITEMS_IN_ASSETS},
DoubleEncoded,
};
use xcm_executor::{
traits::{ConvertLocation, FeeReason},
ExecutorError, FeesMode,
};
#[benchmarks]
mod benchmarks {
use super::*;
#[benchmark]
fn report_holding() -> Result<(), BenchmarkError> {
let (sender_account, sender_location) = account_and_location::<T>(1);
let destination = T::valid_destination().map_err(|_| BenchmarkError::Skip)?;
let (expected_fees_mode, expected_assets_in_holding) =
T::DeliveryHelper::ensure_successful_delivery(
&sender_location,
&destination,
FeeReason::Report,
);
let sender_account_balance_before = T::TransactAsset::balance(&sender_account);
// generate holding and add possible required fees
let holding = if let Some(expected_assets_in_holding) = expected_assets_in_holding {
let mut holding = T::worst_case_holding(expected_assets_in_holding.len() as u32);
for a in expected_assets_in_holding.into_inner() {
holding.push(a);
}
holding
} else {
T::worst_case_holding(0)
};
let mut executor = new_executor::<T>(sender_location);
executor.set_holding(holding.clone().into());
if let Some(expected_fees_mode) = expected_fees_mode {
executor.set_fees_mode(expected_fees_mode);
}
let instruction = Instruction::<XcmCallOf<T>>::ReportHolding {
response_info: QueryResponseInfo {
destination,
query_id: Default::default(),
max_weight: Weight::MAX,
},
// Worst case is looking through all holdings for every asset explicitly - respecting
// the limit `MAX_ITEMS_IN_ASSETS`.
assets: Definite(
holding
.into_inner()
.into_iter()
.take(MAX_ITEMS_IN_ASSETS)
.collect::<Vec<_>>()
.into(),
),
};
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
// Check we charged the delivery fees
assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before);
Ok(())
}
// This benchmark does not use any additional orders or instructions. This should be managed
// by the `deep` and `shallow` implementation.
#[benchmark]
fn buy_execution() -> Result<(), BenchmarkError> {
let holding = T::worst_case_holding(0).into();
let mut executor = new_executor::<T>(Default::default());
executor.set_holding(holding);
// The worst case we want for buy execution in terms of
// fee asset and weight
let (fee_asset, weight_limit) = T::worst_case_for_trader()?;
let instruction = Instruction::<XcmCallOf<T>>::BuyExecution {
fees: fee_asset,
weight_limit: weight_limit.into(),
};
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
Ok(())
}
#[benchmark]
fn pay_fees() -> Result<(), BenchmarkError> {
let holding = T::worst_case_holding(0).into();
let mut executor = new_executor::<T>(Default::default());
executor.set_holding(holding);
// Set some weight to be paid for.
executor.set_message_weight(Weight::from_parts(100_000_000, 100_000));
let (fee_asset, _): (Asset, WeightLimit) = T::worst_case_for_trader().unwrap();
let instruction = Instruction::<XcmCallOf<T>>::PayFees { asset: fee_asset };
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
Ok(())
}
#[benchmark]
fn asset_claimer() -> Result<(), BenchmarkError> {
let mut executor = new_executor::<T>(Default::default());
let (_, sender_location) = account_and_location::<T>(1);
let instruction = Instruction::SetHints {
hints: BoundedVec::<Hint, HintNumVariants>::truncate_from(vec![AssetClaimer {
location: sender_location.clone(),
}]),
};
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
assert_eq!(executor.asset_claimer(), Some(sender_location.clone()));
Ok(())
}
#[benchmark]
fn query_response() -> Result<(), BenchmarkError> {
let mut executor = new_executor::<T>(Default::default());
let (query_id, response) = T::worst_case_response();
let max_weight = Weight::MAX;
let querier: Option<Location> = Some(Here.into());
let instruction = Instruction::QueryResponse { query_id, response, max_weight, querier };
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
// The assert above is enough to show this XCM succeeded
Ok(())
}
// We don't care about the call itself, since that is accounted for in the weight parameter
// and included in the final weight calculation. So this is just the overhead of submitting
// a noop call.
#[benchmark]
fn transact() -> Result<(), BenchmarkError> {
let (origin, noop_call) = T::transact_origin_and_runtime_call()?;
let mut executor = new_executor::<T>(origin);
let double_encoded_noop_call: DoubleEncoded<_> = noop_call.encode().into();
let instruction = Instruction::Transact {
origin_kind: OriginKind::SovereignAccount,
call: double_encoded_noop_call,
fallback_max_weight: None,
};
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
// TODO Make the assertion configurable?
Ok(())
}
#[benchmark]
fn refund_surplus() -> Result<(), BenchmarkError> {
let mut executor = new_executor::<T>(Default::default());
let holding_assets = T::worst_case_holding(1);
// We can already buy execution since we'll load the holding register manually
let (asset_for_fees, _): (Asset, WeightLimit) = T::worst_case_for_trader().unwrap();
let previous_xcm = Xcm(vec![BuyExecution {
fees: asset_for_fees,
weight_limit: Limited(Weight::from_parts(1337, 1337)),
}]);
executor.set_holding(holding_assets.into());
executor.set_total_surplus(Weight::from_parts(1337, 1337));
executor.set_total_refunded(Weight::zero());
executor
.bench_process(previous_xcm)
.expect("Holding has been loaded, so we can buy execution here");
let instruction = Instruction::<XcmCallOf<T>>::RefundSurplus;
let xcm = Xcm(vec![instruction]);
#[block]
{
let _result = executor.bench_process(xcm)?;
}
assert_eq!(executor.total_surplus(), &Weight::from_parts(1337, 1337));
assert_eq!(executor.total_refunded(), &Weight::from_parts(1337, 1337));
Ok(())
}
#[benchmark]
fn set_error_handler() -> Result<(), BenchmarkError> {
let mut executor = new_executor::<T>(Default::default());
let instruction = Instruction::<XcmCallOf<T>>::SetErrorHandler(Xcm(vec![]));
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
assert_eq!(executor.error_handler(), &Xcm(vec![]));
Ok(())
}
#[benchmark]
fn set_appendix() -> Result<(), BenchmarkError> {
let mut executor = new_executor::<T>(Default::default());
let appendix = Xcm(vec![]);
let instruction = Instruction::<XcmCallOf<T>>::SetAppendix(appendix);
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
assert_eq!(executor.appendix(), &Xcm(vec![]));
Ok(())
}
#[benchmark]
fn clear_error() -> Result<(), BenchmarkError> {
let mut executor = new_executor::<T>(Default::default());
executor.set_error(Some((5u32, XcmError::Overflow)));
let instruction = Instruction::<XcmCallOf<T>>::ClearError;
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
assert!(executor.error().is_none());
Ok(())
}
#[benchmark]
fn descend_origin() -> Result<(), BenchmarkError> {
let mut executor = new_executor::<T>(Default::default());
let who = Junctions::from([OnlyChild, OnlyChild]);
let instruction = Instruction::DescendOrigin(who.clone());
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
assert_eq!(executor.origin(), &Some(Location { parents: 0, interior: who }),);
Ok(())
}
#[benchmark]
fn execute_with_origin() -> Result<(), BenchmarkError> {
let mut executor = new_executor::<T>(Default::default());
let who: Junctions = Junctions::from([AccountId32 { id: [0u8; 32], network: None }]);
let instruction = Instruction::ExecuteWithOrigin {
descendant_origin: Some(who.clone()),
xcm: Xcm(vec![]),
};
let xcm = Xcm(vec![instruction]);
#[block]
{
executor
.bench_process(xcm)
.map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
}
assert_eq!(executor.origin(), &Some(Location { parents: 0, interior: Here }),);
Ok(())
}
#[benchmark]
fn clear_origin() -> Result<(), BenchmarkError> {
let mut executor = new_executor::<T>(Default::default());
let instruction = Instruction::ClearOrigin;
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
assert_eq!(executor.origin(), &None);
Ok(())
}
#[benchmark]
fn report_error() -> Result<(), BenchmarkError> {
let (sender_account, sender_location) = account_and_location::<T>(1);
let query_id = Default::default();
let max_weight = Default::default();
let destination = T::valid_destination().map_err(|_| BenchmarkError::Skip)?;
let (expected_fees_mode, expected_assets_in_holding) =
T::DeliveryHelper::ensure_successful_delivery(
&sender_location,
&destination,
FeeReason::Report,
);
let sender_account_balance_before = T::TransactAsset::balance(&sender_account);
let mut executor = new_executor::<T>(sender_location);
if let Some(expected_fees_mode) = expected_fees_mode {
executor.set_fees_mode(expected_fees_mode);
}
if let Some(expected_assets_in_holding) = expected_assets_in_holding {
executor.set_holding(expected_assets_in_holding.into());
}
executor.set_error(Some((0u32, XcmError::Unimplemented)));
let instruction =
Instruction::ReportError(QueryResponseInfo { query_id, destination, max_weight });
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
// Check we charged the delivery fees
assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before);
Ok(())
}
#[benchmark]
fn claim_asset() -> Result<(), BenchmarkError> {
use xcm_executor::traits::DropAssets;
let (origin, ticket, assets) = T::claimable_asset()?;
// We place some items into the asset trap to claim.
<T::XcmConfig as xcm_executor::Config>::AssetTrap::drop_assets(
&origin,
assets.clone().into(),
&XcmContext { origin: Some(origin.clone()), message_id: [0; 32], topic: None },
);
// Assets should be in the trap now.
let mut executor = new_executor::<T>(origin);
let instruction = Instruction::ClaimAsset { assets: assets.clone(), ticket };
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
assert!(executor.holding().ensure_contains(&assets).is_ok());
Ok(())
}
#[benchmark]
fn trap() -> Result<(), BenchmarkError> {
let mut executor = new_executor::<T>(Default::default());
let instruction = Instruction::Trap(10);
let xcm = Xcm(vec![instruction]);
// In order to access result in the verification below, it needs to be defined here.
let result;
#[block]
{
result = executor.bench_process(xcm);
}
assert!(matches!(result, Err(ExecutorError { xcm_error: XcmError::Trap(10), .. })));
Ok(())
}
#[benchmark]
fn subscribe_version() -> Result<(), BenchmarkError> {
use xcm_executor::traits::VersionChangeNotifier;
let origin = T::subscribe_origin()?;
let query_id = Default::default();
let max_response_weight = Default::default();
let mut executor = new_executor::<T>(origin.clone());
let instruction = Instruction::SubscribeVersion { query_id, max_response_weight };
let xcm = Xcm(vec![instruction]);
T::DeliveryHelper::ensure_successful_delivery(&origin, &origin, FeeReason::QueryPallet);
#[block]
{
executor.bench_process(xcm)?;
}
assert!(<T::XcmConfig as xcm_executor::Config>::SubscriptionService::is_subscribed(
&origin
));
Ok(())
}
#[benchmark]
fn unsubscribe_version() -> Result<(), BenchmarkError> {
use xcm_executor::traits::VersionChangeNotifier;
// First we need to subscribe to notifications.
let (origin, _) = T::transact_origin_and_runtime_call()?;
T::DeliveryHelper::ensure_successful_delivery(&origin, &origin, FeeReason::QueryPallet);
let query_id = Default::default();
let max_response_weight = Default::default();
<T::XcmConfig as xcm_executor::Config>::SubscriptionService::start(
&origin,
query_id,
max_response_weight,
&XcmContext { origin: Some(origin.clone()), message_id: [0; 32], topic: None },
)
.map_err(|_| "Could not start subscription")?;
assert!(<T::XcmConfig as xcm_executor::Config>::SubscriptionService::is_subscribed(
&origin
));
let mut executor = new_executor::<T>(origin.clone());
let instruction = Instruction::UnsubscribeVersion;
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
assert!(!<T::XcmConfig as xcm_executor::Config>::SubscriptionService::is_subscribed(
&origin
));
Ok(())
}
#[benchmark]
fn burn_asset() -> Result<(), BenchmarkError> {
let holding = T::worst_case_holding(0);
let assets = holding.clone();
let mut executor = new_executor::<T>(Default::default());
executor.set_holding(holding.into());
let instruction = Instruction::BurnAsset(assets.into());
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
assert!(executor.holding().is_empty());
Ok(())
}
#[benchmark]
fn expect_asset() -> Result<(), BenchmarkError> {
let holding = T::worst_case_holding(0);
let assets = holding.clone();
let mut executor = new_executor::<T>(Default::default());
executor.set_holding(holding.into());
let instruction = Instruction::ExpectAsset(assets.into());
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
// `execute` completing successfully is as good as we can check.
Ok(())
}
#[benchmark]
fn expect_origin() -> Result<(), BenchmarkError> {
let expected_origin = Parent.into();
let mut executor = new_executor::<T>(Default::default());
let instruction = Instruction::ExpectOrigin(Some(expected_origin));
let xcm = Xcm(vec![instruction]);
let mut _result = Ok(());
#[block]
{
_result = executor.bench_process(xcm);
}
assert!(matches!(
_result,
Err(ExecutorError { xcm_error: XcmError::ExpectationFalse, .. })
));
Ok(())
}
#[benchmark]
fn expect_error() -> Result<(), BenchmarkError> {
let mut executor = new_executor::<T>(Default::default());
executor.set_error(Some((3u32, XcmError::Overflow)));
let instruction = Instruction::ExpectError(None);
let xcm = Xcm(vec![instruction]);
let mut _result = Ok(());
#[block]
{
_result = executor.bench_process(xcm);
}
assert!(matches!(
_result,
Err(ExecutorError { xcm_error: XcmError::ExpectationFalse, .. })
));
Ok(())
}
#[benchmark]
fn expect_transact_status() -> Result<(), BenchmarkError> {
let mut executor = new_executor::<T>(Default::default());
let worst_error =
|| -> MaybeErrorCode { vec![0; MaxDispatchErrorLen::get() as usize].into() };
executor.set_transact_status(worst_error());
let instruction = Instruction::ExpectTransactStatus(worst_error());
let xcm = Xcm(vec![instruction]);
let mut _result = Ok(());
#[block]
{
_result = executor.bench_process(xcm);
}
assert!(matches!(_result, Ok(..)));
Ok(())
}
#[benchmark]
fn query_pallet() -> Result<(), BenchmarkError> {
let (sender_account, sender_location) = account_and_location::<T>(1);
let query_id = Default::default();
let destination = T::valid_destination().map_err(|_| BenchmarkError::Skip)?;
let max_weight = Default::default();
let (expected_fees_mode, expected_assets_in_holding) =
T::DeliveryHelper::ensure_successful_delivery(
&sender_location,
&destination,
FeeReason::QueryPallet,
);
let sender_account_balance_before = T::TransactAsset::balance(&sender_account);
let mut executor = new_executor::<T>(sender_location);
if let Some(expected_fees_mode) = expected_fees_mode {
executor.set_fees_mode(expected_fees_mode);
}
if let Some(expected_assets_in_holding) = expected_assets_in_holding {
executor.set_holding(expected_assets_in_holding.into());
}
let valid_pallet = T::valid_pallet();
let instruction = Instruction::QueryPallet {
module_name: valid_pallet.module_name.as_bytes().to_vec(),
response_info: QueryResponseInfo { destination, query_id, max_weight },
};
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
// Check we charged the delivery fees
assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before);
// TODO: Potentially add new trait to XcmSender to detect a queued outgoing message. #4426
Ok(())
}
#[benchmark]
fn expect_pallet() -> Result<(), BenchmarkError> {
let mut executor = new_executor::<T>(Default::default());
let valid_pallet = T::valid_pallet();
let instruction = Instruction::ExpectPallet {
index: valid_pallet.index as u32,
name: valid_pallet.name.as_bytes().to_vec(),
module_name: valid_pallet.module_name.as_bytes().to_vec(),
crate_major: valid_pallet.crate_version.major.into(),
min_crate_minor: valid_pallet.crate_version.minor.into(),
};
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
// the execution succeeding is all we need to verify this xcm was successful
Ok(())
}
#[benchmark]
fn report_transact_status() -> Result<(), BenchmarkError> {
let (sender_account, sender_location) = account_and_location::<T>(1);
let query_id = Default::default();
let destination = T::valid_destination().map_err(|_| BenchmarkError::Skip)?;
let max_weight = Default::default();
let (expected_fees_mode, expected_assets_in_holding) =
T::DeliveryHelper::ensure_successful_delivery(
&sender_location,
&destination,
FeeReason::Report,
);
let sender_account_balance_before = T::TransactAsset::balance(&sender_account);
let mut executor = new_executor::<T>(sender_location);
if let Some(expected_fees_mode) = expected_fees_mode {
executor.set_fees_mode(expected_fees_mode);
}
if let Some(expected_assets_in_holding) = expected_assets_in_holding {
executor.set_holding(expected_assets_in_holding.into());
}
executor.set_transact_status(b"MyError".to_vec().into());
let instruction = Instruction::ReportTransactStatus(QueryResponseInfo {
query_id,
destination,
max_weight,
});
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
// Check we charged the delivery fees
assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before);
// TODO: Potentially add new trait to XcmSender to detect a queued outgoing message. #4426
Ok(())
}
#[benchmark]
fn clear_transact_status() -> Result<(), BenchmarkError> {
let mut executor = new_executor::<T>(Default::default());
executor.set_transact_status(b"MyError".to_vec().into());
let instruction = Instruction::ClearTransactStatus;
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
assert_eq!(executor.transact_status(), &MaybeErrorCode::Success);
Ok(())
}
#[benchmark]
fn set_topic() -> Result<(), BenchmarkError> {
let mut executor = new_executor::<T>(Default::default());
let instruction = Instruction::SetTopic([1; 32]);
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
assert_eq!(executor.topic(), &Some([1; 32]));
Ok(())
}
#[benchmark]
fn clear_topic() -> Result<(), BenchmarkError> {
let mut executor = new_executor::<T>(Default::default());
executor.set_topic(Some([2; 32]));
let instruction = Instruction::ClearTopic;
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
assert_eq!(executor.topic(), &None);
Ok(())
}
#[benchmark]
fn exchange_asset() -> Result<(), BenchmarkError> {
let (give, want) = T::worst_case_asset_exchange().map_err(|_| BenchmarkError::Skip)?;
let assets = give.clone();
let mut executor = new_executor::<T>(Default::default());
executor.set_holding(give.into());
let instruction =
Instruction::ExchangeAsset { give: assets.into(), want: want.clone(), maximal: true };
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
assert!(executor.holding().contains(&want.into()));
Ok(())
}
#[benchmark]
fn universal_origin() -> Result<(), BenchmarkError> {
let (origin, alias) = T::universal_alias().map_err(|_| BenchmarkError::Skip)?;
let mut executor = new_executor::<T>(origin);
let instruction = Instruction::UniversalOrigin(alias);
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
use frame_support::traits::Get;
let universal_location = <T::XcmConfig as xcm_executor::Config>::UniversalLocation::get();
assert_eq!(
executor.origin(),
&Some(Junctions::from([alias]).relative_to(&universal_location))
);
Ok(())
}
#[benchmark]
fn export_message(x: Linear<1, 1000>) -> Result<(), BenchmarkError> {
// The `inner_xcm` influences `ExportMessage` total weight based on
// `inner_xcm.encoded_size()`, so for this benchmark use smallest encoded instruction
// to approximate weight per "unit" of encoded size; then actual weight can be estimated
// to be `inner_xcm.encoded_size() * benchmarked_unit`.
// Use `ClearOrigin` as the small encoded instruction.
let inner_xcm = Xcm(vec![ClearOrigin; x as usize]);
// Get `origin`, `network` and `destination` from configured runtime.
let (origin, network, destination) = T::export_message_origin_and_destination()?;
let (expected_fees_mode, expected_assets_in_holding) =
T::DeliveryHelper::ensure_successful_delivery(
&origin,
&destination.clone().into(),
FeeReason::Export { network, destination: destination.clone() },
);
let sender_account = T::AccountIdConverter::convert_location(&origin).unwrap();
let sender_account_balance_before = T::TransactAsset::balance(&sender_account);
let mut executor = new_executor::<T>(origin);
if let Some(expected_fees_mode) = expected_fees_mode {
executor.set_fees_mode(expected_fees_mode);
}
if let Some(expected_assets_in_holding) = expected_assets_in_holding {
executor.set_holding(expected_assets_in_holding.into());
}
let xcm =
Xcm(vec![ExportMessage { network, destination: destination.clone(), xcm: inner_xcm }]);
#[block]
{
executor.bench_process(xcm)?;
}
// Check we charged the delivery fees
assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before);
// TODO: Potentially add new trait to XcmSender to detect a queued outgoing message. #4426
Ok(())
}
#[benchmark]
fn set_fees_mode() -> Result<(), BenchmarkError> {
let mut executor = new_executor::<T>(Default::default());
executor.set_fees_mode(FeesMode { jit_withdraw: false });
let instruction = Instruction::SetFeesMode { jit_withdraw: true };
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
assert_eq!(executor.fees_mode(), &FeesMode { jit_withdraw: true });
Ok(())
}
#[benchmark]
fn lock_asset() -> Result<(), BenchmarkError> {
let (unlocker, owner, asset) = T::unlockable_asset()?;
let (expected_fees_mode, expected_assets_in_holding) =
T::DeliveryHelper::ensure_successful_delivery(&owner, &unlocker, FeeReason::LockAsset);
let sender_account = T::AccountIdConverter::convert_location(&owner).unwrap();
let sender_account_balance_before = T::TransactAsset::balance(&sender_account);
// generate holding and add possible required fees
let mut holding: Assets = asset.clone().into();
if let Some(expected_assets_in_holding) = expected_assets_in_holding {
for a in expected_assets_in_holding.into_inner() {
holding.push(a);
}
};
let mut executor = new_executor::<T>(owner);
executor.set_holding(holding.into());
if let Some(expected_fees_mode) = expected_fees_mode {
executor.set_fees_mode(expected_fees_mode);
}
let instruction = Instruction::LockAsset { asset, unlocker };
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
// Check delivery fees
assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before);
// TODO: Potentially add new trait to XcmSender to detect a queued outgoing message. #4426
Ok(())
}
#[benchmark]
fn unlock_asset() -> Result<(), BenchmarkError> {
use xcm_executor::traits::{AssetLock, Enact};
let (unlocker, owner, asset) = T::unlockable_asset()?;
let mut executor = new_executor::<T>(unlocker.clone());
// We first place the asset in lock first...
<T::XcmConfig as xcm_executor::Config>::AssetLocker::prepare_lock(
unlocker,
asset.clone(),
owner.clone(),
)
.map_err(|_| BenchmarkError::Skip)?
.enact()
.map_err(|_| BenchmarkError::Skip)?;
// ... then unlock them with the UnlockAsset instruction.
let instruction = Instruction::UnlockAsset { asset, target: owner };
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
Ok(())
}
#[benchmark]
fn note_unlockable() -> Result<(), BenchmarkError> {
use xcm_executor::traits::{AssetLock, Enact};
let (unlocker, owner, asset) = T::unlockable_asset()?;
let mut executor = new_executor::<T>(unlocker.clone());
// We first place the asset in lock first...
<T::XcmConfig as xcm_executor::Config>::AssetLocker::prepare_lock(
unlocker,
asset.clone(),
owner.clone(),
)
.map_err(|_| BenchmarkError::Skip)?
.enact()
.map_err(|_| BenchmarkError::Skip)?;
// ... then note them as unlockable with the NoteUnlockable instruction.
let instruction = Instruction::NoteUnlockable { asset, owner };
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
Ok(())
}
#[benchmark]
fn request_unlock() -> Result<(), BenchmarkError> {
use xcm_executor::traits::{AssetLock, Enact};
let (locker, owner, asset) = T::unlockable_asset()?;
// We first place the asset in lock first...
<T::XcmConfig as xcm_executor::Config>::AssetLocker::prepare_lock(
locker.clone(),
asset.clone(),
owner.clone(),
)
.map_err(|_| BenchmarkError::Skip)?
.enact()
.map_err(|_| BenchmarkError::Skip)?;
let (expected_fees_mode, expected_assets_in_holding) =
T::DeliveryHelper::ensure_successful_delivery(
&owner,
&locker,
FeeReason::RequestUnlock,
);
let sender_account = T::AccountIdConverter::convert_location(&owner).unwrap();
let sender_account_balance_before = T::TransactAsset::balance(&sender_account);
// ... then request for an unlock with the RequestUnlock instruction.
let mut executor = new_executor::<T>(owner);
if let Some(expected_fees_mode) = expected_fees_mode {
executor.set_fees_mode(expected_fees_mode);
}
if let Some(expected_assets_in_holding) = expected_assets_in_holding {
executor.set_holding(expected_assets_in_holding.into());
}
let instruction = Instruction::RequestUnlock { asset, locker };
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
// Check we charged the delivery fees
assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before);
// TODO: Potentially add new trait to XcmSender to detect a queued outgoing message. #4426
Ok(())
}
#[benchmark]
fn unpaid_execution() -> Result<(), BenchmarkError> {
let mut executor = new_executor::<T>(Default::default());
executor.set_origin(Some(Here.into()));
let instruction = Instruction::<XcmCallOf<T>>::UnpaidExecution {
weight_limit: WeightLimit::Unlimited,
check_origin: Some(Here.into()),
};
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
Ok(())
}
#[benchmark]
fn alias_origin() -> Result<(), BenchmarkError> {
let (origin, target) = T::alias_origin().map_err(|_| BenchmarkError::Skip)?;
let mut executor = new_executor::<T>(origin);
let instruction = Instruction::AliasOrigin(target.clone());
let xcm = Xcm(vec![instruction]);
#[block]
{
executor.bench_process(xcm)?;
}
assert_eq!(executor.origin(), &Some(target));
Ok(())
}
impl_benchmark_test_suite!(
Pallet,
crate::generic::mock::new_test_ext(),
crate::generic::mock::Test
);
}
@@ -0,0 +1,221 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! A mock runtime for XCM benchmarking.
use crate::{generic, mock::*, *};
use codec::Decode;
use frame_support::{
derive_impl, parameter_types,
traits::{Contains, Everything, OriginTrait},
};
use sp_runtime::traits::TrailingZeroInput;
use xcm_builder::{
test_utils::{
AssetsInHolding, TestAssetExchanger, TestAssetLocker, TestAssetTrap,
TestSubscriptionService, TestUniversalAliases,
},
AliasForeignAccountId32, AllowUnpaidExecutionFrom, EnsureDecodableXcm,
FrameTransactionalProcessor,
};
use xcm_executor::traits::ConvertOrigin;
type Block = frame_system::mocking::MockBlock<Test>;
frame_support::construct_runtime!(
pub enum Test
{
System: frame_system,
Balances: pallet_balances,
XcmGenericBenchmarks: generic,
}
);
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Test {
type Block = Block;
type AccountData = pallet_balances::AccountData<u64>;
}
/// The benchmarks in this pallet should never need an asset transactor to begin with.
pub struct NoAssetTransactor;
impl xcm_executor::traits::TransactAsset for NoAssetTransactor {
fn deposit_asset(_: &Asset, _: &Location, _: Option<&XcmContext>) -> Result<(), XcmError> {
unreachable!();
}
fn withdraw_asset(
_: &Asset,
_: &Location,
_: Option<&XcmContext>,
) -> Result<AssetsInHolding, XcmError> {
unreachable!();
}
}
parameter_types! {
pub const MaxInstructions: u32 = 100;
pub const MaxAssetsIntoHolding: u32 = 64;
}
pub struct OnlyTeyrchains;
impl Contains<Location> for OnlyTeyrchains {
fn contains(location: &Location) -> bool {
matches!(location.unpack(), (0, [Teyrchain(_)]))
}
}
type Aliasers = AliasForeignAccountId32<OnlyTeyrchains>;
pub struct XcmConfig;
impl xcm_executor::Config for XcmConfig {
type RuntimeCall = RuntimeCall;
type XcmSender = EnsureDecodableXcm<DevNull>;
type XcmEventEmitter = ();
type AssetTransactor = NoAssetTransactor;
type OriginConverter = AlwaysSignedByDefault<RuntimeOrigin>;
type IsReserve = AllAssetLocationsPass;
type IsTeleporter = ();
type UniversalLocation = UniversalLocation;
type Barrier = AllowUnpaidExecutionFrom<Everything>;
type Weigher = xcm_builder::FixedWeightBounds<UnitWeightCost, RuntimeCall, MaxInstructions>;
type Trader = xcm_builder::FixedRateOfFungible<WeightPrice, ()>;
type ResponseHandler = DevNull;
type AssetTrap = TestAssetTrap;
type AssetLocker = TestAssetLocker;
type AssetExchanger = TestAssetExchanger;
type AssetClaims = TestAssetTrap;
type SubscriptionService = TestSubscriptionService;
type PalletInstancesInfo = AllPalletsWithSystem;
type MaxAssetsIntoHolding = MaxAssetsIntoHolding;
type FeeManager = ();
// No bridges yet...
type MessageExporter = ();
type UniversalAliases = TestUniversalAliases;
type CallDispatcher = RuntimeCall;
type SafeCallFilter = Everything;
type Aliasers = Aliasers;
type TransactionalProcessor = FrameTransactionalProcessor;
type HrmpNewChannelOpenRequestHandler = ();
type HrmpChannelAcceptedHandler = ();
type HrmpChannelClosingHandler = ();
type XcmRecorder = ();
}
parameter_types! {
pub const ExistentialDeposit: u64 = 7;
}
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
impl pallet_balances::Config for Test {
type ReserveIdentifier = [u8; 8];
type AccountStore = System;
}
impl crate::Config for Test {
type XcmConfig = XcmConfig;
type AccountIdConverter = AccountIdConverter;
type DeliveryHelper = ();
fn valid_destination() -> Result<Location, BenchmarkError> {
let valid_destination: Location =
Junction::AccountId32 { network: None, id: [0u8; 32] }.into();
Ok(valid_destination)
}
fn worst_case_holding(depositable_count: u32) -> Assets {
generate_holding_assets(
<XcmConfig as xcm_executor::Config>::MaxAssetsIntoHolding::get() - depositable_count,
)
}
}
impl generic::Config for Test {
type TransactAsset = Balances;
type RuntimeCall = RuntimeCall;
fn worst_case_response() -> (u64, Response) {
let assets: Assets = (AssetId(Here.into()), 100).into();
(0, Response::Assets(assets))
}
fn worst_case_asset_exchange() -> Result<(Assets, Assets), BenchmarkError> {
Ok(Default::default())
}
fn universal_alias() -> Result<(Location, Junction), BenchmarkError> {
Ok((Here.into(), GlobalConsensus(ByGenesis([0; 32]))))
}
fn transact_origin_and_runtime_call(
) -> Result<(Location, <Self as generic::Config>::RuntimeCall), BenchmarkError> {
Ok((Default::default(), frame_system::Call::remark_with_event { remark: vec![] }.into()))
}
fn subscribe_origin() -> Result<Location, BenchmarkError> {
Ok(Default::default())
}
fn claimable_asset() -> Result<(Location, Location, Assets), BenchmarkError> {
let assets: Assets = (AssetId(Here.into()), 100).into();
let ticket = Location { parents: 0, interior: [GeneralIndex(0)].into() };
Ok((Default::default(), ticket, assets))
}
fn worst_case_for_trader() -> Result<(Asset, WeightLimit), BenchmarkError> {
Ok((Asset { id: AssetId(Here.into()), fun: Fungible(1_000_000) }, WeightLimit::Unlimited))
}
fn unlockable_asset() -> Result<(Location, Location, Asset), BenchmarkError> {
let assets: Asset = (AssetId(Here.into()), 100).into();
Ok((Default::default(), account_id_junction::<Test>(1).into(), assets))
}
fn export_message_origin_and_destination(
) -> Result<(Location, NetworkId, InteriorLocation), BenchmarkError> {
// No MessageExporter in tests
Err(BenchmarkError::Skip)
}
fn alias_origin() -> Result<(Location, Location), BenchmarkError> {
let origin: Location = (Teyrchain(1), AccountId32 { network: None, id: [0; 32] }).into();
let target: Location = AccountId32 { network: None, id: [0; 32] }.into();
Ok((origin, target))
}
}
#[cfg(feature = "runtime-benchmarks")]
pub fn new_test_ext() -> sp_io::TestExternalities {
use sp_runtime::BuildStorage;
let t = RuntimeGenesisConfig { ..Default::default() }.build_storage().unwrap();
sp_tracing::try_init_simple();
t.into()
}
pub struct AlwaysSignedByDefault<RuntimeOrigin>(core::marker::PhantomData<RuntimeOrigin>);
impl<RuntimeOrigin> ConvertOrigin<RuntimeOrigin> for AlwaysSignedByDefault<RuntimeOrigin>
where
RuntimeOrigin: OriginTrait,
<RuntimeOrigin as OriginTrait>::AccountId: Decode,
{
fn convert_origin(
_origin: impl Into<Location>,
_kind: OriginKind,
) -> Result<RuntimeOrigin, Location> {
Ok(RuntimeOrigin::signed(
<RuntimeOrigin as OriginTrait>::AccountId::decode(&mut TrailingZeroInput::zeroes())
.expect("infinite length input; no invalid inputs for type; qed"),
))
}
}
@@ -0,0 +1,115 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
pub use pallet::*;
#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking;
#[cfg(test)]
mod mock;
#[frame_support::pallet]
pub mod pallet {
use frame_benchmarking::BenchmarkError;
use frame_support::{dispatch::GetDispatchInfo, pallet_prelude::Encode};
use sp_runtime::traits::Dispatchable;
use xcm::latest::{
Asset, Assets, InteriorLocation, Junction, Location, NetworkId, Response, WeightLimit,
};
#[pallet::config]
pub trait Config<I: 'static = ()>: frame_system::Config + crate::Config {
type RuntimeCall: Dispatchable<RuntimeOrigin = Self::RuntimeOrigin>
+ GetDispatchInfo
+ From<frame_system::Call<Self>>
+ Encode;
/// The type of `fungible` that is being used under the hood.
///
/// This is useful for testing and checking.
type TransactAsset: frame_support::traits::fungible::Mutate<Self::AccountId>;
/// The response which causes the most runtime weight.
fn worst_case_response() -> (u64, Response);
/// The pair of asset collections which causes the most runtime weight if demanded to be
/// exchanged.
///
/// The first element in the returned tuple represents the assets that are being exchanged
/// from, whereas the second element represents the assets that are being exchanged to.
///
/// If set to `Err`, benchmarks which rely on an `exchange_asset` will be skipped.
fn worst_case_asset_exchange() -> Result<(Assets, Assets), BenchmarkError>;
/// A `(Location, Junction)` that is one of the `UniversalAliases` configured by the
/// XCM executor.
///
/// If set to `Err`, benchmarks which rely on a universal alias will be skipped.
fn universal_alias() -> Result<(Location, Junction), BenchmarkError>;
/// The `Location` and `RuntimeCall` used for successful transaction XCMs.
///
/// If set to `Err`, benchmarks which rely on a `transact_origin_and_runtime_call` will be
/// skipped.
fn transact_origin_and_runtime_call(
) -> Result<(Location, <Self as crate::generic::Config<I>>::RuntimeCall), BenchmarkError>;
/// A valid `Location` we can successfully subscribe to.
///
/// If set to `Err`, benchmarks which rely on a `subscribe_origin` will be skipped.
fn subscribe_origin() -> Result<Location, BenchmarkError>;
/// Return an origin, ticket, and assets that can be trapped and claimed.
fn claimable_asset() -> Result<(Location, Location, Assets), BenchmarkError>;
/// The worst case buy execution weight limit and
/// asset to trigger the Trader::buy_execution in the XCM executor
/// Used to buy weight in benchmarks, for example in
/// `refund_surplus`.
fn worst_case_for_trader() -> Result<(Asset, WeightLimit), BenchmarkError>;
/// Return an unlocker, owner and assets that can be locked and unlocked.
fn unlockable_asset() -> Result<(Location, Location, Asset), BenchmarkError>;
/// A `(Location, NetworkId, InteriorLocation)` we can successfully export message
/// to.
///
/// If set to `Err`, benchmarks which rely on `export_message` will be skipped.
fn export_message_origin_and_destination(
) -> Result<(Location, NetworkId, InteriorLocation), BenchmarkError>;
/// A `(Location, Location)` that is one of the `Aliasers` configured by the XCM
/// executor.
///
/// If set to `Err`, benchmarks which rely on a universal alias will be skipped.
fn alias_origin() -> Result<(Location, Location), BenchmarkError>;
/// Returns a valid pallet info for `ExpectPallet` or `QueryPallet` benchmark.
///
/// By default returns `frame_system::Pallet` info with expected pallet index `0`.
fn valid_pallet() -> frame_support::traits::PalletInfoData {
frame_support::traits::PalletInfoData {
index: <frame_system::Pallet<Self> as frame_support::traits::PalletInfoAccess>::index(),
name: <frame_system::Pallet<Self> as frame_support::traits::PalletInfoAccess>::name(),
module_name: <frame_system::Pallet<Self> as frame_support::traits::PalletInfoAccess>::module_name(),
crate_version: <frame_system::Pallet<Self> as frame_support::traits::PalletInfoAccess>::crate_version(),
}
}
}
#[pallet::pallet]
pub struct Pallet<T, I = ()>(_);
}
@@ -0,0 +1,121 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Pallet that serves no other purpose than benchmarking raw messages [`Xcm`].
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
use alloc::vec::Vec;
use codec::Encode;
use frame_benchmarking::{account, BenchmarkError};
use xcm::latest::prelude::*;
use xcm_builder::EnsureDelivery;
use xcm_executor::{traits::ConvertLocation, Config as XcmConfig};
pub mod fungible;
pub mod generic;
#[cfg(test)]
mod mock;
/// A base trait for all individual pallets
pub trait Config: frame_system::Config {
/// The XCM configurations.
///
/// These might affect the execution of XCM messages, such as defining how the
/// `TransactAsset` is implemented.
type XcmConfig: XcmConfig;
/// A converter between a location to a sovereign account.
type AccountIdConverter: ConvertLocation<Self::AccountId>;
/// Helper that ensures successful delivery for XCM instructions which need `SendXcm`.
type DeliveryHelper: EnsureDelivery;
/// Does any necessary setup to create a valid destination for XCM messages.
/// Returns that destination's location to be used in benchmarks.
fn valid_destination() -> Result<Location, BenchmarkError>;
/// Worst case scenario for a holding account in this runtime.
/// - `depositable_count` specifies the count of assets we plan to add to the holding on top of
/// those generated by the `worst_case_holding` implementation.
fn worst_case_holding(depositable_count: u32) -> Assets;
}
const SEED: u32 = 0;
/// The XCM executor to use for doing stuff.
pub type ExecutorOf<T> = xcm_executor::XcmExecutor<<T as Config>::XcmConfig>;
/// The overarching call type.
pub type RuntimeCallOf<T> = <T as frame_system::Config>::RuntimeCall;
/// The asset transactor of our executor
pub type AssetTransactorOf<T> = <<T as Config>::XcmConfig as XcmConfig>::AssetTransactor;
/// The call type of executor's config. Should eventually resolve to the same overarching call type.
pub type XcmCallOf<T> = <<T as Config>::XcmConfig as XcmConfig>::RuntimeCall;
pub fn generate_holding_assets(max_assets: u32) -> Assets {
let fungibles_amount: u128 = 100;
let holding_fungibles = max_assets / 2;
let holding_non_fungibles = max_assets - holding_fungibles - 1; // -1 because of adding `Here` asset
// add count of `holding_fungibles`
(0..holding_fungibles)
.map(|i| {
Asset {
id: AssetId(GeneralIndex(i as u128).into()),
fun: Fungible(fungibles_amount * (i + 1) as u128), // non-zero amount
}
.into()
})
// add one more `Here` asset
.chain(core::iter::once(Asset { id: AssetId(Here.into()), fun: Fungible(u128::MAX) }))
// add count of `holding_non_fungibles`
.chain((0..holding_non_fungibles).map(|i| Asset {
id: AssetId(GeneralIndex(i as u128).into()),
fun: NonFungible(asset_instance_from(i)),
}))
.collect::<Vec<_>>()
.into()
}
pub fn asset_instance_from(x: u32) -> AssetInstance {
let bytes = x.encode();
let mut instance = [0u8; 4];
instance.copy_from_slice(&bytes);
AssetInstance::Array4(instance)
}
pub fn new_executor<T: Config>(origin: Location) -> ExecutorOf<T> {
ExecutorOf::<T>::new(origin, [0; 32])
}
/// Build a location from an account id.
fn account_id_junction<T: frame_system::Config>(index: u32) -> Junction {
let account: T::AccountId = account("account", index, SEED);
let mut encoded = account.encode();
encoded.resize(32, 0u8);
let mut id = [0u8; 32];
id.copy_from_slice(&encoded);
Junction::AccountId32 { network: None, id }
}
pub fn account_and_location<T: Config>(index: u32) -> (T::AccountId, Location) {
let location: Location = account_id_junction::<T>(index).into();
let account = T::AccountIdConverter::convert_location(&location).unwrap();
(account, location)
}
@@ -0,0 +1,71 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
use crate::*;
use frame_support::{parameter_types, traits::ContainsPair};
use xcm::latest::Weight;
// An xcm sender/receiver akin to > /dev/null
pub struct DevNull;
impl xcm::opaque::latest::SendXcm for DevNull {
type Ticket = ();
fn validate(_: &mut Option<Location>, _: &mut Option<Xcm<()>>) -> SendResult<()> {
Ok(((), Assets::new()))
}
fn deliver(_: ()) -> Result<XcmHash, SendError> {
Ok([0; 32])
}
}
impl xcm_executor::traits::OnResponse for DevNull {
fn expecting_response(_: &Location, _: u64, _: Option<&Location>) -> bool {
false
}
fn on_response(
_: &Location,
_: u64,
_: Option<&Location>,
_: Response,
_: Weight,
_: &XcmContext,
) -> Weight {
Weight::zero()
}
}
pub struct AccountIdConverter;
impl xcm_executor::traits::ConvertLocation<u64> for AccountIdConverter {
fn convert_location(ml: &Location) -> Option<u64> {
match ml.unpack() {
(0, [Junction::AccountId32 { id, .. }]) =>
Some(<u64 as codec::Decode>::decode(&mut &*id.to_vec()).unwrap()),
_ => None,
}
}
}
parameter_types! {
pub UniversalLocation: InteriorLocation = [GlobalConsensus(ByGenesis([1; 32])), Junction::Teyrchain(101)].into();
pub UnitWeightCost: Weight = Weight::from_parts(10, 10);
pub WeightPrice: (AssetId, u128, u128) = (AssetId(Here.into()), 1_000_000, 1024);
}
pub struct AllAssetLocationsPass;
impl ContainsPair<Asset, Location> for AllAssetLocationsPass {
fn contains(_: &Asset, _: &Location) -> bool {
true
}
}
@@ -0,0 +1,64 @@
{{header}}
//! Autogenerated weights for `{{pallet}}`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION {{version}}
//! DATE: {{date}}, STEPS: `{{cmd.steps}}`, REPEAT: `{{cmd.repeat}}`, LOW RANGE: `{{cmd.lowest_range_values}}`, HIGH RANGE: `{{cmd.highest_range_values}}`
//! WORST CASE MAP SIZE: `{{cmd.worst_case_map_values}}`
//! HOSTNAME: `{{hostname}}`, CPU: `{{cpuname}}`
//! WASM-EXECUTION: {{cmd.wasm_execution}}, CHAIN: {{cmd.chain}}, DB CACHE: {{cmd.db_cache}}
// Executed Command:
{{#each args as |arg|}}
// {{arg}}
{{/each}}
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
use frame_support::{traits::Get, weights::Weight};
use core::marker::PhantomData;
/// Weight functions for `{{pallet}}`.
pub struct WeightInfo<T>(PhantomData<T>);
impl<T: frame_system::Config> WeightInfo<T> {
{{#each benchmarks as |benchmark|}}
{{#each benchmark.comments as |comment|}}
/// {{comment}}
{{/each}}
{{#each benchmark.component_ranges as |range|}}
/// The range of component `{{range.name}}` is `[{{range.min}}, {{range.max}}]`.
{{/each}}
pub(crate) fn {{benchmark.name~}}
(
{{~#each benchmark.components as |c| ~}}
{{~#if (not c.is_used)}}_{{/if}}{{c.name}}: u32, {{/each~}}
) -> Weight {
// Proof Size summary in bytes:
// Measured: `{{benchmark.base_recorded_proof_size}}{{#each benchmark.component_recorded_proof_size as |cp|}} + {{cp.name}} * ({{cp.slope}} ±{{underscore cp.error}}){{/each}}`
// Estimated: `{{benchmark.base_calculated_proof_size}}{{#each benchmark.component_calculated_proof_size as |cp|}} + {{cp.name}} * ({{cp.slope}} ±{{underscore cp.error}}){{/each}}`
// Minimum execution time: {{underscore benchmark.min_execution_time}}_000 picoseconds.
Weight::from_parts({{underscore benchmark.base_weight}}, {{benchmark.base_calculated_proof_size}})
{{#each benchmark.component_weight as |cw|}}
// Standard Error: {{underscore cw.error}}
.saturating_add(Weight::from_parts({{underscore cw.slope}}, 0).saturating_mul({{cw.name}}.into()))
{{/each}}
{{#if (ne benchmark.base_reads "0")}}
.saturating_add(T::DbWeight::get().reads({{benchmark.base_reads}}))
{{/if}}
{{#each benchmark.component_reads as |cr|}}
.saturating_add(T::DbWeight::get().reads(({{cr.slope}}_u64).saturating_mul({{cr.name}}.into())))
{{/each}}
{{#if (ne benchmark.base_writes "0")}}
.saturating_add(T::DbWeight::get().writes({{benchmark.base_writes}}))
{{/if}}
{{#each benchmark.component_writes as |cw|}}
.saturating_add(T::DbWeight::get().writes(({{cw.slope}}_u64).saturating_mul({{cw.name}}.into())))
{{/each}}
{{#each benchmark.component_calculated_proof_size as |cp|}}
.saturating_add(Weight::from_parts(0, {{cp.slope}}).saturating_mul({{cp.name}}.into()))
{{/each}}
}
{{/each}}
}
+90
View File
@@ -0,0 +1,90 @@
[package]
name = "pallet-xcm"
version = "7.0.0"
description = "A pallet for handling XCM programs."
authors.workspace = true
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
[lints]
workspace = true
[dependencies]
bounded-collections = { workspace = true }
codec = { features = ["derive"], workspace = true }
scale-info = { features = ["derive"], workspace = true }
serde = { optional = true, features = [
"derive",
], workspace = true, default-features = true }
tracing = { workspace = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
sp-core = { workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
xcm = { workspace = true }
xcm-builder = { workspace = true }
xcm-executor = { workspace = true }
xcm-runtime-apis = { workspace = true }
# marked optional, used in benchmarking
frame-benchmarking = { optional = true, workspace = true }
hex-literal = { workspace = true, default-features = false }
pallet-balances = { optional = true, workspace = true }
[dev-dependencies]
pallet-assets = { workspace = true, default-features = true }
pezkuwi-runtime-teyrchains = { workspace = true, default-features = true }
pezkuwi-teyrchain-primitives = { workspace = true, default-features = true }
sp-tracing = { workspace = true, default-features = true }
xcm-simulator = { workspace = true, default-features = true }
[features]
default = ["std"]
std = [
"bounded-collections/std",
"codec/std",
"frame-benchmarking?/std",
"frame-support/std",
"frame-system/std",
"pallet-balances/std",
"scale-info/std",
"serde",
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
"tracing/std",
"xcm-builder/std",
"xcm-executor/std",
"xcm-runtime-apis/std",
"xcm/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-assets/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"pezkuwi-runtime-teyrchains/runtime-benchmarks",
"pezkuwi-teyrchain-primitives/runtime-benchmarks",
"sp-io/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
"xcm-builder/runtime-benchmarks",
"xcm-executor/runtime-benchmarks",
"xcm-runtime-apis/runtime-benchmarks",
"xcm-simulator/runtime-benchmarks",
"xcm/runtime-benchmarks",
]
test-utils = ["std"]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-assets/try-runtime",
"pallet-balances/try-runtime",
"pezkuwi-runtime-teyrchains/try-runtime",
"sp-runtime/try-runtime",
]
@@ -0,0 +1,85 @@
[package]
name = "pallet-xcm-precompiles"
version = "0.1.0"
authors.workspace = true
edition.workspace = true
license = "Apache-2.0"
homepage.workspace = true
repository.workspace = true
description = "Provides precompiles for `pallet-xcm`"
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { features = ["derive"], workspace = true }
tracing = { workspace = true }
frame-support = { workspace = true }
pallet-revive = { workspace = true }
pallet-xcm = { workspace = true }
xcm = { workspace = true }
xcm-executor = { workspace = true }
[dev-dependencies]
frame-system = { workspace = true, default-features = true }
pallet-assets = { workspace = true, default-features = true }
pallet-balances = { workspace = true, default-features = true }
pallet-timestamp = { workspace = true, default-features = true }
pezkuwi-teyrchain-primitives = { workspace = true, default-features = true }
scale-info = { workspace = true, default-features = true }
sp-io = { workspace = true, default-features = true }
sp-runtime = { workspace = true, default-features = true }
xcm-builder = { workspace = true, default-features = true }
xcm-simulator = { workspace = true, default-features = true }
[features]
default = ["std"]
std = [
"codec/std",
"frame-support/std",
"frame-system/std",
"pallet-assets/std",
"pallet-balances/std",
"pallet-revive/std",
"pallet-timestamp/std",
"pallet-xcm/std",
"pezkuwi-teyrchain-primitives/std",
"scale-info/std",
"sp-io/std",
"sp-runtime/std",
"tracing/std",
"xcm-builder/std",
"xcm-executor/std",
"xcm/std",
]
runtime-benchmarks = [
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-assets/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"pallet-revive/runtime-benchmarks",
"pallet-timestamp/runtime-benchmarks",
"pallet-xcm/runtime-benchmarks",
"pezkuwi-teyrchain-primitives/runtime-benchmarks",
"sp-io/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
"xcm-builder/runtime-benchmarks",
"xcm-executor/runtime-benchmarks",
"xcm-simulator/runtime-benchmarks",
"xcm/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-assets/try-runtime",
"pallet-balances/try-runtime",
"pallet-revive/try-runtime",
"pallet-timestamp/try-runtime",
"pallet-xcm/try-runtime",
"sp-runtime/try-runtime",
]
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/// @dev The on-chain address of the XCM (Cross-Consensus Messaging) precompile.
address constant XCM_PRECOMPILE_ADDRESS = address(0xA0000);
/// @title XCM Precompile Interface
/// @notice A low-level interface for interacting with `pallet_xcm`.
/// It forwards calls directly to the corresponding dispatchable functions,
/// providing access to XCM execution and message passing.
/// @dev Documentation:
/// @dev - XCM: https://docs.pezkuwichain.io/develop/interoperability
/// @dev - SCALE codec: https://docs.pezkuwichain.io/polkadot-protocol/parachain-basics/data-encoding
/// @dev - Weights: https://docs.pezkuwichain.io/polkadot-protocol/parachain-basics/blocks-transactions-fees/fees/#transactions-weights-and-fees
interface IXcm {
/// @notice Weight v2 used for measurement for an XCM execution
struct Weight {
/// @custom:property The computational time used to execute some logic based on reference hardware.
uint64 refTime;
/// @custom:property The size of the proof needed to execute some logic.
uint64 proofSize;
}
/// @notice Executes an XCM message locally on the current chain with the caller's origin.
/// @dev Internally calls `pallet_xcm::execute`.
/// @param message A SCALE-encoded Versioned XCM message.
/// @param weight The maximum allowed `Weight` for execution.
/// @dev Call @custom:function weighMessage(message) to ensure sufficient weight allocation.
function execute(bytes calldata message, Weight calldata weight) external;
/// @notice Sends an XCM message to another parachain or consensus system.
/// @dev Internally calls `pallet_xcm::send`.
/// @param destination SCALE-encoded destination MultiLocation.
/// @param message SCALE-encoded Versioned XCM message.
function send(bytes calldata destination, bytes calldata message) external;
/// @notice Estimates the `Weight` required to execute a given XCM message.
/// @param message SCALE-encoded Versioned XCM message to analyze.
/// @return weight Struct containing estimated `refTime` and `proofSize`.
function weighMessage(bytes calldata message) external view returns (Weight memory weight);
}
@@ -0,0 +1,182 @@
// 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.
// Ensure we're `no_std` when compiling for Wasm.
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
use alloc::vec::Vec;
use codec::{DecodeAll, DecodeLimit};
use core::{fmt, marker::PhantomData, num::NonZero};
use frame_support::dispatch::RawOrigin;
use pallet_revive::{
precompiles::{
alloy::{self, sol_types::SolValue},
AddressMatcher, Error, Ext, Precompile,
},
DispatchInfo, ExecOrigin as Origin, Weight,
};
use pallet_xcm::{Config, WeightInfo};
use tracing::error;
use xcm::{v5, IdentifyVersion, VersionedLocation, VersionedXcm, MAX_XCM_DECODE_DEPTH};
use xcm_executor::traits::WeightBounds;
alloy::sol!("src/interface/IXcm.sol");
use IXcm::IXcmCalls;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
const LOG_TARGET: &str = "xcm::precompiles";
fn revert(error: &impl fmt::Debug, message: &str) -> Error {
error!(target: LOG_TARGET, ?error, "{}", message);
Error::Revert(message.into())
}
// We don't allow XCM versions older than 5.
fn ensure_xcm_version<V: IdentifyVersion>(input: &V) -> Result<(), Error> {
let version = input.identify_version();
if version < v5::VERSION {
return Err(Error::Revert("Only XCM version 5 and onwards are supported.".into()));
}
Ok(())
}
pub struct XcmPrecompile<T>(PhantomData<T>);
impl<Runtime> Precompile for XcmPrecompile<Runtime>
where
Runtime: crate::Config + pallet_revive::Config,
{
type T = Runtime;
const MATCHER: AddressMatcher = AddressMatcher::Fixed(NonZero::new(10).unwrap());
const HAS_CONTRACT_INFO: bool = false;
type Interface = IXcm::IXcmCalls;
fn call(
_address: &[u8; 20],
input: &Self::Interface,
env: &mut impl Ext<T = Self::T>,
) -> Result<Vec<u8>, Error> {
let origin = env.caller();
let frame_origin = match origin {
Origin::Root => RawOrigin::Root.into(),
Origin::Signed(account_id) => RawOrigin::Signed(account_id.clone()).into(),
};
match input {
IXcmCalls::send(_) | IXcmCalls::execute(_) if env.is_read_only() =>
Err(Error::Error(pallet_revive::Error::<Self::T>::StateChangeDenied.into())),
IXcmCalls::send(IXcm::sendCall { destination, message }) => {
let _ = env.charge(<Runtime as Config>::WeightInfo::send())?;
let final_destination = VersionedLocation::decode_all(&mut &destination[..])
.map_err(|error| {
revert(&error, "XCM send failed: Invalid destination format")
})?;
ensure_xcm_version(&final_destination)?;
let final_message = VersionedXcm::<()>::decode_all_with_depth_limit(
MAX_XCM_DECODE_DEPTH,
&mut &message[..],
)
.map_err(|error| revert(&error, "XCM send failed: Invalid message format"))?;
ensure_xcm_version(&final_message)?;
pallet_xcm::Pallet::<Runtime>::send(
frame_origin,
final_destination.into(),
final_message.into(),
)
.map(|_| Vec::new())
.map_err(|error| {
revert(
&error,
"XCM send failed: destination or message format may be incompatible",
)
})
},
IXcmCalls::execute(IXcm::executeCall { message, weight }) => {
let max_weight = Weight::from_parts(weight.refTime, weight.proofSize);
let weight_to_charge =
max_weight.saturating_add(<Runtime as Config>::WeightInfo::execute());
let charged_amount = env.charge(weight_to_charge)?;
let final_message = VersionedXcm::decode_all_with_depth_limit(
MAX_XCM_DECODE_DEPTH,
&mut &message[..],
)
.map_err(|error| revert(&error, "XCM execute failed: Invalid message format"))?;
ensure_xcm_version(&final_message)?;
let result = pallet_xcm::Pallet::<Runtime>::execute(
frame_origin,
final_message.into(),
max_weight,
);
let pre = DispatchInfo {
call_weight: weight_to_charge,
extension_weight: Weight::zero(),
..Default::default()
};
// Adjust gas using actual weight or fallback to initially charged weight
let actual_weight = frame_support::dispatch::extract_actual_weight(&result, &pre);
env.adjust_gas(charged_amount, actual_weight);
result.map(|_| Vec::new()).map_err(|error| {
revert(
&error,
"XCM execute failed: message may be invalid or execution constraints not satisfied"
)
})
},
IXcmCalls::weighMessage(IXcm::weighMessageCall { message }) => {
let _ = env.charge(<Runtime as Config>::WeightInfo::weigh_message())?;
let converted_message = VersionedXcm::decode_all_with_depth_limit(
MAX_XCM_DECODE_DEPTH,
&mut &message[..],
)
.map_err(|error| revert(&error, "XCM weightMessage: Invalid message format"))?;
ensure_xcm_version(&converted_message)?;
let mut final_message = converted_message.try_into().map_err(|error| {
revert(&error, "XCM weightMessage: Conversion to Xcm failed")
})?;
let weight = <<Runtime>::Weigher>::weight(&mut final_message, Weight::MAX)
.map_err(|error| {
revert(&error, "XCM weightMessage: Failed to calculate weight")
})?;
let final_weight =
IXcm::Weight { proofSize: weight.proof_size(), refTime: weight.ref_time() };
Ok(final_weight.abi_encode())
},
}
}
}
@@ -0,0 +1,343 @@
// 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.
pub use core::cell::RefCell;
use frame_support::{
derive_impl, parameter_types,
traits::{
fungible::HoldConsideration, AsEnsureOriginWithArg, ConstU32, Equals, Everything,
EverythingBut, Footprint, Nothing,
},
weights::Weight,
};
use frame_system::EnsureRoot;
use pezkuwi_teyrchain_primitives::primitives::Id as ParaId;
use sp_runtime::{
traits::{Convert, IdentityLookup},
AccountId32, BuildStorage,
};
use xcm::prelude::*;
use xcm_builder::{
AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom,
AllowTopLevelPaidExecutionFrom, ChildTeyrchainConvertsVia, DescribeAllTerminal,
EnsureDecodableXcm, FixedWeightBounds, FungibleAdapter, FungiblesAdapter, HashedDescription,
IsConcrete, MatchedConvertedConcreteId, NoChecking, TakeWeightCredit,
};
use xcm_executor::{
traits::{Identity, JustTry},
XcmExecutor,
};
use xcm_simulator::helpers::derive_topic_id;
use crate::XcmPrecompile;
pub type AccountId = AccountId32;
pub type Balance = u128;
type Block = frame_system::mocking::MockBlock<Test>;
pub const ALICE: AccountId32 = AccountId::new([0u8; 32]);
parameter_types! {
pub const MinimumPeriod: u64 = 1;
}
impl pallet_timestamp::Config for Test {
type Moment = u64;
type OnTimestampSet = ();
type MinimumPeriod = MinimumPeriod;
type WeightInfo = ();
}
frame_support::construct_runtime!(
pub enum Test
{
System: frame_system,
AssetsPallet: pallet_assets,
Balances: pallet_balances,
XcmPallet: pallet_xcm,
Revive: pallet_revive,
Timestamp: pallet_timestamp,
}
);
thread_local! {
pub static SENT_XCM: RefCell<Vec<(Location, Xcm<()>)>> = RefCell::new(Vec::new());
pub static FAIL_SEND_XCM: RefCell<bool> = RefCell::new(false);
}
pub(crate) fn sent_xcm() -> Vec<(Location, Xcm<()>)> {
SENT_XCM.with(|q| (*q.borrow()).clone())
}
/// Sender that never returns error.
pub struct TestSendXcm;
impl SendXcm for TestSendXcm {
type Ticket = (Location, Xcm<()>);
fn validate(
dest: &mut Option<Location>,
msg: &mut Option<Xcm<()>>,
) -> SendResult<(Location, Xcm<()>)> {
if FAIL_SEND_XCM.with(|q| *q.borrow()) {
return Err(SendError::Transport("Intentional send failure used in tests"));
}
let pair = (dest.take().unwrap(), msg.take().unwrap());
Ok((pair, Assets::new()))
}
fn deliver(pair: (Location, Xcm<()>)) -> Result<XcmHash, SendError> {
let message = pair.1.clone();
if message
.iter()
.any(|instr| matches!(instr, ExpectError(Some((1, XcmError::Unimplemented)))))
{
return Err(SendError::Transport("Intentional deliver failure used in tests".into()));
}
let hash = derive_topic_id(&message);
SENT_XCM.with(|q| q.borrow_mut().push(pair));
Ok(hash)
}
}
/// Sender that returns error if `X8` junction and stops routing
pub struct TestSendXcmErrX8;
impl SendXcm for TestSendXcmErrX8 {
type Ticket = (Location, Xcm<()>);
fn validate(
dest: &mut Option<Location>,
_: &mut Option<Xcm<()>>,
) -> SendResult<(Location, Xcm<()>)> {
if dest.as_ref().unwrap().len() == 8 {
dest.take();
Err(SendError::Transport("Destination location full"))
} else {
Err(SendError::NotApplicable)
}
}
fn deliver(pair: (Location, Xcm<()>)) -> Result<XcmHash, SendError> {
let hash = derive_topic_id(&pair.1);
SENT_XCM.with(|q| q.borrow_mut().push(pair));
Ok(hash)
}
}
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Test {
type AccountId = AccountId;
type Lookup = IdentityLookup<Self::AccountId>;
type Block = Block;
type AccountData = pallet_balances::AccountData<Balance>;
}
parameter_types! {
pub ExistentialDeposit: Balance = 1;
}
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
impl pallet_balances::Config for Test {
type Balance = Balance;
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
}
#[cfg(feature = "runtime-benchmarks")]
/// Simple conversion of `u32` into an `AssetId` for use in benchmarking.
pub struct XcmBenchmarkHelper;
#[cfg(feature = "runtime-benchmarks")]
impl pallet_assets::BenchmarkHelper<Location, ()> for XcmBenchmarkHelper {
fn create_asset_id_parameter(id: u32) -> Location {
Location::new(1, [Teyrchain(id)])
}
fn create_reserve_id_parameter(_: u32) {}
}
#[derive_impl(pallet_assets::config_preludes::TestDefaultConfig)]
impl pallet_assets::Config for Test {
type Balance = Balance;
type AssetId = Location;
type AssetIdParameter = Location;
type Currency = Balances;
type CreateOrigin = AsEnsureOriginWithArg<frame_system::EnsureSigned<AccountId>>;
type ForceOrigin = EnsureRoot<AccountId>;
#[cfg(feature = "runtime-benchmarks")]
type BenchmarkHelper = XcmBenchmarkHelper;
}
// This child teyrchain is not configured as trusted reserve or teleport location for any assets.
pub const SOME_PARA_ID: u32 = 2009;
parameter_types! {
pub const RelayLocation: Location = Here.into_location();
pub const AnyNetwork: Option<NetworkId> = None;
pub const BaseXcmWeight: Weight = Weight::from_parts(1_000, 1_000);
pub const MaxInstructions: u32 = 100;
pub const MaxAssetsIntoHolding: u32 = 64;
pub UniversalLocation: InteriorLocation = GlobalConsensus(ByGenesis([0; 32])).into();
pub CheckingAccount: AccountId = XcmPallet::check_account();
}
pub type SovereignAccountOf = (
ChildTeyrchainConvertsVia<ParaId, AccountId>,
AccountId32Aliases<AnyNetwork, AccountId>,
HashedDescription<AccountId, DescribeAllTerminal>,
);
pub type ForeignAssetsConvertedConcreteId = MatchedConvertedConcreteId<
Location,
Balance,
// Excludes relay/parent chain currency
EverythingBut<(Equals<RelayLocation>,)>,
Identity,
JustTry,
>;
pub type AssetTransactors = (
FungibleAdapter<Balances, IsConcrete<RelayLocation>, SovereignAccountOf, AccountId, ()>,
FungiblesAdapter<
AssetsPallet,
ForeignAssetsConvertedConcreteId,
SovereignAccountOf,
AccountId,
NoChecking,
CheckingAccount,
>,
);
pub type Barrier = (
TakeWeightCredit,
AllowTopLevelPaidExecutionFrom<Everything>,
AllowKnownQueryResponses<XcmPallet>,
AllowSubscriptionsFrom<Everything>,
);
pub type XcmRouter = EnsureDecodableXcm<(TestSendXcmErrX8, TestSendXcm)>;
pub struct XcmConfig;
impl xcm_executor::Config for XcmConfig {
type RuntimeCall = RuntimeCall;
type XcmSender = XcmRouter;
type AssetTransactor = AssetTransactors;
type OriginConverter = ();
type IsReserve = ();
type IsTeleporter = ();
type UniversalLocation = UniversalLocation;
type Barrier = Barrier;
type Weigher = FixedWeightBounds<BaseXcmWeight, RuntimeCall, MaxInstructions>;
type Trader = ();
type ResponseHandler = XcmPallet;
type AssetTrap = XcmPallet;
type AssetLocker = ();
type AssetExchanger = ();
type AssetClaims = XcmPallet;
type SubscriptionService = XcmPallet;
type PalletInstancesInfo = AllPalletsWithSystem;
type MaxAssetsIntoHolding = MaxAssetsIntoHolding;
type FeeManager = ();
type MessageExporter = ();
type UniversalAliases = Nothing;
type CallDispatcher = RuntimeCall;
type SafeCallFilter = Everything;
type Aliasers = Nothing;
type TransactionalProcessor = xcm_builder::FrameTransactionalProcessor;
type HrmpNewChannelOpenRequestHandler = ();
type HrmpChannelAcceptedHandler = ();
type HrmpChannelClosingHandler = ();
type XcmRecorder = XcmPallet;
type XcmEventEmitter = XcmPallet;
}
pub type LocalOriginToLocation = xcm_builder::SignedToAccountId32<RuntimeOrigin, AccountId, ()>;
parameter_types! {
pub static AdvertisedXcmVersion: xcm::prelude::XcmVersion = 4;
pub const AuthorizeAliasHoldReason: RuntimeHoldReason = RuntimeHoldReason::XcmPallet(pallet_xcm::HoldReason::AuthorizeAlias);
}
pub struct ConvertDeposit;
impl Convert<Footprint, u128> for ConvertDeposit {
fn convert(a: Footprint) -> u128 {
(a.count * 2 + a.size) as u128
}
}
impl pallet_xcm::Config for Test {
type RuntimeEvent = RuntimeEvent;
type SendXcmOrigin = xcm_builder::EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
type XcmRouter = XcmRouter;
type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
type XcmExecuteFilter = Everything;
type XcmExecutor = XcmExecutor<XcmConfig>;
type XcmTeleportFilter = Nothing;
type XcmReserveTransferFilter = Everything;
type Weigher = FixedWeightBounds<BaseXcmWeight, RuntimeCall, MaxInstructions>;
type UniversalLocation = UniversalLocation;
type RuntimeOrigin = RuntimeOrigin;
type RuntimeCall = RuntimeCall;
const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
type AdvertisedXcmVersion = AdvertisedXcmVersion;
type Currency = Balances;
type CurrencyMatcher = ();
type TrustedLockers = ();
type SovereignAccountOf = ();
type MaxLockers = ConstU32<8>;
type MaxRemoteLockConsumers = ConstU32<0>;
type RemoteLockConsumerIdentifier = ();
type WeightInfo = pallet_xcm::TestWeightInfo;
type AdminOrigin = EnsureRoot<AccountId>;
type AuthorizedAliasConsideration =
HoldConsideration<AccountId, Balances, AuthorizeAliasHoldReason, ConvertDeposit>;
}
#[derive_impl(pallet_revive::config_preludes::TestDefaultConfig)]
impl pallet_revive::Config for Test {
type AddressMapper = pallet_revive::AccountId32Mapper<Self>;
type Balance = Balance;
type Currency = Balances;
type Precompiles = (XcmPrecompile<Self>,);
type Time = Timestamp;
type UploadOrigin = frame_system::EnsureSigned<AccountId>;
type InstantiateOrigin = frame_system::EnsureSigned<AccountId>;
}
pub(crate) fn buy_execution<C>(fees: impl Into<Asset>) -> Instruction<C> {
use xcm::latest::prelude::*;
BuyExecution { fees: fees.into(), weight_limit: Unlimited }
}
pub(crate) fn new_test_ext_with_balances(
balances: Vec<(AccountId, Balance)>,
) -> sp_io::TestExternalities {
new_test_ext_with_balances_and_xcm_version(balances, Some(XCM_VERSION), vec![])
}
pub fn new_test_ext_with_balances_and_xcm_version(
balances: Vec<(AccountId, Balance)>,
safe_xcm_version: Option<XcmVersion>,
supported_version: Vec<(Location, XcmVersion)>,
) -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
pallet_balances::GenesisConfig::<Test> { balances, ..Default::default() }
.assimilate_storage(&mut t)
.unwrap();
pallet_xcm::GenesisConfig::<Test> { safe_xcm_version, supported_version, ..Default::default() }
.assimilate_storage(&mut t)
.unwrap();
pallet_revive::GenesisConfig::<Test> { mapped_accounts: vec![ALICE], ..Default::default() }
.assimilate_storage(&mut t)
.unwrap();
let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(|| System::set_block_number(1));
ext
}
@@ -0,0 +1,713 @@
// 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.
use super::*;
use crate::{
mock::*,
IXcm::{self, weighMessageCall},
VersionedLocation, VersionedXcm,
};
use frame_support::traits::Currency;
use pallet_revive::{
precompiles::{
alloy::{
hex,
sol_types::{SolInterface, SolValue},
},
H160,
},
ExecConfig, U256,
};
use pezkuwi_teyrchain_primitives::primitives::Id as ParaId;
use sp_runtime::traits::AccountIdConversion;
use xcm::{prelude::*, v3, v4};
const BOB: AccountId = AccountId::new([1u8; 32]);
const CHARLIE: AccountId = AccountId::new([2u8; 32]);
const SEND_AMOUNT: u128 = 10;
const CUSTOM_INITIAL_BALANCE: u128 = 100_000_000_000u128;
#[test]
fn test_xcm_send_precompile_works() {
use codec::Encode;
let balances = vec![
(ALICE, CUSTOM_INITIAL_BALANCE),
(ParaId::from(SOME_PARA_ID).into_account_truncating(), CUSTOM_INITIAL_BALANCE),
];
new_test_ext_with_balances(balances).execute_with(|| {
let xcm_precompile_addr = H160::from(
hex::const_decode_to_array(b"00000000000000000000000000000000000A0000").unwrap(),
);
let sender: Location = AccountId32 { network: None, id: ALICE.into() }.into();
let message = Xcm(vec![
ReserveAssetDeposited((Parent, SEND_AMOUNT).into()),
ClearOrigin,
buy_execution((Parent, SEND_AMOUNT)),
DepositAsset { assets: AllCounted(1).into(), beneficiary: sender.clone() },
]);
let versioned_dest: VersionedLocation = RelayLocation::get().into();
let versioned_message: VersionedXcm<()> = VersionedXcm::from(message.clone());
let xcm_send_params = IXcm::sendCall {
destination: versioned_dest.encode().into(),
message: versioned_message.encode().into(),
};
let call = IXcm::IXcmCalls::send(xcm_send_params);
let encoded_call = call.abi_encode();
let result = pallet_revive::Pallet::<Test>::bare_call(
RuntimeOrigin::signed(ALICE),
xcm_precompile_addr,
U256::zero(),
Weight::MAX,
u128::MAX,
encoded_call,
ExecConfig::new_substrate_tx(),
);
assert!(result.result.is_ok());
let sent_message = Xcm(Some(DescendOrigin(sender.clone().try_into().unwrap()))
.into_iter()
.chain(message.0.clone().into_iter())
.collect());
assert_eq!(sent_xcm(), vec![(Here.into(), sent_message)]);
});
}
#[test]
fn test_xcm_send_precompile_to_teyrchain() {
use codec::Encode;
let balances = vec![
(ALICE, CUSTOM_INITIAL_BALANCE),
(ParaId::from(SOME_PARA_ID).into_account_truncating(), CUSTOM_INITIAL_BALANCE),
];
new_test_ext_with_balances(balances).execute_with(|| {
let xcm_precompile_addr = H160::from(
hex::const_decode_to_array(b"00000000000000000000000000000000000A0000").unwrap(),
);
let sender: Location = AccountId32 { network: None, id: ALICE.into() }.into();
let message = Xcm(vec![
ReserveAssetDeposited((Parent, SEND_AMOUNT).into()),
ClearOrigin,
buy_execution((Parent, SEND_AMOUNT)),
DepositAsset { assets: AllCounted(1).into(), beneficiary: sender.clone() },
]);
let destination: VersionedLocation = Teyrchain(SOME_PARA_ID).into();
let versioned_message: VersionedXcm<()> = VersionedXcm::from(message.clone());
let xcm_send_params = IXcm::sendCall {
destination: destination.encode().into(),
message: versioned_message.encode().into(),
};
let call = IXcm::IXcmCalls::send(xcm_send_params);
let encoded_call = call.abi_encode();
let result = pallet_revive::Pallet::<Test>::bare_call(
RuntimeOrigin::signed(ALICE),
xcm_precompile_addr,
U256::zero(),
Weight::MAX,
u128::MAX,
encoded_call,
ExecConfig::new_substrate_tx(),
);
assert!(result.result.is_ok());
let sent_message = Xcm(Some(DescendOrigin(sender.clone().try_into().unwrap()))
.into_iter()
.chain(message.0.clone().into_iter())
.collect());
assert_eq!(sent_xcm(), vec![(Teyrchain(SOME_PARA_ID).into(), sent_message)]);
});
}
#[test]
fn test_xcm_send_precompile_fails() {
use codec::Encode;
let balances = vec![
(ALICE, CUSTOM_INITIAL_BALANCE),
(ParaId::from(SOME_PARA_ID).into_account_truncating(), CUSTOM_INITIAL_BALANCE),
];
new_test_ext_with_balances(balances).execute_with(|| {
let xcm_precompile_addr = H160::from(
hex::const_decode_to_array(b"00000000000000000000000000000000000A0000").unwrap(),
);
let sender: Location = AccountId32 { network: None, id: ALICE.into() }.into();
let message = Xcm(vec![
ReserveAssetDeposited((Parent, SEND_AMOUNT).into()),
buy_execution((Parent, SEND_AMOUNT)),
DepositAsset { assets: AllCounted(1).into(), beneficiary: sender },
]);
let destination: VersionedLocation = VersionedLocation::from(Location::ancestor(8));
let versioned_message: VersionedXcm<()> = VersionedXcm::from(message.clone());
let xcm_send_params = IXcm::sendCall {
destination: destination.encode().into(),
message: versioned_message.encode().into(),
};
let call = IXcm::IXcmCalls::send(xcm_send_params);
let encoded_call = call.abi_encode();
let result = pallet_revive::Pallet::<Test>::bare_call(
RuntimeOrigin::signed(ALICE),
xcm_precompile_addr,
U256::zero(),
Weight::MAX,
u128::MAX,
encoded_call,
ExecConfig::new_substrate_tx(),
);
let return_value = match result.result {
Ok(value) => value,
Err(err) => panic!("XcmSendPrecompile call failed with error: {err:?}"),
};
assert!(return_value.did_revert());
});
}
#[test]
fn send_fails_on_old_location_version() {
use codec::Encode;
let balances = vec![
(ALICE, CUSTOM_INITIAL_BALANCE),
(ParaId::from(SOME_PARA_ID).into_account_truncating(), CUSTOM_INITIAL_BALANCE),
];
new_test_ext_with_balances(balances).execute_with(|| {
let xcm_precompile_addr = H160::from(
hex::const_decode_to_array(b"00000000000000000000000000000000000A0000").unwrap(),
);
let sender: Location = AccountId32 { network: None, id: ALICE.into() }.into();
let message = Xcm(vec![
ReserveAssetDeposited((Parent, SEND_AMOUNT).into()),
ClearOrigin,
buy_execution((Parent, SEND_AMOUNT)),
DepositAsset { assets: AllCounted(1).into(), beneficiary: sender.clone() },
]);
// V4 location is old and will fail.
let destination: VersionedLocation =
VersionedLocation::V4(v4::Junction::Teyrchain(SOME_PARA_ID).into());
let versioned_message: VersionedXcm<RuntimeCall> = VersionedXcm::from(message.clone());
let xcm_send_params = IXcm::sendCall {
destination: destination.encode().into(),
message: versioned_message.encode().into(),
};
let call = IXcm::IXcmCalls::send(xcm_send_params);
let encoded_call = call.abi_encode();
let result = pallet_revive::Pallet::<Test>::bare_call(
RuntimeOrigin::signed(ALICE),
xcm_precompile_addr,
U256::zero(),
Weight::MAX,
u128::MAX,
encoded_call,
ExecConfig::new_substrate_tx(),
);
let return_value = match result.result {
Ok(value) => value,
Err(err) => panic!("XcmSendPrecompile call failed with error: {err:?}"),
};
assert!(return_value.did_revert());
// V3 also fails.
let destination: VersionedLocation =
VersionedLocation::V3(v3::Junction::Teyrchain(SOME_PARA_ID).into());
let versioned_message: VersionedXcm<RuntimeCall> = VersionedXcm::from(message);
let xcm_send_params = IXcm::sendCall {
destination: destination.encode().into(),
message: versioned_message.encode().into(),
};
let call = IXcm::IXcmCalls::send(xcm_send_params);
let encoded_call = call.abi_encode();
let result = pallet_revive::Pallet::<Test>::bare_call(
RuntimeOrigin::signed(ALICE),
xcm_precompile_addr,
U256::zero(),
Weight::MAX,
u128::MAX,
encoded_call,
ExecConfig::new_substrate_tx(),
);
let return_value = match result.result {
Ok(value) => value,
Err(err) => panic!("XcmSendPrecompile call failed with error: {err:?}"),
};
assert!(return_value.did_revert());
});
}
#[test]
fn send_fails_on_old_xcm_version() {
use codec::Encode;
let balances = vec![
(ALICE, CUSTOM_INITIAL_BALANCE),
(ParaId::from(SOME_PARA_ID).into_account_truncating(), CUSTOM_INITIAL_BALANCE),
];
new_test_ext_with_balances(balances).execute_with(|| {
let xcm_precompile_addr = H160::from(
hex::const_decode_to_array(b"00000000000000000000000000000000000A0000").unwrap(),
);
let sender: Location = AccountId32 { network: None, id: ALICE.into() }.into();
let message = Xcm(vec![
ReserveAssetDeposited((Parent, SEND_AMOUNT).into()),
ClearOrigin,
buy_execution((Parent, SEND_AMOUNT)),
DepositAsset { assets: AllCounted(1).into(), beneficiary: sender.clone() },
]);
// V4 is old and fails.
let v4_message: v4::Xcm<RuntimeCall> = message.try_into().unwrap();
let destination: VersionedLocation = Teyrchain(SOME_PARA_ID).into();
let versioned_message: VersionedXcm<RuntimeCall> = VersionedXcm::V4(v4_message.clone());
let xcm_send_params = IXcm::sendCall {
destination: destination.encode().into(),
message: versioned_message.encode().into(),
};
let call = IXcm::IXcmCalls::send(xcm_send_params);
let encoded_call = call.abi_encode();
let result = pallet_revive::Pallet::<Test>::bare_call(
RuntimeOrigin::signed(ALICE),
xcm_precompile_addr,
U256::zero(),
Weight::MAX,
u128::MAX,
encoded_call,
ExecConfig::new_substrate_tx(),
);
let return_value = match result.result {
Ok(value) => value,
Err(err) => panic!("XcmSendPrecompile call failed with error: {err:?}"),
};
assert!(return_value.did_revert());
// With V3 it also fails.
let v3_message: v3::Xcm<RuntimeCall> = v4_message.try_into().unwrap();
let destination: VersionedLocation = Teyrchain(SOME_PARA_ID).into();
let versioned_message: VersionedXcm<RuntimeCall> = VersionedXcm::V3(v3_message);
let xcm_send_params = IXcm::sendCall {
destination: destination.encode().into(),
message: versioned_message.encode().into(),
};
let call = IXcm::IXcmCalls::send(xcm_send_params);
let encoded_call = call.abi_encode();
let result = pallet_revive::Pallet::<Test>::bare_call(
RuntimeOrigin::signed(ALICE),
xcm_precompile_addr,
U256::zero(),
Weight::MAX,
u128::MAX,
encoded_call,
ExecConfig::new_substrate_tx(),
);
let return_value = match result.result {
Ok(value) => value,
Err(err) => panic!("XcmSendPrecompile call failed with error: {err:?}"),
};
assert!(return_value.did_revert());
});
}
#[test]
fn test_xcm_execute_precompile_works() {
use codec::Encode;
let balances = vec![
(ALICE, CUSTOM_INITIAL_BALANCE),
(ParaId::from(SOME_PARA_ID).into_account_truncating(), CUSTOM_INITIAL_BALANCE),
];
new_test_ext_with_balances(balances).execute_with(|| {
let xcm_precompile_addr = H160::from(
hex::const_decode_to_array(b"00000000000000000000000000000000000A0000").unwrap(),
);
let dest: Location = Junction::AccountId32 { network: None, id: BOB.into() }.into();
assert_eq!(Balances::total_balance(&ALICE), CUSTOM_INITIAL_BALANCE);
let message: VersionedXcm<RuntimeCall> = VersionedXcm::from(Xcm(vec![
WithdrawAsset((Here, SEND_AMOUNT).into()),
buy_execution((Here, SEND_AMOUNT)),
DepositAsset { assets: AllCounted(1).into(), beneficiary: dest },
]));
let weight_params = weighMessageCall { message: message.encode().into() };
let weight_call = IXcm::IXcmCalls::weighMessage(weight_params);
let encoded_weight_call = weight_call.abi_encode();
let xcm_weight_results = pallet_revive::Pallet::<Test>::bare_call(
RuntimeOrigin::signed(ALICE),
xcm_precompile_addr,
U256::zero(),
Weight::MAX,
u128::MAX,
encoded_weight_call,
ExecConfig::new_substrate_tx(),
);
let weight_result = match xcm_weight_results.result {
Ok(value) => value,
Err(err) => panic!("XcmExecutePrecompile Failed to decode weight with error {err:?}"),
};
let weight: IXcm::Weight = IXcm::Weight::abi_decode(&weight_result.data[..])
.expect("XcmExecutePrecompile Failed to decode weight");
let xcm_execute_params = IXcm::executeCall { message: message.encode().into(), weight };
let call = IXcm::IXcmCalls::execute(xcm_execute_params);
let encoded_call = call.abi_encode();
let result = pallet_revive::Pallet::<Test>::bare_call(
RuntimeOrigin::signed(ALICE),
xcm_precompile_addr,
U256::zero(),
Weight::MAX,
u128::MAX,
encoded_call,
ExecConfig::new_substrate_tx(),
);
assert!(result.result.is_ok());
assert_eq!(Balances::total_balance(&ALICE), CUSTOM_INITIAL_BALANCE - SEND_AMOUNT);
assert_eq!(Balances::total_balance(&BOB), SEND_AMOUNT);
});
}
#[test]
fn test_xcm_execute_precompile_different_beneficiary() {
use codec::Encode;
let balances = vec![(ALICE, CUSTOM_INITIAL_BALANCE), (CHARLIE, CUSTOM_INITIAL_BALANCE)];
new_test_ext_with_balances(balances).execute_with(|| {
let xcm_precompile_addr = H160::from(
hex::const_decode_to_array(b"00000000000000000000000000000000000A0000").unwrap(),
);
let dest: Location = Junction::AccountId32 { network: None, id: CHARLIE.into() }.into();
assert_eq!(Balances::total_balance(&ALICE), CUSTOM_INITIAL_BALANCE);
let message: VersionedXcm<RuntimeCall> = VersionedXcm::from(Xcm(vec![
WithdrawAsset((Here, SEND_AMOUNT).into()),
buy_execution((Here, SEND_AMOUNT)),
DepositAsset { assets: AllCounted(1).into(), beneficiary: dest },
]));
let weight_params = weighMessageCall { message: message.encode().into() };
let weight_call = IXcm::IXcmCalls::weighMessage(weight_params);
let encoded_weight_call = weight_call.abi_encode();
let xcm_weight_results = pallet_revive::Pallet::<Test>::bare_call(
RuntimeOrigin::signed(ALICE),
xcm_precompile_addr,
U256::zero(),
Weight::MAX,
u128::MAX,
encoded_weight_call,
ExecConfig::new_substrate_tx(),
);
let weight_result = match xcm_weight_results.result {
Ok(value) => value,
Err(err) => panic!("XcmExecutePrecompile Failed to decode weight with error: {err:?}"),
};
let weight: IXcm::Weight = IXcm::Weight::abi_decode(&weight_result.data[..])
.expect("XcmExecutePrecompile Failed to decode weight");
let xcm_execute_params = IXcm::executeCall { message: message.encode().into(), weight };
let call = IXcm::IXcmCalls::execute(xcm_execute_params);
let encoded_call = call.abi_encode();
let result = pallet_revive::Pallet::<Test>::bare_call(
RuntimeOrigin::signed(ALICE),
xcm_precompile_addr,
U256::zero(),
Weight::MAX,
u128::MAX,
encoded_call,
ExecConfig::new_substrate_tx(),
);
let return_value = match result.result {
Ok(value) => value,
Err(err) => panic!("XcmExecutePrecompile call failed with error: {err:?}"),
};
assert!(!return_value.did_revert());
assert_eq!(Balances::total_balance(&ALICE), CUSTOM_INITIAL_BALANCE - SEND_AMOUNT);
assert_eq!(Balances::total_balance(&CHARLIE), CUSTOM_INITIAL_BALANCE + SEND_AMOUNT);
});
}
#[test]
fn test_xcm_execute_precompile_fails() {
use codec::Encode;
let balances = vec![(ALICE, CUSTOM_INITIAL_BALANCE), (BOB, CUSTOM_INITIAL_BALANCE)];
new_test_ext_with_balances(balances).execute_with(|| {
let xcm_precompile_addr = H160::from(
hex::const_decode_to_array(b"00000000000000000000000000000000000A0000").unwrap(),
);
let dest: Location = Junction::AccountId32 { network: None, id: BOB.into() }.into();
assert_eq!(Balances::total_balance(&ALICE), CUSTOM_INITIAL_BALANCE);
let amount_to_send = CUSTOM_INITIAL_BALANCE - ExistentialDeposit::get();
let assets: Assets = (Here, amount_to_send).into();
let message: VersionedXcm<RuntimeCall> = VersionedXcm::from(Xcm(vec![
WithdrawAsset(assets.clone()),
buy_execution(assets.inner()[0].clone()),
DepositAsset { assets: assets.clone().into(), beneficiary: dest },
WithdrawAsset(assets),
]));
let weight_params = weighMessageCall { message: message.encode().into() };
let weight_call = IXcm::IXcmCalls::weighMessage(weight_params);
let encoded_weight_call = weight_call.abi_encode();
let xcm_weight_results = pallet_revive::Pallet::<Test>::bare_call(
RuntimeOrigin::signed(ALICE),
xcm_precompile_addr,
U256::zero(),
Weight::MAX,
u128::MAX,
encoded_weight_call,
ExecConfig::new_substrate_tx(),
);
let weight_result = match xcm_weight_results.result {
Ok(value) => value,
Err(err) => panic!("XcmExecutePrecompile Failed to decode weight with error: {err:?}"),
};
let weight: IXcm::Weight = IXcm::Weight::abi_decode(&weight_result.data[..])
.expect("XcmExecutePrecompile Failed to decode weight");
let xcm_execute_params = IXcm::executeCall { message: message.encode().into(), weight };
let call = IXcm::IXcmCalls::execute(xcm_execute_params);
let encoded_call = call.abi_encode();
let result = pallet_revive::Pallet::<Test>::bare_call(
RuntimeOrigin::signed(ALICE),
xcm_precompile_addr,
U256::zero(),
Weight::MAX,
u128::MAX,
encoded_call,
ExecConfig::new_substrate_tx(),
);
let return_value = match result.result {
Ok(value) => value,
Err(err) => panic!("XcmExecutePrecompile call failed with error: {err:?}"),
};
assert!(return_value.did_revert());
assert_eq!(Balances::total_balance(&ALICE), CUSTOM_INITIAL_BALANCE);
assert_eq!(Balances::total_balance(&BOB), CUSTOM_INITIAL_BALANCE);
});
}
#[test]
fn execute_fails_on_old_version() {
use codec::Encode;
let balances = vec![
(ALICE, CUSTOM_INITIAL_BALANCE),
(ParaId::from(SOME_PARA_ID).into_account_truncating(), CUSTOM_INITIAL_BALANCE),
];
new_test_ext_with_balances(balances).execute_with(|| {
let xcm_precompile_addr = H160::from(
hex::const_decode_to_array(b"00000000000000000000000000000000000A0000").unwrap(),
);
let dest: Location = Junction::AccountId32 { network: None, id: BOB.into() }.into();
assert_eq!(Balances::total_balance(&ALICE), CUSTOM_INITIAL_BALANCE);
let message = Xcm(vec![
WithdrawAsset((Here, SEND_AMOUNT).into()),
buy_execution((Here, SEND_AMOUNT)),
DepositAsset { assets: AllCounted(1).into(), beneficiary: dest },
]);
let versioned_message = VersionedXcm::from(message.clone());
let weight_params = weighMessageCall { message: versioned_message.encode().into() };
let weight_call = IXcm::IXcmCalls::weighMessage(weight_params);
let encoded_weight_call = weight_call.abi_encode();
let xcm_weight_results = pallet_revive::Pallet::<Test>::bare_call(
RuntimeOrigin::signed(ALICE),
xcm_precompile_addr,
U256::zero(),
Weight::MAX,
u128::MAX,
encoded_weight_call,
ExecConfig::new_substrate_tx(),
);
let weight_result = match xcm_weight_results.result {
Ok(value) => value,
Err(err) => panic!("XcmExecutePrecompile Failed to decode weight with error {err:?}"),
};
let weight: IXcm::Weight = IXcm::Weight::abi_decode(&weight_result.data[..])
.expect("XcmExecutePrecompile Failed to decode weight");
// Using a V4 message to check that it fails.
let v4_message: v4::Xcm<RuntimeCall> = message.clone().try_into().unwrap();
let versioned_message = VersionedXcm::V4(v4_message.clone());
let xcm_execute_params = IXcm::executeCall {
message: versioned_message.encode().into(),
weight: weight.clone(),
};
let call = IXcm::IXcmCalls::execute(xcm_execute_params);
let encoded_call = call.abi_encode();
let result = pallet_revive::Pallet::<Test>::bare_call(
RuntimeOrigin::signed(ALICE),
xcm_precompile_addr,
U256::zero(),
Weight::MAX,
u128::MAX,
encoded_call,
ExecConfig::new_substrate_tx(),
);
let return_value = match result.result {
Ok(value) => value,
Err(err) => panic!("XcmExecutePrecompile call failed with error: {err:?}"),
};
assert!(return_value.did_revert());
assert_eq!(Balances::total_balance(&ALICE), CUSTOM_INITIAL_BALANCE);
assert_eq!(Balances::total_balance(&BOB), 0);
// Now using a V3 message.
let v3_message: v3::Xcm<RuntimeCall> = v4_message.try_into().unwrap();
let versioned_message = VersionedXcm::V3(v3_message);
let xcm_execute_params =
IXcm::executeCall { message: versioned_message.encode().into(), weight };
let call = IXcm::IXcmCalls::execute(xcm_execute_params);
let encoded_call = call.abi_encode();
let result = pallet_revive::Pallet::<Test>::bare_call(
RuntimeOrigin::signed(ALICE),
xcm_precompile_addr,
U256::zero(),
Weight::MAX,
u128::MAX,
encoded_call,
ExecConfig::new_substrate_tx(),
);
let return_value = match result.result {
Ok(value) => value,
Err(err) => panic!("XcmExecutePrecompile call failed with error: {err:?}"),
};
assert!(return_value.did_revert());
assert_eq!(Balances::total_balance(&ALICE), CUSTOM_INITIAL_BALANCE);
assert_eq!(Balances::total_balance(&BOB), 0);
});
}
#[test]
fn weight_fails_on_old_version() {
use codec::Encode;
let balances = vec![
(ALICE, CUSTOM_INITIAL_BALANCE),
(ParaId::from(SOME_PARA_ID).into_account_truncating(), CUSTOM_INITIAL_BALANCE),
];
new_test_ext_with_balances(balances).execute_with(|| {
let xcm_precompile_addr = H160::from(
hex::const_decode_to_array(b"00000000000000000000000000000000000A0000").unwrap(),
);
let dest: Location = Junction::AccountId32 { network: None, id: BOB.into() }.into();
assert_eq!(Balances::total_balance(&ALICE), CUSTOM_INITIAL_BALANCE);
let message: Xcm<RuntimeCall> = Xcm(vec![
WithdrawAsset((Here, SEND_AMOUNT).into()),
buy_execution((Here, SEND_AMOUNT)),
DepositAsset { assets: AllCounted(1).into(), beneficiary: dest },
]);
// V4 version is old, fails.
let v4_message: v4::Xcm<RuntimeCall> = message.try_into().unwrap();
let versioned_message = VersionedXcm::V4(v4_message.clone());
let weight_params = weighMessageCall { message: versioned_message.encode().into() };
let weight_call = IXcm::IXcmCalls::weighMessage(weight_params);
let encoded_weight_call = weight_call.abi_encode();
let xcm_weight_results = pallet_revive::Pallet::<Test>::bare_call(
RuntimeOrigin::signed(ALICE),
xcm_precompile_addr,
U256::zero(),
Weight::MAX,
u128::MAX,
encoded_weight_call,
ExecConfig::new_substrate_tx(),
);
let result = match xcm_weight_results.result {
Ok(value) => value,
Err(err) => panic!("XcmExecutePrecompile Failed to decode weight with error {err:?}"),
};
assert!(result.did_revert());
// Now we also try V3.
let v3_message: v3::Xcm<RuntimeCall> = v4_message.try_into().unwrap();
let versioned_message = VersionedXcm::V3(v3_message);
let weight_params = weighMessageCall { message: versioned_message.encode().into() };
let weight_call = IXcm::IXcmCalls::weighMessage(weight_params);
let encoded_weight_call = weight_call.abi_encode();
let xcm_weight_results = pallet_revive::Pallet::<Test>::bare_call(
RuntimeOrigin::signed(ALICE),
xcm_precompile_addr,
U256::zero(),
Weight::MAX,
u128::MAX,
encoded_weight_call,
ExecConfig::new_substrate_tx(),
);
let result = match xcm_weight_results.result {
Ok(value) => value,
Err(err) => panic!("XcmExecutePrecompile Failed to decode weight with error {err:?}"),
};
assert!(result.did_revert());
});
}
+777
View File
@@ -0,0 +1,777 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
use super::*;
use frame_benchmarking::v2::*;
use frame_support::{assert_ok, weights::Weight};
use frame_system::RawOrigin;
use xcm::{latest::prelude::*, MAX_INSTRUCTIONS_TO_DECODE};
use xcm_builder::EnsureDelivery;
use xcm_executor::traits::FeeReason;
type RuntimeOrigin<T> = <T as frame_system::Config>::RuntimeOrigin;
/// Pallet we're benchmarking here.
pub struct Pallet<T: Config>(crate::Pallet<T>);
/// Trait that must be implemented by runtime to be able to benchmark pallet properly.
pub trait Config: crate::Config + pallet_balances::Config {
/// Helper that ensures successful delivery for extrinsics/benchmarks which need `SendXcm`.
type DeliveryHelper: EnsureDelivery;
/// A `Location` that can be reached via `XcmRouter`. Used only in benchmarks.
///
/// If `None`, the benchmarks that depend on a reachable destination will be skipped.
fn reachable_dest() -> Option<Location> {
None
}
/// A `(Asset, Location)` pair representing asset and the destination it can be
/// teleported to. Used only in benchmarks.
///
/// Implementation should also make sure `dest` is reachable/connected.
///
/// If `None`, the benchmarks that depend on this will default to `Weight::MAX`.
fn teleportable_asset_and_dest() -> Option<(Asset, Location)> {
None
}
/// A `(Asset, Location)` pair representing asset and the destination it can be
/// reserve-transferred to. Used only in benchmarks.
///
/// Implementation should also make sure `dest` is reachable/connected.
///
/// If `None`, the benchmarks that depend on this will default to `Weight::MAX`.
fn reserve_transferable_asset_and_dest() -> Option<(Asset, Location)> {
None
}
/// Sets up a complex transfer (usually consisting of a teleport and reserve-based transfer), so
/// that runtime can properly benchmark `transfer_assets()` extrinsic. Should return a tuple
/// `(Asset, AssetId, Location, dyn FnOnce())` representing the assets to transfer, the
/// `AssetId` of the asset to be used for fees, the destination chain for the transfer, and a
/// `verify()` closure to verify the intended transfer side-effects.
///
/// Implementation should make sure the provided assets can be transacted by the runtime, there
/// are enough balances in the involved accounts, and that `dest` is reachable/connected.
///
/// Used only in benchmarks.
///
/// If `None`, the benchmarks that depend on this will default to `Weight::MAX`.
fn set_up_complex_asset_transfer() -> Option<(Assets, AssetId, Location, Box<dyn FnOnce()>)> {
None
}
/// Gets an asset that can be handled by the AssetTransactor.
///
/// Used only in benchmarks.
///
/// Used, for example, in the benchmark for `claim_assets`.
fn get_asset() -> Asset;
}
#[benchmarks]
mod benchmarks {
use super::*;
#[benchmark]
fn send() -> Result<(), BenchmarkError> {
let send_origin =
T::SendXcmOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
if T::SendXcmOrigin::try_origin(send_origin.clone()).is_err() {
return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)));
}
let msg = Xcm(vec![ClearOrigin]);
let versioned_dest: VersionedLocation = T::reachable_dest()
.ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?
.into();
let versioned_msg = VersionedXcm::from(msg);
// Ensure that origin can send to destination
// (e.g. setup delivery fees, ensure router setup, ...)
T::DeliveryHelper::ensure_successful_delivery(
&Default::default(),
&versioned_dest.clone().try_into().unwrap(),
FeeReason::ChargeFees,
);
#[extrinsic_call]
_(send_origin as RuntimeOrigin<T>, Box::new(versioned_dest), Box::new(versioned_msg));
Ok(())
}
#[benchmark]
fn teleport_assets() -> Result<(), BenchmarkError> {
let (asset, destination) = T::teleportable_asset_and_dest()
.ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
let assets: Assets = asset.clone().into();
let caller: T::AccountId = whitelisted_caller();
let send_origin = RawOrigin::Signed(caller.clone());
let origin_location = T::ExecuteXcmOrigin::try_origin(send_origin.clone().into())
.map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
if !T::XcmTeleportFilter::contains(&(origin_location.clone(), assets.clone().into_inner()))
{
return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)));
}
// Ensure that origin can send to destination
// (e.g. setup delivery fees, ensure router setup, ...)
let (_, _) = T::DeliveryHelper::ensure_successful_delivery(
&origin_location,
&destination,
FeeReason::ChargeFees,
);
match &asset.fun {
Fungible(amount) => {
// Add transferred_amount to origin
<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::deposit_asset(
&Asset { fun: Fungible(*amount), id: asset.id.clone() },
&origin_location,
None,
)
.map_err(|error| {
tracing::error!("Fungible asset couldn't be deposited, error: {:?}", error);
BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))
})?;
},
NonFungible(_instance) => {
<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::deposit_asset(
&asset,
&origin_location,
None,
)
.map_err(|error| {
tracing::error!("Nonfungible asset couldn't be deposited, error: {:?}", error);
BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))
})?;
},
};
let recipient = [0u8; 32];
let versioned_dest: VersionedLocation = destination.into();
let versioned_beneficiary: VersionedLocation =
AccountId32 { network: None, id: recipient.into() }.into();
let versioned_assets: VersionedAssets = assets.into();
let fee_asset_id: AssetId = asset.id;
#[extrinsic_call]
_(
send_origin,
Box::new(versioned_dest),
Box::new(versioned_beneficiary),
Box::new(versioned_assets),
Box::new(fee_asset_id.into()),
);
Ok(())
}
#[benchmark]
fn reserve_transfer_assets() -> Result<(), BenchmarkError> {
let (asset, destination) = T::reserve_transferable_asset_and_dest()
.ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
let assets: Assets = asset.clone().into();
let caller: T::AccountId = whitelisted_caller();
let send_origin = RawOrigin::Signed(caller.clone());
let origin_location = T::ExecuteXcmOrigin::try_origin(send_origin.clone().into())
.map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
if !T::XcmReserveTransferFilter::contains(&(
origin_location.clone(),
assets.clone().into_inner(),
)) {
return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)));
}
// Ensure that origin can send to destination
// (e.g. setup delivery fees, ensure router setup, ...)
let (_, _) = T::DeliveryHelper::ensure_successful_delivery(
&origin_location,
&destination,
FeeReason::ChargeFees,
);
match &asset.fun {
Fungible(amount) => {
// Add transferred_amount to origin
<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::deposit_asset(
&Asset { fun: Fungible(*amount), id: asset.id.clone() },
&origin_location,
None,
)
.map_err(|error| {
tracing::error!("Fungible asset couldn't be deposited, error: {:?}", error);
BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))
})?;
},
NonFungible(_instance) => {
<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::deposit_asset(
&asset,
&origin_location,
None,
)
.map_err(|error| {
tracing::error!("Nonfungible asset couldn't be deposited, error: {:?}", error);
BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))
})?;
},
};
let recipient = [0u8; 32];
let versioned_dest: VersionedLocation = destination.clone().into();
let versioned_beneficiary: VersionedLocation =
AccountId32 { network: None, id: recipient.into() }.into();
let versioned_assets: VersionedAssets = assets.into();
let fee_asset_id: AssetId = asset.id.clone();
#[extrinsic_call]
_(
send_origin,
Box::new(versioned_dest),
Box::new(versioned_beneficiary),
Box::new(versioned_assets),
Box::new(fee_asset_id.into()),
);
match &asset.fun {
Fungible(amount) => {
assert_ok!(<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::withdraw_asset(
&Asset { fun: Fungible(*amount), id: asset.id },
&destination,
None,
));
},
NonFungible(_instance) => {
assert_ok!(<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::withdraw_asset(
&asset,
&destination,
None,
));
},
};
Ok(())
}
#[benchmark]
fn transfer_assets() -> Result<(), BenchmarkError> {
let (assets, fee_asset_id, destination, verify_fn) = T::set_up_complex_asset_transfer()
.ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
let caller: T::AccountId = whitelisted_caller();
let send_origin = RawOrigin::Signed(caller.clone());
let recipient = [0u8; 32];
let versioned_dest: VersionedLocation = destination.into();
let versioned_beneficiary: VersionedLocation =
AccountId32 { network: None, id: recipient.into() }.into();
let versioned_assets: VersionedAssets = assets.into();
// Ensure that origin can send to destination
// (e.g. setup delivery fees, ensure router setup, ...)
T::DeliveryHelper::ensure_successful_delivery(
&Default::default(),
&versioned_dest.clone().try_into().unwrap(),
FeeReason::ChargeFees,
);
#[extrinsic_call]
_(
send_origin,
Box::new(versioned_dest),
Box::new(versioned_beneficiary),
Box::new(versioned_assets),
Box::new(fee_asset_id.into()),
WeightLimit::Unlimited,
);
// run provided verification function
verify_fn();
Ok(())
}
#[benchmark]
fn execute() -> Result<(), BenchmarkError> {
let execute_origin =
T::ExecuteXcmOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
let origin_location = T::ExecuteXcmOrigin::try_origin(execute_origin.clone())
.map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
let msg = Xcm(vec![ClearOrigin]);
if !T::XcmExecuteFilter::contains(&(origin_location, msg.clone())) {
return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)));
}
let versioned_msg = VersionedXcm::from(msg);
#[extrinsic_call]
_(execute_origin as RuntimeOrigin<T>, Box::new(versioned_msg), Weight::MAX);
Ok(())
}
#[benchmark]
fn force_xcm_version() -> Result<(), BenchmarkError> {
let loc = T::reachable_dest()
.ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
let xcm_version = 2;
#[extrinsic_call]
_(RawOrigin::Root, Box::new(loc), xcm_version);
Ok(())
}
#[benchmark]
fn force_default_xcm_version() {
#[extrinsic_call]
_(RawOrigin::Root, Some(2))
}
#[benchmark]
fn force_subscribe_version_notify() -> Result<(), BenchmarkError> {
let versioned_loc: VersionedLocation = T::reachable_dest()
.ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?
.into();
// Ensure that origin can send to destination
// (e.g. setup delivery fees, ensure router setup, ...)
T::DeliveryHelper::ensure_successful_delivery(
&Default::default(),
&versioned_loc.clone().try_into().unwrap(),
FeeReason::ChargeFees,
);
#[extrinsic_call]
_(RawOrigin::Root, Box::new(versioned_loc));
Ok(())
}
#[benchmark]
fn force_unsubscribe_version_notify() -> Result<(), BenchmarkError> {
let loc = T::reachable_dest()
.ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
let versioned_loc: VersionedLocation = loc.clone().into();
// Ensure that origin can send to destination
// (e.g. setup delivery fees, ensure router setup, ...)
T::DeliveryHelper::ensure_successful_delivery(
&Default::default(),
&versioned_loc.clone().try_into().unwrap(),
FeeReason::ChargeFees,
);
let _ = crate::Pallet::<T>::request_version_notify(loc);
#[extrinsic_call]
_(RawOrigin::Root, Box::new(versioned_loc));
Ok(())
}
#[benchmark]
fn force_suspension() {
#[extrinsic_call]
_(RawOrigin::Root, true)
}
#[benchmark]
fn migrate_supported_version() {
let old_version = XCM_VERSION - 1;
let loc = VersionedLocation::from(Location::from(Parent));
SupportedVersion::<T>::insert(old_version, loc, old_version);
#[block]
{
crate::Pallet::<T>::lazy_migration(
VersionMigrationStage::MigrateSupportedVersion,
Weight::zero(),
);
}
}
#[benchmark]
fn migrate_version_notifiers() {
let old_version = XCM_VERSION - 1;
let loc = VersionedLocation::from(Location::from(Parent));
VersionNotifiers::<T>::insert(old_version, loc, 0);
#[block]
{
crate::Pallet::<T>::lazy_migration(
VersionMigrationStage::MigrateVersionNotifiers,
Weight::zero(),
);
}
}
#[benchmark]
fn already_notified_target() -> Result<(), BenchmarkError> {
let loc = T::reachable_dest().ok_or(BenchmarkError::Override(
BenchmarkResult::from_weight(T::DbWeight::get().reads(1)),
))?;
let loc = VersionedLocation::from(loc);
let current_version = T::AdvertisedXcmVersion::get();
VersionNotifyTargets::<T>::insert(
current_version,
loc,
(0, Weight::zero(), current_version),
);
#[block]
{
crate::Pallet::<T>::lazy_migration(
VersionMigrationStage::NotifyCurrentTargets(None),
Weight::zero(),
);
}
Ok(())
}
#[benchmark]
fn notify_current_targets() -> Result<(), BenchmarkError> {
let loc = T::reachable_dest().ok_or(BenchmarkError::Override(
BenchmarkResult::from_weight(T::DbWeight::get().reads_writes(1, 3)),
))?;
let loc = VersionedLocation::from(loc);
let current_version = T::AdvertisedXcmVersion::get();
let old_version = current_version - 1;
VersionNotifyTargets::<T>::insert(current_version, loc, (0, Weight::zero(), old_version));
#[block]
{
crate::Pallet::<T>::lazy_migration(
VersionMigrationStage::NotifyCurrentTargets(None),
Weight::zero(),
);
}
Ok(())
}
#[benchmark]
fn notify_target_migration_fail() {
let newer_xcm_version = xcm::prelude::XCM_VERSION;
let older_xcm_version = newer_xcm_version - 1;
let bad_location: Location = Plurality { id: BodyId::Unit, part: BodyPart::Voice }.into();
let bad_location = VersionedLocation::from(bad_location)
.into_version(older_xcm_version)
.expect("Version conversion should work");
let current_version = T::AdvertisedXcmVersion::get();
VersionNotifyTargets::<T>::insert(
current_version,
bad_location,
(0, Weight::zero(), current_version),
);
#[block]
{
crate::Pallet::<T>::lazy_migration(
VersionMigrationStage::MigrateAndNotifyOldTargets,
Weight::zero(),
);
}
}
#[benchmark]
fn migrate_version_notify_targets() {
let current_version = T::AdvertisedXcmVersion::get();
let old_version = current_version - 1;
let loc = VersionedLocation::from(Location::from(Parent));
VersionNotifyTargets::<T>::insert(old_version, loc, (0, Weight::zero(), current_version));
#[block]
{
crate::Pallet::<T>::lazy_migration(
VersionMigrationStage::MigrateAndNotifyOldTargets,
Weight::zero(),
);
}
}
#[benchmark]
fn migrate_and_notify_old_targets() -> Result<(), BenchmarkError> {
let loc = T::reachable_dest().ok_or(BenchmarkError::Override(
BenchmarkResult::from_weight(T::DbWeight::get().reads_writes(1, 3)),
))?;
let loc = VersionedLocation::from(loc);
let old_version = T::AdvertisedXcmVersion::get() - 1;
VersionNotifyTargets::<T>::insert(old_version, loc, (0, Weight::zero(), old_version));
#[block]
{
crate::Pallet::<T>::lazy_migration(
VersionMigrationStage::MigrateAndNotifyOldTargets,
Weight::zero(),
);
}
Ok(())
}
#[benchmark]
fn new_query() {
let responder = Location::from(Parent);
let timeout = 1u32.into();
let match_querier = Location::from(Here);
#[block]
{
crate::Pallet::<T>::new_query(responder, timeout, match_querier);
}
}
#[benchmark]
fn take_response() {
let responder = Location::from(Parent);
let timeout = 1u32.into();
let match_querier = Location::from(Here);
let query_id = crate::Pallet::<T>::new_query(responder, timeout, match_querier);
let infos = (0..xcm::v3::MaxPalletsInfo::get())
.map(|_| {
PalletInfo::new(
u32::MAX,
(0..xcm::v3::MaxPalletNameLen::get())
.map(|_| 97u8)
.collect::<Vec<_>>()
.try_into()
.unwrap(),
(0..xcm::v3::MaxPalletNameLen::get())
.map(|_| 97u8)
.collect::<Vec<_>>()
.try_into()
.unwrap(),
u32::MAX,
u32::MAX,
u32::MAX,
)
.unwrap()
})
.collect::<Vec<_>>();
crate::Pallet::<T>::expect_response(
query_id,
Response::PalletsInfo(infos.try_into().unwrap()),
);
#[block]
{
<crate::Pallet<T> as QueryHandler>::take_response(query_id);
}
}
#[benchmark]
fn claim_assets() -> Result<(), BenchmarkError> {
let claim_origin = RawOrigin::Signed(whitelisted_caller());
let claim_location = T::ExecuteXcmOrigin::try_origin(claim_origin.clone().into())
.map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
let asset: Asset = T::get_asset();
// Trap assets for claiming later
crate::Pallet::<T>::drop_assets(
&claim_location,
asset.clone().into(),
&XcmContext { origin: None, message_id: [0u8; 32], topic: None },
);
let versioned_assets = VersionedAssets::from(Assets::from(asset));
#[extrinsic_call]
_(
claim_origin,
Box::new(versioned_assets),
Box::new(VersionedLocation::from(claim_location)),
);
Ok(())
}
#[benchmark]
fn add_authorized_alias() -> Result<(), BenchmarkError> {
let who: T::AccountId = whitelisted_caller();
let origin = RawOrigin::Signed(who.clone());
let origin_location: VersionedLocation =
T::ExecuteXcmOrigin::try_origin(origin.clone().into())
.map_err(|_| {
tracing::error!(
target: "xcm::benchmarking::pallet_xcm::add_authorized_alias",
?origin,
"try_origin failed",
);
BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))
})?
.into();
// Give some multiple of ED
let balance = T::ExistentialDeposit::get() * 1000000u32.into();
let _ =
<pallet_balances::Pallet::<T> as frame_support::traits::Currency<_>>::make_free_balance_be(&who, balance);
let mut existing_aliases = BoundedVec::<OriginAliaser, MaxAuthorizedAliases>::new();
// prepopulate list with `max-1` aliases to benchmark worst case
for i in 1..MaxAuthorizedAliases::get() {
let alias =
Location::new(1, [Teyrchain(i), AccountId32 { network: None, id: [42_u8; 32] }])
.into();
let aliaser = OriginAliaser { location: alias, expiry: None };
existing_aliases.try_push(aliaser).unwrap()
}
let footprint = aliasers_footprint(existing_aliases.len());
let ticket = TicketOf::<T>::new(&who, footprint).map_err(|e| {
tracing::error!(
target: "xcm::benchmarking::pallet_xcm::add_authorized_alias",
?who,
?footprint,
error=?e,
"could not create ticket",
);
BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))
})?;
let entry = AuthorizedAliasesEntry { aliasers: existing_aliases, ticket };
AuthorizedAliases::<T>::insert(&origin_location, entry);
// now benchmark adding new alias
let aliaser: VersionedLocation =
Location::new(1, [Teyrchain(1234), AccountId32 { network: None, id: [42_u8; 32] }])
.into();
#[extrinsic_call]
_(origin, Box::new(aliaser), None);
Ok(())
}
#[benchmark]
fn remove_authorized_alias() -> Result<(), BenchmarkError> {
let who: T::AccountId = whitelisted_caller();
let origin = RawOrigin::Signed(who.clone());
let error = BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX));
let origin_location =
T::ExecuteXcmOrigin::try_origin(origin.clone().into()).map_err(|_| {
tracing::error!(
target: "xcm::benchmarking::pallet_xcm::remove_authorized_alias",
?origin,
"try_origin failed",
);
error.clone()
})?;
// remove `network` from inner `AccountId32` for easier matching of automatic AccountId ->
// Location conversions.
let origin_location: VersionedLocation = match origin_location.unpack() {
(0, [AccountId32 { network: _, id }]) =>
Location::new(0, [AccountId32 { network: None, id: *id }]).into(),
_ => {
tracing::error!(
target: "xcm::benchmarking::pallet_xcm::remove_authorized_alias",
?origin_location,
"unexpected origin failed",
);
return Err(error.clone());
},
};
// Give some multiple of ED
let balance = T::ExistentialDeposit::get() * 1000000u32.into();
let _ =
<pallet_balances::Pallet::<T> as frame_support::traits::Currency<_>>::make_free_balance_be(&who, balance);
let mut existing_aliases = BoundedVec::<OriginAliaser, MaxAuthorizedAliases>::new();
// prepopulate list with `max` aliases to benchmark worst case
for i in 1..MaxAuthorizedAliases::get() + 1 {
let alias =
Location::new(1, [Teyrchain(i), AccountId32 { network: None, id: [42_u8; 32] }])
.into();
let aliaser = OriginAliaser { location: alias, expiry: None };
existing_aliases.try_push(aliaser).unwrap()
}
let footprint = aliasers_footprint(existing_aliases.len());
let ticket = TicketOf::<T>::new(&who, footprint).map_err(|e| {
tracing::error!(
target: "xcm::benchmarking::pallet_xcm::remove_authorized_alias",
?who,
?footprint,
error=?e,
"could not create ticket",
);
error
})?;
let entry = AuthorizedAliasesEntry { aliasers: existing_aliases, ticket };
AuthorizedAliases::<T>::insert(&origin_location, entry);
// now benchmark removing an alias
let aliaser_to_remove: VersionedLocation =
Location::new(1, [Teyrchain(1), AccountId32 { network: None, id: [42_u8; 32] }]).into();
#[extrinsic_call]
_(origin, Box::new(aliaser_to_remove));
Ok(())
}
#[benchmark]
fn weigh_message() -> Result<(), BenchmarkError> {
let msg = Xcm(vec![ClearOrigin; MAX_INSTRUCTIONS_TO_DECODE.into()]);
let versioned_msg = VersionedXcm::from(msg);
#[block]
{
crate::Pallet::<T>::query_xcm_weight(versioned_msg)
.map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
}
Ok(())
}
impl_benchmark_test_suite!(
Pallet,
crate::mock::new_test_ext_with_balances(Vec::new()),
crate::mock::Test
);
}
pub mod helpers {
use super::*;
pub fn native_teleport_as_asset_transfer<T>(
native_asset_location: Location,
destination: Location,
) -> Option<(Assets, AssetId, Location, Box<dyn FnOnce()>)>
where
T: Config + pallet_balances::Config,
u128: From<<T as pallet_balances::Config>::Balance>,
{
// Relay/native token can be teleported to/from AH.
let amount = T::ExistentialDeposit::get() * 100u32.into();
let assets: Assets =
Asset { fun: Fungible(amount.into()), id: AssetId(native_asset_location.clone()) }
.into();
let fee_asset_id: AssetId = AssetId(native_asset_location);
// Give some multiple of transferred amount
let balance = amount * 10u32.into();
let who = whitelisted_caller();
let _ =
<pallet_balances::Pallet::<T> as frame_support::traits::Currency<_>>::make_free_balance_be(&who, balance);
// verify initial balance
assert_eq!(pallet_balances::Pallet::<T>::free_balance(&who), balance);
// verify transferred successfully
let verify = Box::new(move || {
// verify balance after transfer, decreased by transferred amount (and delivery fees)
assert!(pallet_balances::Pallet::<T>::free_balance(&who) <= balance - amount);
});
Some((assets, fee_asset_id, destination, verify))
}
}
+208
View File
@@ -0,0 +1,208 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Errors for the XCM pallet.
use codec::{Decode, DecodeWithMemTracking, Encode};
use frame_support::PalletError;
use scale_info::TypeInfo;
use xcm::latest::Error as XcmError;
#[derive(
Copy, Clone, Encode, Decode, DecodeWithMemTracking, Eq, PartialEq, Debug, TypeInfo, PalletError,
)]
pub enum ExecutionError {
// Errors that happen due to instructions being executed. These alone are defined in the
// XCM specification.
/// An arithmetic overflow happened.
#[codec(index = 0)]
Overflow,
/// The instruction is intentionally unsupported.
#[codec(index = 1)]
Unimplemented,
/// Origin Register does not contain a value value for a reserve transfer notification.
#[codec(index = 2)]
UntrustedReserveLocation,
/// Origin Register does not contain a value value for a teleport notification.
#[codec(index = 3)]
UntrustedTeleportLocation,
/// `MultiLocation` value too large to descend further.
#[codec(index = 4)]
LocationFull,
/// `MultiLocation` value ascend more parents than known ancestors of local location.
#[codec(index = 5)]
LocationNotInvertible,
/// The Origin Register does not contain a valid value for instruction.
#[codec(index = 6)]
BadOrigin,
/// The location parameter is not a valid value for the instruction.
#[codec(index = 7)]
InvalidLocation,
/// The given asset is not handled.
#[codec(index = 8)]
AssetNotFound,
/// An asset transaction (like withdraw or deposit) failed (typically due to type conversions).
#[codec(index = 9)]
FailedToTransactAsset,
/// An asset cannot be withdrawn, potentially due to lack of ownership, availability or rights.
#[codec(index = 10)]
NotWithdrawable,
/// An asset cannot be deposited under the ownership of a particular location.
#[codec(index = 11)]
LocationCannotHold,
/// Attempt to send a message greater than the maximum supported by the transport protocol.
#[codec(index = 12)]
ExceedsMaxMessageSize,
/// The given message cannot be translated into a format supported by the destination.
#[codec(index = 13)]
DestinationUnsupported,
/// Destination is routable, but there is some issue with the transport mechanism.
#[codec(index = 14)]
Transport,
/// Destination is known to be unroutable.
#[codec(index = 15)]
Unroutable,
/// Used by `ClaimAsset` when the given claim could not be recognized/found.
#[codec(index = 16)]
UnknownClaim,
/// Used by `Transact` when the functor cannot be decoded.
#[codec(index = 17)]
FailedToDecode,
/// Used by `Transact` to indicate that the given weight limit could be breached by the
/// functor.
#[codec(index = 18)]
MaxWeightInvalid,
/// Used by `BuyExecution` when the Holding Register does not contain payable fees.
#[codec(index = 19)]
NotHoldingFees,
/// Used by `BuyExecution` when the fees declared to purchase weight are insufficient.
#[codec(index = 20)]
TooExpensive,
/// Used by the `Trap` instruction to force an error intentionally. Its code is included.
#[codec(index = 21)]
Trap,
/// Used by `ExpectAsset`, `ExpectError` and `ExpectOrigin` when the expectation was not true.
#[codec(index = 22)]
ExpectationFalse,
/// The provided pallet index was not found.
#[codec(index = 23)]
PalletNotFound,
/// The given pallet's name is different to that expected.
#[codec(index = 24)]
NameMismatch,
/// The given pallet's version has an incompatible version to that expected.
#[codec(index = 25)]
VersionIncompatible,
/// The given operation would lead to an overflow of the Holding Register.
#[codec(index = 26)]
HoldingWouldOverflow,
/// The message was unable to be exported.
#[codec(index = 27)]
ExportError,
/// `MultiLocation` value failed to be reanchored.
#[codec(index = 28)]
ReanchorFailed,
/// No deal is possible under the given constraints.
#[codec(index = 29)]
NoDeal,
/// Fees were required which the origin could not pay.
#[codec(index = 30)]
FeesNotMet,
/// Some other error with locking.
#[codec(index = 31)]
LockError,
/// The state was not in a condition where the operation was valid to make.
#[codec(index = 32)]
NoPermission,
/// The universal location of the local consensus is improper.
#[codec(index = 33)]
Unanchored,
/// An asset cannot be deposited, probably because (too much of) it already exists.
#[codec(index = 34)]
NotDepositable,
/// Too many assets matched the given asset filter.
#[codec(index = 35)]
TooManyAssets,
// Errors that happen prior to instructions being executed. These fall outside of the XCM
// spec.
/// XCM version not able to be handled.
UnhandledXcmVersion,
/// Execution of the XCM would potentially result in a greater weight used than weight limit.
WeightLimitReached,
/// The XCM did not pass the barrier condition for execution.
///
/// The barrier condition differs on different chains and in different circumstances, but
/// generally it means that the conditions surrounding the message were not such that the chain
/// considers the message worth spending time executing. Since most chains lift the barrier to
/// execution on appropriate payment, presentation of an NFT voucher, or based on the message
/// origin, it means that none of those were the case.
Barrier,
/// The weight of an XCM message is not computable ahead of execution.
WeightNotComputable,
/// Recursion stack limit reached
// TODO(https://github.com/pezkuwichain/pezkuwi-sdk/issues/148): This should have a fixed index since
// we use it in `FrameTransactionalProcessor` // which is used in instructions.
// Or we should create a different error for that.
ExceedsStackLimit,
}
impl From<XcmError> for ExecutionError {
fn from(error: XcmError) -> Self {
match error {
XcmError::Overflow => Self::Overflow,
XcmError::Unimplemented => Self::Unimplemented,
XcmError::UntrustedReserveLocation => Self::UntrustedReserveLocation,
XcmError::UntrustedTeleportLocation => Self::UntrustedTeleportLocation,
XcmError::LocationFull => Self::LocationFull,
XcmError::LocationNotInvertible => Self::LocationNotInvertible,
XcmError::BadOrigin => Self::BadOrigin,
XcmError::InvalidLocation => Self::InvalidLocation,
XcmError::AssetNotFound => Self::AssetNotFound,
XcmError::FailedToTransactAsset(_) => Self::FailedToTransactAsset,
XcmError::NotWithdrawable => Self::NotWithdrawable,
XcmError::LocationCannotHold => Self::LocationCannotHold,
XcmError::ExceedsMaxMessageSize => Self::ExceedsMaxMessageSize,
XcmError::DestinationUnsupported => Self::DestinationUnsupported,
XcmError::Transport(_) => Self::Transport,
XcmError::Unroutable => Self::Unroutable,
XcmError::UnknownClaim => Self::UnknownClaim,
XcmError::FailedToDecode => Self::FailedToDecode,
XcmError::MaxWeightInvalid => Self::MaxWeightInvalid,
XcmError::NotHoldingFees => Self::NotHoldingFees,
XcmError::TooExpensive => Self::TooExpensive,
XcmError::Trap(_) => Self::Trap,
XcmError::ExpectationFalse => Self::ExpectationFalse,
XcmError::PalletNotFound => Self::PalletNotFound,
XcmError::NameMismatch => Self::NameMismatch,
XcmError::VersionIncompatible => Self::VersionIncompatible,
XcmError::HoldingWouldOverflow => Self::HoldingWouldOverflow,
XcmError::ExportError => Self::ExportError,
XcmError::ReanchorFailed => Self::ReanchorFailed,
XcmError::NoDeal => Self::NoDeal,
XcmError::FeesNotMet => Self::FeesNotMet,
XcmError::LockError => Self::LockError,
XcmError::NoPermission => Self::NoPermission,
XcmError::Unanchored => Self::Unanchored,
XcmError::NotDepositable => Self::NotDepositable,
XcmError::TooManyAssets => Self::TooManyAssets,
XcmError::UnhandledXcmVersion => Self::UnhandledXcmVersion,
XcmError::WeightLimitReached(_) => Self::WeightLimitReached,
XcmError::Barrier => Self::Barrier,
XcmError::WeightNotComputable => Self::WeightNotComputable,
XcmError::ExceedsStackLimit => Self::ExceedsStackLimit,
}
}
}
File diff suppressed because it is too large Load Diff
+542
View File
@@ -0,0 +1,542 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
use crate::{
pallet::CurrentMigration, Config, CurrentXcmVersion, Pallet, VersionMigrationStage,
VersionNotifyTargets,
};
use frame_support::{
pallet_prelude::*,
traits::{OnRuntimeUpgrade, StorageVersion, UncheckedOnRuntimeUpgrade},
weights::Weight,
};
const DEFAULT_PROOF_SIZE: u64 = 64 * 1024;
/// Utilities for handling XCM version migration for the relevant data.
pub mod data {
use crate::*;
/// A trait for handling XCM versioned data migration for the requested `XcmVersion`.
pub(crate) trait NeedsMigration {
type MigratedData;
/// Returns true if data does not match `minimal_allowed_xcm_version`.
fn needs_migration(&self, minimal_allowed_xcm_version: XcmVersion) -> bool;
/// Attempts to migrate data. `Ok(None)` means no migration is needed.
/// `Ok(Some(Self::MigratedData))` should contain the migrated data.
fn try_migrate(self, to_xcm_version: XcmVersion) -> Result<Option<Self::MigratedData>, ()>;
}
/// Implementation of `NeedsMigration` for `LockedFungibles` data.
impl<B, M> NeedsMigration for BoundedVec<(B, VersionedLocation), M> {
type MigratedData = Self;
fn needs_migration(&self, minimal_allowed_xcm_version: XcmVersion) -> bool {
self.iter()
.any(|(_, unlocker)| unlocker.identify_version() < minimal_allowed_xcm_version)
}
fn try_migrate(
mut self,
to_xcm_version: XcmVersion,
) -> Result<Option<Self::MigratedData>, ()> {
let mut was_modified = false;
for locked in self.iter_mut() {
if locked.1.identify_version() < to_xcm_version {
let Ok(new_unlocker) = locked.1.clone().into_version(to_xcm_version) else {
return Err(());
};
locked.1 = new_unlocker;
was_modified = true;
}
}
if was_modified {
Ok(Some(self))
} else {
Ok(None)
}
}
}
/// Implementation of `NeedsMigration` for `Queries` data.
impl<BlockNumber> NeedsMigration for QueryStatus<BlockNumber> {
type MigratedData = Self;
fn needs_migration(&self, minimal_allowed_xcm_version: XcmVersion) -> bool {
match &self {
QueryStatus::Pending { responder, maybe_match_querier, .. } =>
responder.identify_version() < minimal_allowed_xcm_version ||
maybe_match_querier
.as_ref()
.map(|v| v.identify_version() < minimal_allowed_xcm_version)
.unwrap_or(false),
QueryStatus::VersionNotifier { origin, .. } =>
origin.identify_version() < minimal_allowed_xcm_version,
QueryStatus::Ready { response, .. } =>
response.identify_version() < minimal_allowed_xcm_version,
}
}
fn try_migrate(self, to_xcm_version: XcmVersion) -> Result<Option<Self::MigratedData>, ()> {
if !self.needs_migration(to_xcm_version) {
return Ok(None);
}
// do migration
match self {
QueryStatus::Pending { responder, maybe_match_querier, maybe_notify, timeout } => {
let Ok(responder) = responder.into_version(to_xcm_version) else {
return Err(());
};
let Ok(maybe_match_querier) =
maybe_match_querier.map(|mmq| mmq.into_version(to_xcm_version)).transpose()
else {
return Err(());
};
Ok(Some(QueryStatus::Pending {
responder,
maybe_match_querier,
maybe_notify,
timeout,
}))
},
QueryStatus::VersionNotifier { origin, is_active } => origin
.into_version(to_xcm_version)
.map(|origin| Some(QueryStatus::VersionNotifier { origin, is_active })),
QueryStatus::Ready { response, at } => response
.into_version(to_xcm_version)
.map(|response| Some(QueryStatus::Ready { response, at })),
}
}
}
/// Implementation of `NeedsMigration` for `RemoteLockedFungibles` key type.
impl<A> NeedsMigration for (XcmVersion, A, VersionedAssetId) {
type MigratedData = Self;
fn needs_migration(&self, minimal_allowed_xcm_version: XcmVersion) -> bool {
self.0 < minimal_allowed_xcm_version ||
self.2.identify_version() < minimal_allowed_xcm_version
}
fn try_migrate(self, to_xcm_version: XcmVersion) -> Result<Option<Self::MigratedData>, ()> {
if !self.needs_migration(to_xcm_version) {
return Ok(None);
}
let Ok(asset_id) = self.2.into_version(to_xcm_version) else { return Err(()) };
Ok(Some((to_xcm_version, self.1, asset_id)))
}
}
/// Implementation of `NeedsMigration` for `RemoteLockedFungibles` data.
impl<ConsumerIdentifier, MaxConsumers: Get<u32>> NeedsMigration
for RemoteLockedFungibleRecord<ConsumerIdentifier, MaxConsumers>
{
type MigratedData = Self;
fn needs_migration(&self, minimal_allowed_xcm_version: XcmVersion) -> bool {
self.owner.identify_version() < minimal_allowed_xcm_version ||
self.locker.identify_version() < minimal_allowed_xcm_version
}
fn try_migrate(self, to_xcm_version: XcmVersion) -> Result<Option<Self::MigratedData>, ()> {
if !self.needs_migration(to_xcm_version) {
return Ok(None);
}
let RemoteLockedFungibleRecord { amount, owner, locker, consumers } = self;
let Ok(owner) = owner.into_version(to_xcm_version) else { return Err(()) };
let Ok(locker) = locker.into_version(to_xcm_version) else { return Err(()) };
Ok(Some(RemoteLockedFungibleRecord { amount, owner, locker, consumers }))
}
}
/// Implementation of `NeedsMigration` for `AuthorizedAliases` data.
impl<M: Get<u32>, T: Config> NeedsMigration
for (&VersionedLocation, AuthorizedAliasesEntry<TicketOf<T>, M>, PhantomData<T>)
{
type MigratedData = (VersionedLocation, AuthorizedAliasesEntry<TicketOf<T>, M>);
fn needs_migration(&self, required_version: XcmVersion) -> bool {
self.0.identify_version() != required_version ||
self.1
.aliasers
.iter()
.any(|alias| alias.location.identify_version() != required_version)
}
fn try_migrate(
self,
required_version: XcmVersion,
) -> Result<Option<Self::MigratedData>, ()> {
if !self.needs_migration(required_version) {
return Ok(None);
}
let key = if self.0.identify_version() != required_version {
let Ok(converted_key) = self.0.clone().into_version(required_version) else {
return Err(());
};
converted_key
} else {
self.0.clone()
};
let mut new_aliases = BoundedVec::<OriginAliaser, M>::new();
let (aliasers, ticket) = (self.1.aliasers, self.1.ticket);
for alias in aliasers {
let OriginAliaser { mut location, expiry } = alias.clone();
if location.identify_version() != required_version {
location = location.into_version(required_version)?;
}
new_aliases.try_push(OriginAliaser { location, expiry }).map_err(|_| ())?;
}
Ok(Some((key, AuthorizedAliasesEntry { aliasers: new_aliases, ticket })))
}
}
impl<T: Config> Pallet<T> {
/// Migrates relevant data to the `required_xcm_version`.
pub(crate) fn migrate_data_to_xcm_version(
weight: &mut Weight,
required_xcm_version: XcmVersion,
) {
const LOG_TARGET: &str = "runtime::xcm::pallet_xcm::migrate_data_to_xcm_version";
// check and migrate `Queries`
let queries_to_migrate = Queries::<T>::iter().filter_map(|(id, data)| {
weight.saturating_add(T::DbWeight::get().reads(1));
match data.try_migrate(required_xcm_version) {
Ok(Some(new_data)) => Some((id, new_data)),
Ok(None) => None,
Err(_) => {
tracing::error!(
target: LOG_TARGET,
?id,
?required_xcm_version,
"`Queries` cannot be migrated!"
);
None
},
}
});
for (id, new_data) in queries_to_migrate {
tracing::info!(
target: LOG_TARGET,
query_id = ?id,
?new_data,
"Migrating `Queries`"
);
Queries::<T>::insert(id, new_data);
weight.saturating_add(T::DbWeight::get().writes(1));
}
// check and migrate `LockedFungibles`
let locked_fungibles_to_migrate =
LockedFungibles::<T>::iter().filter_map(|(id, data)| {
weight.saturating_add(T::DbWeight::get().reads(1));
match data.try_migrate(required_xcm_version) {
Ok(Some(new_data)) => Some((id, new_data)),
Ok(None) => None,
Err(_) => {
tracing::error!(
target: LOG_TARGET,
?id,
?required_xcm_version,
"`LockedFungibles` cannot be migrated!"
);
None
},
}
});
for (id, new_data) in locked_fungibles_to_migrate {
tracing::info!(
target: LOG_TARGET,
account_id = ?id,
?new_data,
"Migrating `LockedFungibles`"
);
LockedFungibles::<T>::insert(id, new_data);
weight.saturating_add(T::DbWeight::get().writes(1));
}
// check and migrate `RemoteLockedFungibles` - 1. step - just data
let remote_locked_fungibles_to_migrate =
RemoteLockedFungibles::<T>::iter().filter_map(|(id, data)| {
weight.saturating_add(T::DbWeight::get().reads(1));
match data.try_migrate(required_xcm_version) {
Ok(Some(new_data)) => Some((id, new_data)),
Ok(None) => None,
Err(_) => {
tracing::error!(
target: LOG_TARGET,
?id,
?required_xcm_version,
"`RemoteLockedFungibles` data cannot be migrated!"
);
None
},
}
});
for (id, new_data) in remote_locked_fungibles_to_migrate {
tracing::info!(
target: LOG_TARGET,
key = ?id,
amount = ?new_data.amount,
locker = ?new_data.locker,
owner = ?new_data.owner,
consumers_count = ?new_data.consumers.len(),
"Migrating `RemoteLockedFungibles` data"
);
RemoteLockedFungibles::<T>::insert(id, new_data);
weight.saturating_add(T::DbWeight::get().writes(1));
}
// check and migrate `RemoteLockedFungibles` - 2. step - key
let remote_locked_fungibles_keys_to_migrate = RemoteLockedFungibles::<T>::iter_keys()
.filter_map(|key| {
if key.needs_migration(required_xcm_version) {
let old_key = key.clone();
match key.try_migrate(required_xcm_version) {
Ok(Some(new_key)) => Some((old_key, new_key)),
Ok(None) => None,
Err(_) => {
tracing::error!(
target: LOG_TARGET,
id = ?old_key,
?required_xcm_version,
"`RemoteLockedFungibles` key cannot be migrated!"
);
None
},
}
} else {
None
}
});
for (old_key, new_key) in remote_locked_fungibles_keys_to_migrate {
weight.saturating_add(T::DbWeight::get().reads(1));
// make sure, that we don't override accidentally other data
if RemoteLockedFungibles::<T>::get(&new_key).is_some() {
tracing::error!(
target: LOG_TARGET,
?old_key,
?new_key,
"`RemoteLockedFungibles` already contains data for a `new_key`!"
);
// let's just skip for now, could be potentially caused with missing this
// migration before (manual clean-up?).
continue;
}
tracing::info!(
target: LOG_TARGET,
?old_key,
?new_key,
"Migrating `RemoteLockedFungibles` key"
);
// now we can swap the keys
RemoteLockedFungibles::<T>::swap::<
(
NMapKey<Twox64Concat, XcmVersion>,
NMapKey<Blake2_128Concat, T::AccountId>,
NMapKey<Blake2_128Concat, VersionedAssetId>,
),
_,
_,
>(&old_key, &new_key);
weight.saturating_add(T::DbWeight::get().writes(1));
}
// check and migrate `AuthorizedAliases`
let aliases_to_migrate = AuthorizedAliases::<T>::iter().filter_map(|(id, data)| {
weight.saturating_add(T::DbWeight::get().reads(1));
match (&id, data, PhantomData::<T>).try_migrate(required_xcm_version) {
Ok(Some((new_id, new_data))) => Some((id, new_id, new_data)),
Ok(None) => None,
Err(_) => {
tracing::error!(
target: LOG_TARGET,
?id,
?required_xcm_version,
"`AuthorizedAliases` cannot be migrated!"
);
None
},
}
});
let mut count = 0;
for (old_id, new_id, new_data) in aliases_to_migrate {
tracing::info!(
target: LOG_TARGET,
?new_id,
?new_data,
"Migrating `AuthorizedAliases`"
);
AuthorizedAliases::<T>::remove(old_id);
AuthorizedAliases::<T>::insert(new_id, new_data);
count = count + 1;
}
// two writes per key, one to remove old entry, one to write new entry
weight.saturating_add(T::DbWeight::get().writes(count * 2));
}
}
}
pub mod v1 {
use super::*;
use crate::{CurrentMigration, VersionMigrationStage};
/// Named with the 'VersionUnchecked'-prefix because although this implements some version
/// checking, the version checking is not complete as it will begin failing after the upgrade is
/// enacted on-chain.
///
/// Use experimental [`MigrateToV1`] instead.
pub struct VersionUncheckedMigrateToV1<T>(core::marker::PhantomData<T>);
impl<T: Config> UncheckedOnRuntimeUpgrade for VersionUncheckedMigrateToV1<T> {
fn on_runtime_upgrade() -> Weight {
let mut weight = T::DbWeight::get().reads(1);
if StorageVersion::get::<Pallet<T>>() != 0 {
tracing::warn!("skipping v1, should be removed");
return weight;
}
weight.saturating_accrue(T::DbWeight::get().writes(1));
CurrentMigration::<T>::put(VersionMigrationStage::default());
let translate = |pre: (u64, u64, u32)| -> Option<(u64, Weight, u32)> {
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
let translated = (pre.0, Weight::from_parts(pre.1, DEFAULT_PROOF_SIZE), pre.2);
tracing::info!("Migrated VersionNotifyTarget {:?} to {:?}", pre, translated);
Some(translated)
};
VersionNotifyTargets::<T>::translate_values(translate);
tracing::info!("v1 applied successfully");
weight.saturating_accrue(T::DbWeight::get().writes(1));
StorageVersion::new(1).put::<Pallet<T>>();
weight
}
}
/// Version checked migration to v1.
///
/// Wrapped in [`frame_support::migrations::VersionedMigration`] so the pre/post checks don't
/// begin failing after the upgrade is enacted on-chain.
pub type MigrateToV1<T> = frame_support::migrations::VersionedMigration<
0,
1,
VersionUncheckedMigrateToV1<T>,
crate::pallet::Pallet<T>,
<T as frame_system::Config>::DbWeight,
>;
}
/// When adding a new XCM version, we need to run this migration for `pallet_xcm` to ensure that all
/// previously stored data with subkey prefix `XCM_VERSION-1` (and below) are migrated to the
/// `XCM_VERSION`.
///
/// NOTE: This migration can be permanently added to the runtime migrations.
pub struct MigrateToLatestXcmVersion<T>(core::marker::PhantomData<T>);
impl<T: Config> OnRuntimeUpgrade for MigrateToLatestXcmVersion<T> {
fn on_runtime_upgrade() -> Weight {
let mut weight = T::DbWeight::get().reads(1);
// trigger expensive/lazy migration (kind of multi-block)
CurrentMigration::<T>::put(VersionMigrationStage::default());
weight.saturating_accrue(T::DbWeight::get().writes(1));
// migrate other operational data to the latest XCM version in-place
let latest = CurrentXcmVersion::get();
Pallet::<T>::migrate_data_to_xcm_version(&mut weight, latest);
weight
}
#[cfg(feature = "try-runtime")]
fn post_upgrade(_: alloc::vec::Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> {
use data::NeedsMigration;
const LOG_TARGET: &str = "runtime::xcm::pallet_xcm::migrate_to_latest";
let latest = CurrentXcmVersion::get();
let number_of_queries_to_migrate = crate::Queries::<T>::iter()
.filter(|(id, data)| {
let needs_migration = data.needs_migration(latest);
if needs_migration {
tracing::warn!(
target: LOG_TARGET,
query_id = ?id,
query = ?data,
"Query was not migrated!"
)
}
needs_migration
})
.count();
let number_of_locked_fungibles_to_migrate = crate::LockedFungibles::<T>::iter()
.filter_map(|(id, data)| {
if data.needs_migration(latest) {
tracing::warn!(
target: LOG_TARGET,
?id,
?data,
"LockedFungibles item was not migrated!"
);
Some(true)
} else {
None
}
})
.count();
let number_of_remote_locked_fungibles_to_migrate =
crate::RemoteLockedFungibles::<T>::iter()
.filter_map(|(key, data)| {
if key.needs_migration(latest) || data.needs_migration(latest) {
tracing::warn!(
target: LOG_TARGET,
?key,
"RemoteLockedFungibles item was not migrated!"
);
Some(true)
} else {
None
}
})
.count();
ensure!(number_of_queries_to_migrate == 0, "must migrate all `Queries`.");
ensure!(number_of_locked_fungibles_to_migrate == 0, "must migrate all `LockedFungibles`.");
ensure!(
number_of_remote_locked_fungibles_to_migrate == 0,
"must migrate all `RemoteLockedFungibles`."
);
Ok(())
}
}
+751
View File
@@ -0,0 +1,751 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
pub use core::cell::RefCell;
use frame_support::{
construct_runtime, derive_impl, parameter_types,
traits::{
fungible::HoldConsideration, AsEnsureOriginWithArg, ConstU128, ConstU32, Contains, Equals,
Everything, EverythingBut, Footprint, Nothing,
},
weights::Weight,
};
use frame_system::EnsureRoot;
use pezkuwi_runtime_teyrchains::origin;
use pezkuwi_teyrchain_primitives::primitives::Id as ParaId;
use sp_core::H256;
use sp_runtime::{
traits::{Convert, IdentityLookup},
AccountId32, BuildStorage,
};
use xcm::prelude::*;
use xcm_builder::{
AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom,
AllowTopLevelPaidExecutionFrom, Case, ChildSystemTeyrchainAsSuperuser, ChildTeyrchainAsNative,
ChildTeyrchainConvertsVia, DescribeAllTerminal, EnsureDecodableXcm, FixedRateOfFungible,
FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter,
HashedDescription, IsConcrete, MatchedConvertedConcreteId, NoChecking, SendXcmFeeToAccount,
SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit,
XcmFeeManagerFromComponents,
};
use xcm_executor::{
traits::{Identity, JustTry},
XcmExecutor,
};
use xcm_simulator::helpers::derive_topic_id;
use crate::{self as pallet_xcm, TestWeightInfo};
pub type AccountId = AccountId32;
pub type Balance = u128;
type Block = frame_system::mocking::MockBlock<Test>;
#[frame_support::pallet]
pub mod pallet_test_notifier {
use crate::{ensure_response, QueryId};
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use sp_runtime::DispatchResult;
use xcm::latest::prelude::*;
use xcm_executor::traits::QueryHandler;
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config: frame_system::Config + crate::Config {
#[allow(deprecated)]
type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;
type RuntimeOrigin: IsType<<Self as frame_system::Config>::RuntimeOrigin>
+ Into<Result<crate::Origin, <Self as Config>::RuntimeOrigin>>;
type RuntimeCall: IsType<<Self as crate::Config>::RuntimeCall> + From<Call<Self>>;
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
QueryPrepared(QueryId),
NotifyQueryPrepared(QueryId),
ResponseReceived(Location, QueryId, Response),
}
#[pallet::error]
pub enum Error<T> {
UnexpectedId,
BadAccountFormat,
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(Weight::from_parts(1_000_000, 1_000_000))]
pub fn prepare_new_query(origin: OriginFor<T>, querier: Location) -> DispatchResult {
let who = ensure_signed(origin)?;
let id = who
.using_encoded(|mut d| <[u8; 32]>::decode(&mut d))
.map_err(|_| Error::<T>::BadAccountFormat)?;
let qid = <crate::Pallet<T> as QueryHandler>::new_query(
Junction::AccountId32 { network: None, id },
100u32.into(),
querier,
);
Self::deposit_event(Event::<T>::QueryPrepared(qid));
Ok(())
}
#[pallet::call_index(1)]
#[pallet::weight(Weight::from_parts(1_000_000, 1_000_000))]
pub fn prepare_new_notify_query(origin: OriginFor<T>, querier: Location) -> DispatchResult {
let who = ensure_signed(origin)?;
let id = who
.using_encoded(|mut d| <[u8; 32]>::decode(&mut d))
.map_err(|_| Error::<T>::BadAccountFormat)?;
let call =
Call::<T>::notification_received { query_id: 0, response: Default::default() };
let qid = crate::Pallet::<T>::new_notify_query(
Junction::AccountId32 { network: None, id },
<T as Config>::RuntimeCall::from(call),
100u32.into(),
querier,
);
Self::deposit_event(Event::<T>::NotifyQueryPrepared(qid));
Ok(())
}
#[pallet::call_index(2)]
#[pallet::weight(Weight::from_parts(1_000_000, 1_000_000))]
pub fn notification_received(
origin: OriginFor<T>,
query_id: QueryId,
response: Response,
) -> DispatchResult {
let responder = ensure_response(<T as Config>::RuntimeOrigin::from(origin))?;
Self::deposit_event(Event::<T>::ResponseReceived(responder, query_id, response));
Ok(())
}
}
}
construct_runtime!(
pub enum Test
{
System: frame_system,
Balances: pallet_balances,
AssetsPallet: pallet_assets,
ParasOrigin: origin,
XcmPallet: pallet_xcm,
TestNotifier: pallet_test_notifier,
}
);
thread_local! {
pub static SENT_XCM: RefCell<Vec<(Location, Xcm<()>)>> = RefCell::new(Vec::new());
pub static FAIL_SEND_XCM: RefCell<bool> = RefCell::new(false);
}
pub(crate) fn sent_xcm() -> Vec<(Location, Xcm<()>)> {
SENT_XCM.with(|q| (*q.borrow()).clone())
}
pub(crate) fn take_sent_xcm() -> Vec<(Location, Xcm<()>)> {
SENT_XCM.with(|q| {
let mut r = Vec::new();
std::mem::swap(&mut r, &mut *q.borrow_mut());
r
})
}
pub(crate) fn set_send_xcm_artificial_failure(should_fail: bool) {
FAIL_SEND_XCM.with(|q| *q.borrow_mut() = should_fail);
}
/// Sender that never returns error.
pub struct TestSendXcm;
impl SendXcm for TestSendXcm {
type Ticket = (Location, Xcm<()>);
fn validate(
dest: &mut Option<Location>,
msg: &mut Option<Xcm<()>>,
) -> SendResult<(Location, Xcm<()>)> {
if FAIL_SEND_XCM.with(|q| *q.borrow()) {
return Err(SendError::Transport("Intentional send failure used in tests"));
}
let pair = (dest.take().unwrap(), msg.take().unwrap());
Ok((pair, Assets::new()))
}
fn deliver(pair: (Location, Xcm<()>)) -> Result<XcmHash, SendError> {
let message = pair.1.clone();
if message
.iter()
.any(|instr| matches!(instr, ExpectError(Some((1, XcmError::Unimplemented)))))
{
return Err(SendError::Transport("Intentional deliver failure used in tests".into()));
}
let hash = derive_topic_id(&message);
SENT_XCM.with(|q| q.borrow_mut().push(pair));
Ok(hash)
}
}
/// Sender that returns error if `X8` junction and stops routing
pub struct TestSendXcmErrX8;
impl SendXcm for TestSendXcmErrX8 {
type Ticket = (Location, Xcm<()>);
fn validate(
dest: &mut Option<Location>,
_: &mut Option<Xcm<()>>,
) -> SendResult<(Location, Xcm<()>)> {
if dest.as_ref().unwrap().len() == 8 {
dest.take();
Err(SendError::Transport("Destination location full"))
} else {
Err(SendError::NotApplicable)
}
}
fn deliver(pair: (Location, Xcm<()>)) -> Result<XcmHash, SendError> {
let hash = derive_topic_id(&pair.1);
SENT_XCM.with(|q| q.borrow_mut().push(pair));
Ok(hash)
}
}
parameter_types! {
pub Para3000: u32 = 3000;
pub Para3000Location: Location = Teyrchain(Para3000::get()).into();
pub Para3000PaymentAmount: u128 = 1;
pub Para3000PaymentAssets: Assets = Assets::from(Asset::from((Here, Para3000PaymentAmount::get())));
}
/// Sender only sends to `Teyrchain(3000)` destination requiring payment.
pub struct TestPaidForPara3000SendXcm;
impl SendXcm for TestPaidForPara3000SendXcm {
type Ticket = (Location, Xcm<()>);
fn validate(
dest: &mut Option<Location>,
msg: &mut Option<Xcm<()>>,
) -> SendResult<(Location, Xcm<()>)> {
if let Some(dest) = dest.as_ref() {
if !dest.eq(&Para3000Location::get()) {
return Err(SendError::NotApplicable);
}
} else {
return Err(SendError::NotApplicable);
}
let pair = (dest.take().unwrap(), msg.take().unwrap());
Ok((pair, Para3000PaymentAssets::get()))
}
fn deliver(pair: (Location, Xcm<()>)) -> Result<XcmHash, SendError> {
let hash = derive_topic_id(&pair.1);
SENT_XCM.with(|q| q.borrow_mut().push(pair));
Ok(hash)
}
}
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Test {
type RuntimeOrigin = RuntimeOrigin;
type RuntimeCall = RuntimeCall;
type Nonce = u64;
type Hash = H256;
type Hashing = ::sp_runtime::traits::BlakeTwo256;
type AccountId = AccountId;
type Lookup = IdentityLookup<Self::AccountId>;
type Block = Block;
type RuntimeEvent = RuntimeEvent;
type BlockWeights = ();
type BlockLength = ();
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = pallet_balances::AccountData<Balance>;
type OnNewAccount = ();
type OnKilledAccount = ();
type DbWeight = ();
type BaseCallFilter = Everything;
type SystemWeightInfo = ();
type SS58Prefix = ();
type OnSetCode = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
}
parameter_types! {
pub ExistentialDeposit: Balance = 1;
}
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
impl pallet_balances::Config for Test {
type Balance = Balance;
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
}
#[cfg(feature = "runtime-benchmarks")]
/// Simple conversion of `u32` into an `AssetId` for use in benchmarking.
pub struct XcmBenchmarkHelper;
#[cfg(feature = "runtime-benchmarks")]
impl pallet_assets::BenchmarkHelper<Location, ()> for XcmBenchmarkHelper {
fn create_asset_id_parameter(id: u32) -> Location {
Location::new(1, [Teyrchain(id)])
}
fn create_reserve_id_parameter(_: u32) {}
}
impl pallet_assets::Config for Test {
type RuntimeEvent = RuntimeEvent;
type Balance = Balance;
type AssetId = Location;
type AssetIdParameter = Location;
type ReserveData = ();
type Currency = Balances;
type CreateOrigin = AsEnsureOriginWithArg<frame_system::EnsureSigned<AccountId>>;
type ForceOrigin = EnsureRoot<AccountId>;
type AssetDeposit = ConstU128<1>;
type AssetAccountDeposit = ConstU128<10>;
type MetadataDepositBase = ConstU128<1>;
type MetadataDepositPerByte = ConstU128<1>;
type ApprovalDeposit = ConstU128<1>;
type StringLimit = ConstU32<50>;
type Holder = ();
type Freezer = ();
type WeightInfo = ();
type CallbackHandle = ();
type Extra = ();
type RemoveItemsLimit = ConstU32<5>;
#[cfg(feature = "runtime-benchmarks")]
type BenchmarkHelper = XcmBenchmarkHelper;
}
// This child teyrchain is a system teyrchain trusted to teleport native token.
pub const SOME_SYSTEM_PARA: u32 = 1001;
// This child teyrchain acts as trusted reserve for its assets in tests.
// USDT allowed to teleport to/from here.
pub const FOREIGN_ASSET_RESERVE_PARA_ID: u32 = 2001;
// Inner junction of reserve asset on `FOREIGN_ASSET_RESERVE_PARA_ID`.
pub const FOREIGN_ASSET_INNER_JUNCTION: Junction = GeneralIndex(1234567);
// This child teyrchain acts as trusted reserve for say.. USDC that can be used for fees.
pub const USDC_RESERVE_PARA_ID: u32 = 2002;
// Inner junction of reserve asset on `USDC_RESERVE_PARA_ID`.
pub const USDC_INNER_JUNCTION: Junction = PalletInstance(42);
// This child teyrchain is a trusted teleporter for say.. USDT (T from Teleport :)).
// We'll use USDT in tests that teleport fees.
pub const USDT_PARA_ID: u32 = 2003;
// This child teyrchain is not configured as trusted reserve or teleport location for any assets.
pub const OTHER_PARA_ID: u32 = 2009;
// This child teyrchain is used for filtered/disallowed assets.
pub const FILTERED_PARA_ID: u32 = 2010;
parameter_types! {
pub const RelayLocation: Location = Here.into_location();
pub const NativeAsset: Asset = Asset {
fun: Fungible(10),
id: AssetId(Here.into_location()),
};
pub SystemTeyrchainLocation: Location = Location::new(
0,
[Teyrchain(SOME_SYSTEM_PARA)]
);
pub ForeignReserveLocation: Location = Location::new(
0,
[Teyrchain(FOREIGN_ASSET_RESERVE_PARA_ID)]
);
pub PaidParaForeignReserveLocation: Location = Location::new(
0,
[Teyrchain(Para3000::get())]
);
pub ForeignAsset: Asset = Asset {
fun: Fungible(10),
id: AssetId(Location::new(
0,
[Teyrchain(FOREIGN_ASSET_RESERVE_PARA_ID), FOREIGN_ASSET_INNER_JUNCTION],
)),
};
pub PaidParaForeignAsset: Asset = Asset {
fun: Fungible(10),
id: AssetId(Location::new(
0,
[Teyrchain(Para3000::get())],
)),
};
pub UsdcReserveLocation: Location = Location::new(
0,
[Teyrchain(USDC_RESERVE_PARA_ID)]
);
pub Usdc: Asset = Asset {
fun: Fungible(10),
id: AssetId(Location::new(
0,
[Teyrchain(USDC_RESERVE_PARA_ID), USDC_INNER_JUNCTION],
)),
};
pub UsdtTeleportLocation: Location = Location::new(
0,
[Teyrchain(USDT_PARA_ID)]
);
pub Usdt: Asset = Asset {
fun: Fungible(10),
id: AssetId(Location::new(
0,
[Teyrchain(USDT_PARA_ID)],
)),
};
pub FilteredTeleportLocation: Location = Location::new(
0,
[Teyrchain(FILTERED_PARA_ID)]
);
pub FilteredTeleportAsset: Asset = Asset {
fun: Fungible(10),
id: AssetId(Location::new(
0,
[Teyrchain(FILTERED_PARA_ID)],
)),
};
pub const AnyNetwork: Option<NetworkId> = None;
pub UniversalLocation: InteriorLocation = GlobalConsensus(ByGenesis([0; 32])).into();
pub UnitWeightCost: u64 = 1_000;
pub CheckingAccount: AccountId = XcmPallet::check_account();
}
pub type SovereignAccountOf = (
ChildTeyrchainConvertsVia<ParaId, AccountId>,
AccountId32Aliases<AnyNetwork, AccountId>,
HashedDescription<AccountId, DescribeAllTerminal>,
);
pub type ForeignAssetsConvertedConcreteId = MatchedConvertedConcreteId<
Location,
Balance,
// Excludes relay/parent chain currency
EverythingBut<(Equals<RelayLocation>,)>,
Identity,
JustTry,
>;
pub type AssetTransactors = (
FungibleAdapter<Balances, IsConcrete<RelayLocation>, SovereignAccountOf, AccountId, ()>,
FungiblesAdapter<
AssetsPallet,
ForeignAssetsConvertedConcreteId,
SovereignAccountOf,
AccountId,
NoChecking,
CheckingAccount,
>,
);
type LocalOriginConverter = (
SovereignSignedViaLocation<SovereignAccountOf, RuntimeOrigin>,
ChildTeyrchainAsNative<origin::Origin, RuntimeOrigin>,
SignedAccountId32AsNative<AnyNetwork, RuntimeOrigin>,
ChildSystemTeyrchainAsSuperuser<ParaId, RuntimeOrigin>,
);
parameter_types! {
pub const BaseXcmWeight: Weight = Weight::from_parts(1_000, 1_000);
pub CurrencyPerSecondPerByte: (AssetId, u128, u128) = (AssetId(RelayLocation::get()), 1, 1);
pub TrustedLocal: (AssetFilter, Location) = (All.into(), Here.into());
pub TrustedSystemPara: (AssetFilter, Location) = (NativeAsset::get().into(), SystemTeyrchainLocation::get());
pub TrustedUsdt: (AssetFilter, Location) = (Usdt::get().into(), UsdtTeleportLocation::get());
pub TrustedFilteredTeleport: (AssetFilter, Location) = (FilteredTeleportAsset::get().into(), FilteredTeleportLocation::get());
pub TeleportUsdtToForeign: (AssetFilter, Location) = (Usdt::get().into(), ForeignReserveLocation::get());
pub TrustedForeign: (AssetFilter, Location) = (ForeignAsset::get().into(), ForeignReserveLocation::get());
pub TrustedPaidParaForeign: (AssetFilter, Location) = (PaidParaForeignAsset::get().into(), PaidParaForeignReserveLocation::get());
pub TrustedUsdc: (AssetFilter, Location) = (Usdc::get().into(), UsdcReserveLocation::get());
pub const MaxInstructions: u32 = 100;
pub const MaxAssetsIntoHolding: u32 = 64;
pub XcmFeesTargetAccount: AccountId = AccountId::new([167u8; 32]);
}
pub const XCM_FEES_NOT_WAIVED_USER_ACCOUNT: [u8; 32] = [37u8; 32];
pub struct XcmFeesNotWaivedLocations;
impl Contains<Location> for XcmFeesNotWaivedLocations {
fn contains(location: &Location) -> bool {
matches!(
location.unpack(),
(0, [Junction::AccountId32 { network: None, id: XCM_FEES_NOT_WAIVED_USER_ACCOUNT }])
)
}
}
pub type Barrier = (
TakeWeightCredit,
AllowTopLevelPaidExecutionFrom<Everything>,
AllowKnownQueryResponses<XcmPallet>,
AllowSubscriptionsFrom<Everything>,
);
pub type XcmRouter =
EnsureDecodableXcm<(TestPaidForPara3000SendXcm, TestSendXcmErrX8, TestSendXcm)>;
pub type Trader = FixedRateOfFungible<CurrencyPerSecondPerByte, ()>;
pub struct XcmConfig;
impl xcm_executor::Config for XcmConfig {
type RuntimeCall = RuntimeCall;
type XcmSender = XcmRouter;
type XcmEventEmitter = XcmPallet;
type AssetTransactor = AssetTransactors;
type OriginConverter = LocalOriginConverter;
type IsReserve = (Case<TrustedForeign>, Case<TrustedUsdc>, Case<TrustedPaidParaForeign>);
type IsTeleporter = (
Case<TrustedLocal>,
Case<TrustedSystemPara>,
Case<TrustedUsdt>,
Case<TeleportUsdtToForeign>,
Case<TrustedFilteredTeleport>,
);
type UniversalLocation = UniversalLocation;
type Barrier = Barrier;
type Weigher = FixedWeightBounds<BaseXcmWeight, RuntimeCall, MaxInstructions>;
type Trader = Trader;
type ResponseHandler = XcmPallet;
type AssetTrap = XcmPallet;
type AssetLocker = ();
type AssetExchanger = ();
type AssetClaims = XcmPallet;
type SubscriptionService = XcmPallet;
type PalletInstancesInfo = AllPalletsWithSystem;
type MaxAssetsIntoHolding = MaxAssetsIntoHolding;
type FeeManager = XcmFeeManagerFromComponents<
EverythingBut<XcmFeesNotWaivedLocations>,
SendXcmFeeToAccount<Self::AssetTransactor, XcmFeesTargetAccount>,
>;
type MessageExporter = ();
type UniversalAliases = Nothing;
type CallDispatcher = RuntimeCall;
type SafeCallFilter = Everything;
type Aliasers = Nothing;
type TransactionalProcessor = FrameTransactionalProcessor;
type HrmpNewChannelOpenRequestHandler = ();
type HrmpChannelAcceptedHandler = ();
type HrmpChannelClosingHandler = ();
type XcmRecorder = XcmPallet;
}
/// Converts a local signed origin into an XCM location. Forms the basis for local origins
/// sending/executing XCMs.
pub type LocalOriginToLocation = SignedToAccountId32<RuntimeOrigin, AccountId, AnyNetwork>;
parameter_types! {
pub static AdvertisedXcmVersion: pallet_xcm::XcmVersion = 4;
pub const AuthorizeAliasHoldReason: RuntimeHoldReason = RuntimeHoldReason::XcmPallet(pallet_xcm::HoldReason::AuthorizeAlias);
}
pub struct ConvertDeposit;
impl Convert<Footprint, u128> for ConvertDeposit {
fn convert(a: Footprint) -> u128 {
(a.count * 2 + a.size) as u128
}
}
pub struct XcmTeleportFiltered;
impl Contains<(Location, Vec<Asset>)> for XcmTeleportFiltered {
fn contains(t: &(Location, Vec<Asset>)) -> bool {
let filtered = FilteredTeleportAsset::get();
t.1.iter().any(|asset| asset == &filtered)
}
}
impl pallet_xcm::Config for Test {
type RuntimeEvent = RuntimeEvent;
type SendXcmOrigin = xcm_builder::EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
type XcmRouter = XcmRouter;
type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
type XcmExecuteFilter = Everything;
type XcmExecutor = XcmExecutor<XcmConfig>;
type XcmTeleportFilter = EverythingBut<XcmTeleportFiltered>;
type XcmReserveTransferFilter = Everything;
type Weigher = FixedWeightBounds<BaseXcmWeight, RuntimeCall, MaxInstructions>;
type UniversalLocation = UniversalLocation;
type RuntimeOrigin = RuntimeOrigin;
type RuntimeCall = RuntimeCall;
const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
type AdvertisedXcmVersion = AdvertisedXcmVersion;
type AdminOrigin = EnsureRoot<AccountId>;
type TrustedLockers = ();
type SovereignAccountOf = AccountId32Aliases<(), AccountId32>;
type Currency = Balances;
type CurrencyMatcher = IsConcrete<RelayLocation>;
type MaxLockers = frame_support::traits::ConstU32<8>;
type MaxRemoteLockConsumers = frame_support::traits::ConstU32<0>;
type RemoteLockConsumerIdentifier = ();
type WeightInfo = TestWeightInfo;
type AuthorizedAliasConsideration =
HoldConsideration<AccountId, Balances, AuthorizeAliasHoldReason, ConvertDeposit>;
}
impl origin::Config for Test {}
impl pallet_test_notifier::Config for Test {
type RuntimeEvent = RuntimeEvent;
type RuntimeOrigin = RuntimeOrigin;
type RuntimeCall = RuntimeCall;
}
#[cfg(feature = "runtime-benchmarks")]
pub struct TestDeliveryHelper;
#[cfg(feature = "runtime-benchmarks")]
impl xcm_builder::EnsureDelivery for TestDeliveryHelper {
fn ensure_successful_delivery(
origin_ref: &Location,
_dest: &Location,
_fee_reason: xcm_executor::traits::FeeReason,
) -> (Option<xcm_executor::FeesMode>, Option<Assets>) {
use xcm_executor::traits::ConvertLocation;
let account = SovereignAccountOf::convert_location(origin_ref).expect("Valid location");
// Give the existential deposit at least
let balance = ExistentialDeposit::get();
let _ = <Balances as frame_support::traits::Currency<_>>::make_free_balance_be(
&account, balance,
);
(None, None)
}
}
#[cfg(feature = "runtime-benchmarks")]
impl super::benchmarking::Config for Test {
type DeliveryHelper = TestDeliveryHelper;
fn reachable_dest() -> Option<Location> {
Some(Teyrchain(1000).into())
}
fn teleportable_asset_and_dest() -> Option<(Asset, Location)> {
Some((NativeAsset::get(), SystemTeyrchainLocation::get()))
}
fn reserve_transferable_asset_and_dest() -> Option<(Asset, Location)> {
Some((
Asset { fun: Fungible(10), id: AssetId(Here.into_location()) },
Teyrchain(OTHER_PARA_ID).into(),
))
}
fn set_up_complex_asset_transfer() -> Option<(Assets, AssetId, Location, Box<dyn FnOnce()>)> {
use crate::tests::assets_transfer::{into_assets_checked, set_up_foreign_asset};
// Transfer native asset (local reserve) to `USDT_PARA_ID`. Using teleport-trusted USDT for
// fees.
let asset_amount = 10u128;
let fee_amount = 2u128;
let existential_deposit = ExistentialDeposit::get();
let caller = frame_benchmarking::whitelisted_caller();
// Give some multiple of the existential deposit
let balance = asset_amount + existential_deposit * 1000;
let _ = <Balances as frame_support::traits::Currency<_>>::make_free_balance_be(
&caller, balance,
);
// create sufficient foreign asset USDT
let usdt_initial_local_amount = fee_amount * 10;
let (usdt_chain, _, usdt_id_location) = set_up_foreign_asset(
USDT_PARA_ID,
None,
caller.clone(),
usdt_initial_local_amount,
true,
);
// native assets transfer destination is USDT chain (teleport trust only for USDT)
let dest = usdt_chain;
let (assets, fee_asset, _) = into_assets_checked(
// USDT for fees (is sufficient on local chain too) - teleported
(usdt_id_location.clone(), fee_amount).into(),
// native asset to transfer (not used for fees) - local reserve
(Location::here(), asset_amount).into(),
);
// verify initial balances
assert_eq!(Balances::free_balance(&caller), balance);
assert_eq!(
AssetsPallet::balance(usdt_id_location.clone(), &caller),
usdt_initial_local_amount
);
// verify transferred successfully
let verify = Box::new(move || {
// verify balances after transfer, decreased by transferred amounts
assert_eq!(Balances::free_balance(&caller), balance - asset_amount);
assert_eq!(
AssetsPallet::balance(usdt_id_location, &caller),
usdt_initial_local_amount - fee_amount
);
});
Some((assets, fee_asset.id, dest, verify))
}
fn get_asset() -> Asset {
Asset { id: AssetId(Location::here()), fun: Fungible(ExistentialDeposit::get()) }
}
}
pub(crate) fn all_events() -> Vec<RuntimeEvent> {
System::events().into_iter().map(|e| e.event).collect()
}
pub(crate) fn last_events(n: usize) -> Vec<RuntimeEvent> {
let all_events = all_events();
let split_idx = all_events.len().saturating_sub(n);
all_events.split_at(split_idx).1.to_vec()
}
pub(crate) fn last_event() -> RuntimeEvent {
last_events(1).pop().expect("RuntimeEvent expected")
}
pub(crate) fn buy_execution<C>(fees: impl Into<Asset>) -> Instruction<C> {
use xcm::latest::prelude::*;
BuyExecution { fees: fees.into(), weight_limit: Unlimited }
}
pub(crate) fn buy_limited_execution<C>(
fees: impl Into<Asset>,
weight_limit: WeightLimit,
) -> Instruction<C> {
use xcm::latest::prelude::*;
BuyExecution { fees: fees.into(), weight_limit }
}
pub(crate) fn new_test_ext_with_balances(
balances: Vec<(AccountId, Balance)>,
) -> sp_io::TestExternalities {
new_test_ext_with_balances_and_xcm_version(
balances,
// By default set actual latest XCM version
Some(XCM_VERSION),
vec![],
)
}
pub(crate) fn new_test_ext_with_balances_and_xcm_version(
balances: Vec<(AccountId, Balance)>,
safe_xcm_version: Option<XcmVersion>,
supported_version: Vec<(Location, XcmVersion)>,
) -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
pallet_balances::GenesisConfig::<Test> { balances, ..Default::default() }
.assimilate_storage(&mut t)
.unwrap();
pallet_xcm::GenesisConfig::<Test> { safe_xcm_version, supported_version, ..Default::default() }
.assimilate_storage(&mut t)
.unwrap();
let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(|| System::set_block_number(1));
ext
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,162 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Validation of the `transfer_assets` call.
//! This validation is a temporary patch in preparation for the Asset Hub Migration (AHM).
//! This module will be removed after the migration and the determined
//! reserve location will be adjusted accordingly to be Asset Hub.
//! For more information, see <https://github.com/pezkuwichain/pezkuwi-sdk/issues/158>.
use crate::{Config, Error, Pallet};
use alloc::vec::Vec;
use hex_literal::hex;
use sp_core::Get;
use xcm::prelude::*;
use xcm_executor::traits::TransferType;
/// The genesis hash of the Paseo Relay Chain. Used to identify it.
const PASEO_GENESIS_HASH: [u8; 32] =
hex!["77afd6190f1554ad45fd0d31aee62aacc33c6db0ea801129acb813f913e0764f"];
impl<T: Config> Pallet<T> {
/// Check if network native asset reserve transfers should be blocked during Asset Hub
/// Migration.
///
/// During the Asset Hub Migration (AHM), the native network asset's reserve will move
/// from the Relay Chain to Asset Hub. The `transfer_assets` function automatically determines
/// reserves based on asset ID location, which would incorrectly assume Relay Chain as the
/// reserve.
///
/// This function blocks native network asset reserve transfers to prevent issues during
/// the migration.
/// Users should use `limited_reserve_transfer_assets`, `transfer_assets_using_type_and_then` or
/// `execute` instead, which allows explicit reserve specification.
pub(crate) fn ensure_network_asset_reserve_transfer_allowed(
assets: &Vec<Asset>,
fee_asset_id: &AssetId,
assets_transfer_type: &TransferType,
fees_transfer_type: &TransferType,
) -> Result<(), Error<T>> {
// Extract fee asset and check both assets and fees separately.
let mut remaining_assets = assets.clone();
let fee_asset_index =
assets.iter().position(|a| a.id == *fee_asset_id).ok_or(Error::<T>::Empty)?;
let fee_asset = remaining_assets.remove(fee_asset_index);
// Check remaining assets with their transfer type.
Self::ensure_one_transfer_type_allowed(&remaining_assets, &assets_transfer_type)?;
// Check fee asset with its transfer type.
Self::ensure_one_transfer_type_allowed(&[fee_asset], &fees_transfer_type)?;
Ok(())
}
/// Checks that the transfer of `assets` is allowed.
///
/// Returns an error if `transfer_type` is a reserve transfer and the network's native asset is
/// being transferred. Allows the transfer otherwise.
fn ensure_one_transfer_type_allowed(
assets: &[Asset],
transfer_type: &TransferType,
) -> Result<(), Error<T>> {
// Check if any reserve transfer (LocalReserve, DestinationReserve, or RemoteReserve)
// is being attempted.
let is_reserve_transfer = matches!(
transfer_type,
TransferType::LocalReserve |
TransferType::DestinationReserve |
TransferType::RemoteReserve(_)
);
if !is_reserve_transfer {
// If not a reserve transfer (e.g., teleport), allow it.
return Ok(());
}
// Check if any asset is a network native asset.
for asset in assets {
if Self::is_network_native_asset(&asset.id) {
tracing::debug!(
target: "xcm::pallet_xcm::transfer_assets",
asset_id = ?asset.id, ?transfer_type,
"Network native asset reserve transfer blocked in preparation for the Asset Hub Migration. Use `transfer_assets_using_type_and_then` instead and explicitly mention the reserve."
);
// It's error-prone to try to determine the reserve in this circumstances.
return Err(Error::<T>::InvalidAssetUnknownReserve);
}
}
Ok(())
}
/// Check if the given asset ID represents a network native asset based on our
/// UniversalLocation.
///
/// Returns true if the asset is a native network asset (HEZ, KSM, ZGR, PAS) that should be
/// blocked during Asset Hub Migration.
fn is_network_native_asset(asset_id: &AssetId) -> bool {
let universal_location = T::UniversalLocation::get();
let asset_location = &asset_id.0;
match universal_location.len() {
// Case 1: We are on the Relay Chain itself.
// UniversalLocation: GlobalConsensus(Network).
// Network asset ID: Here.
1 => {
if let Some(Junction::GlobalConsensus(network)) = universal_location.first() {
let is_target_network = match network {
NetworkId::Pezkuwi | NetworkId::Kusama => true,
NetworkId::ByGenesis(genesis_hash) => {
// Check if this is Zagros by genesis hash
*genesis_hash == xcm::v5::ZAGROS_GENESIS_HASH ||
*genesis_hash == PASEO_GENESIS_HASH ||
*genesis_hash == xcm::v5::PEZKUWICHAIN_GENESIS_HASH // Used in tests.
},
_ => false,
};
is_target_network && asset_location.is_here()
} else {
false
}
},
// Case 2: We are on a teyrchain within one of the specified networks.
// UniversalLocation: GlobalConsensus(Network)/Teyrchain(id).
// Network asset ID: Parent.
2 => {
if let (Some(Junction::GlobalConsensus(network)), Some(Junction::Teyrchain(_))) =
(universal_location.first(), universal_location.last())
{
let is_target_network = match network {
NetworkId::Pezkuwi | NetworkId::Kusama => true,
NetworkId::ByGenesis(genesis_hash) => {
// Check if this is Zagros by genesis hash
*genesis_hash == xcm::v5::ZAGROS_GENESIS_HASH ||
*genesis_hash == PASEO_GENESIS_HASH ||
*genesis_hash == xcm::v5::PEZKUWICHAIN_GENESIS_HASH // Used in tests.
},
_ => false,
};
is_target_network && *asset_location == Location::parent()
} else {
false
}
},
// Case 3: We are not on a relay or teyrchain. We return false.
_ => false,
}
}
}
+33
View File
@@ -0,0 +1,33 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// 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.
use xcm::latest::XcmHash;
/// Finds the message ID of the first `XcmPallet::Sent` event in the given events.
pub fn find_xcm_sent_message_id<T>(
events: impl IntoIterator<Item = <T as crate::Config>::RuntimeEvent>,
) -> Option<XcmHash>
where
T: crate::Config,
<T as crate::Config>::RuntimeEvent: TryInto<crate::Event<T>>,
{
events.into_iter().find_map(|event| {
if let Ok(crate::Event::Sent { message_id, .. }) = event.try_into() {
Some(message_id)
} else {
None
}
})
}
+36
View File
@@ -0,0 +1,36 @@
[package]
name = "xcm-procedural"
description = "Procedural macros for XCM"
authors.workspace = true
edition.workspace = true
license.workspace = true
version = "7.0.0"
publish = true
homepage.workspace = true
repository.workspace = true
[lints]
workspace = true
[lib]
proc-macro = true
[dependencies]
Inflector = { workspace = true }
proc-macro2 = { workspace = true }
quote = { workspace = true }
syn = { workspace = true }
[dev-dependencies]
trybuild = { features = ["diff"], workspace = true }
# NOTE: we have to explicitly specify `std` because of trybuild
# https://github.com/paritytech/polkadot-sdk/pull/5167
xcm = { workspace = true, default-features = true, features = ["std"] }
# For testing macros.
frame-support = { workspace = true }
[features]
runtime-benchmarks = [
"frame-support/runtime-benchmarks",
"xcm/runtime-benchmarks",
]
@@ -0,0 +1,456 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Derive macro for creating XCMs with a builder pattern
use inflector::Inflector;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::{
Data, DataEnum, DeriveInput, Error, Expr, ExprLit, Field, Fields, GenericArgument, Ident, Lit,
Meta, MetaNameValue, PathArguments, Result, Type, TypePath, Variant,
};
pub fn derive(input: DeriveInput) -> Result<TokenStream2> {
let data_enum = match &input.data {
Data::Enum(data_enum) => data_enum,
_ => return Err(Error::new_spanned(&input, "Expected the `Instruction` enum")),
};
let builder_raw_impl = generate_builder_raw_impl(&input.ident, data_enum)?;
let builder_impl = generate_builder_impl(&input.ident, data_enum)?;
let builder_unpaid_impl = generate_builder_unpaid_impl(&input.ident, data_enum)?;
let output = quote! {
/// A trait for types that track state inside the XcmBuilder
pub trait XcmBuilderState {}
/// Access to all the instructions
pub enum AnythingGoes {}
/// You need to pay for execution
pub enum PaymentRequired {}
/// The holding register was loaded, now to buy execution
pub enum LoadedHolding {}
/// Need to explicitly state it won't pay for fees
pub enum ExplicitUnpaidRequired {}
impl XcmBuilderState for AnythingGoes {}
impl XcmBuilderState for PaymentRequired {}
impl XcmBuilderState for LoadedHolding {}
impl XcmBuilderState for ExplicitUnpaidRequired {}
/// Type used to build XCM programs
pub struct XcmBuilder<Call, S: XcmBuilderState> {
pub(crate) instructions: Vec<Instruction<Call>>,
pub state: core::marker::PhantomData<S>,
}
impl<Call> Xcm<Call> {
pub fn builder() -> XcmBuilder<Call, PaymentRequired> {
XcmBuilder::<Call, PaymentRequired> {
instructions: Vec::new(),
state: core::marker::PhantomData,
}
}
pub fn builder_unpaid() -> XcmBuilder<Call, ExplicitUnpaidRequired> {
XcmBuilder::<Call, ExplicitUnpaidRequired> {
instructions: Vec::new(),
state: core::marker::PhantomData,
}
}
pub fn builder_unsafe() -> XcmBuilder<Call, AnythingGoes> {
XcmBuilder::<Call, AnythingGoes> {
instructions: Vec::new(),
state: core::marker::PhantomData,
}
}
}
#builder_impl
#builder_unpaid_impl
#builder_raw_impl
};
Ok(output)
}
fn generate_builder_raw_impl(name: &Ident, data_enum: &DataEnum) -> Result<TokenStream2> {
let methods = data_enum
.variants
.iter()
.map(|variant| convert_variant_to_method(name, variant, None))
.collect::<Result<Vec<_>>>()?;
let output = quote! {
impl<Call> XcmBuilder<Call, AnythingGoes> {
#(#methods)*
pub fn build(self) -> Xcm<Call> {
Xcm(self.instructions)
}
}
};
Ok(output)
}
fn generate_builder_impl(name: &Ident, data_enum: &DataEnum) -> Result<TokenStream2> {
// We first require an instruction that load the holding register
let load_holding_variants = data_enum
.variants
.iter()
.map(|variant| {
let maybe_builder_attr = variant.attrs.iter().find(|attr| match attr.meta {
Meta::List(ref list) => list.path.is_ident("builder"),
_ => false,
});
let builder_attr = match maybe_builder_attr {
Some(builder) => builder.clone(),
None => return Ok(None), /* It's not going to be an instruction that loads the
* holding register */
};
let Meta::List(ref list) = builder_attr.meta else { unreachable!("We checked before") };
let inner_ident: Ident = syn::parse2(list.tokens.clone()).map_err(|_| {
Error::new_spanned(
&builder_attr,
"Expected `builder(loads_holding)` or `builder(pays_fees)`",
)
})?;
let loads_holding_ident: Ident = syn::parse_quote!(loads_holding);
let pays_fees_ident: Ident = syn::parse_quote!(pays_fees);
if inner_ident == loads_holding_ident {
Ok(Some(variant))
} else if inner_ident == pays_fees_ident {
Ok(None)
} else {
Err(Error::new_spanned(
&builder_attr,
"Expected `builder(loads_holding)` or `builder(pays_fees)`",
))
}
})
.collect::<Result<Vec<_>>>()?;
let load_holding_methods = load_holding_variants
.into_iter()
.flatten()
.map(|variant| {
let method = convert_variant_to_method(
name,
variant,
Some(quote! { XcmBuilder<Call, LoadedHolding> }),
)?;
Ok(method)
})
.collect::<Result<Vec<_>>>()?;
let first_impl = quote! {
impl<Call> XcmBuilder<Call, PaymentRequired> {
#(#load_holding_methods)*
}
};
// Some operations are allowed after the holding register is loaded
let allowed_after_load_holding_methods: Vec<TokenStream2> = data_enum
.variants
.iter()
.filter(|variant| variant.ident == "ClearOrigin" || variant.ident == "SetHints")
.map(|variant| {
let method = convert_variant_to_method(name, variant, None)?;
Ok(method)
})
.collect::<Result<Vec<_>>>()?;
// Then we require fees to be paid
let pay_fees_variants = data_enum
.variants
.iter()
.map(|variant| {
let maybe_builder_attr = variant.attrs.iter().find(|attr| match attr.meta {
Meta::List(ref list) => list.path.is_ident("builder"),
_ => false,
});
let builder_attr = match maybe_builder_attr {
Some(builder) => builder.clone(),
None => return Ok(None), /* It's not going to be an instruction that pays fees */
};
let Meta::List(ref list) = builder_attr.meta else { unreachable!("We checked before") };
let inner_ident: Ident = syn::parse2(list.tokens.clone()).map_err(|_| {
Error::new_spanned(
&builder_attr,
"Expected `builder(loads_holding)` or `builder(pays_fees)`",
)
})?;
let ident_to_match: Ident = syn::parse_quote!(pays_fees);
if inner_ident == ident_to_match {
Ok(Some(variant))
} else {
Ok(None) // Must have been `loads_holding` instead.
}
})
.collect::<Result<Vec<_>>>()?;
let pay_fees_methods = pay_fees_variants
.into_iter()
.flatten()
.map(|variant| {
let method = convert_variant_to_method(
name,
variant,
Some(quote! { XcmBuilder<Call, AnythingGoes> }),
)?;
Ok(method)
})
.collect::<Result<Vec<_>>>()?;
let second_impl = quote! {
impl<Call> XcmBuilder<Call, LoadedHolding> {
#(#allowed_after_load_holding_methods)*
#(#pay_fees_methods)*
}
};
let output = quote! {
#first_impl
#second_impl
};
Ok(output)
}
fn generate_builder_unpaid_impl(name: &Ident, data_enum: &DataEnum) -> Result<TokenStream2> {
let unpaid_execution_variant = data_enum
.variants
.iter()
.find(|variant| variant.ident == "UnpaidExecution")
.ok_or(Error::new_spanned(&data_enum.variants, "No UnpaidExecution instruction"))?;
let method = convert_variant_to_method(
name,
&unpaid_execution_variant,
Some(quote! { XcmBuilder<Call, AnythingGoes> }),
)?;
Ok(quote! {
impl<Call> XcmBuilder<Call, ExplicitUnpaidRequired> {
#method
}
})
}
// Small helper enum to differentiate between fields that use a `BoundedVec`
// and the rest.
enum BoundedOrNormal {
Normal(Field),
Bounded(Field),
}
// Have to call with `XcmBuilder<Call, LoadedHolding>` in allowed_after_load_holding_methods.
fn convert_variant_to_method(
name: &Ident,
variant: &Variant,
maybe_return_type: Option<TokenStream2>,
) -> Result<TokenStream2> {
let variant_name = &variant.ident;
let method_name_string = &variant_name.to_string().to_snake_case();
let method_name = syn::Ident::new(method_name_string, variant_name.span());
let docs = get_doc_comments(variant);
let method = match &variant.fields {
Fields::Unit =>
if let Some(return_type) = maybe_return_type {
quote! {
pub fn #method_name(self) -> #return_type {
let mut new_instructions = self.instructions;
new_instructions.push(#name::<Call>::#variant_name);
XcmBuilder {
instructions: new_instructions,
state: core::marker::PhantomData,
}
}
}
} else {
quote! {
pub fn #method_name(mut self) -> Self {
self.instructions.push(#name::<Call>::#variant_name);
self
}
}
},
Fields::Unnamed(fields) => {
let arg_names: Vec<_> = fields
.unnamed
.iter()
.enumerate()
.map(|(index, _)| format_ident!("arg{}", index))
.collect();
let arg_types: Vec<_> = fields.unnamed.iter().map(|field| &field.ty).collect();
if let Some(return_type) = maybe_return_type {
quote! {
pub fn #method_name(self, #(#arg_names: impl Into<#arg_types>),*) -> #return_type {
let mut new_instructions = self.instructions;
#(let #arg_names = #arg_names.into();)*
new_instructions.push(#name::<Call>::#variant_name(#(#arg_names),*));
XcmBuilder {
instructions: new_instructions,
state: core::marker::PhantomData,
}
}
}
} else {
quote! {
pub fn #method_name(mut self, #(#arg_names: impl Into<#arg_types>),*) -> Self {
#(let #arg_names = #arg_names.into();)*
self.instructions.push(#name::<Call>::#variant_name(#(#arg_names),*));
self
}
}
}
},
Fields::Named(fields) => {
let fields: Vec<_> = fields
.named
.iter()
.map(|field| {
if let Type::Path(TypePath { path, .. }) = &field.ty {
for segment in &path.segments {
if segment.ident == format_ident!("BoundedVec") {
return BoundedOrNormal::Bounded(field.clone());
}
}
BoundedOrNormal::Normal(field.clone())
} else {
BoundedOrNormal::Normal(field.clone())
}
})
.collect();
let arg_names: Vec<_> = fields
.iter()
.map(|field| match field {
BoundedOrNormal::Bounded(field) => &field.ident,
BoundedOrNormal::Normal(field) => &field.ident,
})
.collect();
let arg_types: Vec<_> = fields
.iter()
.map(|field| match field {
BoundedOrNormal::Bounded(field) => {
let inner_type =
extract_generic_argument(&field.ty, 0, "BoundedVec's inner type")?;
Ok(quote! {
Vec<#inner_type>
})
},
BoundedOrNormal::Normal(field) => {
let inner_type = &field.ty;
Ok(quote! {
impl Into<#inner_type>
})
},
})
.collect::<Result<Vec<_>>>()?;
let bounded_names: Vec<_> = fields
.iter()
.filter_map(|field| match field {
BoundedOrNormal::Bounded(field) => Some(&field.ident),
BoundedOrNormal::Normal(_) => None,
})
.collect();
let normal_names: Vec<_> = fields
.iter()
.filter_map(|field| match field {
BoundedOrNormal::Normal(field) => Some(&field.ident),
BoundedOrNormal::Bounded(_) => None,
})
.collect();
let comma_in_the_middle = if normal_names.is_empty() {
quote! {}
} else {
quote! {,}
};
if let Some(return_type) = maybe_return_type {
quote! {
pub fn #method_name(self, #(#arg_names: #arg_types),*) -> #return_type {
let mut new_instructions = self.instructions;
#(let #normal_names = #normal_names.into();)*
#(let #bounded_names = BoundedVec::truncate_from(#bounded_names);)*
new_instructions.push(#name::<Call>::#variant_name { #(#normal_names),* #comma_in_the_middle #(#bounded_names),* });
XcmBuilder {
instructions: new_instructions,
state: core::marker::PhantomData,
}
}
}
} else {
quote! {
pub fn #method_name(mut self, #(#arg_names: #arg_types),*) -> Self {
#(let #normal_names = #normal_names.into();)*
#(let #bounded_names = BoundedVec::truncate_from(#bounded_names);)*
self.instructions.push(#name::<Call>::#variant_name { #(#normal_names),* #comma_in_the_middle #(#bounded_names),* });
self
}
}
}
},
};
Ok(quote! {
#(#docs)*
#method
})
}
fn get_doc_comments(variant: &Variant) -> Vec<TokenStream2> {
variant
.attrs
.iter()
.filter_map(|attr| match &attr.meta {
Meta::NameValue(MetaNameValue {
value: Expr::Lit(ExprLit { lit: Lit::Str(literal), .. }),
..
}) if attr.path().is_ident("doc") => Some(literal.value()),
_ => None,
})
.map(|doc| syn::parse_str::<TokenStream2>(&format!("/// {}", doc)).unwrap())
.collect()
}
fn extract_generic_argument<'a>(
field_ty: &'a Type,
index: usize,
expected_msg: &str,
) -> Result<&'a Ident> {
if let Type::Path(type_path) = field_ty {
if let Some(segment) = type_path.path.segments.last() {
if let PathArguments::AngleBracketed(angle_brackets) = &segment.arguments {
let args: Vec<_> = angle_brackets.args.iter().collect();
if let Some(GenericArgument::Type(Type::Path(TypePath { path, .. }))) =
args.get(index)
{
return path.get_ident().ok_or_else(|| {
Error::new_spanned(
path,
format!("Expected an identifier for {}", expected_msg),
)
});
}
return Err(Error::new_spanned(
angle_brackets,
format!("Expected a generic argument at index {} for {}", index, expected_msg),
));
}
return Err(Error::new_spanned(
&segment.arguments,
format!("Expected angle-bracketed arguments for {}", expected_msg),
));
}
return Err(Error::new_spanned(
&type_path.path,
format!("Expected at least one path segment for {}", expected_msg),
));
}
Err(Error::new_spanned(field_ty, format!("Expected a path type for {}", expected_msg)))
}
@@ -0,0 +1,38 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Simple derive macro for getting the number of variants in an enum.
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::{Data, DeriveInput, Error, Result};
pub fn derive(input: DeriveInput) -> Result<TokenStream2> {
let data_enum = match &input.data {
Data::Enum(data_enum) => data_enum,
_ => return Err(Error::new_spanned(&input, "Expected an enum.")),
};
let ident = format_ident!("{}NumVariants", input.ident);
let number_of_variants: usize = data_enum.variants.iter().count();
Ok(quote! {
pub struct #ident;
impl ::frame_support::traits::Get<u32> for #ident {
fn get() -> u32 {
#number_of_variants as u32
}
}
})
}
+97
View File
@@ -0,0 +1,97 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Procedural macros used in XCM.
use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput};
mod builder_pattern;
mod enum_variants;
mod v3;
mod v4;
mod v5;
mod weight_info;
#[proc_macro_derive(XcmWeightInfoTrait)]
pub fn derive_xcm_weight_info(item: TokenStream) -> TokenStream {
weight_info::derive(item)
}
#[proc_macro]
pub fn impl_conversion_functions_for_multilocation_v3(input: TokenStream) -> TokenStream {
v3::multilocation::generate_conversion_functions(input)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}
#[proc_macro]
pub fn impl_conversion_functions_for_junctions_v3(input: TokenStream) -> TokenStream {
v3::junctions::generate_conversion_functions(input)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}
#[proc_macro]
pub fn impl_conversion_functions_for_location_v4(input: TokenStream) -> TokenStream {
v4::location::generate_conversion_functions(input)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}
#[proc_macro]
pub fn impl_conversion_functions_for_junctions_v4(input: TokenStream) -> TokenStream {
v4::junctions::generate_conversion_functions(input)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}
#[proc_macro]
pub fn impl_conversion_functions_for_junctions_v5(input: TokenStream) -> TokenStream {
v5::junctions::generate_conversion_functions(input)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}
#[proc_macro]
pub fn impl_conversion_functions_for_location_v5(input: TokenStream) -> TokenStream {
v5::location::generate_conversion_functions(input)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}
/// This is called on the `Instruction` enum, not on the `Xcm` struct,
/// and allows for the following syntax for building XCMs:
/// let message = Xcm::builder()
/// .withdraw_asset(assets)
/// .buy_execution(fees, weight_limit)
/// .deposit_asset(assets, beneficiary)
/// .build();
#[proc_macro_derive(Builder, attributes(builder))]
pub fn derive_builder(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
builder_pattern::derive(input)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}
#[proc_macro_derive(NumVariants)]
pub fn derive_num_variants(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
enum_variants::derive(input)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}
+195
View File
@@ -0,0 +1,195 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote};
use syn::{Result, Token};
const MAX_JUNCTIONS: usize = 8;
pub mod multilocation {
use super::*;
pub fn generate_conversion_functions(input: proc_macro::TokenStream) -> Result<TokenStream> {
if !input.is_empty() {
return Err(syn::Error::new(Span::call_site(), "No arguments expected"));
}
let from_tuples = generate_conversion_from_tuples(8, 8);
Ok(quote! {
#from_tuples
})
}
fn generate_conversion_from_tuples(max_junctions: usize, max_parents: usize) -> TokenStream {
let mut from_tuples = (0..=max_junctions)
.map(|num_junctions| {
let types = (0..num_junctions).map(|i| format_ident!("J{}", i)).collect::<Vec<_>>();
let idents =
(0..num_junctions).map(|i| format_ident!("j{}", i)).collect::<Vec<_>>();
let array_size = num_junctions;
let interior = if num_junctions == 0 {
quote!(Junctions::Here)
} else {
quote! {
[#(#idents .into()),*].into()
}
};
let mut from_tuple = quote! {
impl< #(#types : Into<Junction>,)* > From<( Ancestor, #( #types ),* )> for MultiLocation {
fn from( ( Ancestor(parents), #(#idents),* ): ( Ancestor, #( #types ),* ) ) -> Self {
MultiLocation { parents, interior: #interior }
}
}
impl From<[Junction; #array_size]> for MultiLocation {
fn from(j: [Junction; #array_size]) -> Self {
let [#(#idents),*] = j;
MultiLocation { parents: 0, interior: #interior }
}
}
};
let from_parent_tuples = (0..=max_parents).map(|cur_parents| {
let parents =
(0..cur_parents).map(|_| format_ident!("Parent")).collect::<Vec<_>>();
let underscores =
(0..cur_parents).map(|_| Token![_](Span::call_site())).collect::<Vec<_>>();
quote! {
impl< #(#types : Into<Junction>,)* > From<( #( #parents , )* #( #types , )* )> for MultiLocation {
fn from( ( #(#underscores,)* #(#idents,)* ): ( #(#parents,)* #(#types,)* ) ) -> Self {
Self { parents: #cur_parents as u8, interior: #interior }
}
}
}
});
from_tuple.extend(from_parent_tuples);
from_tuple
})
.collect::<TokenStream>();
let from_parent_junctions_tuples = (0..=max_parents).map(|cur_parents| {
let parents = (0..cur_parents).map(|_| format_ident!("Parent")).collect::<Vec<_>>();
let underscores =
(0..cur_parents).map(|_| Token![_](Span::call_site())).collect::<Vec<_>>();
quote! {
impl From<( #(#parents,)* Junctions )> for MultiLocation {
fn from( (#(#underscores,)* junctions): ( #(#parents,)* Junctions ) ) -> Self {
MultiLocation { parents: #cur_parents as u8, interior: junctions }
}
}
}
});
from_tuples.extend(from_parent_junctions_tuples);
quote! {
impl From<(Ancestor, Junctions)> for MultiLocation {
fn from((Ancestor(parents), interior): (Ancestor, Junctions)) -> Self {
MultiLocation { parents, interior }
}
}
impl From<Junction> for MultiLocation {
fn from(x: Junction) -> Self {
MultiLocation { parents: 0, interior: [x].into() }
}
}
#from_tuples
}
}
}
pub mod junctions {
use super::*;
pub fn generate_conversion_functions(input: proc_macro::TokenStream) -> Result<TokenStream> {
if !input.is_empty() {
return Err(syn::Error::new(Span::call_site(), "No arguments expected"));
}
// Support up to 8 Parents in a tuple, assuming that most use cases don't go past 8 parents.
let from_v4 = generate_conversion_from_v4();
let from_tuples = generate_conversion_from_tuples(MAX_JUNCTIONS);
Ok(quote! {
#from_v4
#from_tuples
})
}
fn generate_conversion_from_tuples(max_junctions: usize) -> TokenStream {
(1..=max_junctions)
.map(|num_junctions| {
let idents =
(0..num_junctions).map(|i| format_ident!("j{}", i)).collect::<Vec<_>>();
let types = (0..num_junctions).map(|i| format_ident!("J{}", i)).collect::<Vec<_>>();
quote! {
impl<#(#types : Into<Junction>,)*> From<( #(#types,)* )> for Junctions {
fn from( ( #(#idents,)* ): ( #(#types,)* ) ) -> Self {
[#(#idents .into()),*].into()
}
}
}
})
.collect()
}
fn generate_conversion_from_v4() -> TokenStream {
let match_variants = (0..8u8)
.map(|current_number| {
let number_ancestors = current_number + 1;
let variant = format_ident!("X{}", number_ancestors);
let idents =
(0..=current_number).map(|i| format_ident!("j{}", i)).collect::<Vec<_>>();
let convert = idents
.iter()
.map(|ident| {
quote! { let #ident = core::convert::TryInto::try_into(#ident.clone())?; }
})
.collect::<Vec<_>>();
quote! {
crate::v4::Junctions::#variant( junctions ) => {
let [#(#idents),*] = &*junctions;
#(#convert);*
[#(#idents),*].into()
},
}
})
.collect::<TokenStream>();
quote! {
impl core::convert::TryFrom<crate::v4::Junctions> for Junctions {
type Error = ();
fn try_from(mut new: crate::v4::Junctions) -> core::result::Result<Self, Self::Error> {
use Junctions::*;
Ok(match new {
crate::v4::Junctions::Here => Here,
#match_variants
})
}
}
}
}
}
+237
View File
@@ -0,0 +1,237 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote};
use syn::{Result, Token};
const MAX_JUNCTIONS: usize = 8;
pub mod location {
use super::*;
/// Generates conversion functions from other types to the `Location` type:
/// - [PalletInstance(50), GeneralIndex(1984)].into()
/// - (Parent, Teyrchain(1000), AccountId32 { .. }).into()
pub fn generate_conversion_functions(input: proc_macro::TokenStream) -> Result<TokenStream> {
if !input.is_empty() {
return Err(syn::Error::new(Span::call_site(), "No arguments expected"));
}
let from_tuples = generate_conversion_from_tuples(8, 8);
Ok(quote! {
#from_tuples
})
}
fn generate_conversion_from_tuples(max_junctions: usize, max_parents: usize) -> TokenStream {
let mut from_tuples = (0..=max_junctions)
.map(|num_junctions| {
let types = (0..num_junctions).map(|i| format_ident!("J{}", i)).collect::<Vec<_>>();
let idents =
(0..num_junctions).map(|i| format_ident!("j{}", i)).collect::<Vec<_>>();
let array_size = num_junctions;
let interior = if num_junctions == 0 {
quote!(Junctions::Here)
} else {
let variant = format_ident!("X{}", num_junctions);
quote! {
Junctions::#variant( alloc::sync::Arc::new( [#(#idents .into()),*] ) )
}
};
let mut from_tuple = quote! {
impl< #(#types : Into<Junction>,)* > From<( Ancestor, #( #types ),* )> for Location {
fn from( ( Ancestor(parents), #(#idents),* ): ( Ancestor, #( #types ),* ) ) -> Self {
Location { parents, interior: #interior }
}
}
impl From<[Junction; #array_size]> for Location {
fn from(j: [Junction; #array_size]) -> Self {
let [#(#idents),*] = j;
Location { parents: 0, interior: #interior }
}
}
};
let from_parent_tuples = (0..=max_parents).map(|cur_parents| {
let parents =
(0..cur_parents).map(|_| format_ident!("Parent")).collect::<Vec<_>>();
let underscores =
(0..cur_parents).map(|_| Token![_](Span::call_site())).collect::<Vec<_>>();
quote! {
impl< #(#types : Into<Junction>,)* > From<( #( #parents , )* #( #types , )* )> for Location {
fn from( ( #(#underscores,)* #(#idents,)* ): ( #(#parents,)* #(#types,)* ) ) -> Self {
Self { parents: #cur_parents as u8, interior: #interior }
}
}
}
});
from_tuple.extend(from_parent_tuples);
from_tuple
})
.collect::<TokenStream>();
let from_parent_junctions_tuples = (0..=max_parents).map(|cur_parents| {
let parents = (0..cur_parents).map(|_| format_ident!("Parent")).collect::<Vec<_>>();
let underscores =
(0..cur_parents).map(|_| Token![_](Span::call_site())).collect::<Vec<_>>();
quote! {
impl From<( #(#parents,)* Junctions )> for Location {
fn from( (#(#underscores,)* junctions): ( #(#parents,)* Junctions ) ) -> Self {
Location { parents: #cur_parents as u8, interior: junctions }
}
}
}
});
from_tuples.extend(from_parent_junctions_tuples);
quote! {
impl From<(Ancestor, Junctions)> for Location {
fn from((Ancestor(parents), interior): (Ancestor, Junctions)) -> Self {
Location { parents, interior }
}
}
impl From<Junction> for Location {
fn from(x: Junction) -> Self {
Location { parents: 0, interior: [x].into() }
}
}
#from_tuples
}
}
}
pub mod junctions {
use super::*;
pub fn generate_conversion_functions(input: proc_macro::TokenStream) -> Result<TokenStream> {
if !input.is_empty() {
return Err(syn::Error::new(Span::call_site(), "No arguments expected"));
}
// Support up to 8 Parents in a tuple, assuming that most use cases don't go past 8 parents.
let from_v3 = generate_conversion_from_v3(MAX_JUNCTIONS);
let from_v5 = generate_conversion_from_v5(MAX_JUNCTIONS);
let from_tuples = generate_conversion_from_tuples(MAX_JUNCTIONS);
Ok(quote! {
#from_v3
#from_v5
#from_tuples
})
}
fn generate_conversion_from_tuples(max_junctions: usize) -> TokenStream {
(1..=max_junctions)
.map(|num_junctions| {
let idents =
(0..num_junctions).map(|i| format_ident!("j{}", i)).collect::<Vec<_>>();
let types = (0..num_junctions).map(|i| format_ident!("J{}", i)).collect::<Vec<_>>();
quote! {
impl<#(#types : Into<Junction>,)*> From<( #(#types,)* )> for Junctions {
fn from( ( #(#idents,)* ): ( #(#types,)* ) ) -> Self {
[#(#idents .into()),*].into()
}
}
}
})
.collect()
}
fn generate_conversion_from_v3(max_junctions: usize) -> TokenStream {
let match_variants = (0..max_junctions)
.map(|cur_num| {
let num_ancestors = cur_num + 1;
let variant = format_ident!("X{}", num_ancestors);
let idents = (0..=cur_num).map(|i| format_ident!("j{}", i)).collect::<Vec<_>>();
let convert = idents
.iter()
.map(|ident| {
quote! { let #ident = core::convert::TryInto::try_into(#ident.clone())?; }
})
.collect::<Vec<_>>();
quote! {
crate::v3::Junctions::#variant( #(#idents),* ) => {
#(#convert);*;
let junctions: Junctions = [#(#idents),*].into();
junctions
},
}
})
.collect::<TokenStream>();
quote! {
impl core::convert::TryFrom<crate::v3::Junctions> for Junctions {
type Error = ();
fn try_from(mut old: crate::v3::Junctions) -> core::result::Result<Self, ()> {
Ok(match old {
crate::v3::Junctions::Here => Junctions::Here,
#match_variants
})
}
}
}
}
fn generate_conversion_from_v5(max_junctions: usize) -> TokenStream {
let match_variants = (0..max_junctions)
.map(|current_number| {
let number_ancestors = current_number + 1;
let variant = format_ident!("X{}", number_ancestors);
let idents =
(0..=current_number).map(|i| format_ident!("j{}", i)).collect::<Vec<_>>();
let convert = idents
.iter()
.map(|ident| {
quote! { let #ident = core::convert::TryInto::try_into(#ident.clone())?; }
})
.collect::<Vec<_>>();
quote! {
crate::v5::Junctions::#variant( junctions ) => {
let [#(#idents),*] = &*junctions;
#(#convert);*
[#(#idents),*].into()
},
}
})
.collect::<TokenStream>();
quote! {
impl core::convert::TryFrom<crate::v5::Junctions> for Junctions {
type Error = ();
fn try_from(mut new: crate::v5::Junctions) -> core::result::Result<Self, Self::Error> {
use Junctions::*;
Ok(match new {
crate::v5::Junctions::Here => Here,
#match_variants
})
}
}
}
}
}
+198
View File
@@ -0,0 +1,198 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote};
use syn::{Result, Token};
const MAX_JUNCTIONS: usize = 8;
pub mod location {
use super::*;
/// Generates conversion functions from other types to the `Location` type:
/// - [PalletInstance(50), GeneralIndex(1984)].into()
/// - (Parent, Teyrchain(1000), AccountId32 { .. }).into()
pub fn generate_conversion_functions(input: proc_macro::TokenStream) -> Result<TokenStream> {
if !input.is_empty() {
return Err(syn::Error::new(Span::call_site(), "No arguments expected"));
}
let from_tuples = generate_conversion_from_tuples(8, 8);
Ok(quote! {
#from_tuples
})
}
fn generate_conversion_from_tuples(max_junctions: usize, max_parents: usize) -> TokenStream {
let mut from_tuples = (0..=max_junctions)
.map(|num_junctions| {
let types = (0..num_junctions).map(|i| format_ident!("J{}", i)).collect::<Vec<_>>();
let idents =
(0..num_junctions).map(|i| format_ident!("j{}", i)).collect::<Vec<_>>();
let array_size = num_junctions;
let interior = if num_junctions == 0 {
quote!(Junctions::Here)
} else {
let variant = format_ident!("X{}", num_junctions);
quote! {
Junctions::#variant( alloc::sync::Arc::new( [#(#idents .into()),*] ) )
}
};
let mut from_tuple = quote! {
impl< #(#types : Into<Junction>,)* > From<( Ancestor, #( #types ),* )> for Location {
fn from( ( Ancestor(parents), #(#idents),* ): ( Ancestor, #( #types ),* ) ) -> Self {
Location { parents, interior: #interior }
}
}
impl From<[Junction; #array_size]> for Location {
fn from(j: [Junction; #array_size]) -> Self {
let [#(#idents),*] = j;
Location { parents: 0, interior: #interior }
}
}
};
let from_parent_tuples = (0..=max_parents).map(|cur_parents| {
let parents =
(0..cur_parents).map(|_| format_ident!("Parent")).collect::<Vec<_>>();
let underscores =
(0..cur_parents).map(|_| Token![_](Span::call_site())).collect::<Vec<_>>();
quote! {
impl< #(#types : Into<Junction>,)* > From<( #( #parents , )* #( #types , )* )> for Location {
fn from( ( #(#underscores,)* #(#idents,)* ): ( #(#parents,)* #(#types,)* ) ) -> Self {
Self { parents: #cur_parents as u8, interior: #interior }
}
}
}
});
from_tuple.extend(from_parent_tuples);
from_tuple
})
.collect::<TokenStream>();
let from_parent_junctions_tuples = (0..=max_parents).map(|cur_parents| {
let parents = (0..cur_parents).map(|_| format_ident!("Parent")).collect::<Vec<_>>();
let underscores =
(0..cur_parents).map(|_| Token![_](Span::call_site())).collect::<Vec<_>>();
quote! {
impl From<( #(#parents,)* Junctions )> for Location {
fn from( (#(#underscores,)* junctions): ( #(#parents,)* Junctions ) ) -> Self {
Location { parents: #cur_parents as u8, interior: junctions }
}
}
}
});
from_tuples.extend(from_parent_junctions_tuples);
quote! {
impl From<(Ancestor, Junctions)> for Location {
fn from((Ancestor(parents), interior): (Ancestor, Junctions)) -> Self {
Location { parents, interior }
}
}
impl From<Junction> for Location {
fn from(x: Junction) -> Self {
Location { parents: 0, interior: [x].into() }
}
}
#from_tuples
}
}
}
pub mod junctions {
use super::*;
pub fn generate_conversion_functions(input: proc_macro::TokenStream) -> Result<TokenStream> {
if !input.is_empty() {
return Err(syn::Error::new(Span::call_site(), "No arguments expected"));
}
// Support up to 8 Parents in a tuple, assuming that most use cases don't go past 8 parents.
let from_v4 = generate_conversion_from_v4(MAX_JUNCTIONS);
let from_tuples = generate_conversion_from_tuples(MAX_JUNCTIONS);
Ok(quote! {
#from_v4
#from_tuples
})
}
fn generate_conversion_from_tuples(max_junctions: usize) -> TokenStream {
(1..=max_junctions)
.map(|num_junctions| {
let idents =
(0..num_junctions).map(|i| format_ident!("j{}", i)).collect::<Vec<_>>();
let types = (0..num_junctions).map(|i| format_ident!("J{}", i)).collect::<Vec<_>>();
quote! {
impl<#(#types : Into<Junction>,)*> From<( #(#types,)* )> for Junctions {
fn from( ( #(#idents,)* ): ( #(#types,)* ) ) -> Self {
[#(#idents .into()),*].into()
}
}
}
})
.collect()
}
fn generate_conversion_from_v4(max_junctions: usize) -> TokenStream {
let match_variants = (0..max_junctions)
.map(|cur_num| {
let num_ancestors = cur_num + 1;
let variant = format_ident!("X{}", num_ancestors);
let idents = (0..=cur_num).map(|i| format_ident!("j{}", i)).collect::<Vec<_>>();
let convert = idents
.iter()
.enumerate()
.map(|(index, ident)| {
quote! { let #ident = core::convert::TryInto::try_into(slice[#index].clone())?; }
})
.collect::<Vec<_>>();
quote! {
crate::v4::Junctions::#variant( arc ) => {
let slice = &arc[..];
#(#convert);*;
let junctions: Junctions = [#(#idents),*].into();
junctions
},
}
})
.collect::<TokenStream>();
quote! {
impl core::convert::TryFrom<crate::v4::Junctions> for Junctions {
type Error = ();
fn try_from(mut old: crate::v4::Junctions) -> core::result::Result<Self, ()> {
Ok(match old {
crate::v4::Junctions::Here => Junctions::Here,
#match_variants
})
}
}
}
}
}
+67
View File
@@ -0,0 +1,67 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
use inflector::Inflector;
use quote::format_ident;
pub fn derive(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input: syn::DeriveInput = match syn::parse(item) {
Ok(input) => input,
Err(e) => return e.into_compile_error().into(),
};
let syn::DeriveInput { generics, data, .. } = input;
match data {
syn::Data::Enum(syn::DataEnum { variants, .. }) => {
let methods = variants.into_iter().map(|syn::Variant { ident, fields, .. }| {
let snake_cased_ident = format_ident!("{}", ident.to_string().to_snake_case());
let ref_fields =
fields.into_iter().enumerate().map(|(idx, syn::Field { ident, ty, .. })| {
let field_name = ident.unwrap_or_else(|| format_ident!("_{}", idx));
let field_ty = match ty {
syn::Type::Reference(r) => {
// If the type is already a reference, do nothing
quote::quote!(#r)
},
t => {
// Otherwise, make it a reference
quote::quote!(&#t)
},
};
quote::quote!(#field_name: #field_ty,)
});
quote::quote!(fn #snake_cased_ident( #(#ref_fields)* ) -> Weight;)
});
let res = quote::quote! {
pub trait XcmWeightInfo #generics {
#(#methods)*
}
};
res.into()
},
syn::Data::Struct(syn::DataStruct { struct_token, .. }) => {
let msg = "structs are not supported by 'derive(XcmWeightInfo)'";
syn::Error::new(struct_token.span, msg).into_compile_error().into()
},
syn::Data::Union(syn::DataUnion { union_token, .. }) => {
let msg = "unions are not supported by 'derive(XcmWeightInfo)'";
syn::Error::new(union_token.span, msg).into_compile_error().into()
},
}
}
@@ -0,0 +1,161 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Test the methods generated by the Builder derive macro.
//! Tests directly on the actual Xcm struct and Instruction enum.
use frame_support::BoundedVec;
use xcm::latest::prelude::*;
#[test]
fn builder_pattern_works() {
let asset: Asset = (Here, 100u128).into();
let beneficiary: Location = [0u8; 32].into();
let message: Xcm<()> = Xcm::builder()
.receive_teleported_asset(asset.clone())
.buy_execution(asset.clone(), Unlimited)
.deposit_asset(asset.clone(), beneficiary.clone())
.build();
assert_eq!(
message,
Xcm(vec![
ReceiveTeleportedAsset(asset.clone().into()),
BuyExecution { fees: asset.clone(), weight_limit: Unlimited },
DepositAsset { assets: asset.into(), beneficiary },
])
);
}
#[test]
fn default_builder_requires_buy_execution() {
let asset: Asset = (Here, 100u128).into();
let beneficiary: Location = AccountId32 { id: [0u8; 32], network: None }.into();
// This is invalid, since it doesn't pay for fees.
// This is enforced by the runtime, because the build() method doesn't exist
// on the resulting type.
// let message: Xcm<()> = Xcm::builder()
// .withdraw_asset(asset.clone().into())
// .deposit_asset(asset.into(), beneficiary)
// .build();
// To be able to do that, we need to use the explicitly unpaid variant
let message: Xcm<()> = Xcm::builder_unpaid()
.unpaid_execution(Unlimited, None)
.withdraw_asset(asset.clone())
.deposit_asset(asset.clone(), beneficiary.clone())
.build(); // This works
assert_eq!(
message,
Xcm(vec![
UnpaidExecution { weight_limit: Unlimited, check_origin: None },
WithdrawAsset(asset.clone().into()),
DepositAsset { assets: asset.clone().into(), beneficiary: beneficiary.clone() },
])
);
// The other option doesn't have any limits whatsoever, so it should
// only be used when you really know what you're doing.
let message: Xcm<()> = Xcm::builder_unsafe()
.withdraw_asset(asset.clone())
.deposit_asset(asset.clone(), beneficiary.clone())
.build();
assert_eq!(
message,
Xcm(vec![
WithdrawAsset(asset.clone().into()),
DepositAsset { assets: asset.clone().into(), beneficiary },
])
);
}
#[test]
fn default_builder_allows_clear_origin_before_buy_execution() {
let asset: Asset = (Here, 100u128).into();
let beneficiary: Location = [0u8; 32].into();
let message: Xcm<()> = Xcm::builder()
.receive_teleported_asset(asset.clone())
.clear_origin()
.buy_execution(asset.clone(), Unlimited)
.deposit_asset(asset.clone(), beneficiary.clone())
.build();
assert_eq!(
message,
Xcm(vec![
ReceiveTeleportedAsset(asset.clone().into()),
ClearOrigin,
BuyExecution { fees: asset.clone(), weight_limit: Unlimited },
DepositAsset { assets: asset.into(), beneficiary },
])
);
}
#[test]
fn bounded_vecs_use_vecs_and_truncate_them() {
let claimer = Location::parent();
// We can use a vec instead of a bounded vec for specifying hints.
let xcm: Xcm<()> = Xcm::builder_unsafe()
.set_hints(vec![AssetClaimer { location: claimer.clone() }])
.build();
assert_eq!(
xcm,
Xcm(vec![SetHints {
hints: BoundedVec::<Hint, HintNumVariants>::truncate_from(vec![AssetClaimer {
location: Location::parent()
},]),
},])
);
// If we include more than the limit they'll get truncated.
let xcm: Xcm<()> = Xcm::builder_unsafe()
.set_hints(vec![
AssetClaimer { location: claimer.clone() },
AssetClaimer { location: Location::here() },
])
.build();
assert_eq!(
xcm,
Xcm(vec![SetHints {
hints: BoundedVec::<Hint, HintNumVariants>::truncate_from(vec![AssetClaimer {
location: Location::parent()
},]),
},])
);
let xcm: Xcm<()> = Xcm::builder()
.withdraw_asset((Here, 100u128))
.set_hints(vec![AssetClaimer { location: claimer }])
.clear_origin()
.pay_fees((Here, 10u128))
.deposit_asset(All, [0u8; 32])
.build();
assert_eq!(
xcm,
Xcm(vec![
WithdrawAsset(Asset { id: AssetId(Location::here()), fun: Fungible(100) }.into()),
SetHints {
hints: BoundedVec::<Hint, HintNumVariants>::truncate_from(vec![AssetClaimer {
location: Location::parent()
}])
},
ClearOrigin,
PayFees { asset: Asset { id: AssetId(Location::here()), fun: Fungible(10) } },
DepositAsset {
assets: All.into(),
beneficiary: AccountId32 { id: [0u8; 32], network: None }.into()
},
])
);
}
@@ -0,0 +1,24 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
use xcm::v3::prelude::*;
#[test]
fn slice_syntax_in_v3_works() {
let old_junctions = Junctions::X2(Teyrchain(1), PalletInstance(1));
let new_junctions = Junctions::from([Teyrchain(1), PalletInstance(1)]);
assert_eq!(old_junctions, new_junctions);
}
@@ -0,0 +1,33 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Test the struct generated by the `NumVariants` derive macro.
use frame_support::traits::Get;
use xcm_procedural::NumVariants;
#[allow(dead_code)]
#[derive(NumVariants)]
enum SomeEnum {
Variant1,
Variant2,
Variant3,
}
#[test]
fn num_variants_works() {
assert_eq!(SomeEnumNumVariants::get(), 3);
}
+31
View File
@@ -0,0 +1,31 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! UI tests for XCM procedural macros
#[test]
fn ui() {
// Only run the ui tests when `RUN_UI_TESTS` is set.
if std::env::var("RUN_UI_TESTS").is_err() {
return;
}
// As trybuild is using `cargo check`, we don't need the real WASM binaries.
std::env::set_var("SKIP_WASM_BUILD", "1");
let t = trybuild::TestCases::new();
t.compile_fail("tests/ui/**/*.rs");
}
@@ -0,0 +1,32 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Test error when using a badly formatted attribute.
use xcm_procedural::Builder;
struct Xcm<Call>(pub Vec<Instruction<Call>>);
#[derive(Builder)]
enum Instruction<Call> {
#[builder(funds_holding = 2)]
WithdrawAsset(u128),
BuyExecution { fees: u128 },
UnpaidExecution { weight_limit: (u32, u32) },
Transact { call: Call },
}
fn main() {}
@@ -0,0 +1,5 @@
error: Expected `builder(loads_holding)` or `builder(pays_fees)`
--> tests/ui/builder_pattern/badly_formatted_attribute.rs:25:5
|
25 | #[builder(funds_holding = 2)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -0,0 +1,29 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Test error when there's no `UnpaidExecution` instruction.
use xcm_procedural::Builder;
struct Xcm<Call>(pub Vec<Instruction<Call>>);
#[derive(Builder)]
enum Instruction<Call> {
BuyExecution { fees: u128 },
Transact { call: Call },
}
fn main() {}
@@ -0,0 +1,6 @@
error: No UnpaidExecution instruction
--> tests/ui/builder_pattern/no_unpaid_execution.rs:25:5
|
25 | / BuyExecution { fees: u128 },
26 | | Transact { call: Call },
| |____________________________^
@@ -0,0 +1,5 @@
error: Expected `builder(loads_holding)` or `builder(pays_fees)`
--> tests/ui/builder_pattern/unexpected_attribute.rs:25:5
|
25 | #[builder(funds_holding)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -0,0 +1,25 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Test error when attaching the derive builder macro to something
//! other than the XCM `Instruction` enum.
use xcm_procedural::Builder;
#[derive(Builder)]
struct SomeStruct;
fn main() {}
@@ -0,0 +1,5 @@
error: Expected the `Instruction` enum
--> tests/ui/builder_pattern/wrong_target.rs:23:1
|
23 | struct SomeStruct;
| ^^^^^^^^^^^^^^^^^^
+136
View File
@@ -0,0 +1,136 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
use crate::MAX_XCM_DECODE_DEPTH;
use alloc::vec::Vec;
use codec::{Decode, DecodeLimit, DecodeWithMemTracking, Encode};
/// Wrapper around the encoded and decoded versions of a value.
/// Caches the decoded value once computed.
#[derive(Encode, Decode, DecodeWithMemTracking, scale_info::TypeInfo)]
#[codec(encode_bound())]
#[codec(decode_bound())]
#[scale_info(bounds(), skip_type_params(T))]
#[scale_info(replace_segment("staging_xcm", "xcm"))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub struct DoubleEncoded<T> {
encoded: Vec<u8>,
#[codec(skip)]
decoded: Option<T>,
}
impl<T> Clone for DoubleEncoded<T> {
fn clone(&self) -> Self {
Self { encoded: self.encoded.clone(), decoded: None }
}
}
impl<T> PartialEq for DoubleEncoded<T> {
fn eq(&self, other: &Self) -> bool {
self.encoded.eq(&other.encoded)
}
}
impl<T> Eq for DoubleEncoded<T> {}
impl<T> core::fmt::Debug for DoubleEncoded<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
array_bytes::bytes2hex("0x", &self.encoded).fmt(f)
}
}
impl<T> From<Vec<u8>> for DoubleEncoded<T> {
fn from(encoded: Vec<u8>) -> Self {
Self { encoded, decoded: None }
}
}
impl<T> DoubleEncoded<T> {
pub fn into<S>(self) -> DoubleEncoded<S> {
DoubleEncoded::from(self)
}
pub fn from<S>(e: DoubleEncoded<S>) -> Self {
Self { encoded: e.encoded, decoded: None }
}
/// Provides an API similar to `AsRef` that provides access to the inner value.
/// `AsRef` implementation would expect an `&Option<T>` return type.
pub fn as_ref(&self) -> Option<&T> {
self.decoded.as_ref()
}
/// Access the encoded data.
pub fn into_encoded(self) -> Vec<u8> {
self.encoded
}
}
impl<T: Decode> DoubleEncoded<T> {
/// Decode the inner encoded value and store it.
/// Returns a reference to the value in case of success and `Err(())` in case the decoding
/// fails.
pub fn ensure_decoded(&mut self) -> Result<&T, ()> {
if self.decoded.is_none() {
self.decoded =
T::decode_all_with_depth_limit(MAX_XCM_DECODE_DEPTH, &mut &self.encoded[..]).ok();
}
self.decoded.as_ref().ok_or(())
}
/// Move the decoded value out or (if not present) decode `encoded`.
pub fn take_decoded(&mut self) -> Result<T, ()> {
self.decoded
.take()
.or_else(|| {
T::decode_all_with_depth_limit(MAX_XCM_DECODE_DEPTH, &mut &self.encoded[..]).ok()
})
.ok_or(())
}
/// Provides an API similar to `TryInto` that allows fallible conversion to the inner value
/// type. `TryInto` implementation would collide with std blanket implementation based on
/// `TryFrom`.
pub fn try_into(mut self) -> Result<T, ()> {
self.ensure_decoded()?;
self.decoded.ok_or(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ensure_decoded_works() {
let val: u64 = 42;
let mut encoded: DoubleEncoded<_> = Encode::encode(&val).into();
assert_eq!(encoded.ensure_decoded(), Ok(&val));
}
#[test]
fn take_decoded_works() {
let val: u64 = 42;
let mut encoded: DoubleEncoded<_> = Encode::encode(&val).into();
assert_eq!(encoded.take_decoded(), Ok(val));
}
#[test]
fn try_into_works() {
let val: u64 = 42;
let encoded: DoubleEncoded<_> = Encode::encode(&val).into();
assert_eq!(encoded.try_into(), Ok(val));
}
}
+700
View File
@@ -0,0 +1,700 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Cross-Consensus Message format data structures.
// NOTE, this crate is meant to be used in many different environments, notably wasm, but not
// necessarily related to FRAME or even Substrate.
//
// Hence, `no_std` rather than sp-runtime.
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
use codec::{
Decode, DecodeLimit, DecodeWithMemTracking, Encode, Error as CodecError, Input, MaxEncodedLen,
};
use derive_where::derive_where;
use frame_support::dispatch::GetDispatchInfo;
use scale_info::TypeInfo;
pub mod v3;
pub mod v4;
pub mod v5;
pub mod lts {
pub use super::v4::*;
}
pub mod latest {
pub use super::v5::*;
}
mod double_encoded;
pub use double_encoded::DoubleEncoded;
mod utils;
#[cfg(test)]
mod tests;
/// Maximum nesting level for XCM decoding.
pub const MAX_XCM_DECODE_DEPTH: u32 = 8;
/// The maximal number of instructions in an XCM before decoding fails.
///
/// This is a deliberate limit - not a technical one.
pub const MAX_INSTRUCTIONS_TO_DECODE: u8 = 100;
/// A version of XCM.
pub type Version = u32;
#[derive(Clone, Eq, PartialEq, Debug)]
pub enum Unsupported {}
impl Encode for Unsupported {}
impl Decode for Unsupported {
fn decode<I: Input>(_: &mut I) -> Result<Self, CodecError> {
Err("Not decodable".into())
}
}
/// Attempt to convert `self` into a particular version of itself.
pub trait IntoVersion: Sized {
/// Consume `self` and return same value expressed in some particular `version` of XCM.
fn into_version(self, version: Version) -> Result<Self, ()>;
/// Consume `self` and return same value expressed the latest version of XCM.
fn into_latest(self) -> Result<Self, ()> {
self.into_version(latest::VERSION)
}
}
pub trait TryAs<T> {
fn try_as(&self) -> Result<&T, ()>;
}
// Macro that generated versioned wrapper types.
// NOTE: converting a v4 type into a versioned type will make it v5.
macro_rules! versioned_type {
($(#[$attr:meta])* pub enum $n:ident {
$(#[$index3:meta])+
V3($v3:ty),
$(#[$index4:meta])+
V4($v4:ty),
$(#[$index5:meta])+
V5($v5:ty),
}) => {
#[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, DecodeWithMemTracking, TypeInfo)]
#[codec(encode_bound())]
#[codec(decode_bound())]
#[scale_info(replace_segment("staging_xcm", "xcm"))]
$(#[$attr])*
pub enum $n {
$(#[$index3])*
V3($v3),
$(#[$index4])*
V4($v4),
$(#[$index5])*
V5($v5),
}
impl $n {
pub fn try_as<T>(&self) -> Result<&T, ()> where Self: TryAs<T> {
<Self as TryAs<T>>::try_as(&self)
}
}
impl TryAs<$v3> for $n {
fn try_as(&self) -> Result<&$v3, ()> {
match &self {
Self::V3(ref x) => Ok(x),
_ => Err(()),
}
}
}
impl TryAs<$v4> for $n {
fn try_as(&self) -> Result<&$v4, ()> {
match &self {
Self::V4(ref x) => Ok(x),
_ => Err(()),
}
}
}
impl TryAs<$v5> for $n {
fn try_as(&self) -> Result<&$v5, ()> {
match &self {
Self::V5(ref x) => Ok(x),
_ => Err(()),
}
}
}
impl IntoVersion for $n {
fn into_version(self, n: Version) -> Result<Self, ()> {
let version = self.identify_version();
if version == n {
Ok(self)
} else {
Ok(match n {
3 => Self::V3(self.try_into()?),
4 => Self::V4(self.try_into()?),
5 => Self::V5(self.try_into()?),
_ => return Err(()),
})
}
}
}
impl From<$v3> for $n {
fn from(x: $v3) -> Self {
$n::V3(x.into())
}
}
impl<T: Into<$v5>> From<T> for $n {
fn from(x: T) -> Self {
$n::V5(x.into())
}
}
impl TryFrom<$n> for $v3 {
type Error = ();
fn try_from(x: $n) -> Result<Self, ()> {
use $n::*;
match x {
V3(x) => Ok(x),
V4(x) => x.try_into().map_err(|_| ()),
V5(x) => {
let v4: $v4 = x.try_into().map_err(|_| ())?;
v4.try_into().map_err(|_| ())
}
}
}
}
impl TryFrom<$n> for $v4 {
type Error = ();
fn try_from(x: $n) -> Result<Self, ()> {
use $n::*;
match x {
V3(x) => x.try_into().map_err(|_| ()),
V4(x) => Ok(x),
V5(x) => x.try_into().map_err(|_| ()),
}
}
}
impl TryFrom<$n> for $v5 {
type Error = ();
fn try_from(x: $n) -> Result<Self, ()> {
use $n::*;
match x {
V3(x) => {
let v4: $v4 = x.try_into().map_err(|_| ())?;
v4.try_into().map_err(|_| ())
},
V4(x) => x.try_into().map_err(|_| ()),
V5(x) => Ok(x),
}
}
}
impl MaxEncodedLen for $n {
fn max_encoded_len() -> usize {
<$v3>::max_encoded_len()
}
}
impl IdentifyVersion for $n {
fn identify_version(&self) -> Version {
use $n::*;
match self {
V3(_) => v3::VERSION,
V4(_) => v4::VERSION,
V5(_) => v5::VERSION,
}
}
}
};
}
versioned_type! {
/// A single version's `AssetId` value, together with its version code.
pub enum VersionedAssetId {
#[codec(index = 3)]
V3(v3::AssetId),
#[codec(index = 4)]
V4(v4::AssetId),
#[codec(index = 5)]
V5(v5::AssetId),
}
}
versioned_type! {
/// A single version's `Response` value, together with its version code.
pub enum VersionedResponse {
#[codec(index = 3)]
V3(v3::Response),
#[codec(index = 4)]
V4(v4::Response),
#[codec(index = 5)]
V5(v5::Response),
}
}
versioned_type! {
/// A single `NetworkId` value, together with its version code.
pub enum VersionedNetworkId {
#[codec(index = 3)]
V3(v3::NetworkId),
#[codec(index = 4)]
V4(v4::NetworkId),
#[codec(index = 5)]
V5(v5::NetworkId),
}
}
versioned_type! {
/// A single `Junction` value, together with its version code.
pub enum VersionedJunction {
#[codec(index = 3)]
V3(v3::Junction),
#[codec(index = 4)]
V4(v4::Junction),
#[codec(index = 5)]
V5(v5::Junction),
}
}
versioned_type! {
/// A single `Location` value, together with its version code.
#[derive(Ord, PartialOrd)]
pub enum VersionedLocation {
#[codec(index = 3)]
V3(v3::MultiLocation),
#[codec(index = 4)]
V4(v4::Location),
#[codec(index = 5)]
V5(v5::Location),
}
}
versioned_type! {
/// A single `InteriorLocation` value, together with its version code.
pub enum VersionedInteriorLocation {
#[codec(index = 3)]
V3(v3::InteriorMultiLocation),
#[codec(index = 4)]
V4(v4::InteriorLocation),
#[codec(index = 5)]
V5(v5::InteriorLocation),
}
}
versioned_type! {
/// A single `Asset` value, together with its version code.
pub enum VersionedAsset {
#[codec(index = 3)]
V3(v3::MultiAsset),
#[codec(index = 4)]
V4(v4::Asset),
#[codec(index = 5)]
V5(v5::Asset),
}
}
versioned_type! {
/// A single `MultiAssets` value, together with its version code.
pub enum VersionedAssets {
#[codec(index = 3)]
V3(v3::MultiAssets),
#[codec(index = 4)]
V4(v4::Assets),
#[codec(index = 5)]
V5(v5::Assets),
}
}
/// A single XCM message, together with its version code.
#[derive(Encode, Decode, DecodeWithMemTracking, TypeInfo)]
#[derive_where(Clone, Eq, PartialEq, Debug)]
#[codec(encode_bound())]
#[codec(decode_bound())]
#[scale_info(bounds(), skip_type_params(RuntimeCall))]
#[scale_info(replace_segment("staging_xcm", "xcm"))]
pub enum VersionedXcm<RuntimeCall> {
#[codec(index = 3)]
V3(v3::Xcm<RuntimeCall>),
#[codec(index = 4)]
V4(v4::Xcm<RuntimeCall>),
#[codec(index = 5)]
V5(v5::Xcm<RuntimeCall>),
}
impl<C: Decode + GetDispatchInfo> IntoVersion for VersionedXcm<C> {
fn into_version(self, n: Version) -> Result<Self, ()> {
Ok(match n {
3 => Self::V3(self.try_into()?),
4 => Self::V4(self.try_into()?),
5 => Self::V5(self.try_into()?),
_ => return Err(()),
})
}
}
impl<C> IdentifyVersion for VersionedXcm<C> {
fn identify_version(&self) -> Version {
match self {
Self::V3(_) => v3::VERSION,
Self::V4(_) => v4::VERSION,
Self::V5(_) => v5::VERSION,
}
}
}
impl<C> VersionedXcm<C> {
/// Checks if the XCM is decodable. Consequently, it checks all decoding constraints,
/// such as `MAX_XCM_DECODE_DEPTH`, `MAX_ITEMS_IN_ASSETS` or `MAX_INSTRUCTIONS_TO_DECODE`.
///
/// Note that this uses the limit of the sender - not the receiver. It is a best effort.
pub fn check_is_decodable(&self) -> Result<(), ()> {
self.using_encoded(|mut enc| {
Self::decode_all_with_depth_limit(MAX_XCM_DECODE_DEPTH, &mut enc).map(|_| ())
})
.map_err(|e| {
tracing::error!(target: "xcm::check_is_decodable", error=?e, xcm=?self, "Decode error!");
()
})
}
}
impl<RuntimeCall> From<v3::Xcm<RuntimeCall>> for VersionedXcm<RuntimeCall> {
fn from(x: v3::Xcm<RuntimeCall>) -> Self {
VersionedXcm::V3(x)
}
}
impl<RuntimeCall> From<v4::Xcm<RuntimeCall>> for VersionedXcm<RuntimeCall> {
fn from(x: v4::Xcm<RuntimeCall>) -> Self {
VersionedXcm::V4(x)
}
}
impl<RuntimeCall> From<v5::Xcm<RuntimeCall>> for VersionedXcm<RuntimeCall> {
fn from(x: v5::Xcm<RuntimeCall>) -> Self {
VersionedXcm::V5(x)
}
}
impl<Call: Decode + GetDispatchInfo> TryFrom<VersionedXcm<Call>> for v3::Xcm<Call> {
type Error = ();
fn try_from(x: VersionedXcm<Call>) -> Result<Self, ()> {
use VersionedXcm::*;
match x {
V3(x) => Ok(x),
V4(x) => x.try_into(),
V5(x) => {
let v4: v4::Xcm<Call> = x.try_into()?;
v4.try_into()
},
}
}
}
impl<Call: Decode + GetDispatchInfo> TryFrom<VersionedXcm<Call>> for v4::Xcm<Call> {
type Error = ();
fn try_from(x: VersionedXcm<Call>) -> Result<Self, ()> {
use VersionedXcm::*;
match x {
V3(x) => x.try_into(),
V4(x) => Ok(x),
V5(x) => x.try_into(),
}
}
}
impl<Call: Decode + GetDispatchInfo> TryFrom<VersionedXcm<Call>> for v5::Xcm<Call> {
type Error = ();
fn try_from(x: VersionedXcm<Call>) -> Result<Self, ()> {
use VersionedXcm::*;
match x {
V3(x) => {
let v4: v4::Xcm<Call> = x.try_into()?;
v4.try_into()
},
V4(x) => x.try_into(),
V5(x) => Ok(x),
}
}
}
/// Convert an `Xcm` datum into a `VersionedXcm`, based on a destination `Location` which will
/// interpret it.
pub trait WrapVersion {
fn wrap_version<RuntimeCall: Decode + GetDispatchInfo>(
dest: &latest::Location,
xcm: impl Into<VersionedXcm<RuntimeCall>>,
) -> Result<VersionedXcm<RuntimeCall>, ()>;
}
/// Used to get the version out of a versioned type.
// TODO(XCMv5): This could be `GetVersion` and we change the current one to `GetVersionFor`.
pub trait IdentifyVersion {
fn identify_version(&self) -> Version;
}
/// Check and return the `Version` that should be used for the `Xcm` datum for the destination
/// `Location`, which will interpret it.
pub trait GetVersion {
fn get_version_for(dest: &latest::Location) -> Option<Version>;
}
/// `()` implementation does nothing with the XCM, just sending with whatever version it was
/// authored as.
impl WrapVersion for () {
fn wrap_version<RuntimeCall>(
_: &latest::Location,
xcm: impl Into<VersionedXcm<RuntimeCall>>,
) -> Result<VersionedXcm<RuntimeCall>, ()> {
Ok(xcm.into())
}
}
/// `WrapVersion` implementation which attempts to always convert the XCM to version 3 before
/// wrapping it.
pub struct AlwaysV3;
impl WrapVersion for AlwaysV3 {
fn wrap_version<Call: Decode + GetDispatchInfo>(
_: &latest::Location,
xcm: impl Into<VersionedXcm<Call>>,
) -> Result<VersionedXcm<Call>, ()> {
Ok(VersionedXcm::<Call>::V3(xcm.into().try_into()?))
}
}
impl GetVersion for AlwaysV3 {
fn get_version_for(_dest: &latest::Location) -> Option<Version> {
Some(v3::VERSION)
}
}
/// `WrapVersion` implementation which attempts to always convert the XCM to version 4 before
/// wrapping it.
pub struct AlwaysV4;
impl WrapVersion for AlwaysV4 {
fn wrap_version<Call: Decode + GetDispatchInfo>(
_: &latest::Location,
xcm: impl Into<VersionedXcm<Call>>,
) -> Result<VersionedXcm<Call>, ()> {
Ok(VersionedXcm::<Call>::V4(xcm.into().try_into()?))
}
}
impl GetVersion for AlwaysV4 {
fn get_version_for(_dest: &latest::Location) -> Option<Version> {
Some(v4::VERSION)
}
}
/// `WrapVersion` implementation which attempts to always convert the XCM to version 5 before
/// wrapping it.
pub struct AlwaysV5;
impl WrapVersion for AlwaysV5 {
fn wrap_version<Call: Decode + GetDispatchInfo>(
_: &latest::Location,
xcm: impl Into<VersionedXcm<Call>>,
) -> Result<VersionedXcm<Call>, ()> {
Ok(VersionedXcm::<Call>::V5(xcm.into().try_into()?))
}
}
impl GetVersion for AlwaysV5 {
fn get_version_for(_dest: &latest::Location) -> Option<Version> {
Some(v5::VERSION)
}
}
/// `WrapVersion` implementation which attempts to always convert the XCM to the latest version
/// before wrapping it.
pub type AlwaysLatest = AlwaysV5;
/// `WrapVersion` implementation which attempts to always convert the XCM to the most recent Long-
/// Term-Support version before wrapping it.
pub type AlwaysLts = AlwaysV4;
pub mod prelude {
pub use super::{
latest::prelude::*, AlwaysLatest, AlwaysLts, AlwaysV3, AlwaysV4, AlwaysV5, GetVersion,
IdentifyVersion, IntoVersion, Unsupported, Version as XcmVersion, VersionedAsset,
VersionedAssetId, VersionedAssets, VersionedInteriorLocation, VersionedLocation,
VersionedResponse, VersionedXcm, WrapVersion,
};
/// The minimal supported XCM version
pub const MIN_XCM_VERSION: XcmVersion = 3;
}
pub mod opaque {
pub mod v3 {
// Everything from v3
pub use crate::v3::*;
// Then override with the opaque types in v3
pub use crate::v3::opaque::{Instruction, Xcm};
}
pub mod v4 {
// Everything from v4
pub use crate::v4::*;
// Then override with the opaque types in v4
pub use crate::v4::opaque::{Instruction, Xcm};
}
pub mod v5 {
// Everything from v4
pub use crate::v5::*;
// Then override with the opaque types in v5
pub use crate::v5::opaque::{Instruction, Xcm};
}
pub mod latest {
pub use super::v5::*;
}
pub mod lts {
pub use super::v4::*;
}
/// The basic `VersionedXcm` type which just uses the `Vec<u8>` as an encoded call.
pub type VersionedXcm = super::VersionedXcm<()>;
}
#[test]
fn conversion_works() {
use latest::prelude::*;
let assets: Assets = (Here, 1u128).into();
let _: VersionedAssets = assets.into();
}
#[test]
fn size_limits() {
extern crate std;
let mut test_failed = false;
macro_rules! check_sizes {
($(($kind:ty, $expected:expr),)+) => {
$({
let s = core::mem::size_of::<$kind>();
// Since the types often affect the size of other types in which they're included
// it is more convenient to check multiple types at the same time and only fail
// the test at the end. For debugging it's also useful to print out all of the sizes,
// even if they're within the expected range.
if s > $expected {
test_failed = true;
std::eprintln!(
"assertion failed: size of '{}' is {} (which is more than the expected {})",
stringify!($kind),
s,
$expected
);
} else {
std::println!(
"type '{}' is of size {} which is within the expected {}",
stringify!($kind),
s,
$expected
);
}
})+
}
}
check_sizes! {
(crate::latest::Instruction<()>, 128),
(crate::latest::Asset, 80),
(crate::latest::Location, 24),
(crate::latest::AssetId, 40),
(crate::latest::Junctions, 16),
(crate::latest::Junction, 88),
(crate::latest::Response, 40),
(crate::latest::AssetInstance, 48),
(crate::latest::NetworkId, 48),
(crate::latest::BodyId, 32),
(crate::latest::Assets, 24),
(crate::latest::BodyPart, 12),
}
assert!(!test_failed);
}
#[test]
fn check_is_decodable_works() {
use crate::{
latest::{
prelude::{GeneralIndex, ReserveAssetDeposited, SetAppendix},
Assets, Xcm, MAX_ITEMS_IN_ASSETS,
},
MAX_INSTRUCTIONS_TO_DECODE,
};
// closure generates assets of `count`
let assets = |count| {
let mut assets = Assets::new();
for i in 0..count {
assets.push((GeneralIndex(i as u128), 100).into());
}
assets
};
// closer generates `Xcm` with nested instructions of `depth`
let with_instr = |depth| {
let mut xcm = Xcm::<()>(vec![]);
for _ in 0..depth - 1 {
xcm = Xcm::<()>(vec![SetAppendix(xcm)]);
}
xcm
};
// `MAX_INSTRUCTIONS_TO_DECODE` check
assert!(VersionedXcm::<()>::from(Xcm(vec![
ReserveAssetDeposited(assets(1));
(MAX_INSTRUCTIONS_TO_DECODE - 1) as usize
]))
.check_is_decodable()
.is_ok());
assert!(VersionedXcm::<()>::from(Xcm(vec![
ReserveAssetDeposited(assets(1));
MAX_INSTRUCTIONS_TO_DECODE as usize
]))
.check_is_decodable()
.is_ok());
assert!(VersionedXcm::<()>::from(Xcm(vec![
ReserveAssetDeposited(assets(1));
(MAX_INSTRUCTIONS_TO_DECODE + 1) as usize
]))
.check_is_decodable()
.is_err());
// `MAX_XCM_DECODE_DEPTH` check
assert!(VersionedXcm::<()>::from(with_instr(MAX_XCM_DECODE_DEPTH - 1))
.check_is_decodable()
.is_ok());
assert!(VersionedXcm::<()>::from(with_instr(MAX_XCM_DECODE_DEPTH))
.check_is_decodable()
.is_ok());
assert!(VersionedXcm::<()>::from(with_instr(MAX_XCM_DECODE_DEPTH + 1))
.check_is_decodable()
.is_err());
// `MAX_ITEMS_IN_ASSETS` check
assert!(VersionedXcm::<()>::from(Xcm(vec![ReserveAssetDeposited(assets(
MAX_ITEMS_IN_ASSETS
))]))
.check_is_decodable()
.is_ok());
assert!(VersionedXcm::<()>::from(Xcm(vec![ReserveAssetDeposited(assets(
MAX_ITEMS_IN_ASSETS - 1
))]))
.check_is_decodable()
.is_ok());
assert!(VersionedXcm::<()>::from(Xcm(vec![ReserveAssetDeposited(assets(
MAX_ITEMS_IN_ASSETS + 1
))]))
.check_is_decodable()
.is_err());
}
+267
View File
@@ -0,0 +1,267 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
use crate::*;
use alloc::vec;
#[test]
fn encode_decode_versioned_asset_id_v3() {
let asset_id = VersionedAssetId::V3(v3::AssetId::Abstract([1; 32]));
let encoded = asset_id.encode();
assert_eq!(
encoded,
hex_literal::hex!("03010101010101010101010101010101010101010101010101010101010101010101"),
"encode format changed"
);
assert_eq!(encoded[0], 3, "bad version number");
let decoded = VersionedAssetId::decode(&mut &encoded[..]).unwrap();
assert_eq!(asset_id, decoded);
}
#[test]
fn encode_decode_versioned_response_v3() {
let response = VersionedResponse::V3(v3::Response::Null);
let encoded = response.encode();
assert_eq!(encoded, hex_literal::hex!("0300"), "encode format changed");
assert_eq!(encoded[0], 3, "bad version number");
let decoded = VersionedResponse::decode(&mut &encoded[..]).unwrap();
assert_eq!(response, decoded);
}
#[test]
fn encode_decode_versioned_response_v4() {
let response = VersionedResponse::V4(v4::Response::Null);
let encoded = response.encode();
assert_eq!(encoded, hex_literal::hex!("0400"), "encode format changed");
assert_eq!(encoded[0], 4, "bad version number");
let decoded = VersionedResponse::decode(&mut &encoded[..]).unwrap();
assert_eq!(response, decoded);
}
#[test]
fn encode_decode_versioned_response_v5() {
let response = VersionedResponse::V5(v5::Response::Null);
let encoded = response.encode();
assert_eq!(encoded, hex_literal::hex!("0500"), "encode format changed");
assert_eq!(encoded[0], 5, "bad version number");
let decoded = VersionedResponse::decode(&mut &encoded[..]).unwrap();
assert_eq!(response, decoded);
}
#[test]
fn encode_decode_versioned_location_v3() {
let location = VersionedLocation::V3(v3::MultiLocation::new(0, v3::Junctions::Here));
let encoded = location.encode();
assert_eq!(encoded, hex_literal::hex!("030000"), "encode format changed");
assert_eq!(encoded[0], 3, "bad version number");
let decoded = VersionedLocation::decode(&mut &encoded[..]).unwrap();
assert_eq!(location, decoded);
}
#[test]
fn encode_decode_versioned_location_v4() {
let location = VersionedLocation::V4(v4::Location::new(0, v4::Junctions::Here));
let encoded = location.encode();
assert_eq!(encoded, hex_literal::hex!("040000"), "encode format changed");
assert_eq!(encoded[0], 4, "bad version number");
let decoded = VersionedLocation::decode(&mut &encoded[..]).unwrap();
assert_eq!(location, decoded);
}
#[test]
fn encode_decode_versioned_location_v5() {
let location = VersionedLocation::V5(v5::Location::new(0, v5::Junctions::Here));
let encoded = location.encode();
assert_eq!(encoded, hex_literal::hex!("050000"), "encode format changed");
assert_eq!(encoded[0], 5, "bad version number");
let decoded = VersionedLocation::decode(&mut &encoded[..]).unwrap();
assert_eq!(location, decoded);
}
#[test]
fn encode_decode_versioned_interior_location_v3() {
let location = VersionedInteriorLocation::V3(v3::InteriorMultiLocation::Here);
let encoded = location.encode();
assert_eq!(encoded, hex_literal::hex!("0300"), "encode format changed");
assert_eq!(encoded[0], 3, "bad version number");
let decoded = VersionedInteriorLocation::decode(&mut &encoded[..]).unwrap();
assert_eq!(location, decoded);
}
#[test]
fn encode_decode_versioned_interior_location_v4() {
let location = VersionedInteriorLocation::V4(v4::InteriorLocation::Here);
let encoded = location.encode();
assert_eq!(encoded, hex_literal::hex!("0400"), "encode format changed");
assert_eq!(encoded[0], 4, "bad version number");
let decoded = VersionedInteriorLocation::decode(&mut &encoded[..]).unwrap();
assert_eq!(location, decoded);
}
#[test]
fn encode_decode_versioned_interior_location_v5() {
let location = VersionedInteriorLocation::V5(v5::InteriorLocation::Here);
let encoded = location.encode();
assert_eq!(encoded, hex_literal::hex!("0500"), "encode format changed");
assert_eq!(encoded[0], 5, "bad version number");
let decoded = VersionedInteriorLocation::decode(&mut &encoded[..]).unwrap();
assert_eq!(location, decoded);
}
#[test]
fn encode_decode_versioned_asset_v3() {
let asset = VersionedAsset::V3(v3::MultiAsset::from((v3::MultiLocation::default(), 1)));
let encoded = asset.encode();
assert_eq!(encoded, hex_literal::hex!("030000000004"), "encode format changed");
assert_eq!(encoded[0], 3, "bad version number");
let decoded = VersionedAsset::decode(&mut &encoded[..]).unwrap();
assert_eq!(asset, decoded);
}
#[test]
fn encode_decode_versioned_asset_v4() {
let asset = VersionedAsset::V4(v4::Asset::from((v4::Location::default(), 1)));
let encoded = asset.encode();
assert_eq!(encoded, hex_literal::hex!("0400000004"), "encode format changed");
assert_eq!(encoded[0], 4, "bad version number");
let decoded = VersionedAsset::decode(&mut &encoded[..]).unwrap();
assert_eq!(asset, decoded);
}
#[test]
fn encode_decode_versioned_asset_v5() {
let asset = VersionedAsset::V5(v5::Asset::from((v5::Location::default(), 1)));
let encoded = asset.encode();
assert_eq!(encoded, hex_literal::hex!("0500000004"), "encode format changed");
assert_eq!(encoded[0], 5, "bad version number");
let decoded = VersionedAsset::decode(&mut &encoded[..]).unwrap();
assert_eq!(asset, decoded);
}
#[test]
fn encode_decode_versioned_assets_v3() {
let assets = VersionedAssets::V3(v3::MultiAssets::from(vec![
(v3::MultiAsset::from((v3::MultiLocation::default(), 1))),
]));
let encoded = assets.encode();
assert_eq!(encoded, hex_literal::hex!("03040000000004"), "encode format changed");
assert_eq!(encoded[0], 3, "bad version number");
let decoded = VersionedAssets::decode(&mut &encoded[..]).unwrap();
assert_eq!(assets, decoded);
}
#[test]
fn encode_decode_versioned_assets_v4() {
let assets = VersionedAssets::V4(v4::Assets::from(vec![
(v4::Asset::from((v4::Location::default(), 1))),
]));
let encoded = assets.encode();
assert_eq!(encoded, hex_literal::hex!("040400000004"), "encode format changed");
assert_eq!(encoded[0], 4, "bad version number");
let decoded = VersionedAssets::decode(&mut &encoded[..]).unwrap();
assert_eq!(assets, decoded);
}
#[test]
fn encode_decode_versioned_assets_v5() {
let assets = VersionedAssets::V5(v5::Assets::from(vec![
(v5::Asset::from((v5::Location::default(), 1))),
]));
let encoded = assets.encode();
assert_eq!(encoded, hex_literal::hex!("050400000004"), "encode format changed");
assert_eq!(encoded[0], 5, "bad version number");
let decoded = VersionedAssets::decode(&mut &encoded[..]).unwrap();
assert_eq!(assets, decoded);
}
#[test]
fn encode_decode_versioned_xcm_v3() {
let xcm = VersionedXcm::V3(v3::Xcm::<()>::new());
let encoded = xcm.encode();
assert_eq!(encoded, hex_literal::hex!("0300"), "encode format changed");
assert_eq!(encoded[0], 3, "bad version number");
let decoded = VersionedXcm::decode(&mut &encoded[..]).unwrap();
assert_eq!(xcm, decoded);
}
#[test]
fn encode_decode_versioned_xcm_v4() {
let xcm = VersionedXcm::V4(v4::Xcm::<()>::new());
let encoded = xcm.encode();
assert_eq!(encoded, hex_literal::hex!("0400"), "encode format changed");
assert_eq!(encoded[0], 4, "bad version number");
let decoded = VersionedXcm::decode(&mut &encoded[..]).unwrap();
assert_eq!(xcm, decoded);
}
#[test]
fn encode_decode_versioned_xcm_v5() {
let xcm = VersionedXcm::V5(v5::Xcm::<()>::new());
let encoded = xcm.encode();
assert_eq!(encoded, hex_literal::hex!("0500"), "encode format changed");
assert_eq!(encoded[0], 5, "bad version number");
let decoded = VersionedXcm::decode(&mut &encoded[..]).unwrap();
assert_eq!(xcm, decoded);
}
// With the renaming of the crate to `staging-xcm` the naming in the metadata changed as well and
// this broke downstream users. This test ensures that the name in the metadata isn't changed.
#[test]
fn ensure_type_info_is_correct() {
let type_info = VersionedXcm::<()>::type_info();
assert_eq!(type_info.path.segments, vec!["xcm", "VersionedXcm"]);
let type_info = VersionedAssetId::type_info();
assert_eq!(type_info.path.segments, vec!["xcm", "VersionedAssetId"]);
}
+46
View File
@@ -0,0 +1,46 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! XCM utils for internal use.
use crate::MAX_INSTRUCTIONS_TO_DECODE;
use alloc::vec::Vec;
use codec::{decode_vec_with_len, Compact, Decode};
environmental::environmental!(instructions_count: u8);
/// Decode a `vec` of XCM instructions.
///
/// This function keeps track of nested XCM instructions and enforces a total limit of
/// `MAX_INSTRUCTIONS_TO_DECODE`.
pub fn decode_xcm_instructions<I: codec::Input, T: Decode>(
input: &mut I,
) -> Result<Vec<T>, codec::Error> {
instructions_count::using_once(&mut 0, || {
let vec_len: u32 = <Compact<u32>>::decode(input)?.into();
instructions_count::with(|count| {
*count = count.saturating_add(vec_len as u8);
if *count > MAX_INSTRUCTIONS_TO_DECODE {
return Err(codec::Error::from("Max instructions exceeded"));
}
Ok(())
})
.unwrap_or(Err(codec::Error::from("Error calling `instructions_count::with()`")))?;
let decoded_instructions = decode_vec_with_len(input, vec_len as usize)?;
Ok(decoded_instructions)
})
}
+404
View File
@@ -0,0 +1,404 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Support data structures for `MultiLocation`, primarily the `Junction` datatype.
use super::{Junctions, MultiLocation};
use crate::{
v4::{Junction as NewJunction, NetworkId as NewNetworkId},
VersionedLocation,
};
use bounded_collections::{BoundedSlice, BoundedVec, ConstU32};
use codec::{self, Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use serde::{Deserialize, Serialize};
/// A global identifier of a data structure existing within consensus.
///
/// Maintenance note: Networks with global consensus and which are practically bridgeable within the
/// Pezkuwi ecosystem are given preference over explicit naming in this enumeration.
#[derive(
Copy,
Clone,
Eq,
PartialEq,
Ord,
PartialOrd,
Encode,
Decode,
DecodeWithMemTracking,
Debug,
TypeInfo,
MaxEncodedLen,
Serialize,
Deserialize,
)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[scale_info(replace_segment("staging_xcm", "xcm"))]
pub enum NetworkId {
/// Network specified by the first 32 bytes of its genesis block.
ByGenesis([u8; 32]),
/// Network defined by the first 32-bytes of the hash and number of some block it contains.
ByFork { block_number: u64, block_hash: [u8; 32] },
/// The Pezkuwi mainnet Relay-chain.
Pezkuwi,
/// The Kusama canary-net Relay-chain.
Kusama,
/// The Zagros testnet Relay-chain.
Zagros,
/// The Pezkuwichain testnet Relay-chain.
Pezkuwichain,
/// The Wococo testnet Relay-chain.
Wococo,
/// An Ethereum network specified by its chain ID.
Ethereum {
/// The EIP-155 chain ID.
#[codec(compact)]
chain_id: u64,
},
/// The Bitcoin network, including hard-forks supported by Bitcoin Core development team.
BitcoinCore,
/// The Bitcoin network, including hard-forks supported by Bitcoin Cash developers.
BitcoinCash,
/// The Pezkuwi Bulletin chain.
PezkuwiBulletin,
}
impl From<NewNetworkId> for Option<NetworkId> {
fn from(new: NewNetworkId) -> Self {
Some(NetworkId::from(new))
}
}
impl From<NewNetworkId> for NetworkId {
fn from(new: NewNetworkId) -> Self {
use NewNetworkId::*;
match new {
ByGenesis(hash) => Self::ByGenesis(hash),
ByFork { block_number, block_hash } => Self::ByFork { block_number, block_hash },
Pezkuwi => Self::Pezkuwi,
Kusama => Self::Kusama,
Zagros => Self::Zagros,
Pezkuwichain => Self::Pezkuwichain,
Wococo => Self::Wococo,
Ethereum { chain_id } => Self::Ethereum { chain_id },
BitcoinCore => Self::BitcoinCore,
BitcoinCash => Self::BitcoinCash,
PezkuwiBulletin => Self::PezkuwiBulletin,
}
}
}
/// An identifier of a pluralistic body.
#[derive(
Copy,
Clone,
Eq,
PartialEq,
Ord,
PartialOrd,
Encode,
Decode,
DecodeWithMemTracking,
Debug,
TypeInfo,
MaxEncodedLen,
Serialize,
Deserialize,
)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[scale_info(replace_segment("staging_xcm", "xcm"))]
pub enum BodyId {
/// The only body in its context.
Unit,
/// A named body.
Moniker([u8; 4]),
/// An indexed body.
Index(#[codec(compact)] u32),
/// The unambiguous executive body (for Pezkuwi, this would be the Pezkuwi council).
Executive,
/// The unambiguous technical body (for Pezkuwi, this would be the Technical Committee).
Technical,
/// The unambiguous legislative body (for Pezkuwi, this could be considered the opinion of a
/// majority of lock-voters).
Legislative,
/// The unambiguous judicial body (this doesn't exist on Pezkuwi, but if it were to get a
/// "grand oracle", it may be considered as that).
Judicial,
/// The unambiguous defense body (for Pezkuwi, an opinion on the topic given via a public
/// referendum on the `staking_admin` track).
Defense,
/// The unambiguous administration body (for Pezkuwi, an opinion on the topic given via a
/// public referendum on the `general_admin` track).
Administration,
/// The unambiguous treasury body (for Pezkuwi, an opinion on the topic given via a public
/// referendum on the `treasurer` track).
Treasury,
}
/// A part of a pluralistic body.
#[derive(
Copy,
Clone,
Eq,
PartialEq,
Ord,
PartialOrd,
Encode,
Decode,
DecodeWithMemTracking,
Debug,
TypeInfo,
MaxEncodedLen,
Serialize,
Deserialize,
)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[scale_info(replace_segment("staging_xcm", "xcm"))]
pub enum BodyPart {
/// The body's declaration, under whatever means it decides.
Voice,
/// A given number of members of the body.
Members {
#[codec(compact)]
count: u32,
},
/// A given number of members of the body, out of some larger caucus.
Fraction {
#[codec(compact)]
nom: u32,
#[codec(compact)]
denom: u32,
},
/// No less than the given proportion of members of the body.
AtLeastProportion {
#[codec(compact)]
nom: u32,
#[codec(compact)]
denom: u32,
},
/// More than the given proportion of members of the body.
MoreThanProportion {
#[codec(compact)]
nom: u32,
#[codec(compact)]
denom: u32,
},
}
impl BodyPart {
/// Returns `true` if the part represents a strict majority (> 50%) of the body in question.
pub fn is_majority(&self) -> bool {
match self {
BodyPart::Fraction { nom, denom } if *nom * 2 > *denom => true,
BodyPart::AtLeastProportion { nom, denom } if *nom * 2 > *denom => true,
BodyPart::MoreThanProportion { nom, denom } if *nom * 2 >= *denom => true,
_ => false,
}
}
}
/// A single item in a path to describe the relative location of a consensus system.
///
/// Each item assumes a pre-existing location as its context and is defined in terms of it.
#[derive(
Copy,
Clone,
Eq,
PartialEq,
Ord,
PartialOrd,
Encode,
Decode,
DecodeWithMemTracking,
Debug,
TypeInfo,
MaxEncodedLen,
Serialize,
Deserialize,
)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[scale_info(replace_segment("staging_xcm", "xcm"))]
pub enum Junction {
/// An indexed teyrchain belonging to and operated by the context.
///
/// Generally used when the context is a Pezkuwi Relay-chain.
Teyrchain(#[codec(compact)] u32),
/// A 32-byte identifier for an account of a specific network that is respected as a sovereign
/// endpoint within the context.
///
/// Generally used when the context is a Substrate-based chain.
AccountId32 { network: Option<NetworkId>, id: [u8; 32] },
/// An 8-byte index for an account of a specific network that is respected as a sovereign
/// endpoint within the context.
///
/// May be used when the context is a Frame-based chain and includes e.g. an indices pallet.
AccountIndex64 {
network: Option<NetworkId>,
#[codec(compact)]
index: u64,
},
/// A 20-byte identifier for an account of a specific network that is respected as a sovereign
/// endpoint within the context.
///
/// May be used when the context is an Ethereum or Bitcoin chain or smart-contract.
AccountKey20 { network: Option<NetworkId>, key: [u8; 20] },
/// An instanced, indexed pallet that forms a constituent part of the context.
///
/// Generally used when the context is a Frame-based chain.
// TODO XCMv4 inner should be `Compact<u32>`.
PalletInstance(u8),
/// A non-descript index within the context location.
///
/// Usage will vary widely owing to its generality.
///
/// NOTE: Try to avoid using this and instead use a more specific item.
GeneralIndex(#[codec(compact)] u128),
/// A nondescript array datum, 32 bytes, acting as a key within the context
/// location.
///
/// Usage will vary widely owing to its generality.
///
/// NOTE: Try to avoid using this and instead use a more specific item.
// Note this is implemented as an array with a length rather than using `BoundedVec` owing to
// the bound for `Copy`.
GeneralKey { length: u8, data: [u8; 32] },
/// The unambiguous child.
///
/// Not currently used except as a fallback when deriving context.
OnlyChild,
/// A pluralistic body existing within consensus.
///
/// Typical to be used to represent a governance origin of a chain, but could in principle be
/// used to represent things such as multisigs also.
Plurality { id: BodyId, part: BodyPart },
/// A global network capable of externalizing its own consensus. This is not generally
/// meaningful outside of the universal level.
GlobalConsensus(NetworkId),
}
impl From<NetworkId> for Junction {
fn from(n: NetworkId) -> Self {
Self::GlobalConsensus(n)
}
}
impl From<[u8; 32]> for Junction {
fn from(id: [u8; 32]) -> Self {
Self::AccountId32 { network: None, id }
}
}
impl From<BoundedVec<u8, ConstU32<32>>> for Junction {
fn from(key: BoundedVec<u8, ConstU32<32>>) -> Self {
key.as_bounded_slice().into()
}
}
impl<'a> From<BoundedSlice<'a, u8, ConstU32<32>>> for Junction {
fn from(key: BoundedSlice<'a, u8, ConstU32<32>>) -> Self {
let mut data = [0u8; 32];
data[..key.len()].copy_from_slice(&key[..]);
Self::GeneralKey { length: key.len() as u8, data }
}
}
impl<'a> TryFrom<&'a Junction> for BoundedSlice<'a, u8, ConstU32<32>> {
type Error = ();
fn try_from(key: &'a Junction) -> Result<Self, ()> {
match key {
Junction::GeneralKey { length, data } =>
BoundedSlice::try_from(&data[..data.len().min(*length as usize)]).map_err(|_| ()),
_ => Err(()),
}
}
}
impl From<[u8; 20]> for Junction {
fn from(key: [u8; 20]) -> Self {
Self::AccountKey20 { network: None, key }
}
}
impl From<u64> for Junction {
fn from(index: u64) -> Self {
Self::AccountIndex64 { network: None, index }
}
}
impl From<u128> for Junction {
fn from(id: u128) -> Self {
Self::GeneralIndex(id)
}
}
impl TryFrom<NewJunction> for Junction {
type Error = ();
fn try_from(value: NewJunction) -> Result<Self, Self::Error> {
use NewJunction::*;
Ok(match value {
Teyrchain(id) => Self::Teyrchain(id),
AccountId32 { network: maybe_network, id } =>
Self::AccountId32 { network: maybe_network.map(|network| network.into()), id },
AccountIndex64 { network: maybe_network, index } =>
Self::AccountIndex64 { network: maybe_network.map(|network| network.into()), index },
AccountKey20 { network: maybe_network, key } =>
Self::AccountKey20 { network: maybe_network.map(|network| network.into()), key },
PalletInstance(index) => Self::PalletInstance(index),
GeneralIndex(id) => Self::GeneralIndex(id),
GeneralKey { length, data } => Self::GeneralKey { length, data },
OnlyChild => Self::OnlyChild,
Plurality { id, part } => Self::Plurality { id, part },
GlobalConsensus(network) => Self::GlobalConsensus(network.into()),
})
}
}
impl Junction {
/// Convert `self` into a `MultiLocation` containing 0 parents.
///
/// Similar to `Into::into`, except that this method can be used in a const evaluation context.
pub const fn into_location(self) -> MultiLocation {
MultiLocation { parents: 0, interior: Junctions::X1(self) }
}
/// Convert `self` into a `MultiLocation` containing `n` parents.
///
/// Similar to `Self::into_location`, with the added ability to specify the number of parent
/// junctions.
pub const fn into_exterior(self, n: u8) -> MultiLocation {
MultiLocation { parents: n, interior: Junctions::X1(self) }
}
/// Convert `self` into a `VersionedLocation` containing 0 parents.
///
/// Similar to `Into::into`, except that this method can be used in a const evaluation context.
pub const fn into_versioned(self) -> VersionedLocation {
self.into_location().into_versioned()
}
/// Remove the `NetworkId` value.
pub fn remove_network_id(&mut self) {
use Junction::*;
match self {
AccountId32 { ref mut network, .. } |
AccountIndex64 { ref mut network, .. } |
AccountKey20 { ref mut network, .. } => *network = None,
_ => {},
}
}
}
+738
View File
@@ -0,0 +1,738 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! XCM `Junctions`/`InteriorMultiLocation` datatype.
use super::{Junction, MultiLocation, NetworkId};
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
use core::{mem, result};
use scale_info::TypeInfo;
/// Maximum number of `Junction`s that a `Junctions` can contain.
pub(crate) const MAX_JUNCTIONS: usize = 8;
/// Non-parent junctions that can be constructed, up to the length of 8. This specific `Junctions`
/// implementation uses a Rust `enum` in order to make pattern matching easier.
///
/// Parent junctions cannot be constructed with this type. Refer to `MultiLocation` for
/// instructions on constructing parent junctions.
#[derive(
Copy,
Clone,
Eq,
PartialEq,
Ord,
PartialOrd,
Encode,
Decode,
DecodeWithMemTracking,
Debug,
TypeInfo,
MaxEncodedLen,
serde::Serialize,
serde::Deserialize,
)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[scale_info(replace_segment("staging_xcm", "xcm"))]
pub enum Junctions {
/// The interpreting consensus system.
Here,
/// A relative path comprising 1 junction.
X1(Junction),
/// A relative path comprising 2 junctions.
X2(Junction, Junction),
/// A relative path comprising 3 junctions.
X3(Junction, Junction, Junction),
/// A relative path comprising 4 junctions.
X4(Junction, Junction, Junction, Junction),
/// A relative path comprising 5 junctions.
X5(Junction, Junction, Junction, Junction, Junction),
/// A relative path comprising 6 junctions.
X6(Junction, Junction, Junction, Junction, Junction, Junction),
/// A relative path comprising 7 junctions.
X7(Junction, Junction, Junction, Junction, Junction, Junction, Junction),
/// A relative path comprising 8 junctions.
X8(Junction, Junction, Junction, Junction, Junction, Junction, Junction, Junction),
}
macro_rules! impl_junction {
($count:expr, $variant:ident, ($($index:literal),+)) => {
/// Additional helper for building junctions
/// Useful for converting to future XCM versions
impl From<[Junction; $count]> for Junctions {
fn from(junctions: [Junction; $count]) -> Self {
Self::$variant($(junctions[$index]),*)
}
}
};
}
impl_junction!(1, X1, (0));
impl_junction!(2, X2, (0, 1));
impl_junction!(3, X3, (0, 1, 2));
impl_junction!(4, X4, (0, 1, 2, 3));
impl_junction!(5, X5, (0, 1, 2, 3, 4));
impl_junction!(6, X6, (0, 1, 2, 3, 4, 5));
impl_junction!(7, X7, (0, 1, 2, 3, 4, 5, 6));
impl_junction!(8, X8, (0, 1, 2, 3, 4, 5, 6, 7));
pub struct JunctionsIterator(Junctions);
impl Iterator for JunctionsIterator {
type Item = Junction;
fn next(&mut self) -> Option<Junction> {
self.0.take_first()
}
}
impl DoubleEndedIterator for JunctionsIterator {
fn next_back(&mut self) -> Option<Junction> {
self.0.take_last()
}
}
pub struct JunctionsRefIterator<'a> {
junctions: &'a Junctions,
next: usize,
back: usize,
}
impl<'a> Iterator for JunctionsRefIterator<'a> {
type Item = &'a Junction;
fn next(&mut self) -> Option<&'a Junction> {
if self.next.saturating_add(self.back) >= self.junctions.len() {
return None;
}
let result = self.junctions.at(self.next);
self.next += 1;
result
}
}
impl<'a> DoubleEndedIterator for JunctionsRefIterator<'a> {
fn next_back(&mut self) -> Option<&'a Junction> {
let next_back = self.back.saturating_add(1);
// checked_sub here, because if the result is less than 0, we end iteration
let index = self.junctions.len().checked_sub(next_back)?;
if self.next > index {
return None;
}
self.back = next_back;
self.junctions.at(index)
}
}
impl<'a> IntoIterator for &'a Junctions {
type Item = &'a Junction;
type IntoIter = JunctionsRefIterator<'a>;
fn into_iter(self) -> Self::IntoIter {
JunctionsRefIterator { junctions: self, next: 0, back: 0 }
}
}
impl IntoIterator for Junctions {
type Item = Junction;
type IntoIter = JunctionsIterator;
fn into_iter(self) -> Self::IntoIter {
JunctionsIterator(self)
}
}
impl Junctions {
/// Convert `self` into a `MultiLocation` containing 0 parents.
///
/// Similar to `Into::into`, except that this method can be used in a const evaluation context.
pub const fn into_location(self) -> MultiLocation {
MultiLocation { parents: 0, interior: self }
}
/// Convert `self` into a `MultiLocation` containing `n` parents.
///
/// Similar to `Self::into_location`, with the added ability to specify the number of parent
/// junctions.
pub const fn into_exterior(self, n: u8) -> MultiLocation {
MultiLocation { parents: n, interior: self }
}
/// Remove the `NetworkId` value in any `Junction`s.
pub fn remove_network_id(&mut self) {
self.for_each_mut(Junction::remove_network_id);
}
/// Treating `self` as the universal context, return the location of the local consensus system
/// from the point of view of the given `target`.
pub fn invert_target(mut self, target: &MultiLocation) -> Result<MultiLocation, ()> {
let mut junctions = Self::Here;
for _ in 0..target.parent_count() {
junctions = junctions
.pushed_front_with(self.take_last().unwrap_or(Junction::OnlyChild))
.map_err(|_| ())?;
}
let parents = target.interior().len() as u8;
Ok(MultiLocation::new(parents, junctions))
}
/// Execute a function `f` on every junction. We use this since we cannot implement a mutable
/// `Iterator` without unsafe code.
pub fn for_each_mut(&mut self, mut x: impl FnMut(&mut Junction)) {
match self {
Junctions::Here => {},
Junctions::X1(a) => {
x(a);
},
Junctions::X2(a, b) => {
x(a);
x(b);
},
Junctions::X3(a, b, c) => {
x(a);
x(b);
x(c);
},
Junctions::X4(a, b, c, d) => {
x(a);
x(b);
x(c);
x(d);
},
Junctions::X5(a, b, c, d, e) => {
x(a);
x(b);
x(c);
x(d);
x(e);
},
Junctions::X6(a, b, c, d, e, f) => {
x(a);
x(b);
x(c);
x(d);
x(e);
x(f);
},
Junctions::X7(a, b, c, d, e, f, g) => {
x(a);
x(b);
x(c);
x(d);
x(e);
x(f);
x(g);
},
Junctions::X8(a, b, c, d, e, f, g, h) => {
x(a);
x(b);
x(c);
x(d);
x(e);
x(f);
x(g);
x(h);
},
}
}
/// Extract the network ID treating this value as a universal location.
///
/// This will return an `Err` if the first item is not a `GlobalConsensus`, which would indicate
/// that this value is not a universal location.
pub fn global_consensus(&self) -> Result<NetworkId, ()> {
if let Some(Junction::GlobalConsensus(network)) = self.first() {
Ok(*network)
} else {
Err(())
}
}
/// Extract the network ID and the interior consensus location, treating this value as a
/// universal location.
///
/// This will return an `Err` if the first item is not a `GlobalConsensus`, which would indicate
/// that this value is not a universal location.
pub fn split_global(self) -> Result<(NetworkId, Junctions), ()> {
match self.split_first() {
(location, Some(Junction::GlobalConsensus(network))) => Ok((network, location)),
_ => return Err(()),
}
}
/// Treat `self` as a universal location and the context of `relative`, returning the universal
/// location of relative.
///
/// This will return an error if `relative` has as many (or more) parents than there are
/// junctions in `self`, implying that relative refers into a different global consensus.
pub fn within_global(mut self, relative: MultiLocation) -> Result<Self, ()> {
if self.len() <= relative.parents as usize {
return Err(());
}
for _ in 0..relative.parents {
self.take_last();
}
for j in relative.interior {
self.push(j).map_err(|_| ())?;
}
Ok(self)
}
/// Consumes `self` and returns how `viewer` would address it locally.
pub fn relative_to(mut self, viewer: &Junctions) -> MultiLocation {
let mut i = 0;
while match (self.first(), viewer.at(i)) {
(Some(x), Some(y)) => x == y,
_ => false,
} {
self = self.split_first().0;
// NOTE: Cannot overflow as loop can only iterate at most `MAX_JUNCTIONS` times.
i += 1;
}
// AUDIT NOTES:
// - above loop ensures that `i <= viewer.len()`.
// - `viewer.len()` is at most `MAX_JUNCTIONS`, so won't overflow a `u8`.
MultiLocation { parents: (viewer.len() - i) as u8, interior: self }
}
/// Returns first junction, or `None` if the location is empty.
pub fn first(&self) -> Option<&Junction> {
match &self {
Junctions::Here => None,
Junctions::X1(ref a) => Some(a),
Junctions::X2(ref a, ..) => Some(a),
Junctions::X3(ref a, ..) => Some(a),
Junctions::X4(ref a, ..) => Some(a),
Junctions::X5(ref a, ..) => Some(a),
Junctions::X6(ref a, ..) => Some(a),
Junctions::X7(ref a, ..) => Some(a),
Junctions::X8(ref a, ..) => Some(a),
}
}
/// Returns last junction, or `None` if the location is empty.
pub fn last(&self) -> Option<&Junction> {
match &self {
Junctions::Here => None,
Junctions::X1(ref a) => Some(a),
Junctions::X2(.., ref a) => Some(a),
Junctions::X3(.., ref a) => Some(a),
Junctions::X4(.., ref a) => Some(a),
Junctions::X5(.., ref a) => Some(a),
Junctions::X6(.., ref a) => Some(a),
Junctions::X7(.., ref a) => Some(a),
Junctions::X8(.., ref a) => Some(a),
}
}
/// Splits off the first junction, returning the remaining suffix (first item in tuple) and the
/// first element (second item in tuple) or `None` if it was empty.
pub fn split_first(self) -> (Junctions, Option<Junction>) {
match self {
Junctions::Here => (Junctions::Here, None),
Junctions::X1(a) => (Junctions::Here, Some(a)),
Junctions::X2(a, b) => (Junctions::X1(b), Some(a)),
Junctions::X3(a, b, c) => (Junctions::X2(b, c), Some(a)),
Junctions::X4(a, b, c, d) => (Junctions::X3(b, c, d), Some(a)),
Junctions::X5(a, b, c, d, e) => (Junctions::X4(b, c, d, e), Some(a)),
Junctions::X6(a, b, c, d, e, f) => (Junctions::X5(b, c, d, e, f), Some(a)),
Junctions::X7(a, b, c, d, e, f, g) => (Junctions::X6(b, c, d, e, f, g), Some(a)),
Junctions::X8(a, b, c, d, e, f, g, h) => (Junctions::X7(b, c, d, e, f, g, h), Some(a)),
}
}
/// Splits off the last junction, returning the remaining prefix (first item in tuple) and the
/// last element (second item in tuple) or `None` if it was empty.
pub fn split_last(self) -> (Junctions, Option<Junction>) {
match self {
Junctions::Here => (Junctions::Here, None),
Junctions::X1(a) => (Junctions::Here, Some(a)),
Junctions::X2(a, b) => (Junctions::X1(a), Some(b)),
Junctions::X3(a, b, c) => (Junctions::X2(a, b), Some(c)),
Junctions::X4(a, b, c, d) => (Junctions::X3(a, b, c), Some(d)),
Junctions::X5(a, b, c, d, e) => (Junctions::X4(a, b, c, d), Some(e)),
Junctions::X6(a, b, c, d, e, f) => (Junctions::X5(a, b, c, d, e), Some(f)),
Junctions::X7(a, b, c, d, e, f, g) => (Junctions::X6(a, b, c, d, e, f), Some(g)),
Junctions::X8(a, b, c, d, e, f, g, h) => (Junctions::X7(a, b, c, d, e, f, g), Some(h)),
}
}
/// Removes the first element from `self`, returning it (or `None` if it was empty).
pub fn take_first(&mut self) -> Option<Junction> {
let mut d = Junctions::Here;
mem::swap(&mut *self, &mut d);
let (tail, head) = d.split_first();
*self = tail;
head
}
/// Removes the last element from `self`, returning it (or `None` if it was empty).
pub fn take_last(&mut self) -> Option<Junction> {
let mut d = Junctions::Here;
mem::swap(&mut *self, &mut d);
let (head, tail) = d.split_last();
*self = head;
tail
}
/// Mutates `self` to be appended with `new` or returns an `Err` with `new` if would overflow.
pub fn push(&mut self, new: impl Into<Junction>) -> result::Result<(), Junction> {
let new = new.into();
let mut dummy = Junctions::Here;
mem::swap(self, &mut dummy);
match dummy.pushed_with(new) {
Ok(s) => {
*self = s;
Ok(())
},
Err((s, j)) => {
*self = s;
Err(j)
},
}
}
/// Mutates `self` to be prepended with `new` or returns an `Err` with `new` if would overflow.
pub fn push_front(&mut self, new: impl Into<Junction>) -> result::Result<(), Junction> {
let new = new.into();
let mut dummy = Junctions::Here;
mem::swap(self, &mut dummy);
match dummy.pushed_front_with(new) {
Ok(s) => {
*self = s;
Ok(())
},
Err((s, j)) => {
*self = s;
Err(j)
},
}
}
/// Consumes `self` and returns a `Junctions` suffixed with `new`, or an `Err` with the
/// original value of `self` and `new` in case of overflow.
pub fn pushed_with(self, new: impl Into<Junction>) -> result::Result<Self, (Self, Junction)> {
let new = new.into();
Ok(match self {
Junctions::Here => Junctions::X1(new),
Junctions::X1(a) => Junctions::X2(a, new),
Junctions::X2(a, b) => Junctions::X3(a, b, new),
Junctions::X3(a, b, c) => Junctions::X4(a, b, c, new),
Junctions::X4(a, b, c, d) => Junctions::X5(a, b, c, d, new),
Junctions::X5(a, b, c, d, e) => Junctions::X6(a, b, c, d, e, new),
Junctions::X6(a, b, c, d, e, f) => Junctions::X7(a, b, c, d, e, f, new),
Junctions::X7(a, b, c, d, e, f, g) => Junctions::X8(a, b, c, d, e, f, g, new),
s => Err((s, new))?,
})
}
/// Consumes `self` and returns a `Junctions` prefixed with `new`, or an `Err` with the
/// original value of `self` and `new` in case of overflow.
pub fn pushed_front_with(
self,
new: impl Into<Junction>,
) -> result::Result<Self, (Self, Junction)> {
let new = new.into();
Ok(match self {
Junctions::Here => Junctions::X1(new),
Junctions::X1(a) => Junctions::X2(new, a),
Junctions::X2(a, b) => Junctions::X3(new, a, b),
Junctions::X3(a, b, c) => Junctions::X4(new, a, b, c),
Junctions::X4(a, b, c, d) => Junctions::X5(new, a, b, c, d),
Junctions::X5(a, b, c, d, e) => Junctions::X6(new, a, b, c, d, e),
Junctions::X6(a, b, c, d, e, f) => Junctions::X7(new, a, b, c, d, e, f),
Junctions::X7(a, b, c, d, e, f, g) => Junctions::X8(new, a, b, c, d, e, f, g),
s => Err((s, new))?,
})
}
/// Mutate `self` so that it is suffixed with `suffix`.
///
/// Does not modify `self` and returns `Err` with `suffix` in case of overflow.
///
/// # Example
/// ```rust
/// # use staging_xcm::v3::{Junctions::*, Junction::*, MultiLocation};
/// let mut m = X1(Teyrchain(21));
/// assert_eq!(m.append_with(X1(PalletInstance(3))), Ok(()));
/// assert_eq!(m, X2(Teyrchain(21), PalletInstance(3)));
/// ```
pub fn append_with(&mut self, suffix: impl Into<Junctions>) -> Result<(), Junctions> {
let suffix = suffix.into();
if self.len().saturating_add(suffix.len()) > MAX_JUNCTIONS {
return Err(suffix);
}
for j in suffix.into_iter() {
self.push(j).expect("Already checked the sum of the len()s; qed")
}
Ok(())
}
/// Returns the number of junctions in `self`.
pub const fn len(&self) -> usize {
match &self {
Junctions::Here => 0,
Junctions::X1(..) => 1,
Junctions::X2(..) => 2,
Junctions::X3(..) => 3,
Junctions::X4(..) => 4,
Junctions::X5(..) => 5,
Junctions::X6(..) => 6,
Junctions::X7(..) => 7,
Junctions::X8(..) => 8,
}
}
/// Returns the junction at index `i`, or `None` if the location doesn't contain that many
/// elements.
pub fn at(&self, i: usize) -> Option<&Junction> {
Some(match (i, self) {
(0, Junctions::X1(ref a)) => a,
(0, Junctions::X2(ref a, ..)) => a,
(0, Junctions::X3(ref a, ..)) => a,
(0, Junctions::X4(ref a, ..)) => a,
(0, Junctions::X5(ref a, ..)) => a,
(0, Junctions::X6(ref a, ..)) => a,
(0, Junctions::X7(ref a, ..)) => a,
(0, Junctions::X8(ref a, ..)) => a,
(1, Junctions::X2(_, ref a)) => a,
(1, Junctions::X3(_, ref a, ..)) => a,
(1, Junctions::X4(_, ref a, ..)) => a,
(1, Junctions::X5(_, ref a, ..)) => a,
(1, Junctions::X6(_, ref a, ..)) => a,
(1, Junctions::X7(_, ref a, ..)) => a,
(1, Junctions::X8(_, ref a, ..)) => a,
(2, Junctions::X3(_, _, ref a)) => a,
(2, Junctions::X4(_, _, ref a, ..)) => a,
(2, Junctions::X5(_, _, ref a, ..)) => a,
(2, Junctions::X6(_, _, ref a, ..)) => a,
(2, Junctions::X7(_, _, ref a, ..)) => a,
(2, Junctions::X8(_, _, ref a, ..)) => a,
(3, Junctions::X4(_, _, _, ref a)) => a,
(3, Junctions::X5(_, _, _, ref a, ..)) => a,
(3, Junctions::X6(_, _, _, ref a, ..)) => a,
(3, Junctions::X7(_, _, _, ref a, ..)) => a,
(3, Junctions::X8(_, _, _, ref a, ..)) => a,
(4, Junctions::X5(_, _, _, _, ref a)) => a,
(4, Junctions::X6(_, _, _, _, ref a, ..)) => a,
(4, Junctions::X7(_, _, _, _, ref a, ..)) => a,
(4, Junctions::X8(_, _, _, _, ref a, ..)) => a,
(5, Junctions::X6(_, _, _, _, _, ref a)) => a,
(5, Junctions::X7(_, _, _, _, _, ref a, ..)) => a,
(5, Junctions::X8(_, _, _, _, _, ref a, ..)) => a,
(6, Junctions::X7(_, _, _, _, _, _, ref a)) => a,
(6, Junctions::X8(_, _, _, _, _, _, ref a, ..)) => a,
(7, Junctions::X8(_, _, _, _, _, _, _, ref a)) => a,
_ => return None,
})
}
/// Returns a mutable reference to the junction at index `i`, or `None` if the location doesn't
/// contain that many elements.
pub fn at_mut(&mut self, i: usize) -> Option<&mut Junction> {
Some(match (i, self) {
(0, Junctions::X1(ref mut a)) => a,
(0, Junctions::X2(ref mut a, ..)) => a,
(0, Junctions::X3(ref mut a, ..)) => a,
(0, Junctions::X4(ref mut a, ..)) => a,
(0, Junctions::X5(ref mut a, ..)) => a,
(0, Junctions::X6(ref mut a, ..)) => a,
(0, Junctions::X7(ref mut a, ..)) => a,
(0, Junctions::X8(ref mut a, ..)) => a,
(1, Junctions::X2(_, ref mut a)) => a,
(1, Junctions::X3(_, ref mut a, ..)) => a,
(1, Junctions::X4(_, ref mut a, ..)) => a,
(1, Junctions::X5(_, ref mut a, ..)) => a,
(1, Junctions::X6(_, ref mut a, ..)) => a,
(1, Junctions::X7(_, ref mut a, ..)) => a,
(1, Junctions::X8(_, ref mut a, ..)) => a,
(2, Junctions::X3(_, _, ref mut a)) => a,
(2, Junctions::X4(_, _, ref mut a, ..)) => a,
(2, Junctions::X5(_, _, ref mut a, ..)) => a,
(2, Junctions::X6(_, _, ref mut a, ..)) => a,
(2, Junctions::X7(_, _, ref mut a, ..)) => a,
(2, Junctions::X8(_, _, ref mut a, ..)) => a,
(3, Junctions::X4(_, _, _, ref mut a)) => a,
(3, Junctions::X5(_, _, _, ref mut a, ..)) => a,
(3, Junctions::X6(_, _, _, ref mut a, ..)) => a,
(3, Junctions::X7(_, _, _, ref mut a, ..)) => a,
(3, Junctions::X8(_, _, _, ref mut a, ..)) => a,
(4, Junctions::X5(_, _, _, _, ref mut a)) => a,
(4, Junctions::X6(_, _, _, _, ref mut a, ..)) => a,
(4, Junctions::X7(_, _, _, _, ref mut a, ..)) => a,
(4, Junctions::X8(_, _, _, _, ref mut a, ..)) => a,
(5, Junctions::X6(_, _, _, _, _, ref mut a)) => a,
(5, Junctions::X7(_, _, _, _, _, ref mut a, ..)) => a,
(5, Junctions::X8(_, _, _, _, _, ref mut a, ..)) => a,
(6, Junctions::X7(_, _, _, _, _, _, ref mut a)) => a,
(6, Junctions::X8(_, _, _, _, _, _, ref mut a, ..)) => a,
(7, Junctions::X8(_, _, _, _, _, _, _, ref mut a)) => a,
_ => return None,
})
}
/// Returns a reference iterator over the junctions.
pub fn iter(&self) -> JunctionsRefIterator<'_> {
JunctionsRefIterator { junctions: self, next: 0, back: 0 }
}
/// Ensures that self begins with `prefix` and that it has a single `Junction` item following.
/// If so, returns a reference to this `Junction` item.
///
/// # Example
/// ```rust
/// # use staging_xcm::v3::{Junctions::*, Junction::*};
/// let mut m = X3(Teyrchain(2), PalletInstance(3), OnlyChild);
/// assert_eq!(m.match_and_split(&X2(Teyrchain(2), PalletInstance(3))), Some(&OnlyChild));
/// assert_eq!(m.match_and_split(&X1(Teyrchain(2))), None);
/// ```
pub fn match_and_split(&self, prefix: &Junctions) -> Option<&Junction> {
if prefix.len() + 1 != self.len() {
return None;
}
for i in 0..prefix.len() {
if prefix.at(i) != self.at(i) {
return None;
}
}
return self.at(prefix.len());
}
pub fn starts_with(&self, prefix: &Junctions) -> bool {
prefix.len() <= self.len() && prefix.iter().zip(self.iter()).all(|(x, y)| x == y)
}
}
impl TryFrom<MultiLocation> for Junctions {
type Error = MultiLocation;
fn try_from(x: MultiLocation) -> result::Result<Self, MultiLocation> {
if x.parents > 0 {
Err(x)
} else {
Ok(x.interior)
}
}
}
impl<T: Into<Junction>> From<T> for Junctions {
fn from(x: T) -> Self {
Self::X1(x.into())
}
}
impl From<[Junction; 0]> for Junctions {
fn from(_: [Junction; 0]) -> Self {
Self::Here
}
}
impl From<()> for Junctions {
fn from(_: ()) -> Self {
Self::Here
}
}
xcm_procedural::impl_conversion_functions_for_junctions_v3!();
#[cfg(test)]
mod tests {
use super::{super::prelude::*, *};
#[test]
fn inverting_works() {
let context: InteriorMultiLocation = (Teyrchain(1000), PalletInstance(42)).into();
let target = (Parent, PalletInstance(69)).into();
let expected = (Parent, PalletInstance(42)).into();
let inverted = context.invert_target(&target).unwrap();
assert_eq!(inverted, expected);
let context: InteriorMultiLocation =
(Teyrchain(1000), PalletInstance(42), GeneralIndex(1)).into();
let target = (Parent, Parent, PalletInstance(69), GeneralIndex(2)).into();
let expected = (Parent, Parent, PalletInstance(42), GeneralIndex(1)).into();
let inverted = context.invert_target(&target).unwrap();
assert_eq!(inverted, expected);
}
#[test]
fn relative_to_works() {
use Junctions::*;
use NetworkId::*;
assert_eq!(X1(Pezkuwi.into()).relative_to(&X1(Kusama.into())), (Parent, Pezkuwi).into());
let base = X3(Kusama.into(), Teyrchain(1), PalletInstance(1));
// Ancestors.
assert_eq!(Here.relative_to(&base), (Parent, Parent, Parent).into());
assert_eq!(X1(Kusama.into()).relative_to(&base), (Parent, Parent).into());
assert_eq!(X2(Kusama.into(), Teyrchain(1)).relative_to(&base), (Parent,).into());
assert_eq!(
X3(Kusama.into(), Teyrchain(1), PalletInstance(1)).relative_to(&base),
Here.into()
);
// Ancestors with one child.
assert_eq!(X1(Pezkuwi.into()).relative_to(&base), (Parent, Parent, Parent, Pezkuwi).into());
assert_eq!(
X2(Kusama.into(), Teyrchain(2)).relative_to(&base),
(Parent, Parent, Teyrchain(2)).into()
);
assert_eq!(
X3(Kusama.into(), Teyrchain(1), PalletInstance(2)).relative_to(&base),
(Parent, PalletInstance(2)).into()
);
assert_eq!(
X4(Kusama.into(), Teyrchain(1), PalletInstance(1), [1u8; 32].into()).relative_to(&base),
([1u8; 32],).into()
);
// Ancestors with grandchildren.
assert_eq!(
X2(Pezkuwi.into(), Teyrchain(1)).relative_to(&base),
(Parent, Parent, Parent, Pezkuwi, Teyrchain(1)).into()
);
assert_eq!(
X3(Kusama.into(), Teyrchain(2), PalletInstance(1)).relative_to(&base),
(Parent, Parent, Teyrchain(2), PalletInstance(1)).into()
);
assert_eq!(
X4(Kusama.into(), Teyrchain(1), PalletInstance(2), [1u8; 32].into()).relative_to(&base),
(Parent, PalletInstance(2), [1u8; 32]).into()
);
assert_eq!(
X5(Kusama.into(), Teyrchain(1), PalletInstance(1), [1u8; 32].into(), 1u128.into())
.relative_to(&base),
([1u8; 32], 1u128).into()
);
}
#[test]
fn global_consensus_works() {
use Junctions::*;
use NetworkId::*;
assert_eq!(X1(Pezkuwi.into()).global_consensus(), Ok(Pezkuwi));
assert_eq!(X2(Kusama.into(), 1u64.into()).global_consensus(), Ok(Kusama));
assert_eq!(Here.global_consensus(), Err(()));
assert_eq!(X1(1u64.into()).global_consensus(), Err(()));
assert_eq!(X2(1u64.into(), Kusama.into()).global_consensus(), Err(()));
}
#[test]
fn test_conversion() {
use super::{Junction::*, Junctions::*, NetworkId::*};
let x: Junctions = GlobalConsensus(Pezkuwi).into();
assert_eq!(x, X1(GlobalConsensus(Pezkuwi)));
let x: Junctions = Pezkuwi.into();
assert_eq!(x, X1(GlobalConsensus(Pezkuwi)));
let x: Junctions = (Pezkuwi, Kusama).into();
assert_eq!(x, X2(GlobalConsensus(Pezkuwi), GlobalConsensus(Kusama)));
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+754
View File
@@ -0,0 +1,754 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! XCM `MultiLocation` datatype.
use super::{Junction, Junctions};
use crate::{v4::Location as NewMultiLocation, VersionedLocation};
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
use core::result;
use scale_info::TypeInfo;
/// A relative path between state-bearing consensus systems.
///
/// A location in a consensus system is defined as an *isolatable state machine* held within global
/// consensus. The location in question need not have a sophisticated consensus algorithm of its
/// own; a single account within Ethereum, for example, could be considered a location.
///
/// A very-much non-exhaustive list of types of location include:
/// - A (normal, layer-1) block chain, e.g. the Bitcoin mainnet or a teyrchain.
/// - A layer-0 super-chain, e.g. the Pezkuwi Relay chain.
/// - A layer-2 smart contract, e.g. an ERC-20 on Ethereum.
/// - A logical functional component of a chain, e.g. a single instance of a pallet on a Frame-based
/// Substrate chain.
/// - An account.
///
/// A `MultiLocation` is a *relative identifier*, meaning that it can only be used to define the
/// relative path between two locations, and cannot generally be used to refer to a location
/// universally. It is comprised of an integer number of parents specifying the number of times to
/// "escape" upwards into the containing consensus system and then a number of *junctions*, each
/// diving down and specifying some interior portion of state (which may be considered a
/// "sub-consensus" system).
///
/// This specific `MultiLocation` implementation uses a `Junctions` datatype which is a Rust `enum`
/// in order to make pattern matching easier. There are occasions where it is important to ensure
/// that a value is strictly an interior location, in those cases, `Junctions` may be used.
///
/// The `MultiLocation` value of `Null` simply refers to the interpreting consensus system.
#[derive(
Copy,
Clone,
Decode,
Encode,
DecodeWithMemTracking,
Eq,
PartialEq,
Ord,
PartialOrd,
Debug,
TypeInfo,
MaxEncodedLen,
serde::Serialize,
serde::Deserialize,
)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub struct MultiLocation {
/// The number of parent junctions at the beginning of this `MultiLocation`.
pub parents: u8,
/// The interior (i.e. non-parent) junctions that this `MultiLocation` contains.
pub interior: Junctions,
}
/// Type alias for a better transition to V4.
pub type Location = MultiLocation;
impl Default for MultiLocation {
fn default() -> Self {
Self { parents: 0, interior: Junctions::Here }
}
}
/// A relative location which is constrained to be an interior location of the context.
///
/// See also `MultiLocation`.
pub type InteriorMultiLocation = Junctions;
impl MultiLocation {
/// Creates a new `MultiLocation` with the given number of parents and interior junctions.
pub fn new(parents: u8, interior: impl Into<Junctions>) -> MultiLocation {
MultiLocation { parents, interior: interior.into() }
}
/// Consume `self` and return the equivalent `VersionedLocation` value.
pub const fn into_versioned(self) -> VersionedLocation {
VersionedLocation::V3(self)
}
/// Creates a new `MultiLocation` with 0 parents and a `Here` interior.
///
/// The resulting `MultiLocation` can be interpreted as the "current consensus system".
pub const fn here() -> MultiLocation {
MultiLocation { parents: 0, interior: Junctions::Here }
}
/// Creates a new `MultiLocation` which evaluates to the parent context.
pub const fn parent() -> MultiLocation {
MultiLocation { parents: 1, interior: Junctions::Here }
}
/// Creates a new `MultiLocation` which evaluates to the grand parent context.
pub const fn grandparent() -> MultiLocation {
MultiLocation { parents: 2, interior: Junctions::Here }
}
/// Creates a new `MultiLocation` with `parents` and an empty (`Here`) interior.
pub const fn ancestor(parents: u8) -> MultiLocation {
MultiLocation { parents, interior: Junctions::Here }
}
/// Whether the `MultiLocation` has no parents and has a `Here` interior.
pub const fn is_here(&self) -> bool {
self.parents == 0 && self.interior.len() == 0
}
/// Remove the `NetworkId` value in any interior `Junction`s.
pub fn remove_network_id(&mut self) {
self.interior.remove_network_id();
}
/// Return a reference to the interior field.
pub fn interior(&self) -> &Junctions {
&self.interior
}
/// Return a mutable reference to the interior field.
pub fn interior_mut(&mut self) -> &mut Junctions {
&mut self.interior
}
/// Returns the number of `Parent` junctions at the beginning of `self`.
pub const fn parent_count(&self) -> u8 {
self.parents
}
/// Returns boolean indicating whether `self` contains only the specified amount of
/// parents and no interior junctions.
pub const fn contains_parents_only(&self, count: u8) -> bool {
matches!(self.interior, Junctions::Here) && self.parents == count
}
/// Returns the number of parents and junctions in `self`.
pub const fn len(&self) -> usize {
self.parent_count() as usize + self.interior.len()
}
/// Returns the first interior junction, or `None` if the location is empty or contains only
/// parents.
pub fn first_interior(&self) -> Option<&Junction> {
self.interior.first()
}
/// Returns last junction, or `None` if the location is empty or contains only parents.
pub fn last(&self) -> Option<&Junction> {
self.interior.last()
}
/// Splits off the first interior junction, returning the remaining suffix (first item in tuple)
/// and the first element (second item in tuple) or `None` if it was empty.
pub fn split_first_interior(self) -> (MultiLocation, Option<Junction>) {
let MultiLocation { parents, interior: junctions } = self;
let (suffix, first) = junctions.split_first();
let multilocation = MultiLocation { parents, interior: suffix };
(multilocation, first)
}
/// Splits off the last interior junction, returning the remaining prefix (first item in tuple)
/// and the last element (second item in tuple) or `None` if it was empty or if `self` only
/// contains parents.
pub fn split_last_interior(self) -> (MultiLocation, Option<Junction>) {
let MultiLocation { parents, interior: junctions } = self;
let (prefix, last) = junctions.split_last();
let multilocation = MultiLocation { parents, interior: prefix };
(multilocation, last)
}
/// Mutates `self`, suffixing its interior junctions with `new`. Returns `Err` with `new` in
/// case of overflow.
pub fn push_interior(&mut self, new: impl Into<Junction>) -> result::Result<(), Junction> {
self.interior.push(new)
}
/// Mutates `self`, prefixing its interior junctions with `new`. Returns `Err` with `new` in
/// case of overflow.
pub fn push_front_interior(
&mut self,
new: impl Into<Junction>,
) -> result::Result<(), Junction> {
self.interior.push_front(new)
}
/// Consumes `self` and returns a `MultiLocation` suffixed with `new`, or an `Err` with
/// the original value of `self` in case of overflow.
pub fn pushed_with_interior(
self,
new: impl Into<Junction>,
) -> result::Result<Self, (Self, Junction)> {
match self.interior.pushed_with(new) {
Ok(i) => Ok(MultiLocation { interior: i, parents: self.parents }),
Err((i, j)) => Err((MultiLocation { interior: i, parents: self.parents }, j)),
}
}
/// Consumes `self` and returns a `MultiLocation` prefixed with `new`, or an `Err` with the
/// original value of `self` in case of overflow.
pub fn pushed_front_with_interior(
self,
new: impl Into<Junction>,
) -> result::Result<Self, (Self, Junction)> {
match self.interior.pushed_front_with(new) {
Ok(i) => Ok(MultiLocation { interior: i, parents: self.parents }),
Err((i, j)) => Err((MultiLocation { interior: i, parents: self.parents }, j)),
}
}
/// Returns the junction at index `i`, or `None` if the location is a parent or if the location
/// does not contain that many elements.
pub fn at(&self, i: usize) -> Option<&Junction> {
let num_parents = self.parents as usize;
if i < num_parents {
return None;
}
self.interior.at(i - num_parents)
}
/// Returns a mutable reference to the junction at index `i`, or `None` if the location is a
/// parent or if it doesn't contain that many elements.
pub fn at_mut(&mut self, i: usize) -> Option<&mut Junction> {
let num_parents = self.parents as usize;
if i < num_parents {
return None;
}
self.interior.at_mut(i - num_parents)
}
/// Decrements the parent count by 1.
pub fn dec_parent(&mut self) {
self.parents = self.parents.saturating_sub(1);
}
/// Removes the first interior junction from `self`, returning it
/// (or `None` if it was empty or if `self` contains only parents).
pub fn take_first_interior(&mut self) -> Option<Junction> {
self.interior.take_first()
}
/// Removes the last element from `interior`, returning it (or `None` if it was empty or if
/// `self` only contains parents).
pub fn take_last(&mut self) -> Option<Junction> {
self.interior.take_last()
}
/// Ensures that `self` has the same number of parents as `prefix`, its junctions begins with
/// the junctions of `prefix` and that it has a single `Junction` item following.
/// If so, returns a reference to this `Junction` item.
///
/// # Example
/// ```rust
/// # use staging_xcm::v3::{Junctions::*, Junction::*, MultiLocation};
/// let mut m = MultiLocation::new(1, X2(PalletInstance(3), OnlyChild));
/// assert_eq!(
/// m.match_and_split(&MultiLocation::new(1, X1(PalletInstance(3)))),
/// Some(&OnlyChild),
/// );
/// assert_eq!(m.match_and_split(&MultiLocation::new(1, Here)), None);
/// ```
pub fn match_and_split(&self, prefix: &MultiLocation) -> Option<&Junction> {
if self.parents != prefix.parents {
return None;
}
self.interior.match_and_split(&prefix.interior)
}
pub fn starts_with(&self, prefix: &MultiLocation) -> bool {
self.parents == prefix.parents && self.interior.starts_with(&prefix.interior)
}
/// Mutate `self` so that it is suffixed with `suffix`.
///
/// Does not modify `self` and returns `Err` with `suffix` in case of overflow.
///
/// # Example
/// ```rust
/// # use staging_xcm::v3::{Junctions::*, Junction::*, MultiLocation, Parent};
/// let mut m: MultiLocation = (Parent, Teyrchain(21), 69u64).into();
/// assert_eq!(m.append_with((Parent, PalletInstance(3))), Ok(()));
/// assert_eq!(m, MultiLocation::new(1, X2(Teyrchain(21), PalletInstance(3))));
/// ```
pub fn append_with(&mut self, suffix: impl Into<Self>) -> Result<(), Self> {
let prefix = core::mem::replace(self, suffix.into());
match self.prepend_with(prefix) {
Ok(()) => Ok(()),
Err(prefix) => Err(core::mem::replace(self, prefix)),
}
}
/// Consume `self` and return its value suffixed with `suffix`.
///
/// Returns `Err` with the original value of `self` and `suffix` in case of overflow.
///
/// # Example
/// ```rust
/// # use staging_xcm::v3::{Junctions::*, Junction::*, MultiLocation, Parent};
/// let mut m: MultiLocation = (Parent, Teyrchain(21), 69u64).into();
/// let r = m.appended_with((Parent, PalletInstance(3))).unwrap();
/// assert_eq!(r, MultiLocation::new(1, X2(Teyrchain(21), PalletInstance(3))));
/// ```
pub fn appended_with(mut self, suffix: impl Into<Self>) -> Result<Self, (Self, Self)> {
match self.append_with(suffix) {
Ok(()) => Ok(self),
Err(suffix) => Err((self, suffix)),
}
}
/// Mutate `self` so that it is prefixed with `prefix`.
///
/// Does not modify `self` and returns `Err` with `prefix` in case of overflow.
///
/// # Example
/// ```rust
/// # use staging_xcm::v3::{Junctions::*, Junction::*, MultiLocation, Parent};
/// let mut m: MultiLocation = (Parent, Parent, PalletInstance(3)).into();
/// assert_eq!(m.prepend_with((Parent, Teyrchain(21), OnlyChild)), Ok(()));
/// assert_eq!(m, MultiLocation::new(1, X1(PalletInstance(3))));
/// ```
pub fn prepend_with(&mut self, prefix: impl Into<Self>) -> Result<(), Self> {
// prefix self (suffix)
// P .. P I .. I p .. p i .. i
let mut prefix = prefix.into();
let prepend_interior = prefix.interior.len().saturating_sub(self.parents as usize);
let final_interior = self.interior.len().saturating_add(prepend_interior);
if final_interior > super::junctions::MAX_JUNCTIONS {
return Err(prefix);
}
let suffix_parents = (self.parents as usize).saturating_sub(prefix.interior.len());
let final_parents = (prefix.parents as usize).saturating_add(suffix_parents);
if final_parents > 255 {
return Err(prefix);
}
// cancel out the final item on the prefix interior for one of the suffix's parents.
while self.parents > 0 && prefix.take_last().is_some() {
self.dec_parent();
}
// now we have either removed all suffix's parents or prefix interior.
// this means we can combine the prefix's and suffix's remaining parents/interior since
// we know that with at least one empty, the overall order will be respected:
// prefix self (suffix)
// P .. P (I) p .. p i .. i => P + p .. (no I) i
// -- or --
// P .. P I .. I (p) i .. i => P (no p) .. I + i
self.parents = self.parents.saturating_add(prefix.parents);
for j in prefix.interior.into_iter().rev() {
self.push_front_interior(j)
.expect("final_interior no greater than MAX_JUNCTIONS; qed");
}
Ok(())
}
/// Consume `self` and return its value prefixed with `prefix`.
///
/// Returns `Err` with the original value of `self` and `prefix` in case of overflow.
///
/// # Example
/// ```rust
/// # use staging_xcm::v3::{Junctions::*, Junction::*, MultiLocation, Parent};
/// let m: MultiLocation = (Parent, Parent, PalletInstance(3)).into();
/// let r = m.prepended_with((Parent, Teyrchain(21), OnlyChild)).unwrap();
/// assert_eq!(r, MultiLocation::new(1, X1(PalletInstance(3))));
/// ```
pub fn prepended_with(mut self, prefix: impl Into<Self>) -> Result<Self, (Self, Self)> {
match self.prepend_with(prefix) {
Ok(()) => Ok(self),
Err(prefix) => Err((self, prefix)),
}
}
/// Mutate `self` so that it represents the same location from the point of view of `target`.
/// The context of `self` is provided as `context`.
///
/// Does not modify `self` in case of overflow.
pub fn reanchor(
&mut self,
target: &MultiLocation,
context: InteriorMultiLocation,
) -> Result<(), ()> {
// TODO: https://github.com/paritytech/polkadot/issues/4489 Optimize this.
// 1. Use our `context` to figure out how the `target` would address us.
let inverted_target = context.invert_target(target)?;
// 2. Prepend `inverted_target` to `self` to get self's location from the perspective of
// `target`.
self.prepend_with(inverted_target).map_err(|_| ())?;
// 3. Given that we know some of `target` context, ensure that any parents in `self` are
// strictly needed.
self.simplify(target.interior());
Ok(())
}
/// Consume `self` and return a new value representing the same location from the point of view
/// of `target`. The context of `self` is provided as `context`.
///
/// Returns the original `self` in case of overflow.
pub fn reanchored(
mut self,
target: &MultiLocation,
context: InteriorMultiLocation,
) -> Result<Self, Self> {
match self.reanchor(target, context) {
Ok(()) => Ok(self),
Err(()) => Err(self),
}
}
/// Remove any unneeded parents/junctions in `self` based on the given context it will be
/// interpreted in.
pub fn simplify(&mut self, context: &Junctions) {
if context.len() < self.parents as usize {
// Not enough context
return;
}
while self.parents > 0 {
let maybe = context.at(context.len() - (self.parents as usize));
match (self.interior.first(), maybe) {
(Some(i), Some(j)) if i == j => {
self.interior.take_first();
self.parents -= 1;
},
_ => break,
}
}
}
/// Return the MultiLocation subsection identifying the chain that `self` points to.
pub fn chain_location(&self) -> MultiLocation {
let mut clone = *self;
// start popping junctions until we reach chain identifier
while let Some(j) = clone.last() {
if matches!(j, Junction::Teyrchain(_) | Junction::GlobalConsensus(_)) {
// return chain subsection
return clone;
} else {
(clone, _) = clone.split_last_interior();
}
}
MultiLocation::new(clone.parents, Junctions::Here)
}
}
impl TryFrom<NewMultiLocation> for Option<MultiLocation> {
type Error = ();
fn try_from(new: NewMultiLocation) -> result::Result<Self, Self::Error> {
Ok(Some(MultiLocation::try_from(new)?))
}
}
impl TryFrom<NewMultiLocation> for MultiLocation {
type Error = ();
fn try_from(new: NewMultiLocation) -> result::Result<Self, ()> {
Ok(MultiLocation {
parents: new.parent_count(),
interior: new.interior().clone().try_into()?,
})
}
}
/// A unit struct which can be converted into a `MultiLocation` of `parents` value 1.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct Parent;
impl From<Parent> for MultiLocation {
fn from(_: Parent) -> Self {
MultiLocation { parents: 1, interior: Junctions::Here }
}
}
/// A tuple struct which can be converted into a `MultiLocation` of `parents` value 1 with the inner
/// interior.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct ParentThen(pub Junctions);
impl From<ParentThen> for MultiLocation {
fn from(ParentThen(interior): ParentThen) -> Self {
MultiLocation { parents: 1, interior }
}
}
/// A unit struct which can be converted into a `MultiLocation` of the inner `parents` value.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct Ancestor(pub u8);
impl From<Ancestor> for MultiLocation {
fn from(Ancestor(parents): Ancestor) -> Self {
MultiLocation { parents, interior: Junctions::Here }
}
}
/// A unit struct which can be converted into a `MultiLocation` of the inner `parents` value and the
/// inner interior.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct AncestorThen<Interior>(pub u8, pub Interior);
impl<Interior: Into<Junctions>> From<AncestorThen<Interior>> for MultiLocation {
fn from(AncestorThen(parents, interior): AncestorThen<Interior>) -> Self {
MultiLocation { parents, interior: interior.into() }
}
}
xcm_procedural::impl_conversion_functions_for_multilocation_v3!();
#[cfg(test)]
mod tests {
use crate::v3::prelude::*;
use codec::{Decode, Encode};
#[test]
fn conversion_works() {
let x: MultiLocation = Parent.into();
assert_eq!(x, MultiLocation { parents: 1, interior: Here });
// let x: MultiLocation = (Parent,).into();
// assert_eq!(x, MultiLocation { parents: 1, interior: Here });
// let x: MultiLocation = (Parent, Parent).into();
// assert_eq!(x, MultiLocation { parents: 2, interior: Here });
let x: MultiLocation = (Parent, Parent, OnlyChild).into();
assert_eq!(x, MultiLocation { parents: 2, interior: OnlyChild.into() });
let x: MultiLocation = OnlyChild.into();
assert_eq!(x, MultiLocation { parents: 0, interior: OnlyChild.into() });
let x: MultiLocation = (OnlyChild,).into();
assert_eq!(x, MultiLocation { parents: 0, interior: OnlyChild.into() });
}
#[test]
fn simplify_basic_works() {
let mut location: MultiLocation =
(Parent, Parent, Teyrchain(1000), PalletInstance(42), GeneralIndex(69)).into();
let context = X2(Teyrchain(1000), PalletInstance(42));
let expected = GeneralIndex(69).into();
location.simplify(&context);
assert_eq!(location, expected);
let mut location: MultiLocation = (Parent, PalletInstance(42), GeneralIndex(69)).into();
let context = X1(PalletInstance(42));
let expected = GeneralIndex(69).into();
location.simplify(&context);
assert_eq!(location, expected);
let mut location: MultiLocation = (Parent, PalletInstance(42), GeneralIndex(69)).into();
let context = X2(Teyrchain(1000), PalletInstance(42));
let expected = GeneralIndex(69).into();
location.simplify(&context);
assert_eq!(location, expected);
let mut location: MultiLocation =
(Parent, Parent, Teyrchain(1000), PalletInstance(42), GeneralIndex(69)).into();
let context = X3(OnlyChild, Teyrchain(1000), PalletInstance(42));
let expected = GeneralIndex(69).into();
location.simplify(&context);
assert_eq!(location, expected);
}
#[test]
fn simplify_incompatible_location_fails() {
let mut location: MultiLocation =
(Parent, Parent, Teyrchain(1000), PalletInstance(42), GeneralIndex(69)).into();
let context = X3(Teyrchain(1000), PalletInstance(42), GeneralIndex(42));
let expected =
(Parent, Parent, Teyrchain(1000), PalletInstance(42), GeneralIndex(69)).into();
location.simplify(&context);
assert_eq!(location, expected);
let mut location: MultiLocation =
(Parent, Parent, Teyrchain(1000), PalletInstance(42), GeneralIndex(69)).into();
let context = X1(Teyrchain(1000));
let expected =
(Parent, Parent, Teyrchain(1000), PalletInstance(42), GeneralIndex(69)).into();
location.simplify(&context);
assert_eq!(location, expected);
}
#[test]
fn reanchor_works() {
let mut id: MultiLocation = (Parent, Teyrchain(1000), GeneralIndex(42)).into();
let context = Teyrchain(2000).into();
let target = (Parent, Teyrchain(1000)).into();
let expected = GeneralIndex(42).into();
id.reanchor(&target, context).unwrap();
assert_eq!(id, expected);
}
#[test]
fn encode_and_decode_works() {
let m = MultiLocation {
parents: 1,
interior: X2(Teyrchain(42), AccountIndex64 { network: None, index: 23 }),
};
let encoded = m.encode();
assert_eq!(encoded, [1, 2, 0, 168, 2, 0, 92].to_vec());
let decoded = MultiLocation::decode(&mut &encoded[..]);
assert_eq!(decoded, Ok(m));
}
#[test]
fn match_and_split_works() {
let m = MultiLocation {
parents: 1,
interior: X2(Teyrchain(42), AccountIndex64 { network: None, index: 23 }),
};
assert_eq!(m.match_and_split(&MultiLocation { parents: 1, interior: Here }), None);
assert_eq!(
m.match_and_split(&MultiLocation { parents: 1, interior: X1(Teyrchain(42)) }),
Some(&AccountIndex64 { network: None, index: 23 })
);
assert_eq!(m.match_and_split(&m), None);
}
#[test]
fn append_with_works() {
let acc = AccountIndex64 { network: None, index: 23 };
let mut m = MultiLocation { parents: 1, interior: X1(Teyrchain(42)) };
assert_eq!(m.append_with(X2(PalletInstance(3), acc)), Ok(()));
assert_eq!(
m,
MultiLocation { parents: 1, interior: X3(Teyrchain(42), PalletInstance(3), acc) }
);
// cannot append to create overly long multilocation
let acc = AccountIndex64 { network: None, index: 23 };
let m = MultiLocation {
parents: 254,
interior: X5(Teyrchain(42), OnlyChild, OnlyChild, OnlyChild, OnlyChild),
};
let suffix: MultiLocation = (PalletInstance(3), acc, OnlyChild, OnlyChild).into();
assert_eq!(m.clone().append_with(suffix), Err(suffix));
}
#[test]
fn prepend_with_works() {
let mut m = MultiLocation {
parents: 1,
interior: X2(Teyrchain(42), AccountIndex64 { network: None, index: 23 }),
};
assert_eq!(m.prepend_with(MultiLocation { parents: 1, interior: X1(OnlyChild) }), Ok(()));
assert_eq!(
m,
MultiLocation {
parents: 1,
interior: X2(Teyrchain(42), AccountIndex64 { network: None, index: 23 })
}
);
// cannot prepend to create overly long multilocation
let mut m = MultiLocation { parents: 254, interior: X1(Teyrchain(42)) };
let prefix = MultiLocation { parents: 2, interior: Here };
assert_eq!(m.prepend_with(prefix), Err(prefix));
let prefix = MultiLocation { parents: 1, interior: Here };
assert_eq!(m.prepend_with(prefix), Ok(()));
assert_eq!(m, MultiLocation { parents: 255, interior: X1(Teyrchain(42)) });
}
#[test]
fn double_ended_ref_iteration_works() {
let m = X3(Teyrchain(1000), Teyrchain(3), PalletInstance(5));
let mut iter = m.iter();
let first = iter.next().unwrap();
assert_eq!(first, &Teyrchain(1000));
let third = iter.next_back().unwrap();
assert_eq!(third, &PalletInstance(5));
let second = iter.next_back().unwrap();
assert_eq!(iter.next(), None);
assert_eq!(iter.next_back(), None);
assert_eq!(second, &Teyrchain(3));
let res = Here
.pushed_with(*first)
.unwrap()
.pushed_with(*second)
.unwrap()
.pushed_with(*third)
.unwrap();
assert_eq!(m, res);
// make sure there's no funny business with the 0 indexing
let m = Here;
let mut iter = m.iter();
assert_eq!(iter.next(), None);
assert_eq!(iter.next_back(), None);
}
#[test]
fn chain_location_works() {
// Relay-chain or teyrchain context pointing to local resource,
let relay_to_local = MultiLocation::new(0, (PalletInstance(42), GeneralIndex(42)));
assert_eq!(relay_to_local.chain_location(), MultiLocation::here());
// Relay-chain context pointing to child teyrchain,
let relay_to_child =
MultiLocation::new(0, (Teyrchain(42), PalletInstance(42), GeneralIndex(42)));
let expected = MultiLocation::new(0, Teyrchain(42));
assert_eq!(relay_to_child.chain_location(), expected);
// Relay-chain context pointing to different consensus relay,
let relay_to_remote_relay =
MultiLocation::new(1, (GlobalConsensus(Kusama), PalletInstance(42), GeneralIndex(42)));
let expected = MultiLocation::new(1, GlobalConsensus(Kusama));
assert_eq!(relay_to_remote_relay.chain_location(), expected);
// Relay-chain context pointing to different consensus teyrchain,
let relay_to_remote_para = MultiLocation::new(
1,
(GlobalConsensus(Kusama), Teyrchain(42), PalletInstance(42), GeneralIndex(42)),
);
let expected = MultiLocation::new(1, (GlobalConsensus(Kusama), Teyrchain(42)));
assert_eq!(relay_to_remote_para.chain_location(), expected);
// Teyrchain context pointing to relay chain,
let para_to_relay = MultiLocation::new(1, (PalletInstance(42), GeneralIndex(42)));
assert_eq!(para_to_relay.chain_location(), MultiLocation::parent());
// Teyrchain context pointing to sibling teyrchain,
let para_to_sibling =
MultiLocation::new(1, (Teyrchain(42), PalletInstance(42), GeneralIndex(42)));
let expected = MultiLocation::new(1, Teyrchain(42));
assert_eq!(para_to_sibling.chain_location(), expected);
// Teyrchain context pointing to different consensus relay,
let para_to_remote_relay =
MultiLocation::new(2, (GlobalConsensus(Kusama), PalletInstance(42), GeneralIndex(42)));
let expected = MultiLocation::new(2, GlobalConsensus(Kusama));
assert_eq!(para_to_remote_relay.chain_location(), expected);
// Teyrchain context pointing to different consensus teyrchain,
let para_to_remote_para = MultiLocation::new(
2,
(GlobalConsensus(Kusama), Teyrchain(42), PalletInstance(42), GeneralIndex(42)),
);
let expected = MultiLocation::new(2, (GlobalConsensus(Kusama), Teyrchain(42)));
assert_eq!(para_to_remote_para.chain_location(), expected);
}
}
+574
View File
@@ -0,0 +1,574 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Cross-Consensus Message format data structures.
use crate::v5::Error as NewError;
use core::result;
use scale_info::TypeInfo;
pub use sp_weights::Weight;
// A simple trait to get the weight of some object.
pub trait GetWeight<W> {
fn weight(&self) -> sp_weights::Weight;
}
use super::*;
/// Error codes used in XCM. The first errors codes have explicit indices and are part of the XCM
/// format. Those trailing are merely part of the XCM implementation; there is no expectation that
/// they will retain the same index over time.
#[derive(
Copy,
Clone,
Encode,
Decode,
DecodeWithMemTracking,
Eq,
PartialEq,
Debug,
TypeInfo,
MaxEncodedLen,
)]
#[scale_info(replace_segment("staging_xcm", "xcm"))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub enum Error {
// Errors that happen due to instructions being executed. These alone are defined in the
// XCM specification.
/// An arithmetic overflow happened.
#[codec(index = 0)]
Overflow,
/// The instruction is intentionally unsupported.
#[codec(index = 1)]
Unimplemented,
/// Origin Register does not contain a value value for a reserve transfer notification.
#[codec(index = 2)]
UntrustedReserveLocation,
/// Origin Register does not contain a value value for a teleport notification.
#[codec(index = 3)]
UntrustedTeleportLocation,
/// `MultiLocation` value too large to descend further.
#[codec(index = 4)]
LocationFull,
/// `MultiLocation` value ascend more parents than known ancestors of local location.
#[codec(index = 5)]
LocationNotInvertible,
/// The Origin Register does not contain a valid value for instruction.
#[codec(index = 6)]
BadOrigin,
/// The location parameter is not a valid value for the instruction.
#[codec(index = 7)]
InvalidLocation,
/// The given asset is not handled.
#[codec(index = 8)]
AssetNotFound,
/// An asset transaction (like withdraw or deposit) failed (typically due to type conversions).
#[codec(index = 9)]
FailedToTransactAsset(#[codec(skip)] &'static str),
/// An asset cannot be withdrawn, potentially due to lack of ownership, availability or rights.
#[codec(index = 10)]
NotWithdrawable,
/// An asset cannot be deposited under the ownership of a particular location.
#[codec(index = 11)]
LocationCannotHold,
/// Attempt to send a message greater than the maximum supported by the transport protocol.
#[codec(index = 12)]
ExceedsMaxMessageSize,
/// The given message cannot be translated into a format supported by the destination.
#[codec(index = 13)]
DestinationUnsupported,
/// Destination is routable, but there is some issue with the transport mechanism.
#[codec(index = 14)]
Transport(#[codec(skip)] &'static str),
/// Destination is known to be unroutable.
#[codec(index = 15)]
Unroutable,
/// Used by `ClaimAsset` when the given claim could not be recognized/found.
#[codec(index = 16)]
UnknownClaim,
/// Used by `Transact` when the functor cannot be decoded.
#[codec(index = 17)]
FailedToDecode,
/// Used by `Transact` to indicate that the given weight limit could be breached by the
/// functor.
#[codec(index = 18)]
MaxWeightInvalid,
/// Used by `BuyExecution` when the Holding Register does not contain payable fees.
#[codec(index = 19)]
NotHoldingFees,
/// Used by `BuyExecution` when the fees declared to purchase weight are insufficient.
#[codec(index = 20)]
TooExpensive,
/// Used by the `Trap` instruction to force an error intentionally. Its code is included.
#[codec(index = 21)]
Trap(u64),
/// Used by `ExpectAsset`, `ExpectError` and `ExpectOrigin` when the expectation was not true.
#[codec(index = 22)]
ExpectationFalse,
/// The provided pallet index was not found.
#[codec(index = 23)]
PalletNotFound,
/// The given pallet's name is different to that expected.
#[codec(index = 24)]
NameMismatch,
/// The given pallet's version has an incompatible version to that expected.
#[codec(index = 25)]
VersionIncompatible,
/// The given operation would lead to an overflow of the Holding Register.
#[codec(index = 26)]
HoldingWouldOverflow,
/// The message was unable to be exported.
#[codec(index = 27)]
ExportError,
/// `MultiLocation` value failed to be reanchored.
#[codec(index = 28)]
ReanchorFailed,
/// No deal is possible under the given constraints.
#[codec(index = 29)]
NoDeal,
/// Fees were required which the origin could not pay.
#[codec(index = 30)]
FeesNotMet,
/// Some other error with locking.
#[codec(index = 31)]
LockError,
/// The state was not in a condition where the operation was valid to make.
#[codec(index = 32)]
NoPermission,
/// The universal location of the local consensus is improper.
#[codec(index = 33)]
Unanchored,
/// An asset cannot be deposited, probably because (too much of) it already exists.
#[codec(index = 34)]
NotDepositable,
// Errors that happen prior to instructions being executed. These fall outside of the XCM
// spec.
/// XCM version not able to be handled.
UnhandledXcmVersion,
/// Execution of the XCM would potentially result in a greater weight used than weight limit.
WeightLimitReached(Weight),
/// The XCM did not pass the barrier condition for execution.
///
/// The barrier condition differs on different chains and in different circumstances, but
/// generally it means that the conditions surrounding the message were not such that the chain
/// considers the message worth spending time executing. Since most chains lift the barrier to
/// execution on appropriate payment, presentation of an NFT voucher, or based on the message
/// origin, it means that none of those were the case.
Barrier,
/// The weight of an XCM message is not computable ahead of execution.
WeightNotComputable,
/// Recursion stack limit reached
ExceedsStackLimit,
}
impl TryFrom<NewError> for Error {
type Error = ();
fn try_from(new_error: NewError) -> result::Result<Error, ()> {
use NewError::*;
Ok(match new_error {
Overflow => Self::Overflow,
Unimplemented => Self::Unimplemented,
UntrustedReserveLocation => Self::UntrustedReserveLocation,
UntrustedTeleportLocation => Self::UntrustedTeleportLocation,
LocationFull => Self::LocationFull,
LocationNotInvertible => Self::LocationNotInvertible,
BadOrigin => Self::BadOrigin,
InvalidLocation => Self::InvalidLocation,
AssetNotFound => Self::AssetNotFound,
FailedToTransactAsset(s) => Self::FailedToTransactAsset(s),
NotWithdrawable => Self::NotWithdrawable,
LocationCannotHold => Self::LocationCannotHold,
ExceedsMaxMessageSize => Self::ExceedsMaxMessageSize,
DestinationUnsupported => Self::DestinationUnsupported,
Transport(s) => Self::Transport(s),
Unroutable => Self::Unroutable,
UnknownClaim => Self::UnknownClaim,
FailedToDecode => Self::FailedToDecode,
MaxWeightInvalid => Self::MaxWeightInvalid,
NotHoldingFees => Self::NotHoldingFees,
TooExpensive => Self::TooExpensive,
Trap(i) => Self::Trap(i),
ExpectationFalse => Self::ExpectationFalse,
PalletNotFound => Self::PalletNotFound,
NameMismatch => Self::NameMismatch,
VersionIncompatible => Self::VersionIncompatible,
HoldingWouldOverflow => Self::HoldingWouldOverflow,
ExportError => Self::ExportError,
ReanchorFailed => Self::ReanchorFailed,
NoDeal => Self::NoDeal,
FeesNotMet => Self::FeesNotMet,
LockError => Self::LockError,
NoPermission => Self::NoPermission,
Unanchored => Self::Unanchored,
NotDepositable => Self::NotDepositable,
_ => return Err(()),
})
}
}
impl From<SendError> for Error {
fn from(e: SendError) -> Self {
match e {
SendError::NotApplicable | SendError::Unroutable | SendError::MissingArgument =>
Error::Unroutable,
SendError::Transport(s) => Error::Transport(s),
SendError::DestinationUnsupported => Error::DestinationUnsupported,
SendError::ExceedsMaxMessageSize => Error::ExceedsMaxMessageSize,
SendError::Fees => Error::FeesNotMet,
}
}
}
pub type Result = result::Result<(), Error>;
/// Outcome of an XCM execution.
#[derive(Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo)]
#[scale_info(replace_segment("staging_xcm", "xcm"))]
pub enum Outcome {
/// Execution completed successfully; given weight was used.
Complete(Weight),
/// Execution started, but did not complete successfully due to the given error; given weight
/// was used.
Incomplete(Weight, Error),
/// Execution did not start due to the given error.
Error(Error),
}
impl Outcome {
pub fn ensure_complete(self) -> result::Result<Weight, Error> {
match self {
Outcome::Complete(weight) => Ok(weight),
Outcome::Incomplete(_, e) => Err(e),
Outcome::Error(e) => Err(e),
}
}
pub fn ensure_execution(self) -> result::Result<Weight, Error> {
match self {
Outcome::Complete(w) => Ok(w),
Outcome::Incomplete(w, _) => Ok(w),
Outcome::Error(e) => Err(e),
}
}
/// How much weight was used by the XCM execution attempt.
pub fn weight_used(&self) -> Weight {
match self {
Outcome::Complete(w) => *w,
Outcome::Incomplete(w, _) => *w,
Outcome::Error(_) => Weight::zero(),
}
}
}
pub trait PreparedMessage {
fn weight_of(&self) -> Weight;
}
/// Type of XCM message executor.
pub trait ExecuteXcm<Call> {
type Prepared: PreparedMessage;
fn prepare(message: Xcm<Call>) -> result::Result<Self::Prepared, Xcm<Call>>;
fn execute(
origin: impl Into<MultiLocation>,
pre: Self::Prepared,
id: &mut XcmHash,
weight_credit: Weight,
) -> Outcome;
fn prepare_and_execute(
origin: impl Into<MultiLocation>,
message: Xcm<Call>,
id: &mut XcmHash,
weight_limit: Weight,
weight_credit: Weight,
) -> Outcome {
let pre = match Self::prepare(message) {
Ok(x) => x,
Err(_) => return Outcome::Error(Error::WeightNotComputable),
};
let xcm_weight = pre.weight_of();
if xcm_weight.any_gt(weight_limit) {
return Outcome::Error(Error::WeightLimitReached(xcm_weight));
}
Self::execute(origin, pre, id, weight_credit)
}
/// Execute some XCM `message` with the message `hash` from `origin` using no more than
/// `weight_limit` weight.
///
/// The weight limit is a basic hard-limit and the implementation may place further
/// restrictions or requirements on weight and other aspects.
fn execute_xcm(
origin: impl Into<MultiLocation>,
message: Xcm<Call>,
hash: XcmHash,
weight_limit: Weight,
) -> Outcome {
let origin = origin.into();
tracing::trace!(
target: "xcm::execute_xcm",
?origin,
?message,
?weight_limit,
);
Self::execute_xcm_in_credit(origin, message, hash, weight_limit, Weight::zero())
}
/// Execute some XCM `message` with the message `hash` from `origin` using no more than
/// `weight_limit` weight.
///
/// Some amount of `weight_credit` may be provided which, depending on the implementation, may
/// allow execution without associated payment.
fn execute_xcm_in_credit(
origin: impl Into<MultiLocation>,
message: Xcm<Call>,
mut hash: XcmHash,
weight_limit: Weight,
weight_credit: Weight,
) -> Outcome {
let pre = match Self::prepare(message) {
Ok(x) => x,
Err(_) => return Outcome::Error(Error::WeightNotComputable),
};
let xcm_weight = pre.weight_of();
if xcm_weight.any_gt(weight_limit) {
return Outcome::Error(Error::WeightLimitReached(xcm_weight));
}
Self::execute(origin, pre, &mut hash, weight_credit)
}
/// Deduct some `fees` to the sovereign account of the given `location` and place them as per
/// the convention for fees.
fn charge_fees(location: impl Into<MultiLocation>, fees: MultiAssets) -> Result;
}
pub enum Weightless {}
impl PreparedMessage for Weightless {
fn weight_of(&self) -> Weight {
unreachable!()
}
}
impl<C> ExecuteXcm<C> for () {
type Prepared = Weightless;
fn prepare(message: Xcm<C>) -> result::Result<Self::Prepared, Xcm<C>> {
Err(message)
}
fn execute(
_: impl Into<MultiLocation>,
_: Self::Prepared,
_: &mut XcmHash,
_: Weight,
) -> Outcome {
unreachable!()
}
fn charge_fees(_location: impl Into<MultiLocation>, _fees: MultiAssets) -> Result {
Err(Error::Unimplemented)
}
}
/// Error result value when attempting to send an XCM message.
#[derive(
Clone, Encode, Decode, DecodeWithMemTracking, Eq, PartialEq, Debug, scale_info::TypeInfo,
)]
#[scale_info(replace_segment("staging_xcm", "xcm"))]
pub enum SendError {
/// The message and destination combination was not recognized as being reachable.
///
/// This is not considered fatal: if there are alternative transport routes available, then
/// they may be attempted.
NotApplicable,
/// Destination is routable, but there is some issue with the transport mechanism. This is
/// considered fatal.
/// A human-readable explanation of the specific issue is provided.
Transport(#[codec(skip)] &'static str),
/// Destination is known to be unroutable. This is considered fatal.
Unroutable,
/// The given message cannot be translated into a format that the destination can be expected
/// to interpret.
DestinationUnsupported,
/// Message could not be sent due to its size exceeding the maximum allowed by the transport
/// layer.
ExceedsMaxMessageSize,
/// A needed argument is `None` when it should be `Some`.
MissingArgument,
/// Fees needed to be paid in order to send the message and they were unavailable.
Fees,
}
/// A hash type for identifying messages.
pub type XcmHash = [u8; 32];
/// Result value when attempting to send an XCM message.
pub type SendResult<T> = result::Result<(T, MultiAssets), SendError>;
/// Utility for sending an XCM message to a given location.
///
/// These can be amalgamated in tuples to form sophisticated routing systems. In tuple format, each
/// router might return `NotApplicable` to pass the execution to the next sender item. Note that
/// each `NotApplicable` might alter the destination and the XCM message for to the next router.
///
/// # Example
/// ```rust
/// # use codec::Encode;
/// # use staging_xcm::v3::{prelude::*, Weight};
/// # use staging_xcm::VersionedXcm;
/// # use std::convert::Infallible;
///
/// /// A sender that only passes the message through and does nothing.
/// struct Sender1;
/// impl SendXcm for Sender1 {
/// type Ticket = Infallible;
/// fn validate(_: &mut Option<MultiLocation>, _: &mut Option<Xcm<()>>) -> SendResult<Infallible> {
/// Err(SendError::NotApplicable)
/// }
/// fn deliver(_: Infallible) -> Result<XcmHash, SendError> {
/// unreachable!()
/// }
/// }
///
/// /// A sender that accepts a message that has an X2 junction, otherwise stops the routing.
/// struct Sender2;
/// impl SendXcm for Sender2 {
/// type Ticket = ();
/// fn validate(destination: &mut Option<MultiLocation>, message: &mut Option<Xcm<()>>) -> SendResult<()> {
/// match destination.as_ref().ok_or(SendError::MissingArgument)? {
/// MultiLocation { parents: 0, interior: X2(j1, j2) } => Ok(((), MultiAssets::new())),
/// _ => Err(SendError::Unroutable),
/// }
/// }
/// fn deliver(_: ()) -> Result<XcmHash, SendError> {
/// Ok([0; 32])
/// }
/// }
///
/// /// A sender that accepts a message from a parent, passing through otherwise.
/// struct Sender3;
/// impl SendXcm for Sender3 {
/// type Ticket = ();
/// fn validate(destination: &mut Option<MultiLocation>, message: &mut Option<Xcm<()>>) -> SendResult<()> {
/// match destination.as_ref().ok_or(SendError::MissingArgument)? {
/// MultiLocation { parents: 1, interior: Here } => Ok(((), MultiAssets::new())),
/// _ => Err(SendError::NotApplicable),
/// }
/// }
/// fn deliver(_: ()) -> Result<XcmHash, SendError> {
/// Ok([0; 32])
/// }
/// }
///
/// // A call to send via XCM. We don't really care about this.
/// # fn main() {
/// let call: Vec<u8> = ().encode();
/// let message = Xcm(vec![Instruction::Transact {
/// origin_kind: OriginKind::Superuser,
/// require_weight_at_most: Weight::zero(),
/// call: call.into(),
/// }]);
/// let message_hash = message.using_encoded(sp_io::hashing::blake2_256);
///
/// // Sender2 will block this.
/// assert!(send_xcm::<(Sender1, Sender2, Sender3)>(Parent.into(), message.clone()).is_err());
///
/// // Sender3 will catch this.
/// assert!(send_xcm::<(Sender1, Sender3)>(Parent.into(), message.clone()).is_ok());
/// # }
/// ```
pub trait SendXcm {
/// Intermediate value which connects the two phases of the send operation.
type Ticket;
/// Check whether the given `message` is deliverable to the given `destination` and if so
/// determine the cost which will be paid by this chain to do so, returning a `Validated` token
/// which can be used to enact delivery.
///
/// The `destination` and `message` must be `Some` (or else an error will be returned) and they
/// may only be consumed if the `Err` is not `NotApplicable`.
///
/// If it is not a destination which can be reached with this type but possibly could by others,
/// then this *MUST* return `NotApplicable`. Any other error will cause the tuple
/// implementation to exit early without trying other type fields.
fn validate(
destination: &mut Option<MultiLocation>,
message: &mut Option<Xcm<()>>,
) -> SendResult<Self::Ticket>;
/// Actually carry out the delivery operation for a previously validated message sending.
fn deliver(ticket: Self::Ticket) -> result::Result<XcmHash, SendError>;
}
#[impl_trait_for_tuples::impl_for_tuples(30)]
impl SendXcm for Tuple {
for_tuples! { type Ticket = (#( Option<Tuple::Ticket> ),* ); }
fn validate(
destination: &mut Option<MultiLocation>,
message: &mut Option<Xcm<()>>,
) -> SendResult<Self::Ticket> {
let mut maybe_cost: Option<MultiAssets> = None;
let one_ticket: Self::Ticket = (for_tuples! { #(
if maybe_cost.is_some() {
None
} else {
match Tuple::validate(destination, message) {
Err(SendError::NotApplicable) => None,
Err(e) => { return Err(e) },
Ok((v, c)) => {
maybe_cost = Some(c);
Some(v)
},
}
}
),* });
if let Some(cost) = maybe_cost {
Ok((one_ticket, cost))
} else {
Err(SendError::NotApplicable)
}
}
fn deliver(one_ticket: Self::Ticket) -> result::Result<XcmHash, SendError> {
for_tuples!( #(
if let Some(validated) = one_ticket.Tuple {
return Tuple::deliver(validated);
}
)* );
Err(SendError::Unroutable)
}
}
/// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps
/// both in `Some` before passing them as mutable references into `T::send_xcm`.
pub fn validate_send<T: SendXcm>(dest: MultiLocation, msg: Xcm<()>) -> SendResult<T::Ticket> {
T::validate(&mut Some(dest), &mut Some(msg))
}
/// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps
/// both in `Some` before passing them as mutable references into `T::send_xcm`.
///
/// Returns either `Ok` with the price of the delivery, or `Err` with the reason why the message
/// could not be sent.
///
/// Generally you'll want to validate and get the price first to ensure that the sender can pay it
/// before actually doing the delivery.
pub fn send_xcm<T: SendXcm>(
dest: MultiLocation,
msg: Xcm<()>,
) -> result::Result<(XcmHash, MultiAssets), SendError> {
let (ticket, price) = T::validate(&mut Some(dest), &mut Some(msg))?;
let hash = T::deliver(ticket)?;
Ok((hash, price))
}
File diff suppressed because it is too large Load Diff
+363
View File
@@ -0,0 +1,363 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Support data structures for `Location`, primarily the `Junction` datatype.
use super::Location;
pub use crate::v3::{BodyId, BodyPart};
use crate::{
v3::{Junction as OldJunction, NetworkId as OldNetworkId},
v5::{Junction as NewJunction, NetworkId as NewNetworkId},
VersionedLocation,
};
use bounded_collections::{BoundedSlice, BoundedVec, ConstU32};
use codec::{self, Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use serde::{Deserialize, Serialize};
/// A single item in a path to describe the relative location of a consensus system.
///
/// Each item assumes a pre-existing location as its context and is defined in terms of it.
#[derive(
Copy,
Clone,
Eq,
PartialEq,
Ord,
PartialOrd,
Encode,
Decode,
DecodeWithMemTracking,
Debug,
TypeInfo,
MaxEncodedLen,
Serialize,
Deserialize,
)]
pub enum Junction {
/// An indexed teyrchain belonging to and operated by the context.
///
/// Generally used when the context is a Pezkuwi Relay-chain.
Teyrchain(#[codec(compact)] u32),
/// A 32-byte identifier for an account of a specific network that is respected as a sovereign
/// endpoint within the context.
///
/// Generally used when the context is a Substrate-based chain.
AccountId32 { network: Option<NetworkId>, id: [u8; 32] },
/// An 8-byte index for an account of a specific network that is respected as a sovereign
/// endpoint within the context.
///
/// May be used when the context is a Frame-based chain and includes e.g. an indices pallet.
AccountIndex64 {
network: Option<NetworkId>,
#[codec(compact)]
index: u64,
},
/// A 20-byte identifier for an account of a specific network that is respected as a sovereign
/// endpoint within the context.
///
/// May be used when the context is an Ethereum or Bitcoin chain or smart-contract.
AccountKey20 { network: Option<NetworkId>, key: [u8; 20] },
/// An instanced, indexed pallet that forms a constituent part of the context.
///
/// Generally used when the context is a Frame-based chain.
PalletInstance(u8),
/// A non-descript index within the context location.
///
/// Usage will vary widely owing to its generality.
///
/// NOTE: Try to avoid using this and instead use a more specific item.
GeneralIndex(#[codec(compact)] u128),
/// A nondescript array datum, 32 bytes, acting as a key within the context
/// location.
///
/// Usage will vary widely owing to its generality.
///
/// NOTE: Try to avoid using this and instead use a more specific item.
// Note this is implemented as an array with a length rather than using `BoundedVec` owing to
// the bound for `Copy`.
GeneralKey { length: u8, data: [u8; 32] },
/// The unambiguous child.
///
/// Not currently used except as a fallback when deriving context.
OnlyChild,
/// A pluralistic body existing within consensus.
///
/// Typical to be used to represent a governance origin of a chain, but could in principle be
/// used to represent things such as multisigs also.
Plurality { id: BodyId, part: BodyPart },
/// A global network capable of externalizing its own consensus. This is not generally
/// meaningful outside of the universal level.
GlobalConsensus(NetworkId),
}
impl From<NewNetworkId> for Option<NetworkId> {
fn from(new: NewNetworkId) -> Self {
Some(NetworkId::from(new))
}
}
impl From<NewNetworkId> for NetworkId {
fn from(new: NewNetworkId) -> Self {
use NewNetworkId::*;
match new {
ByGenesis(hash) => Self::ByGenesis(hash),
ByFork { block_number, block_hash } => Self::ByFork { block_number, block_hash },
Pezkuwi => Self::Pezkuwi,
Kusama => Self::Kusama,
Ethereum { chain_id } => Self::Ethereum { chain_id },
BitcoinCore => Self::BitcoinCore,
BitcoinCash => Self::BitcoinCash,
PezkuwiBulletin => Self::PezkuwiBulletin,
}
}
}
/// A global identifier of a data structure existing within consensus.
///
/// Maintenance note: Networks with global consensus and which are practically bridgeable within the
/// Pezkuwi ecosystem are given preference over explicit naming in this enumeration.
#[derive(
Copy,
Clone,
Eq,
PartialEq,
Ord,
PartialOrd,
Encode,
Decode,
DecodeWithMemTracking,
Debug,
TypeInfo,
MaxEncodedLen,
Serialize,
Deserialize,
)]
pub enum NetworkId {
/// Network specified by the first 32 bytes of its genesis block.
ByGenesis([u8; 32]),
/// Network defined by the first 32-bytes of the hash and number of some block it contains.
ByFork { block_number: u64, block_hash: [u8; 32] },
/// The Pezkuwi mainnet Relay-chain.
Pezkuwi,
/// The Kusama canary-net Relay-chain.
Kusama,
/// The Zagros testnet Relay-chain.
Zagros,
/// The Pezkuwichain testnet Relay-chain.
Pezkuwichain,
/// The Wococo testnet Relay-chain.
Wococo,
/// An Ethereum network specified by its chain ID.
Ethereum {
/// The EIP-155 chain ID.
#[codec(compact)]
chain_id: u64,
},
/// The Bitcoin network, including hard-forks supported by Bitcoin Core development team.
BitcoinCore,
/// The Bitcoin network, including hard-forks supported by Bitcoin Cash developers.
BitcoinCash,
/// The Pezkuwi Bulletin chain.
PezkuwiBulletin,
}
impl From<OldNetworkId> for Option<NetworkId> {
fn from(old: OldNetworkId) -> Self {
Some(NetworkId::from(old))
}
}
impl From<OldNetworkId> for NetworkId {
fn from(old: OldNetworkId) -> Self {
use OldNetworkId::*;
match old {
ByGenesis(hash) => Self::ByGenesis(hash),
ByFork { block_number, block_hash } => Self::ByFork { block_number, block_hash },
Pezkuwi => Self::Pezkuwi,
Kusama => Self::Kusama,
Zagros => Self::Zagros,
Pezkuwichain => Self::Pezkuwichain,
Wococo => Self::Wococo,
Ethereum { chain_id } => Self::Ethereum { chain_id },
BitcoinCore => Self::BitcoinCore,
BitcoinCash => Self::BitcoinCash,
PezkuwiBulletin => Self::PezkuwiBulletin,
}
}
}
impl From<NetworkId> for Junction {
fn from(n: NetworkId) -> Self {
Self::GlobalConsensus(n)
}
}
impl From<[u8; 32]> for Junction {
fn from(id: [u8; 32]) -> Self {
Self::AccountId32 { network: None, id }
}
}
impl From<BoundedVec<u8, ConstU32<32>>> for Junction {
fn from(key: BoundedVec<u8, ConstU32<32>>) -> Self {
key.as_bounded_slice().into()
}
}
impl<'a> From<BoundedSlice<'a, u8, ConstU32<32>>> for Junction {
fn from(key: BoundedSlice<'a, u8, ConstU32<32>>) -> Self {
let mut data = [0u8; 32];
data[..key.len()].copy_from_slice(&key[..]);
Self::GeneralKey { length: key.len() as u8, data }
}
}
impl<'a> TryFrom<&'a Junction> for BoundedSlice<'a, u8, ConstU32<32>> {
type Error = ();
fn try_from(key: &'a Junction) -> Result<Self, ()> {
match key {
Junction::GeneralKey { length, data } =>
BoundedSlice::try_from(&data[..data.len().min(*length as usize)]).map_err(|_| ()),
_ => Err(()),
}
}
}
impl From<[u8; 20]> for Junction {
fn from(key: [u8; 20]) -> Self {
Self::AccountKey20 { network: None, key }
}
}
impl From<u64> for Junction {
fn from(index: u64) -> Self {
Self::AccountIndex64 { network: None, index }
}
}
impl From<u128> for Junction {
fn from(id: u128) -> Self {
Self::GeneralIndex(id)
}
}
impl TryFrom<OldJunction> for Junction {
type Error = ();
fn try_from(value: OldJunction) -> Result<Self, ()> {
use OldJunction::*;
Ok(match value {
Teyrchain(id) => Self::Teyrchain(id),
AccountId32 { network: maybe_network, id } =>
Self::AccountId32 { network: maybe_network.map(|network| network.into()), id },
AccountIndex64 { network: maybe_network, index } =>
Self::AccountIndex64 { network: maybe_network.map(|network| network.into()), index },
AccountKey20 { network: maybe_network, key } =>
Self::AccountKey20 { network: maybe_network.map(|network| network.into()), key },
PalletInstance(index) => Self::PalletInstance(index),
GeneralIndex(id) => Self::GeneralIndex(id),
GeneralKey { length, data } => Self::GeneralKey { length, data },
OnlyChild => Self::OnlyChild,
Plurality { id, part } => Self::Plurality { id, part },
GlobalConsensus(network) => Self::GlobalConsensus(network.into()),
})
}
}
impl TryFrom<NewJunction> for Junction {
type Error = ();
fn try_from(value: NewJunction) -> Result<Self, Self::Error> {
use NewJunction::*;
Ok(match value {
Teyrchain(id) => Self::Teyrchain(id),
AccountId32 { network: maybe_network, id } =>
Self::AccountId32 { network: maybe_network.map(|network| network.into()), id },
AccountIndex64 { network: maybe_network, index } =>
Self::AccountIndex64 { network: maybe_network.map(|network| network.into()), index },
AccountKey20 { network: maybe_network, key } =>
Self::AccountKey20 { network: maybe_network.map(|network| network.into()), key },
PalletInstance(index) => Self::PalletInstance(index),
GeneralIndex(id) => Self::GeneralIndex(id),
GeneralKey { length, data } => Self::GeneralKey { length, data },
OnlyChild => Self::OnlyChild,
Plurality { id, part } => Self::Plurality { id, part },
GlobalConsensus(network) => Self::GlobalConsensus(network.into()),
})
}
}
impl Junction {
/// Convert `self` into a `Location` containing 0 parents.
///
/// Similar to `Into::into`, except that this method can be used in a const evaluation context.
pub fn into_location(self) -> Location {
Location::new(0, [self])
}
/// Convert `self` into a `Location` containing `n` parents.
///
/// Similar to `Self::into_location`, with the added ability to specify the number of parent
/// junctions.
pub fn into_exterior(self, n: u8) -> Location {
Location::new(n, [self])
}
/// Convert `self` into a `VersionedLocation` containing 0 parents.
///
/// Similar to `Into::into`, except that this method can be used in a const evaluation context.
pub fn into_versioned(self) -> VersionedLocation {
self.into_location().into_versioned()
}
/// Remove the `NetworkId` value.
pub fn remove_network_id(&mut self) {
use Junction::*;
match self {
AccountId32 { ref mut network, .. } |
AccountIndex64 { ref mut network, .. } |
AccountKey20 { ref mut network, .. } => *network = None,
_ => {},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
#[test]
fn junction_round_trip_works() {
let j = Junction::GeneralKey { length: 32, data: [1u8; 32] };
let k = Junction::try_from(OldJunction::try_from(j).unwrap()).unwrap();
assert_eq!(j, k);
let j = OldJunction::GeneralKey { length: 32, data: [1u8; 32] };
let k = OldJunction::try_from(Junction::try_from(j).unwrap()).unwrap();
assert_eq!(j, k);
let j = Junction::from(BoundedVec::try_from(vec![1u8, 2, 3, 4]).unwrap());
let k = Junction::try_from(OldJunction::try_from(j).unwrap()).unwrap();
assert_eq!(j, k);
let s: BoundedSlice<_, _> = (&k).try_into().unwrap();
assert_eq!(s, &[1u8, 2, 3, 4][..]);
let j = OldJunction::GeneralKey { length: 32, data: [1u8; 32] };
let k = OldJunction::try_from(Junction::try_from(j).unwrap()).unwrap();
assert_eq!(j, k);
}
}
+724
View File
@@ -0,0 +1,724 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! XCM `Junctions`/`InteriorLocation` datatype.
use super::{Junction, Location, NetworkId};
use alloc::sync::Arc;
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
use core::{mem, ops::Range, result};
use scale_info::TypeInfo;
/// Maximum number of `Junction`s that a `Junctions` can contain.
pub(crate) const MAX_JUNCTIONS: usize = 8;
/// Non-parent junctions that can be constructed, up to the length of 8. This specific `Junctions`
/// implementation uses a Rust `enum` in order to make pattern matching easier.
///
/// Parent junctions cannot be constructed with this type. Refer to `Location` for
/// instructions on constructing parent junctions.
#[derive(
Clone,
Eq,
PartialEq,
Ord,
PartialOrd,
Encode,
Decode,
DecodeWithMemTracking,
Debug,
TypeInfo,
MaxEncodedLen,
serde::Serialize,
serde::Deserialize,
)]
pub enum Junctions {
/// The interpreting consensus system.
Here,
/// A relative path comprising 1 junction.
X1(Arc<[Junction; 1]>),
/// A relative path comprising 2 junctions.
X2(Arc<[Junction; 2]>),
/// A relative path comprising 3 junctions.
X3(Arc<[Junction; 3]>),
/// A relative path comprising 4 junctions.
X4(Arc<[Junction; 4]>),
/// A relative path comprising 5 junctions.
X5(Arc<[Junction; 5]>),
/// A relative path comprising 6 junctions.
X6(Arc<[Junction; 6]>),
/// A relative path comprising 7 junctions.
X7(Arc<[Junction; 7]>),
/// A relative path comprising 8 junctions.
X8(Arc<[Junction; 8]>),
}
macro_rules! impl_junctions {
($count:expr, $variant:ident) => {
impl From<[Junction; $count]> for Junctions {
fn from(junctions: [Junction; $count]) -> Self {
Self::$variant(Arc::new(junctions))
}
}
impl PartialEq<[Junction; $count]> for Junctions {
fn eq(&self, rhs: &[Junction; $count]) -> bool {
self.as_slice() == rhs
}
}
};
}
impl_junctions!(1, X1);
impl_junctions!(2, X2);
impl_junctions!(3, X3);
impl_junctions!(4, X4);
impl_junctions!(5, X5);
impl_junctions!(6, X6);
impl_junctions!(7, X7);
impl_junctions!(8, X8);
pub struct JunctionsIterator {
junctions: Junctions,
range: Range<usize>,
}
impl Iterator for JunctionsIterator {
type Item = Junction;
fn next(&mut self) -> Option<Junction> {
self.junctions.at(self.range.next()?).cloned()
}
}
impl DoubleEndedIterator for JunctionsIterator {
fn next_back(&mut self) -> Option<Junction> {
self.junctions.at(self.range.next_back()?).cloned()
}
}
pub struct JunctionsRefIterator<'a> {
junctions: &'a Junctions,
range: Range<usize>,
}
impl<'a> Iterator for JunctionsRefIterator<'a> {
type Item = &'a Junction;
fn next(&mut self) -> Option<&'a Junction> {
self.junctions.at(self.range.next()?)
}
}
impl<'a> DoubleEndedIterator for JunctionsRefIterator<'a> {
fn next_back(&mut self) -> Option<&'a Junction> {
self.junctions.at(self.range.next_back()?)
}
}
impl<'a> IntoIterator for &'a Junctions {
type Item = &'a Junction;
type IntoIter = JunctionsRefIterator<'a>;
fn into_iter(self) -> Self::IntoIter {
JunctionsRefIterator { junctions: self, range: 0..self.len() }
}
}
impl IntoIterator for Junctions {
type Item = Junction;
type IntoIter = JunctionsIterator;
fn into_iter(self) -> Self::IntoIter {
JunctionsIterator { range: 0..self.len(), junctions: self }
}
}
impl Junctions {
/// Convert `self` into a `Location` containing 0 parents.
///
/// Similar to `Into::into`, except that this method can be used in a const evaluation context.
pub const fn into_location(self) -> Location {
Location { parents: 0, interior: self }
}
/// Convert `self` into a `Location` containing `n` parents.
///
/// Similar to `Self::into_location`, with the added ability to specify the number of parent
/// junctions.
pub const fn into_exterior(self, n: u8) -> Location {
Location { parents: n, interior: self }
}
/// Casts `self` into a slice containing `Junction`s.
pub fn as_slice(&self) -> &[Junction] {
match self {
Junctions::Here => &[],
Junctions::X1(ref a) => &a[..],
Junctions::X2(ref a) => &a[..],
Junctions::X3(ref a) => &a[..],
Junctions::X4(ref a) => &a[..],
Junctions::X5(ref a) => &a[..],
Junctions::X6(ref a) => &a[..],
Junctions::X7(ref a) => &a[..],
Junctions::X8(ref a) => &a[..],
}
}
/// Casts `self` into a mutable slice containing `Junction`s.
pub fn as_slice_mut(&mut self) -> &mut [Junction] {
match self {
Junctions::Here => &mut [],
Junctions::X1(ref mut a) => &mut Arc::make_mut(a)[..],
Junctions::X2(ref mut a) => &mut Arc::make_mut(a)[..],
Junctions::X3(ref mut a) => &mut Arc::make_mut(a)[..],
Junctions::X4(ref mut a) => &mut Arc::make_mut(a)[..],
Junctions::X5(ref mut a) => &mut Arc::make_mut(a)[..],
Junctions::X6(ref mut a) => &mut Arc::make_mut(a)[..],
Junctions::X7(ref mut a) => &mut Arc::make_mut(a)[..],
Junctions::X8(ref mut a) => &mut Arc::make_mut(a)[..],
}
}
/// Remove the `NetworkId` value in any `Junction`s.
pub fn remove_network_id(&mut self) {
self.for_each_mut(Junction::remove_network_id);
}
/// Treating `self` as the universal context, return the location of the local consensus system
/// from the point of view of the given `target`.
pub fn invert_target(&self, target: &Location) -> Result<Location, ()> {
let mut itself = self.clone();
let mut junctions = Self::Here;
for _ in 0..target.parent_count() {
junctions = junctions
.pushed_front_with(itself.take_last().unwrap_or(Junction::OnlyChild))
.map_err(|_| ())?;
}
let parents = target.interior().len() as u8;
Ok(Location::new(parents, junctions))
}
/// Execute a function `f` on every junction. We use this since we cannot implement a mutable
/// `Iterator` without unsafe code.
pub fn for_each_mut(&mut self, x: impl FnMut(&mut Junction)) {
self.as_slice_mut().iter_mut().for_each(x)
}
/// Extract the network ID treating this value as a universal location.
///
/// This will return an `Err` if the first item is not a `GlobalConsensus`, which would indicate
/// that this value is not a universal location.
pub fn global_consensus(&self) -> Result<NetworkId, ()> {
if let Some(Junction::GlobalConsensus(network)) = self.first() {
Ok(*network)
} else {
Err(())
}
}
/// Extract the network ID and the interior consensus location, treating this value as a
/// universal location.
///
/// This will return an `Err` if the first item is not a `GlobalConsensus`, which would indicate
/// that this value is not a universal location.
pub fn split_global(self) -> Result<(NetworkId, Junctions), ()> {
match self.split_first() {
(location, Some(Junction::GlobalConsensus(network))) => Ok((network, location)),
_ => return Err(()),
}
}
/// Treat `self` as a universal location and the context of `relative`, returning the universal
/// location of relative.
///
/// This will return an error if `relative` has as many (or more) parents than there are
/// junctions in `self`, implying that relative refers into a different global consensus.
pub fn within_global(mut self, relative: Location) -> Result<Self, ()> {
if self.len() <= relative.parent_count() as usize {
return Err(());
}
for _ in 0..relative.parent_count() {
self.take_last();
}
for j in relative.interior() {
self.push(*j).map_err(|_| ())?;
}
Ok(self)
}
/// Consumes `self` and returns how `viewer` would address it locally.
pub fn relative_to(mut self, viewer: &Junctions) -> Location {
let mut i = 0;
while match (self.first(), viewer.at(i)) {
(Some(x), Some(y)) => x == y,
_ => false,
} {
self = self.split_first().0;
// NOTE: Cannot overflow as loop can only iterate at most `MAX_JUNCTIONS` times.
i += 1;
}
// AUDIT NOTES:
// - above loop ensures that `i <= viewer.len()`.
// - `viewer.len()` is at most `MAX_JUNCTIONS`, so won't overflow a `u8`.
Location::new((viewer.len() - i) as u8, self)
}
/// Returns first junction, or `None` if the location is empty.
pub fn first(&self) -> Option<&Junction> {
self.as_slice().first()
}
/// Returns last junction, or `None` if the location is empty.
pub fn last(&self) -> Option<&Junction> {
self.as_slice().last()
}
/// Splits off the first junction, returning the remaining suffix (first item in tuple) and the
/// first element (second item in tuple) or `None` if it was empty.
pub fn split_first(self) -> (Junctions, Option<Junction>) {
match self {
Junctions::Here => (Junctions::Here, None),
Junctions::X1(xs) => {
let [a] = *xs;
(Junctions::Here, Some(a))
},
Junctions::X2(xs) => {
let [a, b] = *xs;
([b].into(), Some(a))
},
Junctions::X3(xs) => {
let [a, b, c] = *xs;
([b, c].into(), Some(a))
},
Junctions::X4(xs) => {
let [a, b, c, d] = *xs;
([b, c, d].into(), Some(a))
},
Junctions::X5(xs) => {
let [a, b, c, d, e] = *xs;
([b, c, d, e].into(), Some(a))
},
Junctions::X6(xs) => {
let [a, b, c, d, e, f] = *xs;
([b, c, d, e, f].into(), Some(a))
},
Junctions::X7(xs) => {
let [a, b, c, d, e, f, g] = *xs;
([b, c, d, e, f, g].into(), Some(a))
},
Junctions::X8(xs) => {
let [a, b, c, d, e, f, g, h] = *xs;
([b, c, d, e, f, g, h].into(), Some(a))
},
}
}
/// Splits off the last junction, returning the remaining prefix (first item in tuple) and the
/// last element (second item in tuple) or `None` if it was empty.
pub fn split_last(self) -> (Junctions, Option<Junction>) {
match self {
Junctions::Here => (Junctions::Here, None),
Junctions::X1(xs) => {
let [a] = *xs;
(Junctions::Here, Some(a))
},
Junctions::X2(xs) => {
let [a, b] = *xs;
([a].into(), Some(b))
},
Junctions::X3(xs) => {
let [a, b, c] = *xs;
([a, b].into(), Some(c))
},
Junctions::X4(xs) => {
let [a, b, c, d] = *xs;
([a, b, c].into(), Some(d))
},
Junctions::X5(xs) => {
let [a, b, c, d, e] = *xs;
([a, b, c, d].into(), Some(e))
},
Junctions::X6(xs) => {
let [a, b, c, d, e, f] = *xs;
([a, b, c, d, e].into(), Some(f))
},
Junctions::X7(xs) => {
let [a, b, c, d, e, f, g] = *xs;
([a, b, c, d, e, f].into(), Some(g))
},
Junctions::X8(xs) => {
let [a, b, c, d, e, f, g, h] = *xs;
([a, b, c, d, e, f, g].into(), Some(h))
},
}
}
/// Removes the first element from `self`, returning it (or `None` if it was empty).
pub fn take_first(&mut self) -> Option<Junction> {
let mut d = Junctions::Here;
mem::swap(&mut *self, &mut d);
let (tail, head) = d.split_first();
*self = tail;
head
}
/// Removes the last element from `self`, returning it (or `None` if it was empty).
pub fn take_last(&mut self) -> Option<Junction> {
let mut d = Junctions::Here;
mem::swap(&mut *self, &mut d);
let (head, tail) = d.split_last();
*self = head;
tail
}
/// Mutates `self` to be appended with `new` or returns an `Err` with `new` if would overflow.
pub fn push(&mut self, new: impl Into<Junction>) -> result::Result<(), Junction> {
let new = new.into();
let mut dummy = Junctions::Here;
mem::swap(self, &mut dummy);
match dummy.pushed_with(new) {
Ok(s) => {
*self = s;
Ok(())
},
Err((s, j)) => {
*self = s;
Err(j)
},
}
}
/// Mutates `self` to be prepended with `new` or returns an `Err` with `new` if would overflow.
pub fn push_front(&mut self, new: impl Into<Junction>) -> result::Result<(), Junction> {
let new = new.into();
let mut dummy = Junctions::Here;
mem::swap(self, &mut dummy);
match dummy.pushed_front_with(new) {
Ok(s) => {
*self = s;
Ok(())
},
Err((s, j)) => {
*self = s;
Err(j)
},
}
}
/// Consumes `self` and returns a `Junctions` suffixed with `new`, or an `Err` with the
/// original value of `self` and `new` in case of overflow.
pub fn pushed_with(self, new: impl Into<Junction>) -> result::Result<Self, (Self, Junction)> {
let new = new.into();
Ok(match self {
Junctions::Here => [new].into(),
Junctions::X1(xs) => {
let [a] = *xs;
[a, new].into()
},
Junctions::X2(xs) => {
let [a, b] = *xs;
[a, b, new].into()
},
Junctions::X3(xs) => {
let [a, b, c] = *xs;
[a, b, c, new].into()
},
Junctions::X4(xs) => {
let [a, b, c, d] = *xs;
[a, b, c, d, new].into()
},
Junctions::X5(xs) => {
let [a, b, c, d, e] = *xs;
[a, b, c, d, e, new].into()
},
Junctions::X6(xs) => {
let [a, b, c, d, e, f] = *xs;
[a, b, c, d, e, f, new].into()
},
Junctions::X7(xs) => {
let [a, b, c, d, e, f, g] = *xs;
[a, b, c, d, e, f, g, new].into()
},
s => Err((s, new))?,
})
}
/// Consumes `self` and returns a `Junctions` prefixed with `new`, or an `Err` with the
/// original value of `self` and `new` in case of overflow.
pub fn pushed_front_with(
self,
new: impl Into<Junction>,
) -> result::Result<Self, (Self, Junction)> {
let new = new.into();
Ok(match self {
Junctions::Here => [new].into(),
Junctions::X1(xs) => {
let [a] = *xs;
[new, a].into()
},
Junctions::X2(xs) => {
let [a, b] = *xs;
[new, a, b].into()
},
Junctions::X3(xs) => {
let [a, b, c] = *xs;
[new, a, b, c].into()
},
Junctions::X4(xs) => {
let [a, b, c, d] = *xs;
[new, a, b, c, d].into()
},
Junctions::X5(xs) => {
let [a, b, c, d, e] = *xs;
[new, a, b, c, d, e].into()
},
Junctions::X6(xs) => {
let [a, b, c, d, e, f] = *xs;
[new, a, b, c, d, e, f].into()
},
Junctions::X7(xs) => {
let [a, b, c, d, e, f, g] = *xs;
[new, a, b, c, d, e, f, g].into()
},
s => Err((s, new))?,
})
}
/// Mutate `self` so that it is suffixed with `suffix`.
///
/// Does not modify `self` and returns `Err` with `suffix` in case of overflow.
///
/// # Example
/// ```rust
/// # use staging_xcm::v4::{Junctions, Junction::*, Location};
/// # fn main() {
/// let mut m = Junctions::from([Teyrchain(21)]);
/// assert_eq!(m.append_with([PalletInstance(3)]), Ok(()));
/// assert_eq!(m, [Teyrchain(21), PalletInstance(3)]);
/// # }
/// ```
pub fn append_with(&mut self, suffix: impl Into<Junctions>) -> Result<(), Junctions> {
let suffix = suffix.into();
if self.len().saturating_add(suffix.len()) > MAX_JUNCTIONS {
return Err(suffix);
}
for j in suffix.into_iter() {
self.push(j).expect("Already checked the sum of the len()s; qed")
}
Ok(())
}
/// Returns the number of junctions in `self`.
pub fn len(&self) -> usize {
self.as_slice().len()
}
/// Returns the junction at index `i`, or `None` if the location doesn't contain that many
/// elements.
pub fn at(&self, i: usize) -> Option<&Junction> {
self.as_slice().get(i)
}
/// Returns a mutable reference to the junction at index `i`, or `None` if the location doesn't
/// contain that many elements.
pub fn at_mut(&mut self, i: usize) -> Option<&mut Junction> {
self.as_slice_mut().get_mut(i)
}
/// Returns a reference iterator over the junctions.
pub fn iter(&self) -> JunctionsRefIterator<'_> {
JunctionsRefIterator { junctions: self, range: 0..self.len() }
}
/// Ensures that self begins with `prefix` and that it has a single `Junction` item following.
/// If so, returns a reference to this `Junction` item.
///
/// # Example
/// ```rust
/// # use staging_xcm::v4::{Junctions, Junction::*};
/// # fn main() {
/// let mut m = Junctions::from([Teyrchain(2), PalletInstance(3), OnlyChild]);
/// assert_eq!(m.match_and_split(&[Teyrchain(2), PalletInstance(3)].into()), Some(&OnlyChild));
/// assert_eq!(m.match_and_split(&[Teyrchain(2)].into()), None);
/// # }
/// ```
pub fn match_and_split(&self, prefix: &Junctions) -> Option<&Junction> {
if prefix.len() + 1 != self.len() {
return None;
}
for i in 0..prefix.len() {
if prefix.at(i) != self.at(i) {
return None;
}
}
return self.at(prefix.len());
}
pub fn starts_with(&self, prefix: &Junctions) -> bool {
prefix.len() <= self.len() && prefix.iter().zip(self.iter()).all(|(x, y)| x == y)
}
}
impl TryFrom<Location> for Junctions {
type Error = Location;
fn try_from(x: Location) -> result::Result<Self, Location> {
if x.parent_count() > 0 {
Err(x)
} else {
Ok(x.interior().clone())
}
}
}
impl<T: Into<Junction>> From<T> for Junctions {
fn from(x: T) -> Self {
[x.into()].into()
}
}
impl From<[Junction; 0]> for Junctions {
fn from(_: [Junction; 0]) -> Self {
Self::Here
}
}
impl From<()> for Junctions {
fn from(_: ()) -> Self {
Self::Here
}
}
xcm_procedural::impl_conversion_functions_for_junctions_v4!();
#[cfg(test)]
mod tests {
use super::{super::prelude::*, *};
#[test]
fn inverting_works() {
let context: InteriorLocation = (Teyrchain(1000), PalletInstance(42)).into();
let target = (Parent, PalletInstance(69)).into();
let expected = (Parent, PalletInstance(42)).into();
let inverted = context.invert_target(&target).unwrap();
assert_eq!(inverted, expected);
let context: InteriorLocation =
(Teyrchain(1000), PalletInstance(42), GeneralIndex(1)).into();
let target = (Parent, Parent, PalletInstance(69), GeneralIndex(2)).into();
let expected = (Parent, Parent, PalletInstance(42), GeneralIndex(1)).into();
let inverted = context.invert_target(&target).unwrap();
assert_eq!(inverted, expected);
}
#[test]
fn relative_to_works() {
use NetworkId::*;
assert_eq!(
Junctions::from([Pezkuwi.into()]).relative_to(&Junctions::from([Kusama.into()])),
(Parent, Pezkuwi).into()
);
let base = Junctions::from([Kusama.into(), Teyrchain(1), PalletInstance(1)]);
// Ancestors.
assert_eq!(Here.relative_to(&base), (Parent, Parent, Parent).into());
assert_eq!(Junctions::from([Kusama.into()]).relative_to(&base), (Parent, Parent).into());
assert_eq!(
Junctions::from([Kusama.into(), Teyrchain(1)]).relative_to(&base),
(Parent,).into()
);
assert_eq!(
Junctions::from([Kusama.into(), Teyrchain(1), PalletInstance(1)]).relative_to(&base),
Here.into()
);
// Ancestors with one child.
assert_eq!(
Junctions::from([Pezkuwi.into()]).relative_to(&base),
(Parent, Parent, Parent, Pezkuwi).into()
);
assert_eq!(
Junctions::from([Kusama.into(), Teyrchain(2)]).relative_to(&base),
(Parent, Parent, Teyrchain(2)).into()
);
assert_eq!(
Junctions::from([Kusama.into(), Teyrchain(1), PalletInstance(2)]).relative_to(&base),
(Parent, PalletInstance(2)).into()
);
assert_eq!(
Junctions::from([Kusama.into(), Teyrchain(1), PalletInstance(1), [1u8; 32].into()])
.relative_to(&base),
([1u8; 32],).into()
);
// Ancestors with grandchildren.
assert_eq!(
Junctions::from([Pezkuwi.into(), Teyrchain(1)]).relative_to(&base),
(Parent, Parent, Parent, Pezkuwi, Teyrchain(1)).into()
);
assert_eq!(
Junctions::from([Kusama.into(), Teyrchain(2), PalletInstance(1)]).relative_to(&base),
(Parent, Parent, Teyrchain(2), PalletInstance(1)).into()
);
assert_eq!(
Junctions::from([Kusama.into(), Teyrchain(1), PalletInstance(2), [1u8; 32].into()])
.relative_to(&base),
(Parent, PalletInstance(2), [1u8; 32]).into()
);
assert_eq!(
Junctions::from([
Kusama.into(),
Teyrchain(1),
PalletInstance(1),
[1u8; 32].into(),
1u128.into()
])
.relative_to(&base),
([1u8; 32], 1u128).into()
);
}
#[test]
fn global_consensus_works() {
use NetworkId::*;
assert_eq!(Junctions::from([Pezkuwi.into()]).global_consensus(), Ok(Pezkuwi));
assert_eq!(Junctions::from([Kusama.into(), 1u64.into()]).global_consensus(), Ok(Kusama));
assert_eq!(Here.global_consensus(), Err(()));
assert_eq!(Junctions::from([1u64.into()]).global_consensus(), Err(()));
assert_eq!(Junctions::from([1u64.into(), Kusama.into()]).global_consensus(), Err(()));
}
#[test]
fn test_conversion() {
use super::{Junction::*, NetworkId::*};
let x: Junctions = GlobalConsensus(Pezkuwi).into();
assert_eq!(x, Junctions::from([GlobalConsensus(Pezkuwi)]));
let x: Junctions = Pezkuwi.into();
assert_eq!(x, Junctions::from([GlobalConsensus(Pezkuwi)]));
let x: Junctions = (Pezkuwi, Kusama).into();
assert_eq!(x, Junctions::from([GlobalConsensus(Pezkuwi), GlobalConsensus(Kusama)]));
}
#[test]
fn encode_decode_junctions_works() {
let original = Junctions::from([
Pezkuwi.into(),
Kusama.into(),
1u64.into(),
GlobalConsensus(Pezkuwi),
Teyrchain(123),
PalletInstance(45),
]);
let encoded = original.encode();
assert_eq!(encoded, &[6, 9, 2, 9, 3, 2, 0, 4, 9, 2, 0, 237, 1, 4, 45]);
let decoded = Junctions::decode(&mut &encoded[..]).unwrap();
assert_eq!(decoded, original);
}
}
+770
View File
@@ -0,0 +1,770 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! XCM `Location` datatype.
use super::{traits::Reanchorable, Junction, Junctions};
use crate::{v3::MultiLocation as OldLocation, v5::Location as NewLocation, VersionedLocation};
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
use core::result;
use scale_info::TypeInfo;
/// A relative path between state-bearing consensus systems.
///
/// A location in a consensus system is defined as an *isolatable state machine* held within global
/// consensus. The location in question need not have a sophisticated consensus algorithm of its
/// own; a single account within Ethereum, for example, could be considered a location.
///
/// A very-much non-exhaustive list of types of location include:
/// - A (normal, layer-1) block chain, e.g. the Bitcoin mainnet or a teyrchain.
/// - A layer-0 super-chain, e.g. the Pezkuwi Relay chain.
/// - A layer-2 smart contract, e.g. an ERC-20 on Ethereum.
/// - A logical functional component of a chain, e.g. a single instance of a pallet on a Frame-based
/// Substrate chain.
/// - An account.
///
/// A `Location` is a *relative identifier*, meaning that it can only be used to define the
/// relative path between two locations, and cannot generally be used to refer to a location
/// universally. It is comprised of an integer number of parents specifying the number of times to
/// "escape" upwards into the containing consensus system and then a number of *junctions*, each
/// diving down and specifying some interior portion of state (which may be considered a
/// "sub-consensus" system).
///
/// This specific `Location` implementation uses a `Junctions` datatype which is a Rust `enum`
/// in order to make pattern matching easier. There are occasions where it is important to ensure
/// that a value is strictly an interior location, in those cases, `Junctions` may be used.
///
/// The `Location` value of `Null` simply refers to the interpreting consensus system.
#[derive(
Clone,
Decode,
DecodeWithMemTracking,
Encode,
Eq,
PartialEq,
Ord,
PartialOrd,
Debug,
TypeInfo,
MaxEncodedLen,
serde::Serialize,
serde::Deserialize,
)]
pub struct Location {
/// The number of parent junctions at the beginning of this `Location`.
pub parents: u8,
/// The interior (i.e. non-parent) junctions that this `Location` contains.
pub interior: Junctions,
}
impl Default for Location {
fn default() -> Self {
Self { parents: 0, interior: Junctions::Here }
}
}
/// A relative location which is constrained to be an interior location of the context.
///
/// See also `Location`.
pub type InteriorLocation = Junctions;
impl Location {
/// Creates a new `Location` with the given number of parents and interior junctions.
pub fn new(parents: u8, interior: impl Into<Junctions>) -> Location {
Location { parents, interior: interior.into() }
}
/// Consume `self` and return the equivalent `VersionedLocation` value.
pub const fn into_versioned(self) -> VersionedLocation {
VersionedLocation::V4(self)
}
/// Creates a new `Location` with 0 parents and a `Here` interior.
///
/// The resulting `Location` can be interpreted as the "current consensus system".
pub const fn here() -> Location {
Location { parents: 0, interior: Junctions::Here }
}
/// Creates a new `Location` which evaluates to the parent context.
pub const fn parent() -> Location {
Location { parents: 1, interior: Junctions::Here }
}
/// Creates a new `Location` with `parents` and an empty (`Here`) interior.
pub const fn ancestor(parents: u8) -> Location {
Location { parents, interior: Junctions::Here }
}
/// Whether the `Location` has no parents and has a `Here` interior.
pub fn is_here(&self) -> bool {
self.parents == 0 && self.interior.len() == 0
}
/// Remove the `NetworkId` value in any interior `Junction`s.
pub fn remove_network_id(&mut self) {
self.interior.remove_network_id();
}
/// Return a reference to the interior field.
pub fn interior(&self) -> &Junctions {
&self.interior
}
/// Return a mutable reference to the interior field.
pub fn interior_mut(&mut self) -> &mut Junctions {
&mut self.interior
}
/// Returns the number of `Parent` junctions at the beginning of `self`.
pub const fn parent_count(&self) -> u8 {
self.parents
}
/// Returns the parent count and the interior [`Junctions`] as a tuple.
///
/// To be used when pattern matching, for example:
///
/// ```rust
/// # use staging_xcm::v4::{Junctions::*, Junction::*, Location};
/// fn get_teyrchain_id(loc: &Location) -> Option<u32> {
/// match loc.unpack() {
/// (0, [Teyrchain(id)]) => Some(*id),
/// _ => None
/// }
/// }
/// ```
pub fn unpack(&self) -> (u8, &[Junction]) {
(self.parents, self.interior.as_slice())
}
/// Returns boolean indicating whether `self` contains only the specified amount of
/// parents and no interior junctions.
pub const fn contains_parents_only(&self, count: u8) -> bool {
matches!(self.interior, Junctions::Here) && self.parents == count
}
/// Returns the number of parents and junctions in `self`.
pub fn len(&self) -> usize {
self.parent_count() as usize + self.interior.len()
}
/// Returns the first interior junction, or `None` if the location is empty or contains only
/// parents.
pub fn first_interior(&self) -> Option<&Junction> {
self.interior.first()
}
/// Returns last junction, or `None` if the location is empty or contains only parents.
pub fn last(&self) -> Option<&Junction> {
self.interior.last()
}
/// Splits off the first interior junction, returning the remaining suffix (first item in tuple)
/// and the first element (second item in tuple) or `None` if it was empty.
pub fn split_first_interior(self) -> (Location, Option<Junction>) {
let Location { parents, interior: junctions } = self;
let (suffix, first) = junctions.split_first();
let location = Location { parents, interior: suffix };
(location, first)
}
/// Splits off the last interior junction, returning the remaining prefix (first item in tuple)
/// and the last element (second item in tuple) or `None` if it was empty or if `self` only
/// contains parents.
pub fn split_last_interior(self) -> (Location, Option<Junction>) {
let Location { parents, interior: junctions } = self;
let (prefix, last) = junctions.split_last();
let location = Location { parents, interior: prefix };
(location, last)
}
/// Mutates `self`, suffixing its interior junctions with `new`. Returns `Err` with `new` in
/// case of overflow.
pub fn push_interior(&mut self, new: impl Into<Junction>) -> result::Result<(), Junction> {
self.interior.push(new)
}
/// Mutates `self`, prefixing its interior junctions with `new`. Returns `Err` with `new` in
/// case of overflow.
pub fn push_front_interior(
&mut self,
new: impl Into<Junction>,
) -> result::Result<(), Junction> {
self.interior.push_front(new)
}
/// Consumes `self` and returns a `Location` suffixed with `new`, or an `Err` with
/// the original value of `self` in case of overflow.
pub fn pushed_with_interior(
self,
new: impl Into<Junction>,
) -> result::Result<Self, (Self, Junction)> {
match self.interior.pushed_with(new) {
Ok(i) => Ok(Location { interior: i, parents: self.parents }),
Err((i, j)) => Err((Location { interior: i, parents: self.parents }, j)),
}
}
/// Consumes `self` and returns a `Location` prefixed with `new`, or an `Err` with the
/// original value of `self` in case of overflow.
pub fn pushed_front_with_interior(
self,
new: impl Into<Junction>,
) -> result::Result<Self, (Self, Junction)> {
match self.interior.pushed_front_with(new) {
Ok(i) => Ok(Location { interior: i, parents: self.parents }),
Err((i, j)) => Err((Location { interior: i, parents: self.parents }, j)),
}
}
/// Returns the junction at index `i`, or `None` if the location is a parent or if the location
/// does not contain that many elements.
pub fn at(&self, i: usize) -> Option<&Junction> {
let num_parents = self.parents as usize;
if i < num_parents {
return None;
}
self.interior.at(i - num_parents)
}
/// Returns a mutable reference to the junction at index `i`, or `None` if the location is a
/// parent or if it doesn't contain that many elements.
pub fn at_mut(&mut self, i: usize) -> Option<&mut Junction> {
let num_parents = self.parents as usize;
if i < num_parents {
return None;
}
self.interior.at_mut(i - num_parents)
}
/// Decrements the parent count by 1.
pub fn dec_parent(&mut self) {
self.parents = self.parents.saturating_sub(1);
}
/// Removes the first interior junction from `self`, returning it
/// (or `None` if it was empty or if `self` contains only parents).
pub fn take_first_interior(&mut self) -> Option<Junction> {
self.interior.take_first()
}
/// Removes the last element from `interior`, returning it (or `None` if it was empty or if
/// `self` only contains parents).
pub fn take_last(&mut self) -> Option<Junction> {
self.interior.take_last()
}
/// Ensures that `self` has the same number of parents as `prefix`, its junctions begins with
/// the junctions of `prefix` and that it has a single `Junction` item following.
/// If so, returns a reference to this `Junction` item.
///
/// # Example
/// ```rust
/// # use staging_xcm::v4::{Junctions::*, Junction::*, Location};
/// # fn main() {
/// let mut m = Location::new(1, [PalletInstance(3), OnlyChild]);
/// assert_eq!(
/// m.match_and_split(&Location::new(1, [PalletInstance(3)])),
/// Some(&OnlyChild),
/// );
/// assert_eq!(m.match_and_split(&Location::new(1, Here)), None);
/// # }
/// ```
pub fn match_and_split(&self, prefix: &Location) -> Option<&Junction> {
if self.parents != prefix.parents {
return None;
}
self.interior.match_and_split(&prefix.interior)
}
pub fn starts_with(&self, prefix: &Location) -> bool {
self.parents == prefix.parents && self.interior.starts_with(&prefix.interior)
}
/// Mutate `self` so that it is suffixed with `suffix`.
///
/// Does not modify `self` and returns `Err` with `suffix` in case of overflow.
///
/// # Example
/// ```rust
/// # use staging_xcm::v4::{Junctions::*, Junction::*, Location, Parent};
/// # fn main() {
/// let mut m: Location = (Parent, Teyrchain(21), 69u64).into();
/// assert_eq!(m.append_with((Parent, PalletInstance(3))), Ok(()));
/// assert_eq!(m, Location::new(1, [Teyrchain(21), PalletInstance(3)]));
/// # }
/// ```
pub fn append_with(&mut self, suffix: impl Into<Self>) -> Result<(), Self> {
let prefix = core::mem::replace(self, suffix.into());
match self.prepend_with(prefix) {
Ok(()) => Ok(()),
Err(prefix) => Err(core::mem::replace(self, prefix)),
}
}
/// Consume `self` and return its value suffixed with `suffix`.
///
/// Returns `Err` with the original value of `self` and `suffix` in case of overflow.
///
/// # Example
/// ```rust
/// # use staging_xcm::v4::{Junctions::*, Junction::*, Location, Parent};
/// # fn main() {
/// let mut m: Location = (Parent, Teyrchain(21), 69u64).into();
/// let r = m.appended_with((Parent, PalletInstance(3))).unwrap();
/// assert_eq!(r, Location::new(1, [Teyrchain(21), PalletInstance(3)]));
/// # }
/// ```
pub fn appended_with(mut self, suffix: impl Into<Self>) -> Result<Self, (Self, Self)> {
match self.append_with(suffix) {
Ok(()) => Ok(self),
Err(suffix) => Err((self, suffix)),
}
}
/// Mutate `self` so that it is prefixed with `prefix`.
///
/// Does not modify `self` and returns `Err` with `prefix` in case of overflow.
///
/// # Example
/// ```rust
/// # use staging_xcm::v4::{Junctions::*, Junction::*, Location, Parent};
/// # fn main() {
/// let mut m: Location = (Parent, Parent, PalletInstance(3)).into();
/// assert_eq!(m.prepend_with((Parent, Teyrchain(21), OnlyChild)), Ok(()));
/// assert_eq!(m, Location::new(1, [PalletInstance(3)]));
/// # }
/// ```
pub fn prepend_with(&mut self, prefix: impl Into<Self>) -> Result<(), Self> {
// prefix self (suffix)
// P .. P I .. I p .. p i .. i
let mut prefix = prefix.into();
let prepend_interior = prefix.interior.len().saturating_sub(self.parents as usize);
let final_interior = self.interior.len().saturating_add(prepend_interior);
if final_interior > super::junctions::MAX_JUNCTIONS {
return Err(prefix);
}
let suffix_parents = (self.parents as usize).saturating_sub(prefix.interior.len());
let final_parents = (prefix.parents as usize).saturating_add(suffix_parents);
if final_parents > 255 {
return Err(prefix);
}
// cancel out the final item on the prefix interior for one of the suffix's parents.
while self.parents > 0 && prefix.take_last().is_some() {
self.dec_parent();
}
// now we have either removed all suffix's parents or prefix interior.
// this means we can combine the prefix's and suffix's remaining parents/interior since
// we know that with at least one empty, the overall order will be respected:
// prefix self (suffix)
// P .. P (I) p .. p i .. i => P + p .. (no I) i
// -- or --
// P .. P I .. I (p) i .. i => P (no p) .. I + i
self.parents = self.parents.saturating_add(prefix.parents);
for j in prefix.interior.into_iter().rev() {
self.push_front_interior(j)
.expect("final_interior no greater than MAX_JUNCTIONS; qed");
}
Ok(())
}
/// Consume `self` and return its value prefixed with `prefix`.
///
/// Returns `Err` with the original value of `self` and `prefix` in case of overflow.
///
/// # Example
/// ```rust
/// # use staging_xcm::v4::{Junctions::*, Junction::*, Location, Parent};
/// # fn main() {
/// let m: Location = (Parent, Parent, PalletInstance(3)).into();
/// let r = m.prepended_with((Parent, Teyrchain(21), OnlyChild)).unwrap();
/// assert_eq!(r, Location::new(1, [PalletInstance(3)]));
/// # }
/// ```
pub fn prepended_with(mut self, prefix: impl Into<Self>) -> Result<Self, (Self, Self)> {
match self.prepend_with(prefix) {
Ok(()) => Ok(self),
Err(prefix) => Err((self, prefix)),
}
}
/// Remove any unneeded parents/junctions in `self` based on the given context it will be
/// interpreted in.
pub fn simplify(&mut self, context: &Junctions) {
if context.len() < self.parents as usize {
// Not enough context
return;
}
while self.parents > 0 {
let maybe = context.at(context.len() - (self.parents as usize));
match (self.interior.first(), maybe) {
(Some(i), Some(j)) if i == j => {
self.interior.take_first();
self.parents -= 1;
},
_ => break,
}
}
}
/// Return the Location subsection identifying the chain that `self` points to.
pub fn chain_location(&self) -> Location {
let mut clone = self.clone();
// start popping junctions until we reach chain identifier
while let Some(j) = clone.last() {
if matches!(j, Junction::Teyrchain(_) | Junction::GlobalConsensus(_)) {
// return chain subsection
return clone;
} else {
(clone, _) = clone.split_last_interior();
}
}
Location::new(clone.parents, Junctions::Here)
}
}
impl Reanchorable for Location {
type Error = Self;
/// Mutate `self` so that it represents the same location from the point of view of `target`.
/// The context of `self` is provided as `context`.
///
/// Does not modify `self` in case of overflow.
fn reanchor(&mut self, target: &Location, context: &InteriorLocation) -> Result<(), ()> {
// TODO: https://github.com/paritytech/polkadot/issues/4489 Optimize this.
// 1. Use our `context` to figure out how the `target` would address us.
let inverted_target = context.invert_target(target)?;
// 2. Prepend `inverted_target` to `self` to get self's location from the perspective of
// `target`.
self.prepend_with(inverted_target).map_err(|_| ())?;
// 3. Given that we know some of `target` context, ensure that any parents in `self` are
// strictly needed.
self.simplify(target.interior());
Ok(())
}
/// Consume `self` and return a new value representing the same location from the point of view
/// of `target`. The context of `self` is provided as `context`.
///
/// Returns the original `self` in case of overflow.
fn reanchored(mut self, target: &Location, context: &InteriorLocation) -> Result<Self, Self> {
match self.reanchor(target, context) {
Ok(()) => Ok(self),
Err(()) => Err(self),
}
}
}
impl TryFrom<OldLocation> for Option<Location> {
type Error = ();
fn try_from(value: OldLocation) -> result::Result<Self, Self::Error> {
Ok(Some(Location::try_from(value)?))
}
}
impl TryFrom<OldLocation> for Location {
type Error = ();
fn try_from(x: OldLocation) -> result::Result<Self, ()> {
Ok(Location { parents: x.parents, interior: x.interior.try_into()? })
}
}
impl TryFrom<NewLocation> for Option<Location> {
type Error = ();
fn try_from(new: NewLocation) -> result::Result<Self, Self::Error> {
Ok(Some(Location::try_from(new)?))
}
}
impl TryFrom<NewLocation> for Location {
type Error = ();
fn try_from(new: NewLocation) -> result::Result<Self, ()> {
Ok(Location { parents: new.parent_count(), interior: new.interior().clone().try_into()? })
}
}
/// A unit struct which can be converted into a `Location` of `parents` value 1.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct Parent;
impl From<Parent> for Location {
fn from(_: Parent) -> Self {
Location { parents: 1, interior: Junctions::Here }
}
}
/// A tuple struct which can be converted into a `Location` of `parents` value 1 with the inner
/// interior.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct ParentThen(pub Junctions);
impl From<ParentThen> for Location {
fn from(ParentThen(interior): ParentThen) -> Self {
Location { parents: 1, interior }
}
}
/// A unit struct which can be converted into a `Location` of the inner `parents` value.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct Ancestor(pub u8);
impl From<Ancestor> for Location {
fn from(Ancestor(parents): Ancestor) -> Self {
Location { parents, interior: Junctions::Here }
}
}
/// A unit struct which can be converted into a `Location` of the inner `parents` value and the
/// inner interior.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct AncestorThen<Interior>(pub u8, pub Interior);
impl<Interior: Into<Junctions>> From<AncestorThen<Interior>> for Location {
fn from(AncestorThen(parents, interior): AncestorThen<Interior>) -> Self {
Location { parents, interior: interior.into() }
}
}
impl From<[u8; 32]> for Location {
fn from(bytes: [u8; 32]) -> Self {
let junction: Junction = bytes.into();
junction.into()
}
}
impl From<sp_runtime::AccountId32> for Location {
fn from(id: sp_runtime::AccountId32) -> Self {
Junction::AccountId32 { network: None, id: id.into() }.into()
}
}
xcm_procedural::impl_conversion_functions_for_location_v4!();
#[cfg(test)]
mod tests {
use crate::v4::prelude::*;
use codec::{Decode, Encode};
#[test]
fn conversion_works() {
let x: Location = Parent.into();
assert_eq!(x, Location { parents: 1, interior: Here });
// let x: Location = (Parent,).into();
// assert_eq!(x, Location { parents: 1, interior: Here });
// let x: Location = (Parent, Parent).into();
// assert_eq!(x, Location { parents: 2, interior: Here });
let x: Location = (Parent, Parent, OnlyChild).into();
assert_eq!(x, Location { parents: 2, interior: OnlyChild.into() });
let x: Location = OnlyChild.into();
assert_eq!(x, Location { parents: 0, interior: OnlyChild.into() });
let x: Location = (OnlyChild,).into();
assert_eq!(x, Location { parents: 0, interior: OnlyChild.into() });
}
#[test]
fn simplify_basic_works() {
let mut location: Location =
(Parent, Parent, Teyrchain(1000), PalletInstance(42), GeneralIndex(69)).into();
let context = [Teyrchain(1000), PalletInstance(42)].into();
let expected = GeneralIndex(69).into();
location.simplify(&context);
assert_eq!(location, expected);
let mut location: Location = (Parent, PalletInstance(42), GeneralIndex(69)).into();
let context = [PalletInstance(42)].into();
let expected = GeneralIndex(69).into();
location.simplify(&context);
assert_eq!(location, expected);
let mut location: Location = (Parent, PalletInstance(42), GeneralIndex(69)).into();
let context = [Teyrchain(1000), PalletInstance(42)].into();
let expected = GeneralIndex(69).into();
location.simplify(&context);
assert_eq!(location, expected);
let mut location: Location =
(Parent, Parent, Teyrchain(1000), PalletInstance(42), GeneralIndex(69)).into();
let context = [OnlyChild, Teyrchain(1000), PalletInstance(42)].into();
let expected = GeneralIndex(69).into();
location.simplify(&context);
assert_eq!(location, expected);
}
#[test]
fn simplify_incompatible_location_fails() {
let mut location: Location =
(Parent, Parent, Teyrchain(1000), PalletInstance(42), GeneralIndex(69)).into();
let context = [Teyrchain(1000), PalletInstance(42), GeneralIndex(42)].into();
let expected =
(Parent, Parent, Teyrchain(1000), PalletInstance(42), GeneralIndex(69)).into();
location.simplify(&context);
assert_eq!(location, expected);
let mut location: Location =
(Parent, Parent, Teyrchain(1000), PalletInstance(42), GeneralIndex(69)).into();
let context = [Teyrchain(1000)].into();
let expected =
(Parent, Parent, Teyrchain(1000), PalletInstance(42), GeneralIndex(69)).into();
location.simplify(&context);
assert_eq!(location, expected);
}
#[test]
fn reanchor_works() {
let mut id: Location = (Parent, Teyrchain(1000), GeneralIndex(42)).into();
let context = Teyrchain(2000).into();
let target = (Parent, Teyrchain(1000)).into();
let expected = GeneralIndex(42).into();
id.reanchor(&target, &context).unwrap();
assert_eq!(id, expected);
}
#[test]
fn encode_and_decode_works() {
let m = Location {
parents: 1,
interior: [Teyrchain(42), AccountIndex64 { network: None, index: 23 }].into(),
};
let encoded = m.encode();
assert_eq!(encoded, [1, 2, 0, 168, 2, 0, 92].to_vec());
let decoded = Location::decode(&mut &encoded[..]);
assert_eq!(decoded, Ok(m));
}
#[test]
fn match_and_split_works() {
let m = Location {
parents: 1,
interior: [Teyrchain(42), AccountIndex64 { network: None, index: 23 }].into(),
};
assert_eq!(m.match_and_split(&Location { parents: 1, interior: Here }), None);
assert_eq!(
m.match_and_split(&Location { parents: 1, interior: [Teyrchain(42)].into() }),
Some(&AccountIndex64 { network: None, index: 23 })
);
assert_eq!(m.match_and_split(&m), None);
}
#[test]
fn append_with_works() {
let acc = AccountIndex64 { network: None, index: 23 };
let mut m = Location { parents: 1, interior: [Teyrchain(42)].into() };
assert_eq!(m.append_with([PalletInstance(3), acc]), Ok(()));
assert_eq!(
m,
Location { parents: 1, interior: [Teyrchain(42), PalletInstance(3), acc].into() }
);
// cannot append to create overly long location
let acc = AccountIndex64 { network: None, index: 23 };
let m = Location {
parents: 254,
interior: [Teyrchain(42), OnlyChild, OnlyChild, OnlyChild, OnlyChild].into(),
};
let suffix: Location = (PalletInstance(3), acc, OnlyChild, OnlyChild).into();
assert_eq!(m.clone().append_with(suffix.clone()), Err(suffix));
}
#[test]
fn prepend_with_works() {
let mut m = Location {
parents: 1,
interior: [Teyrchain(42), AccountIndex64 { network: None, index: 23 }].into(),
};
assert_eq!(m.prepend_with(Location { parents: 1, interior: [OnlyChild].into() }), Ok(()));
assert_eq!(
m,
Location {
parents: 1,
interior: [Teyrchain(42), AccountIndex64 { network: None, index: 23 }].into()
}
);
// cannot prepend to create overly long location
let mut m = Location { parents: 254, interior: [Teyrchain(42)].into() };
let prefix = Location { parents: 2, interior: Here };
assert_eq!(m.prepend_with(prefix.clone()), Err(prefix));
let prefix = Location { parents: 1, interior: Here };
assert_eq!(m.prepend_with(prefix.clone()), Ok(()));
assert_eq!(m, Location { parents: 255, interior: [Teyrchain(42)].into() });
}
#[test]
fn double_ended_ref_iteration_works() {
let m: Junctions = [Teyrchain(1000), Teyrchain(3), PalletInstance(5)].into();
let mut iter = m.iter();
let first = iter.next().unwrap();
assert_eq!(first, &Teyrchain(1000));
let third = iter.next_back().unwrap();
assert_eq!(third, &PalletInstance(5));
let second = iter.next_back().unwrap();
assert_eq!(iter.next(), None);
assert_eq!(iter.next_back(), None);
assert_eq!(second, &Teyrchain(3));
let res = Here
.pushed_with(*first)
.unwrap()
.pushed_with(*second)
.unwrap()
.pushed_with(*third)
.unwrap();
assert_eq!(m, res);
// make sure there's no funny business with the 0 indexing
let m = Here;
let mut iter = m.iter();
assert_eq!(iter.next(), None);
assert_eq!(iter.next_back(), None);
}
#[test]
fn conversion_from_other_types_works() {
use crate::v3;
fn takes_location<Arg: Into<Location>>(_arg: Arg) {}
takes_location(Parent);
takes_location(Here);
takes_location([Teyrchain(42)]);
takes_location((Ancestor(255), PalletInstance(8)));
takes_location((Ancestor(5), Teyrchain(1), PalletInstance(3)));
takes_location((Ancestor(2), Here));
takes_location(AncestorThen(
3,
[Teyrchain(43), AccountIndex64 { network: None, index: 155 }],
));
takes_location((Parent, AccountId32 { network: None, id: [0; 32] }));
takes_location((Parent, Here));
takes_location(ParentThen([Teyrchain(75)].into()));
takes_location([Teyrchain(100), PalletInstance(3)]);
assert_eq!(v3::Location::from(v3::Junctions::Here).try_into(), Ok(Location::here()));
assert_eq!(v3::Location::from(v3::Parent).try_into(), Ok(Location::parent()));
assert_eq!(
v3::Location::from((v3::Parent, v3::Parent, v3::Junction::GeneralIndex(42u128),))
.try_into(),
Ok(Location { parents: 2, interior: [GeneralIndex(42u128)].into() }),
);
}
}
File diff suppressed because it is too large Load Diff
+312
View File
@@ -0,0 +1,312 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Cross-Consensus Message format data structures.
pub use crate::v3::{Error, Result, SendError, XcmHash};
use codec::{Decode, Encode};
use core::result;
use scale_info::TypeInfo;
pub use sp_weights::Weight;
use super::*;
/// Outcome of an XCM execution.
#[derive(Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo)]
pub enum Outcome {
/// Execution completed successfully; given weight was used.
Complete { used: Weight },
/// Execution started, but did not complete successfully due to the given error; given weight
/// was used.
Incomplete { used: Weight, error: Error },
/// Execution did not start due to the given error.
Error { error: Error },
}
impl Outcome {
pub fn ensure_complete(self) -> Result {
match self {
Outcome::Complete { .. } => Ok(()),
Outcome::Incomplete { error, .. } => Err(error),
Outcome::Error { error, .. } => Err(error),
}
}
pub fn ensure_execution(self) -> result::Result<Weight, Error> {
match self {
Outcome::Complete { used, .. } => Ok(used),
Outcome::Incomplete { used, .. } => Ok(used),
Outcome::Error { error, .. } => Err(error),
}
}
/// How much weight was used by the XCM execution attempt.
pub fn weight_used(&self) -> Weight {
match self {
Outcome::Complete { used, .. } => *used,
Outcome::Incomplete { used, .. } => *used,
Outcome::Error { .. } => Weight::zero(),
}
}
}
impl From<Error> for Outcome {
fn from(error: Error) -> Self {
Self::Error { error }
}
}
pub trait PreparedMessage {
fn weight_of(&self) -> Weight;
}
/// Type of XCM message executor.
pub trait ExecuteXcm<Call> {
type Prepared: PreparedMessage;
fn prepare(message: Xcm<Call>) -> result::Result<Self::Prepared, Xcm<Call>>;
fn execute(
origin: impl Into<Location>,
pre: Self::Prepared,
id: &mut XcmHash,
weight_credit: Weight,
) -> Outcome;
fn prepare_and_execute(
origin: impl Into<Location>,
message: Xcm<Call>,
id: &mut XcmHash,
weight_limit: Weight,
weight_credit: Weight,
) -> Outcome {
let pre = match Self::prepare(message) {
Ok(x) => x,
Err(_) => return Outcome::Error { error: Error::WeightNotComputable },
};
let xcm_weight = pre.weight_of();
if xcm_weight.any_gt(weight_limit) {
return Outcome::Error { error: Error::WeightLimitReached(xcm_weight) };
}
Self::execute(origin, pre, id, weight_credit)
}
/// Deduct some `fees` to the sovereign account of the given `location` and place them as per
/// the convention for fees.
fn charge_fees(location: impl Into<Location>, fees: Assets) -> Result;
}
pub enum Weightless {}
impl PreparedMessage for Weightless {
fn weight_of(&self) -> Weight {
unreachable!()
}
}
impl<C> ExecuteXcm<C> for () {
type Prepared = Weightless;
fn prepare(message: Xcm<C>) -> result::Result<Self::Prepared, Xcm<C>> {
Err(message)
}
fn execute(_: impl Into<Location>, _: Self::Prepared, _: &mut XcmHash, _: Weight) -> Outcome {
unreachable!()
}
fn charge_fees(_location: impl Into<Location>, _fees: Assets) -> Result {
Err(Error::Unimplemented)
}
}
pub trait Reanchorable: Sized {
/// Type to return in case of an error.
type Error: Debug;
/// Mutate `self` so that it represents the same location from the point of view of `target`.
/// The context of `self` is provided as `context`.
///
/// Does not modify `self` in case of overflow.
fn reanchor(
&mut self,
target: &Location,
context: &InteriorLocation,
) -> core::result::Result<(), ()>;
/// Consume `self` and return a new value representing the same location from the point of view
/// of `target`. The context of `self` is provided as `context`.
///
/// Returns the original `self` in case of overflow.
fn reanchored(
self,
target: &Location,
context: &InteriorLocation,
) -> core::result::Result<Self, Self::Error>;
}
/// Result value when attempting to send an XCM message.
pub type SendResult<T> = result::Result<(T, Assets), SendError>;
/// Utility for sending an XCM message to a given location.
///
/// These can be amalgamated in tuples to form sophisticated routing systems. In tuple format, each
/// router might return `NotApplicable` to pass the execution to the next sender item. Note that
/// each `NotApplicable` might alter the destination and the XCM message for to the next router.
///
/// # Example
/// ```rust
/// # use codec::Encode;
/// # use staging_xcm::v4::{prelude::*, Weight};
/// # use staging_xcm::VersionedXcm;
/// # use std::convert::Infallible;
///
/// /// A sender that only passes the message through and does nothing.
/// struct Sender1;
/// impl SendXcm for Sender1 {
/// type Ticket = Infallible;
/// fn validate(_: &mut Option<Location>, _: &mut Option<Xcm<()>>) -> SendResult<Infallible> {
/// Err(SendError::NotApplicable)
/// }
/// fn deliver(_: Infallible) -> Result<XcmHash, SendError> {
/// unreachable!()
/// }
/// }
///
/// /// A sender that accepts a message that has two junctions, otherwise stops the routing.
/// struct Sender2;
/// impl SendXcm for Sender2 {
/// type Ticket = ();
/// fn validate(destination: &mut Option<Location>, message: &mut Option<Xcm<()>>) -> SendResult<()> {
/// match destination.as_ref().ok_or(SendError::MissingArgument)?.unpack() {
/// (0, [j1, j2]) => Ok(((), Assets::new())),
/// _ => Err(SendError::Unroutable),
/// }
/// }
/// fn deliver(_: ()) -> Result<XcmHash, SendError> {
/// Ok([0; 32])
/// }
/// }
///
/// /// A sender that accepts a message from a parent, passing through otherwise.
/// struct Sender3;
/// impl SendXcm for Sender3 {
/// type Ticket = ();
/// fn validate(destination: &mut Option<Location>, message: &mut Option<Xcm<()>>) -> SendResult<()> {
/// match destination.as_ref().ok_or(SendError::MissingArgument)?.unpack() {
/// (1, []) => Ok(((), Assets::new())),
/// _ => Err(SendError::NotApplicable),
/// }
/// }
/// fn deliver(_: ()) -> Result<XcmHash, SendError> {
/// Ok([0; 32])
/// }
/// }
///
/// // A call to send via XCM. We don't really care about this.
/// # fn main() {
/// let call: Vec<u8> = ().encode();
/// let message = Xcm(vec![Instruction::Transact {
/// origin_kind: OriginKind::Superuser,
/// require_weight_at_most: Weight::zero(),
/// call: call.into(),
/// }]);
/// let message_hash = message.using_encoded(sp_io::hashing::blake2_256);
///
/// // Sender2 will block this.
/// assert!(send_xcm::<(Sender1, Sender2, Sender3)>(Parent.into(), message.clone()).is_err());
///
/// // Sender3 will catch this.
/// assert!(send_xcm::<(Sender1, Sender3)>(Parent.into(), message.clone()).is_ok());
/// # }
/// ```
pub trait SendXcm {
/// Intermediate value which connects the two phases of the send operation.
type Ticket;
/// Check whether the given `_message` is deliverable to the given `_destination` and if so
/// determine the cost which will be paid by this chain to do so, returning a `Validated` token
/// which can be used to enact delivery.
///
/// The `destination` and `message` must be `Some` (or else an error will be returned) and they
/// may only be consumed if the `Err` is not `NotApplicable`.
///
/// If it is not a destination which can be reached with this type but possibly could by others,
/// then this *MUST* return `NotApplicable`. Any other error will cause the tuple
/// implementation to exit early without trying other type fields.
fn validate(
destination: &mut Option<Location>,
message: &mut Option<Xcm<()>>,
) -> SendResult<Self::Ticket>;
/// Actually carry out the delivery operation for a previously validated message sending.
fn deliver(ticket: Self::Ticket) -> result::Result<XcmHash, SendError>;
}
#[impl_trait_for_tuples::impl_for_tuples(30)]
impl SendXcm for Tuple {
for_tuples! { type Ticket = (#( Option<Tuple::Ticket> ),* ); }
fn validate(
destination: &mut Option<Location>,
message: &mut Option<Xcm<()>>,
) -> SendResult<Self::Ticket> {
let mut maybe_cost: Option<Assets> = None;
let one_ticket: Self::Ticket = (for_tuples! { #(
if maybe_cost.is_some() {
None
} else {
match Tuple::validate(destination, message) {
Err(SendError::NotApplicable) => None,
Err(e) => { return Err(e) },
Ok((v, c)) => {
maybe_cost = Some(c);
Some(v)
},
}
}
),* });
if let Some(cost) = maybe_cost {
Ok((one_ticket, cost))
} else {
Err(SendError::NotApplicable)
}
}
fn deliver(one_ticket: Self::Ticket) -> result::Result<XcmHash, SendError> {
for_tuples!( #(
if let Some(validated) = one_ticket.Tuple {
return Tuple::deliver(validated);
}
)* );
Err(SendError::Unroutable)
}
}
/// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps
/// both in `Some` before passing them as mutable references into `T::send_xcm`.
pub fn validate_send<T: SendXcm>(dest: Location, msg: Xcm<()>) -> SendResult<T::Ticket> {
T::validate(&mut Some(dest), &mut Some(msg))
}
/// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps
/// both in `Some` before passing them as mutable references into `T::send_xcm`.
///
/// Returns either `Ok` with the price of the delivery, or `Err` with the reason why the message
/// could not be sent.
///
/// Generally you'll want to validate and get the price first to ensure that the sender can pay it
/// before actually doing the delivery.
pub fn send_xcm<T: SendXcm>(
dest: Location,
msg: Xcm<()>,
) -> result::Result<(XcmHash, Assets), SendError> {
let (ticket, price) = T::validate(&mut Some(dest), &mut Some(msg))?;
let hash = T::deliver(ticket)?;
Ok((hash, price))
}
File diff suppressed because it is too large Load Diff
+327
View File
@@ -0,0 +1,327 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Support data structures for `Location`, primarily the `Junction` datatype.
use super::Location;
pub use crate::v4::{BodyId, BodyPart};
use crate::{
v4::{Junction as OldJunction, NetworkId as OldNetworkId},
VersionedLocation,
};
use bounded_collections::{BoundedSlice, BoundedVec, ConstU32};
use codec::{self, Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
use hex_literal::hex;
use scale_info::TypeInfo;
use serde::{Deserialize, Serialize};
/// A single item in a path to describe the relative location of a consensus system.
///
/// Each item assumes a pre-existing location as its context and is defined in terms of it.
#[derive(
Copy,
Clone,
Eq,
PartialEq,
Ord,
PartialOrd,
Encode,
Decode,
DecodeWithMemTracking,
Debug,
TypeInfo,
MaxEncodedLen,
Serialize,
Deserialize,
)]
pub enum Junction {
/// An indexed teyrchain belonging to and operated by the context.
///
/// Generally used when the context is a Pezkuwi Relay-chain.
Teyrchain(#[codec(compact)] u32),
/// A 32-byte identifier for an account of a specific network that is respected as a sovereign
/// endpoint within the context.
///
/// Generally used when the context is a Substrate-based chain.
AccountId32 { network: Option<NetworkId>, id: [u8; 32] },
/// An 8-byte index for an account of a specific network that is respected as a sovereign
/// endpoint within the context.
///
/// May be used when the context is a Frame-based chain and includes e.g. an indices pallet.
AccountIndex64 {
network: Option<NetworkId>,
#[codec(compact)]
index: u64,
},
/// A 20-byte identifier for an account of a specific network that is respected as a sovereign
/// endpoint within the context.
///
/// May be used when the context is an Ethereum or Bitcoin chain or smart-contract.
AccountKey20 { network: Option<NetworkId>, key: [u8; 20] },
/// An instanced, indexed pallet that forms a constituent part of the context.
///
/// Generally used when the context is a Frame-based chain.
PalletInstance(u8),
/// A non-descript index within the context location.
///
/// Usage will vary widely owing to its generality.
///
/// NOTE: Try to avoid using this and instead use a more specific item.
GeneralIndex(#[codec(compact)] u128),
/// A nondescript array datum, 32 bytes, acting as a key within the context
/// location.
///
/// Usage will vary widely owing to its generality.
///
/// NOTE: Try to avoid using this and instead use a more specific item.
// Note this is implemented as an array with a length rather than using `BoundedVec` owing to
// the bound for `Copy`.
GeneralKey { length: u8, data: [u8; 32] },
/// The unambiguous child.
///
/// Not currently used except as a fallback when deriving context.
OnlyChild,
/// A pluralistic body existing within consensus.
///
/// Typical to be used to represent a governance origin of a chain, but could in principle be
/// used to represent things such as multisigs also.
Plurality { id: BodyId, part: BodyPart },
/// A global network capable of externalizing its own consensus. This is not generally
/// meaningful outside of the universal level.
GlobalConsensus(NetworkId),
}
/// The genesis hash of the Zagros testnet. Used to identify it.
pub const ZAGROS_GENESIS_HASH: [u8; 32] =
hex!["e143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e"];
/// The genesis hash of the Pezkuwichain testnet. Used to identify it.
pub const PEZKUWICHAIN_GENESIS_HASH: [u8; 32] =
hex!["6408de7737c59c238890533af25896a2c20608d8b380bb01029acb392781063e"];
/// Dummy genesis hash used instead of defunct networks like Wococo (and soon Pezkuwichain).
pub const DUMMY_GENESIS_HASH: [u8; 32] = [0; 32];
/// A global identifier of a data structure existing within consensus.
///
/// Maintenance note: Networks with global consensus and which are practically bridgeable within the
/// Pezkuwi ecosystem are given preference over explicit naming in this enumeration.
#[derive(
Copy,
Clone,
Eq,
PartialEq,
Ord,
PartialOrd,
Encode,
Decode,
DecodeWithMemTracking,
Debug,
TypeInfo,
MaxEncodedLen,
Serialize,
Deserialize,
)]
pub enum NetworkId {
/// Network specified by the first 32 bytes of its genesis block.
ByGenesis([u8; 32]),
/// Network defined by the first 32-bytes of the hash and number of some block it contains.
ByFork { block_number: u64, block_hash: [u8; 32] },
/// The Pezkuwi mainnet Relay-chain.
Pezkuwi,
/// The Kusama canary-net Relay-chain.
Kusama,
/// An Ethereum network specified by its chain ID.
#[codec(index = 7)]
Ethereum {
/// The EIP-155 chain ID.
#[codec(compact)]
chain_id: u64,
},
/// The Bitcoin network, including hard-forks supported by Bitcoin Core development team.
#[codec(index = 8)]
BitcoinCore,
/// The Bitcoin network, including hard-forks supported by Bitcoin Cash developers.
#[codec(index = 9)]
BitcoinCash,
/// The Pezkuwi Bulletin chain.
#[codec(index = 10)]
PezkuwiBulletin,
}
impl From<OldNetworkId> for Option<NetworkId> {
fn from(old: OldNetworkId) -> Self {
Some(NetworkId::from(old))
}
}
impl From<OldNetworkId> for NetworkId {
fn from(old: OldNetworkId) -> Self {
use OldNetworkId::*;
match old {
ByGenesis(hash) => Self::ByGenesis(hash),
ByFork { block_number, block_hash } => Self::ByFork { block_number, block_hash },
Pezkuwi => Self::Pezkuwi,
Kusama => Self::Kusama,
Zagros => Self::ByGenesis(ZAGROS_GENESIS_HASH),
Pezkuwichain => Self::ByGenesis(PEZKUWICHAIN_GENESIS_HASH),
Wococo => Self::ByGenesis(DUMMY_GENESIS_HASH),
Ethereum { chain_id } => Self::Ethereum { chain_id },
BitcoinCore => Self::BitcoinCore,
BitcoinCash => Self::BitcoinCash,
PezkuwiBulletin => Self::PezkuwiBulletin,
}
}
}
impl From<NetworkId> for Junction {
fn from(n: NetworkId) -> Self {
Self::GlobalConsensus(n)
}
}
impl From<[u8; 32]> for Junction {
fn from(id: [u8; 32]) -> Self {
Self::AccountId32 { network: None, id }
}
}
impl From<BoundedVec<u8, ConstU32<32>>> for Junction {
fn from(key: BoundedVec<u8, ConstU32<32>>) -> Self {
key.as_bounded_slice().into()
}
}
impl<'a> From<BoundedSlice<'a, u8, ConstU32<32>>> for Junction {
fn from(key: BoundedSlice<'a, u8, ConstU32<32>>) -> Self {
let mut data = [0u8; 32];
data[..key.len()].copy_from_slice(&key[..]);
Self::GeneralKey { length: key.len() as u8, data }
}
}
impl<'a> TryFrom<&'a Junction> for BoundedSlice<'a, u8, ConstU32<32>> {
type Error = ();
fn try_from(key: &'a Junction) -> Result<Self, ()> {
match key {
Junction::GeneralKey { length, data } =>
BoundedSlice::try_from(&data[..data.len().min(*length as usize)]).map_err(|_| ()),
_ => Err(()),
}
}
}
impl From<[u8; 20]> for Junction {
fn from(key: [u8; 20]) -> Self {
Self::AccountKey20 { network: None, key }
}
}
impl From<u64> for Junction {
fn from(index: u64) -> Self {
Self::AccountIndex64 { network: None, index }
}
}
impl From<u128> for Junction {
fn from(id: u128) -> Self {
Self::GeneralIndex(id)
}
}
impl TryFrom<OldJunction> for Junction {
type Error = ();
fn try_from(value: OldJunction) -> Result<Self, ()> {
use OldJunction::*;
Ok(match value {
Teyrchain(id) => Self::Teyrchain(id),
AccountId32 { network: maybe_network, id } =>
Self::AccountId32 { network: maybe_network.map(|network| network.into()), id },
AccountIndex64 { network: maybe_network, index } =>
Self::AccountIndex64 { network: maybe_network.map(|network| network.into()), index },
AccountKey20 { network: maybe_network, key } =>
Self::AccountKey20 { network: maybe_network.map(|network| network.into()), key },
PalletInstance(index) => Self::PalletInstance(index),
GeneralIndex(id) => Self::GeneralIndex(id),
GeneralKey { length, data } => Self::GeneralKey { length, data },
OnlyChild => Self::OnlyChild,
Plurality { id, part } => Self::Plurality { id, part },
GlobalConsensus(network) => Self::GlobalConsensus(network.into()),
})
}
}
impl Junction {
/// Convert `self` into a `Location` containing 0 parents.
///
/// Similar to `Into::into`, except that this method can be used in a const evaluation context.
pub fn into_location(self) -> Location {
Location::new(0, [self])
}
/// Convert `self` into a `Location` containing `n` parents.
///
/// Similar to `Self::into_location`, with the added ability to specify the number of parent
/// junctions.
pub fn into_exterior(self, n: u8) -> Location {
Location::new(n, [self])
}
/// Convert `self` into a `VersionedLocation` containing 0 parents.
///
/// Similar to `Into::into`, except that this method can be used in a const evaluation context.
pub fn into_versioned(self) -> VersionedLocation {
self.into_location().into_versioned()
}
/// Remove the `NetworkId` value.
pub fn remove_network_id(&mut self) {
use Junction::*;
match self {
AccountId32 { ref mut network, .. } |
AccountIndex64 { ref mut network, .. } |
AccountKey20 { ref mut network, .. } => *network = None,
_ => {},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
#[test]
fn junction_round_trip_works() {
let j = Junction::GeneralKey { length: 32, data: [1u8; 32] };
let k = Junction::try_from(OldJunction::try_from(j).unwrap()).unwrap();
assert_eq!(j, k);
let j = OldJunction::GeneralKey { length: 32, data: [1u8; 32] };
let k = OldJunction::try_from(Junction::try_from(j).unwrap()).unwrap();
assert_eq!(j, k);
let j = Junction::from(BoundedVec::try_from(vec![1u8, 2, 3, 4]).unwrap());
let k = Junction::try_from(OldJunction::try_from(j).unwrap()).unwrap();
assert_eq!(j, k);
let s: BoundedSlice<_, _> = (&k).try_into().unwrap();
assert_eq!(s, &[1u8, 2, 3, 4][..]);
let j = OldJunction::GeneralKey { length: 32, data: [1u8; 32] };
let k = OldJunction::try_from(Junction::try_from(j).unwrap()).unwrap();
assert_eq!(j, k);
}
}
+724
View File
@@ -0,0 +1,724 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! XCM `Junctions`/`InteriorLocation` datatype.
use super::{Junction, Location, NetworkId};
use alloc::sync::Arc;
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
use core::{mem, ops::Range, result};
use scale_info::TypeInfo;
/// Maximum number of `Junction`s that a `Junctions` can contain.
pub(crate) const MAX_JUNCTIONS: usize = 8;
/// Non-parent junctions that can be constructed, up to the length of 8. This specific `Junctions`
/// implementation uses a Rust `enum` in order to make pattern matching easier.
///
/// Parent junctions cannot be constructed with this type. Refer to `Location` for
/// instructions on constructing parent junctions.
#[derive(
Clone,
Eq,
PartialEq,
Ord,
PartialOrd,
Encode,
Decode,
DecodeWithMemTracking,
Debug,
TypeInfo,
MaxEncodedLen,
serde::Serialize,
serde::Deserialize,
)]
pub enum Junctions {
/// The interpreting consensus system.
Here,
/// A relative path comprising 1 junction.
X1(Arc<[Junction; 1]>),
/// A relative path comprising 2 junctions.
X2(Arc<[Junction; 2]>),
/// A relative path comprising 3 junctions.
X3(Arc<[Junction; 3]>),
/// A relative path comprising 4 junctions.
X4(Arc<[Junction; 4]>),
/// A relative path comprising 5 junctions.
X5(Arc<[Junction; 5]>),
/// A relative path comprising 6 junctions.
X6(Arc<[Junction; 6]>),
/// A relative path comprising 7 junctions.
X7(Arc<[Junction; 7]>),
/// A relative path comprising 8 junctions.
X8(Arc<[Junction; 8]>),
}
macro_rules! impl_junctions {
($count:expr, $variant:ident) => {
impl From<[Junction; $count]> for Junctions {
fn from(junctions: [Junction; $count]) -> Self {
Self::$variant(Arc::new(junctions))
}
}
impl PartialEq<[Junction; $count]> for Junctions {
fn eq(&self, rhs: &[Junction; $count]) -> bool {
self.as_slice() == rhs
}
}
};
}
impl_junctions!(1, X1);
impl_junctions!(2, X2);
impl_junctions!(3, X3);
impl_junctions!(4, X4);
impl_junctions!(5, X5);
impl_junctions!(6, X6);
impl_junctions!(7, X7);
impl_junctions!(8, X8);
pub struct JunctionsIterator {
junctions: Junctions,
range: Range<usize>,
}
impl Iterator for JunctionsIterator {
type Item = Junction;
fn next(&mut self) -> Option<Junction> {
self.junctions.at(self.range.next()?).cloned()
}
}
impl DoubleEndedIterator for JunctionsIterator {
fn next_back(&mut self) -> Option<Junction> {
self.junctions.at(self.range.next_back()?).cloned()
}
}
pub struct JunctionsRefIterator<'a> {
junctions: &'a Junctions,
range: Range<usize>,
}
impl<'a> Iterator for JunctionsRefIterator<'a> {
type Item = &'a Junction;
fn next(&mut self) -> Option<&'a Junction> {
self.junctions.at(self.range.next()?)
}
}
impl<'a> DoubleEndedIterator for JunctionsRefIterator<'a> {
fn next_back(&mut self) -> Option<&'a Junction> {
self.junctions.at(self.range.next_back()?)
}
}
impl<'a> IntoIterator for &'a Junctions {
type Item = &'a Junction;
type IntoIter = JunctionsRefIterator<'a>;
fn into_iter(self) -> Self::IntoIter {
JunctionsRefIterator { junctions: self, range: 0..self.len() }
}
}
impl IntoIterator for Junctions {
type Item = Junction;
type IntoIter = JunctionsIterator;
fn into_iter(self) -> Self::IntoIter {
JunctionsIterator { range: 0..self.len(), junctions: self }
}
}
impl Junctions {
/// Convert `self` into a `Location` containing 0 parents.
///
/// Similar to `Into::into`, except that this method can be used in a const evaluation context.
pub const fn into_location(self) -> Location {
Location { parents: 0, interior: self }
}
/// Convert `self` into a `Location` containing `n` parents.
///
/// Similar to `Self::into_location`, with the added ability to specify the number of parent
/// junctions.
pub const fn into_exterior(self, n: u8) -> Location {
Location { parents: n, interior: self }
}
/// Casts `self` into a slice containing `Junction`s.
pub fn as_slice(&self) -> &[Junction] {
match self {
Junctions::Here => &[],
Junctions::X1(ref a) => &a[..],
Junctions::X2(ref a) => &a[..],
Junctions::X3(ref a) => &a[..],
Junctions::X4(ref a) => &a[..],
Junctions::X5(ref a) => &a[..],
Junctions::X6(ref a) => &a[..],
Junctions::X7(ref a) => &a[..],
Junctions::X8(ref a) => &a[..],
}
}
/// Casts `self` into a mutable slice containing `Junction`s.
pub fn as_slice_mut(&mut self) -> &mut [Junction] {
match self {
Junctions::Here => &mut [],
Junctions::X1(ref mut a) => &mut Arc::make_mut(a)[..],
Junctions::X2(ref mut a) => &mut Arc::make_mut(a)[..],
Junctions::X3(ref mut a) => &mut Arc::make_mut(a)[..],
Junctions::X4(ref mut a) => &mut Arc::make_mut(a)[..],
Junctions::X5(ref mut a) => &mut Arc::make_mut(a)[..],
Junctions::X6(ref mut a) => &mut Arc::make_mut(a)[..],
Junctions::X7(ref mut a) => &mut Arc::make_mut(a)[..],
Junctions::X8(ref mut a) => &mut Arc::make_mut(a)[..],
}
}
/// Remove the `NetworkId` value in any `Junction`s.
pub fn remove_network_id(&mut self) {
self.for_each_mut(Junction::remove_network_id);
}
/// Treating `self` as the universal context, return the location of the local consensus system
/// from the point of view of the given `target`.
pub fn invert_target(&self, target: &Location) -> Result<Location, ()> {
let mut itself = self.clone();
let mut junctions = Self::Here;
for _ in 0..target.parent_count() {
junctions = junctions
.pushed_front_with(itself.take_last().unwrap_or(Junction::OnlyChild))
.map_err(|_| ())?;
}
let parents = target.interior().len() as u8;
Ok(Location::new(parents, junctions))
}
/// Execute a function `f` on every junction. We use this since we cannot implement a mutable
/// `Iterator` without unsafe code.
pub fn for_each_mut(&mut self, x: impl FnMut(&mut Junction)) {
self.as_slice_mut().iter_mut().for_each(x)
}
/// Extract the network ID treating this value as a universal location.
///
/// This will return an `Err` if the first item is not a `GlobalConsensus`, which would indicate
/// that this value is not a universal location.
pub fn global_consensus(&self) -> Result<NetworkId, ()> {
if let Some(Junction::GlobalConsensus(network)) = self.first() {
Ok(*network)
} else {
Err(())
}
}
/// Extract the network ID and the interior consensus location, treating this value as a
/// universal location.
///
/// This will return an `Err` if the first item is not a `GlobalConsensus`, which would indicate
/// that this value is not a universal location.
pub fn split_global(self) -> Result<(NetworkId, Junctions), ()> {
match self.split_first() {
(location, Some(Junction::GlobalConsensus(network))) => Ok((network, location)),
_ => return Err(()),
}
}
/// Treat `self` as a universal location and the context of `relative`, returning the universal
/// location of relative.
///
/// This will return an error if `relative` has as many (or more) parents than there are
/// junctions in `self`, implying that relative refers into a different global consensus.
pub fn within_global(mut self, relative: Location) -> Result<Self, ()> {
if self.len() <= relative.parent_count() as usize {
return Err(());
}
for _ in 0..relative.parent_count() {
self.take_last();
}
for j in relative.interior() {
self.push(*j).map_err(|_| ())?;
}
Ok(self)
}
/// Consumes `self` and returns how `viewer` would address it locally.
pub fn relative_to(mut self, viewer: &Junctions) -> Location {
let mut i = 0;
while match (self.first(), viewer.at(i)) {
(Some(x), Some(y)) => x == y,
_ => false,
} {
self = self.split_first().0;
// NOTE: Cannot overflow as loop can only iterate at most `MAX_JUNCTIONS` times.
i += 1;
}
// AUDIT NOTES:
// - above loop ensures that `i <= viewer.len()`.
// - `viewer.len()` is at most `MAX_JUNCTIONS`, so won't overflow a `u8`.
Location::new((viewer.len() - i) as u8, self)
}
/// Returns first junction, or `None` if the location is empty.
pub fn first(&self) -> Option<&Junction> {
self.as_slice().first()
}
/// Returns last junction, or `None` if the location is empty.
pub fn last(&self) -> Option<&Junction> {
self.as_slice().last()
}
/// Splits off the first junction, returning the remaining suffix (first item in tuple) and the
/// first element (second item in tuple) or `None` if it was empty.
pub fn split_first(self) -> (Junctions, Option<Junction>) {
match self {
Junctions::Here => (Junctions::Here, None),
Junctions::X1(xs) => {
let [a] = *xs;
(Junctions::Here, Some(a))
},
Junctions::X2(xs) => {
let [a, b] = *xs;
([b].into(), Some(a))
},
Junctions::X3(xs) => {
let [a, b, c] = *xs;
([b, c].into(), Some(a))
},
Junctions::X4(xs) => {
let [a, b, c, d] = *xs;
([b, c, d].into(), Some(a))
},
Junctions::X5(xs) => {
let [a, b, c, d, e] = *xs;
([b, c, d, e].into(), Some(a))
},
Junctions::X6(xs) => {
let [a, b, c, d, e, f] = *xs;
([b, c, d, e, f].into(), Some(a))
},
Junctions::X7(xs) => {
let [a, b, c, d, e, f, g] = *xs;
([b, c, d, e, f, g].into(), Some(a))
},
Junctions::X8(xs) => {
let [a, b, c, d, e, f, g, h] = *xs;
([b, c, d, e, f, g, h].into(), Some(a))
},
}
}
/// Splits off the last junction, returning the remaining prefix (first item in tuple) and the
/// last element (second item in tuple) or `None` if it was empty.
pub fn split_last(self) -> (Junctions, Option<Junction>) {
match self {
Junctions::Here => (Junctions::Here, None),
Junctions::X1(xs) => {
let [a] = *xs;
(Junctions::Here, Some(a))
},
Junctions::X2(xs) => {
let [a, b] = *xs;
([a].into(), Some(b))
},
Junctions::X3(xs) => {
let [a, b, c] = *xs;
([a, b].into(), Some(c))
},
Junctions::X4(xs) => {
let [a, b, c, d] = *xs;
([a, b, c].into(), Some(d))
},
Junctions::X5(xs) => {
let [a, b, c, d, e] = *xs;
([a, b, c, d].into(), Some(e))
},
Junctions::X6(xs) => {
let [a, b, c, d, e, f] = *xs;
([a, b, c, d, e].into(), Some(f))
},
Junctions::X7(xs) => {
let [a, b, c, d, e, f, g] = *xs;
([a, b, c, d, e, f].into(), Some(g))
},
Junctions::X8(xs) => {
let [a, b, c, d, e, f, g, h] = *xs;
([a, b, c, d, e, f, g].into(), Some(h))
},
}
}
/// Removes the first element from `self`, returning it (or `None` if it was empty).
pub fn take_first(&mut self) -> Option<Junction> {
let mut d = Junctions::Here;
mem::swap(&mut *self, &mut d);
let (tail, head) = d.split_first();
*self = tail;
head
}
/// Removes the last element from `self`, returning it (or `None` if it was empty).
pub fn take_last(&mut self) -> Option<Junction> {
let mut d = Junctions::Here;
mem::swap(&mut *self, &mut d);
let (head, tail) = d.split_last();
*self = head;
tail
}
/// Mutates `self` to be appended with `new` or returns an `Err` with `new` if would overflow.
pub fn push(&mut self, new: impl Into<Junction>) -> result::Result<(), Junction> {
let new = new.into();
let mut dummy = Junctions::Here;
mem::swap(self, &mut dummy);
match dummy.pushed_with(new) {
Ok(s) => {
*self = s;
Ok(())
},
Err((s, j)) => {
*self = s;
Err(j)
},
}
}
/// Mutates `self` to be prepended with `new` or returns an `Err` with `new` if would overflow.
pub fn push_front(&mut self, new: impl Into<Junction>) -> result::Result<(), Junction> {
let new = new.into();
let mut dummy = Junctions::Here;
mem::swap(self, &mut dummy);
match dummy.pushed_front_with(new) {
Ok(s) => {
*self = s;
Ok(())
},
Err((s, j)) => {
*self = s;
Err(j)
},
}
}
/// Consumes `self` and returns a `Junctions` suffixed with `new`, or an `Err` with the
/// original value of `self` and `new` in case of overflow.
pub fn pushed_with(self, new: impl Into<Junction>) -> result::Result<Self, (Self, Junction)> {
let new = new.into();
Ok(match self {
Junctions::Here => [new].into(),
Junctions::X1(xs) => {
let [a] = *xs;
[a, new].into()
},
Junctions::X2(xs) => {
let [a, b] = *xs;
[a, b, new].into()
},
Junctions::X3(xs) => {
let [a, b, c] = *xs;
[a, b, c, new].into()
},
Junctions::X4(xs) => {
let [a, b, c, d] = *xs;
[a, b, c, d, new].into()
},
Junctions::X5(xs) => {
let [a, b, c, d, e] = *xs;
[a, b, c, d, e, new].into()
},
Junctions::X6(xs) => {
let [a, b, c, d, e, f] = *xs;
[a, b, c, d, e, f, new].into()
},
Junctions::X7(xs) => {
let [a, b, c, d, e, f, g] = *xs;
[a, b, c, d, e, f, g, new].into()
},
s => Err((s, new))?,
})
}
/// Consumes `self` and returns a `Junctions` prefixed with `new`, or an `Err` with the
/// original value of `self` and `new` in case of overflow.
pub fn pushed_front_with(
self,
new: impl Into<Junction>,
) -> result::Result<Self, (Self, Junction)> {
let new = new.into();
Ok(match self {
Junctions::Here => [new].into(),
Junctions::X1(xs) => {
let [a] = *xs;
[new, a].into()
},
Junctions::X2(xs) => {
let [a, b] = *xs;
[new, a, b].into()
},
Junctions::X3(xs) => {
let [a, b, c] = *xs;
[new, a, b, c].into()
},
Junctions::X4(xs) => {
let [a, b, c, d] = *xs;
[new, a, b, c, d].into()
},
Junctions::X5(xs) => {
let [a, b, c, d, e] = *xs;
[new, a, b, c, d, e].into()
},
Junctions::X6(xs) => {
let [a, b, c, d, e, f] = *xs;
[new, a, b, c, d, e, f].into()
},
Junctions::X7(xs) => {
let [a, b, c, d, e, f, g] = *xs;
[new, a, b, c, d, e, f, g].into()
},
s => Err((s, new))?,
})
}
/// Mutate `self` so that it is suffixed with `suffix`.
///
/// Does not modify `self` and returns `Err` with `suffix` in case of overflow.
///
/// # Example
/// ```rust
/// # use staging_xcm::v5::{Junctions, Junction::*, Location};
/// # fn main() {
/// let mut m = Junctions::from([Teyrchain(21)]);
/// assert_eq!(m.append_with([PalletInstance(3)]), Ok(()));
/// assert_eq!(m, [Teyrchain(21), PalletInstance(3)]);
/// # }
/// ```
pub fn append_with(&mut self, suffix: impl Into<Junctions>) -> Result<(), Junctions> {
let suffix = suffix.into();
if self.len().saturating_add(suffix.len()) > MAX_JUNCTIONS {
return Err(suffix);
}
for j in suffix.into_iter() {
self.push(j).expect("Already checked the sum of the len()s; qed")
}
Ok(())
}
/// Returns the number of junctions in `self`.
pub fn len(&self) -> usize {
self.as_slice().len()
}
/// Returns the junction at index `i`, or `None` if the location doesn't contain that many
/// elements.
pub fn at(&self, i: usize) -> Option<&Junction> {
self.as_slice().get(i)
}
/// Returns a mutable reference to the junction at index `i`, or `None` if the location doesn't
/// contain that many elements.
pub fn at_mut(&mut self, i: usize) -> Option<&mut Junction> {
self.as_slice_mut().get_mut(i)
}
/// Returns a reference iterator over the junctions.
pub fn iter(&self) -> JunctionsRefIterator<'_> {
JunctionsRefIterator { junctions: self, range: 0..self.len() }
}
/// Ensures that self begins with `prefix` and that it has a single `Junction` item following.
/// If so, returns a reference to this `Junction` item.
///
/// # Example
/// ```rust
/// # use staging_xcm::v5::{Junctions, Junction::*};
/// # fn main() {
/// let mut m = Junctions::from([Teyrchain(2), PalletInstance(3), OnlyChild]);
/// assert_eq!(m.match_and_split(&[Teyrchain(2), PalletInstance(3)].into()), Some(&OnlyChild));
/// assert_eq!(m.match_and_split(&[Teyrchain(2)].into()), None);
/// # }
/// ```
pub fn match_and_split(&self, prefix: &Junctions) -> Option<&Junction> {
if prefix.len() + 1 != self.len() {
return None;
}
for i in 0..prefix.len() {
if prefix.at(i) != self.at(i) {
return None;
}
}
return self.at(prefix.len());
}
pub fn starts_with(&self, prefix: &Junctions) -> bool {
prefix.len() <= self.len() && prefix.iter().zip(self.iter()).all(|(x, y)| x == y)
}
}
impl TryFrom<Location> for Junctions {
type Error = Location;
fn try_from(x: Location) -> result::Result<Self, Location> {
if x.parent_count() > 0 {
Err(x)
} else {
Ok(x.interior().clone())
}
}
}
impl<T: Into<Junction>> From<T> for Junctions {
fn from(x: T) -> Self {
[x.into()].into()
}
}
impl From<[Junction; 0]> for Junctions {
fn from(_: [Junction; 0]) -> Self {
Self::Here
}
}
impl From<()> for Junctions {
fn from(_: ()) -> Self {
Self::Here
}
}
xcm_procedural::impl_conversion_functions_for_junctions_v5!();
#[cfg(test)]
mod tests {
use super::{super::prelude::*, *};
#[test]
fn inverting_works() {
let context: InteriorLocation = (Teyrchain(1000), PalletInstance(42)).into();
let target = (Parent, PalletInstance(69)).into();
let expected = (Parent, PalletInstance(42)).into();
let inverted = context.invert_target(&target).unwrap();
assert_eq!(inverted, expected);
let context: InteriorLocation =
(Teyrchain(1000), PalletInstance(42), GeneralIndex(1)).into();
let target = (Parent, Parent, PalletInstance(69), GeneralIndex(2)).into();
let expected = (Parent, Parent, PalletInstance(42), GeneralIndex(1)).into();
let inverted = context.invert_target(&target).unwrap();
assert_eq!(inverted, expected);
}
#[test]
fn relative_to_works() {
use NetworkId::*;
assert_eq!(
Junctions::from([Pezkuwi.into()]).relative_to(&Junctions::from([Kusama.into()])),
(Parent, Pezkuwi).into()
);
let base = Junctions::from([Kusama.into(), Teyrchain(1), PalletInstance(1)]);
// Ancestors.
assert_eq!(Here.relative_to(&base), (Parent, Parent, Parent).into());
assert_eq!(Junctions::from([Kusama.into()]).relative_to(&base), (Parent, Parent).into());
assert_eq!(
Junctions::from([Kusama.into(), Teyrchain(1)]).relative_to(&base),
(Parent,).into()
);
assert_eq!(
Junctions::from([Kusama.into(), Teyrchain(1), PalletInstance(1)]).relative_to(&base),
Here.into()
);
// Ancestors with one child.
assert_eq!(
Junctions::from([Pezkuwi.into()]).relative_to(&base),
(Parent, Parent, Parent, Pezkuwi).into()
);
assert_eq!(
Junctions::from([Kusama.into(), Teyrchain(2)]).relative_to(&base),
(Parent, Parent, Teyrchain(2)).into()
);
assert_eq!(
Junctions::from([Kusama.into(), Teyrchain(1), PalletInstance(2)]).relative_to(&base),
(Parent, PalletInstance(2)).into()
);
assert_eq!(
Junctions::from([Kusama.into(), Teyrchain(1), PalletInstance(1), [1u8; 32].into()])
.relative_to(&base),
([1u8; 32],).into()
);
// Ancestors with grandchildren.
assert_eq!(
Junctions::from([Pezkuwi.into(), Teyrchain(1)]).relative_to(&base),
(Parent, Parent, Parent, Pezkuwi, Teyrchain(1)).into()
);
assert_eq!(
Junctions::from([Kusama.into(), Teyrchain(2), PalletInstance(1)]).relative_to(&base),
(Parent, Parent, Teyrchain(2), PalletInstance(1)).into()
);
assert_eq!(
Junctions::from([Kusama.into(), Teyrchain(1), PalletInstance(2), [1u8; 32].into()])
.relative_to(&base),
(Parent, PalletInstance(2), [1u8; 32]).into()
);
assert_eq!(
Junctions::from([
Kusama.into(),
Teyrchain(1),
PalletInstance(1),
[1u8; 32].into(),
1u128.into()
])
.relative_to(&base),
([1u8; 32], 1u128).into()
);
}
#[test]
fn global_consensus_works() {
use NetworkId::*;
assert_eq!(Junctions::from([Pezkuwi.into()]).global_consensus(), Ok(Pezkuwi));
assert_eq!(Junctions::from([Kusama.into(), 1u64.into()]).global_consensus(), Ok(Kusama));
assert_eq!(Here.global_consensus(), Err(()));
assert_eq!(Junctions::from([1u64.into()]).global_consensus(), Err(()));
assert_eq!(Junctions::from([1u64.into(), Kusama.into()]).global_consensus(), Err(()));
}
#[test]
fn test_conversion() {
use super::{Junction::*, NetworkId::*};
let x: Junctions = GlobalConsensus(Pezkuwi).into();
assert_eq!(x, Junctions::from([GlobalConsensus(Pezkuwi)]));
let x: Junctions = Pezkuwi.into();
assert_eq!(x, Junctions::from([GlobalConsensus(Pezkuwi)]));
let x: Junctions = (Pezkuwi, Kusama).into();
assert_eq!(x, Junctions::from([GlobalConsensus(Pezkuwi), GlobalConsensus(Kusama)]));
}
#[test]
fn encode_decode_junctions_works() {
let original = Junctions::from([
Pezkuwi.into(),
Kusama.into(),
1u64.into(),
GlobalConsensus(Pezkuwi),
Teyrchain(123),
PalletInstance(45),
]);
let encoded = original.encode();
assert_eq!(encoded, &[6, 9, 2, 9, 3, 2, 0, 4, 9, 2, 0, 237, 1, 4, 45]);
let decoded = Junctions::decode(&mut &encoded[..]).unwrap();
assert_eq!(decoded, original);
}
}
+756
View File
@@ -0,0 +1,756 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! XCM `Location` datatype.
use super::{traits::Reanchorable, Junction, Junctions};
use crate::{v4::Location as OldLocation, VersionedLocation};
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
use core::result;
use scale_info::TypeInfo;
/// A relative path between state-bearing consensus systems.
///
/// A location in a consensus system is defined as an *isolatable state machine* held within global
/// consensus. The location in question need not have a sophisticated consensus algorithm of its
/// own; a single account within Ethereum, for example, could be considered a location.
///
/// A very-much non-exhaustive list of types of location include:
/// - A (normal, layer-1) block chain, e.g. the Bitcoin mainnet or a teyrchain.
/// - A layer-0 super-chain, e.g. the Pezkuwi Relay chain.
/// - A layer-2 smart contract, e.g. an ERC-20 on Ethereum.
/// - A logical functional component of a chain, e.g. a single instance of a pallet on a Frame-based
/// Substrate chain.
/// - An account.
///
/// A `Location` is a *relative identifier*, meaning that it can only be used to define the
/// relative path between two locations, and cannot generally be used to refer to a location
/// universally. It is comprised of an integer number of parents specifying the number of times to
/// "escape" upwards into the containing consensus system and then a number of *junctions*, each
/// diving down and specifying some interior portion of state (which may be considered a
/// "sub-consensus" system).
///
/// This specific `Location` implementation uses a `Junctions` datatype which is a Rust `enum`
/// in order to make pattern matching easier. There are occasions where it is important to ensure
/// that a value is strictly an interior location, in those cases, `Junctions` may be used.
///
/// The `Location` value of `Null` simply refers to the interpreting consensus system.
#[derive(
Clone,
Decode,
Encode,
DecodeWithMemTracking,
Eq,
PartialEq,
Ord,
PartialOrd,
Debug,
TypeInfo,
MaxEncodedLen,
serde::Serialize,
serde::Deserialize,
)]
pub struct Location {
/// The number of parent junctions at the beginning of this `Location`.
pub parents: u8,
/// The interior (i.e. non-parent) junctions that this `Location` contains.
pub interior: Junctions,
}
impl Default for Location {
fn default() -> Self {
Self::here()
}
}
/// A relative location which is constrained to be an interior location of the context.
///
/// See also `Location`.
pub type InteriorLocation = Junctions;
impl Location {
/// Creates a new `Location` with the given number of parents and interior junctions.
pub fn new(parents: u8, interior: impl Into<Junctions>) -> Location {
Location { parents, interior: interior.into() }
}
/// Consume `self` and return the equivalent `VersionedLocation` value.
pub const fn into_versioned(self) -> VersionedLocation {
VersionedLocation::V5(self)
}
/// Creates a new `Location` with 0 parents and a `Here` interior.
///
/// The resulting `Location` can be interpreted as the "current consensus system".
pub const fn here() -> Location {
Location { parents: 0, interior: Junctions::Here }
}
/// Creates a new `Location` which evaluates to the parent context.
pub const fn parent() -> Location {
Location { parents: 1, interior: Junctions::Here }
}
/// Creates a new `Location` with `parents` and an empty (`Here`) interior.
pub const fn ancestor(parents: u8) -> Location {
Location { parents, interior: Junctions::Here }
}
/// Whether the `Location` has no parents and has a `Here` interior.
pub fn is_here(&self) -> bool {
self.parents == 0 && self.interior.len() == 0
}
/// Remove the `NetworkId` value in any interior `Junction`s.
pub fn remove_network_id(&mut self) {
self.interior.remove_network_id();
}
/// Return a reference to the interior field.
pub fn interior(&self) -> &Junctions {
&self.interior
}
/// Return a mutable reference to the interior field.
pub fn interior_mut(&mut self) -> &mut Junctions {
&mut self.interior
}
/// Returns the number of `Parent` junctions at the beginning of `self`.
pub const fn parent_count(&self) -> u8 {
self.parents
}
/// Returns the parent count and the interior [`Junctions`] as a tuple.
///
/// To be used when pattern matching, for example:
///
/// ```rust
/// # use staging_xcm::v5::{Junctions::*, Junction::*, Location};
/// fn get_teyrchain_id(loc: &Location) -> Option<u32> {
/// match loc.unpack() {
/// (0, [Teyrchain(id)]) => Some(*id),
/// _ => None
/// }
/// }
/// ```
pub fn unpack(&self) -> (u8, &[Junction]) {
(self.parents, self.interior.as_slice())
}
/// Returns boolean indicating whether `self` contains only the specified amount of
/// parents and no interior junctions.
pub const fn contains_parents_only(&self, count: u8) -> bool {
matches!(self.interior, Junctions::Here) && self.parents == count
}
/// Returns the number of parents and junctions in `self`.
pub fn len(&self) -> usize {
self.parent_count() as usize + self.interior.len()
}
/// Returns the first interior junction, or `None` if the location is empty or contains only
/// parents.
pub fn first_interior(&self) -> Option<&Junction> {
self.interior.first()
}
/// Returns last junction, or `None` if the location is empty or contains only parents.
pub fn last(&self) -> Option<&Junction> {
self.interior.last()
}
/// Splits off the first interior junction, returning the remaining suffix (first item in tuple)
/// and the first element (second item in tuple) or `None` if it was empty.
pub fn split_first_interior(self) -> (Location, Option<Junction>) {
let Location { parents, interior: junctions } = self;
let (suffix, first) = junctions.split_first();
let location = Location { parents, interior: suffix };
(location, first)
}
/// Splits off the last interior junction, returning the remaining prefix (first item in tuple)
/// and the last element (second item in tuple) or `None` if it was empty or if `self` only
/// contains parents.
pub fn split_last_interior(self) -> (Location, Option<Junction>) {
let Location { parents, interior: junctions } = self;
let (prefix, last) = junctions.split_last();
let location = Location { parents, interior: prefix };
(location, last)
}
/// Mutates `self`, suffixing its interior junctions with `new`. Returns `Err` with `new` in
/// case of overflow.
pub fn push_interior(&mut self, new: impl Into<Junction>) -> result::Result<(), Junction> {
self.interior.push(new)
}
/// Mutates `self`, prefixing its interior junctions with `new`. Returns `Err` with `new` in
/// case of overflow.
pub fn push_front_interior(
&mut self,
new: impl Into<Junction>,
) -> result::Result<(), Junction> {
self.interior.push_front(new)
}
/// Consumes `self` and returns a `Location` suffixed with `new`, or an `Err` with
/// the original value of `self` in case of overflow.
pub fn pushed_with_interior(
self,
new: impl Into<Junction>,
) -> result::Result<Self, (Self, Junction)> {
match self.interior.pushed_with(new) {
Ok(i) => Ok(Location { interior: i, parents: self.parents }),
Err((i, j)) => Err((Location { interior: i, parents: self.parents }, j)),
}
}
/// Consumes `self` and returns a `Location` prefixed with `new`, or an `Err` with the
/// original value of `self` in case of overflow.
pub fn pushed_front_with_interior(
self,
new: impl Into<Junction>,
) -> result::Result<Self, (Self, Junction)> {
match self.interior.pushed_front_with(new) {
Ok(i) => Ok(Location { interior: i, parents: self.parents }),
Err((i, j)) => Err((Location { interior: i, parents: self.parents }, j)),
}
}
/// Returns the junction at index `i`, or `None` if the location is a parent or if the location
/// does not contain that many elements.
pub fn at(&self, i: usize) -> Option<&Junction> {
let num_parents = self.parents as usize;
if i < num_parents {
return None;
}
self.interior.at(i - num_parents)
}
/// Returns a mutable reference to the junction at index `i`, or `None` if the location is a
/// parent or if it doesn't contain that many elements.
pub fn at_mut(&mut self, i: usize) -> Option<&mut Junction> {
let num_parents = self.parents as usize;
if i < num_parents {
return None;
}
self.interior.at_mut(i - num_parents)
}
/// Decrements the parent count by 1.
pub fn dec_parent(&mut self) {
self.parents = self.parents.saturating_sub(1);
}
/// Removes the first interior junction from `self`, returning it
/// (or `None` if it was empty or if `self` contains only parents).
pub fn take_first_interior(&mut self) -> Option<Junction> {
self.interior.take_first()
}
/// Removes the last element from `interior`, returning it (or `None` if it was empty or if
/// `self` only contains parents).
pub fn take_last(&mut self) -> Option<Junction> {
self.interior.take_last()
}
/// Ensures that `self` has the same number of parents as `prefix`, its junctions begins with
/// the junctions of `prefix` and that it has a single `Junction` item following.
/// If so, returns a reference to this `Junction` item.
///
/// # Example
/// ```rust
/// # use staging_xcm::v5::{Junctions::*, Junction::*, Location};
/// # fn main() {
/// let mut m = Location::new(1, [PalletInstance(3), OnlyChild]);
/// assert_eq!(
/// m.match_and_split(&Location::new(1, [PalletInstance(3)])),
/// Some(&OnlyChild),
/// );
/// assert_eq!(m.match_and_split(&Location::new(1, Here)), None);
/// # }
/// ```
pub fn match_and_split(&self, prefix: &Location) -> Option<&Junction> {
if self.parents != prefix.parents {
return None;
}
self.interior.match_and_split(&prefix.interior)
}
pub fn starts_with(&self, prefix: &Location) -> bool {
self.parents == prefix.parents && self.interior.starts_with(&prefix.interior)
}
/// Mutate `self` so that it is suffixed with `suffix`.
///
/// Does not modify `self` and returns `Err` with `suffix` in case of overflow.
///
/// # Example
/// ```rust
/// # use staging_xcm::v5::{Junctions::*, Junction::*, Location, Parent};
/// # fn main() {
/// let mut m: Location = (Parent, Teyrchain(21), 69u64).into();
/// assert_eq!(m.append_with((Parent, PalletInstance(3))), Ok(()));
/// assert_eq!(m, Location::new(1, [Teyrchain(21), PalletInstance(3)]));
/// # }
/// ```
pub fn append_with(&mut self, suffix: impl Into<Self>) -> Result<(), Self> {
let prefix = core::mem::replace(self, suffix.into());
match self.prepend_with(prefix) {
Ok(()) => Ok(()),
Err(prefix) => Err(core::mem::replace(self, prefix)),
}
}
/// Consume `self` and return its value suffixed with `suffix`.
///
/// Returns `Err` with the original value of `self` and `suffix` in case of overflow.
///
/// # Example
/// ```rust
/// # use staging_xcm::v5::{Junctions::*, Junction::*, Location, Parent};
/// # fn main() {
/// let mut m: Location = (Parent, Teyrchain(21), 69u64).into();
/// let r = m.appended_with((Parent, PalletInstance(3))).unwrap();
/// assert_eq!(r, Location::new(1, [Teyrchain(21), PalletInstance(3)]));
/// # }
/// ```
pub fn appended_with(mut self, suffix: impl Into<Self>) -> Result<Self, (Self, Self)> {
match self.append_with(suffix) {
Ok(()) => Ok(self),
Err(suffix) => Err((self, suffix)),
}
}
/// Mutate `self` so that it is prefixed with `prefix`.
///
/// Does not modify `self` and returns `Err` with `prefix` in case of overflow.
///
/// # Example
/// ```rust
/// # use staging_xcm::v5::{Junctions::*, Junction::*, Location, Parent};
/// # fn main() {
/// let mut m: Location = (Parent, Parent, PalletInstance(3)).into();
/// assert_eq!(m.prepend_with((Parent, Teyrchain(21), OnlyChild)), Ok(()));
/// assert_eq!(m, Location::new(1, [PalletInstance(3)]));
/// # }
/// ```
pub fn prepend_with(&mut self, prefix: impl Into<Self>) -> Result<(), Self> {
// prefix self (suffix)
// P .. P I .. I p .. p i .. i
let mut prefix = prefix.into();
let prepend_interior = prefix.interior.len().saturating_sub(self.parents as usize);
let final_interior = self.interior.len().saturating_add(prepend_interior);
if final_interior > super::junctions::MAX_JUNCTIONS {
return Err(prefix);
}
let suffix_parents = (self.parents as usize).saturating_sub(prefix.interior.len());
let final_parents = (prefix.parents as usize).saturating_add(suffix_parents);
if final_parents > 255 {
return Err(prefix);
}
// cancel out the final item on the prefix interior for one of the suffix's parents.
while self.parents > 0 && prefix.take_last().is_some() {
self.dec_parent();
}
// now we have either removed all suffix's parents or prefix interior.
// this means we can combine the prefix's and suffix's remaining parents/interior since
// we know that with at least one empty, the overall order will be respected:
// prefix self (suffix)
// P .. P (I) p .. p i .. i => P + p .. (no I) i
// -- or --
// P .. P I .. I (p) i .. i => P (no p) .. I + i
self.parents = self.parents.saturating_add(prefix.parents);
for j in prefix.interior.into_iter().rev() {
self.push_front_interior(j)
.expect("final_interior no greater than MAX_JUNCTIONS; qed");
}
Ok(())
}
/// Consume `self` and return its value prefixed with `prefix`.
///
/// Returns `Err` with the original value of `self` and `prefix` in case of overflow.
///
/// # Example
/// ```rust
/// # use staging_xcm::v5::{Junctions::*, Junction::*, Location, Parent};
/// # fn main() {
/// let m: Location = (Parent, Parent, PalletInstance(3)).into();
/// let r = m.prepended_with((Parent, Teyrchain(21), OnlyChild)).unwrap();
/// assert_eq!(r, Location::new(1, [PalletInstance(3)]));
/// # }
/// ```
pub fn prepended_with(mut self, prefix: impl Into<Self>) -> Result<Self, (Self, Self)> {
match self.prepend_with(prefix) {
Ok(()) => Ok(self),
Err(prefix) => Err((self, prefix)),
}
}
/// Remove any unneeded parents/junctions in `self` based on the given context it will be
/// interpreted in.
pub fn simplify(&mut self, context: &Junctions) {
if context.len() < self.parents as usize {
// Not enough context
return;
}
while self.parents > 0 {
let maybe = context.at(context.len() - (self.parents as usize));
match (self.interior.first(), maybe) {
(Some(i), Some(j)) if i == j => {
self.interior.take_first();
self.parents -= 1;
},
_ => break,
}
}
}
/// Return the Location subsection identifying the chain that `self` points to.
pub fn chain_location(&self) -> Location {
let mut clone = self.clone();
// start popping junctions until we reach chain identifier
while let Some(j) = clone.last() {
if matches!(j, Junction::Teyrchain(_) | Junction::GlobalConsensus(_)) {
// return chain subsection
return clone;
} else {
(clone, _) = clone.split_last_interior();
}
}
Location::new(clone.parents, Junctions::Here)
}
}
impl Reanchorable for Location {
type Error = Self;
/// Mutate `self` so that it represents the same location from the point of view of `target`.
/// The context of `self` is provided as `context`.
///
/// Does not modify `self` in case of overflow.
fn reanchor(&mut self, target: &Location, context: &InteriorLocation) -> Result<(), ()> {
// TODO: https://github.com/paritytech/polkadot/issues/4489 Optimize this.
// 1. Use our `context` to figure out how the `target` would address us.
let inverted_target = context.invert_target(target)?;
// 2. Prepend `inverted_target` to `self` to get self's location from the perspective of
// `target`.
self.prepend_with(inverted_target).map_err(|_| ())?;
// 3. Given that we know some of `target` context, ensure that any parents in `self` are
// strictly needed.
self.simplify(target.interior());
Ok(())
}
/// Consume `self` and return a new value representing the same location from the point of view
/// of `target`. The context of `self` is provided as `context`.
///
/// Returns the original `self` in case of overflow.
fn reanchored(mut self, target: &Location, context: &InteriorLocation) -> Result<Self, Self> {
match self.reanchor(target, context) {
Ok(()) => Ok(self),
Err(()) => Err(self),
}
}
}
impl TryFrom<OldLocation> for Option<Location> {
type Error = ();
fn try_from(value: OldLocation) -> result::Result<Self, Self::Error> {
Ok(Some(Location::try_from(value)?))
}
}
impl TryFrom<OldLocation> for Location {
type Error = ();
fn try_from(x: OldLocation) -> result::Result<Self, ()> {
Ok(Location { parents: x.parents, interior: x.interior.try_into()? })
}
}
/// A unit struct which can be converted into a `Location` of `parents` value 1.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct Parent;
impl From<Parent> for Location {
fn from(_: Parent) -> Self {
Location { parents: 1, interior: Junctions::Here }
}
}
/// A tuple struct which can be converted into a `Location` of `parents` value 1 with the inner
/// interior.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct ParentThen(pub Junctions);
impl From<ParentThen> for Location {
fn from(ParentThen(interior): ParentThen) -> Self {
Location { parents: 1, interior }
}
}
/// A unit struct which can be converted into a `Location` of the inner `parents` value.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct Ancestor(pub u8);
impl From<Ancestor> for Location {
fn from(Ancestor(parents): Ancestor) -> Self {
Location { parents, interior: Junctions::Here }
}
}
/// A unit struct which can be converted into a `Location` of the inner `parents` value and the
/// inner interior.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct AncestorThen<Interior>(pub u8, pub Interior);
impl<Interior: Into<Junctions>> From<AncestorThen<Interior>> for Location {
fn from(AncestorThen(parents, interior): AncestorThen<Interior>) -> Self {
Location { parents, interior: interior.into() }
}
}
impl From<[u8; 32]> for Location {
fn from(bytes: [u8; 32]) -> Self {
let junction: Junction = bytes.into();
junction.into()
}
}
impl From<sp_runtime::AccountId32> for Location {
fn from(id: sp_runtime::AccountId32) -> Self {
Junction::AccountId32 { network: None, id: id.into() }.into()
}
}
xcm_procedural::impl_conversion_functions_for_location_v5!();
#[cfg(test)]
mod tests {
use crate::v5::prelude::*;
use codec::{Decode, Encode};
#[test]
fn conversion_works() {
let x: Location = Parent.into();
assert_eq!(x, Location { parents: 1, interior: Here });
// let x: Location = (Parent,).into();
// assert_eq!(x, Location { parents: 1, interior: Here });
// let x: Location = (Parent, Parent).into();
// assert_eq!(x, Location { parents: 2, interior: Here });
let x: Location = (Parent, Parent, OnlyChild).into();
assert_eq!(x, Location { parents: 2, interior: OnlyChild.into() });
let x: Location = OnlyChild.into();
assert_eq!(x, Location { parents: 0, interior: OnlyChild.into() });
let x: Location = (OnlyChild,).into();
assert_eq!(x, Location { parents: 0, interior: OnlyChild.into() });
}
#[test]
fn simplify_basic_works() {
let mut location: Location =
(Parent, Parent, Teyrchain(1000), PalletInstance(42), GeneralIndex(69)).into();
let context = [Teyrchain(1000), PalletInstance(42)].into();
let expected = GeneralIndex(69).into();
location.simplify(&context);
assert_eq!(location, expected);
let mut location: Location = (Parent, PalletInstance(42), GeneralIndex(69)).into();
let context = [PalletInstance(42)].into();
let expected = GeneralIndex(69).into();
location.simplify(&context);
assert_eq!(location, expected);
let mut location: Location = (Parent, PalletInstance(42), GeneralIndex(69)).into();
let context = [Teyrchain(1000), PalletInstance(42)].into();
let expected = GeneralIndex(69).into();
location.simplify(&context);
assert_eq!(location, expected);
let mut location: Location =
(Parent, Parent, Teyrchain(1000), PalletInstance(42), GeneralIndex(69)).into();
let context = [OnlyChild, Teyrchain(1000), PalletInstance(42)].into();
let expected = GeneralIndex(69).into();
location.simplify(&context);
assert_eq!(location, expected);
}
#[test]
fn simplify_incompatible_location_fails() {
let mut location: Location =
(Parent, Parent, Teyrchain(1000), PalletInstance(42), GeneralIndex(69)).into();
let context = [Teyrchain(1000), PalletInstance(42), GeneralIndex(42)].into();
let expected =
(Parent, Parent, Teyrchain(1000), PalletInstance(42), GeneralIndex(69)).into();
location.simplify(&context);
assert_eq!(location, expected);
let mut location: Location =
(Parent, Parent, Teyrchain(1000), PalletInstance(42), GeneralIndex(69)).into();
let context = [Teyrchain(1000)].into();
let expected =
(Parent, Parent, Teyrchain(1000), PalletInstance(42), GeneralIndex(69)).into();
location.simplify(&context);
assert_eq!(location, expected);
}
#[test]
fn reanchor_works() {
let mut id: Location = (Parent, Teyrchain(1000), GeneralIndex(42)).into();
let context = Teyrchain(2000).into();
let target = (Parent, Teyrchain(1000)).into();
let expected = GeneralIndex(42).into();
id.reanchor(&target, &context).unwrap();
assert_eq!(id, expected);
}
#[test]
fn encode_and_decode_works() {
let m = Location {
parents: 1,
interior: [Teyrchain(42), AccountIndex64 { network: None, index: 23 }].into(),
};
let encoded = m.encode();
assert_eq!(encoded, [1, 2, 0, 168, 2, 0, 92].to_vec());
let decoded = Location::decode(&mut &encoded[..]);
assert_eq!(decoded, Ok(m));
}
#[test]
fn match_and_split_works() {
let m = Location {
parents: 1,
interior: [Teyrchain(42), AccountIndex64 { network: None, index: 23 }].into(),
};
assert_eq!(m.match_and_split(&Location { parents: 1, interior: Here }), None);
assert_eq!(
m.match_and_split(&Location { parents: 1, interior: [Teyrchain(42)].into() }),
Some(&AccountIndex64 { network: None, index: 23 })
);
assert_eq!(m.match_and_split(&m), None);
}
#[test]
fn append_with_works() {
let acc = AccountIndex64 { network: None, index: 23 };
let mut m = Location { parents: 1, interior: [Teyrchain(42)].into() };
assert_eq!(m.append_with([PalletInstance(3), acc]), Ok(()));
assert_eq!(
m,
Location { parents: 1, interior: [Teyrchain(42), PalletInstance(3), acc].into() }
);
// cannot append to create overly long location
let acc = AccountIndex64 { network: None, index: 23 };
let m = Location {
parents: 254,
interior: [Teyrchain(42), OnlyChild, OnlyChild, OnlyChild, OnlyChild].into(),
};
let suffix: Location = (PalletInstance(3), acc, OnlyChild, OnlyChild).into();
assert_eq!(m.clone().append_with(suffix.clone()), Err(suffix));
}
#[test]
fn prepend_with_works() {
let mut m = Location {
parents: 1,
interior: [Teyrchain(42), AccountIndex64 { network: None, index: 23 }].into(),
};
assert_eq!(m.prepend_with(Location { parents: 1, interior: [OnlyChild].into() }), Ok(()));
assert_eq!(
m,
Location {
parents: 1,
interior: [Teyrchain(42), AccountIndex64 { network: None, index: 23 }].into()
}
);
// cannot prepend to create overly long location
let mut m = Location { parents: 254, interior: [Teyrchain(42)].into() };
let prefix = Location { parents: 2, interior: Here };
assert_eq!(m.prepend_with(prefix.clone()), Err(prefix));
let prefix = Location { parents: 1, interior: Here };
assert_eq!(m.prepend_with(prefix.clone()), Ok(()));
assert_eq!(m, Location { parents: 255, interior: [Teyrchain(42)].into() });
}
#[test]
fn double_ended_ref_iteration_works() {
let m: Junctions = [Teyrchain(1000), Teyrchain(3), PalletInstance(5)].into();
let mut iter = m.iter();
let first = iter.next().unwrap();
assert_eq!(first, &Teyrchain(1000));
let third = iter.next_back().unwrap();
assert_eq!(third, &PalletInstance(5));
let second = iter.next_back().unwrap();
assert_eq!(iter.next(), None);
assert_eq!(iter.next_back(), None);
assert_eq!(second, &Teyrchain(3));
let res = Here
.pushed_with(*first)
.unwrap()
.pushed_with(*second)
.unwrap()
.pushed_with(*third)
.unwrap();
assert_eq!(m, res);
// make sure there's no funny business with the 0 indexing
let m = Here;
let mut iter = m.iter();
assert_eq!(iter.next(), None);
assert_eq!(iter.next_back(), None);
}
#[test]
fn conversion_from_other_types_works() {
use crate::v4;
fn takes_location<Arg: Into<Location>>(_arg: Arg) {}
takes_location(Parent);
takes_location(Here);
takes_location([Teyrchain(42)]);
takes_location((Ancestor(255), PalletInstance(8)));
takes_location((Ancestor(5), Teyrchain(1), PalletInstance(3)));
takes_location((Ancestor(2), Here));
takes_location(AncestorThen(
3,
[Teyrchain(43), AccountIndex64 { network: None, index: 155 }],
));
takes_location((Parent, AccountId32 { network: None, id: [0; 32] }));
takes_location((Parent, Here));
takes_location(ParentThen([Teyrchain(75)].into()));
takes_location([Teyrchain(100), PalletInstance(3)]);
assert_eq!(v4::Location::from(v4::Junctions::Here).try_into(), Ok(Location::here()));
assert_eq!(v4::Location::from(v4::Parent).try_into(), Ok(Location::parent()));
assert_eq!(
v4::Location::from((v4::Parent, v4::Parent, v4::Junction::GeneralIndex(42u128),))
.try_into(),
Ok(Location { parents: 2, interior: [GeneralIndex(42u128)].into() }),
);
}
}
File diff suppressed because it is too large Load Diff
+553
View File
@@ -0,0 +1,553 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Cross-Consensus Message format data structures.
pub use crate::v3::{Error as OldError, SendError, XcmHash};
use codec::{Decode, DecodeWithMemTracking, Encode};
use core::result;
use scale_info::TypeInfo;
pub use sp_weights::Weight;
use super::*;
/// Error codes used in XCM. The first errors codes have explicit indices and are part of the XCM
/// format. Those trailing are merely part of the XCM implementation; there is no expectation that
/// they will retain the same index over time.
#[derive(
Copy,
Clone,
Encode,
Decode,
DecodeWithMemTracking,
Eq,
PartialEq,
Debug,
TypeInfo,
MaxEncodedLen,
)]
#[scale_info(replace_segment("staging_xcm", "xcm"))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub enum Error {
// Errors that happen due to instructions being executed. These alone are defined in the
// XCM specification.
/// An arithmetic overflow happened.
#[codec(index = 0)]
Overflow,
/// The instruction is intentionally unsupported.
#[codec(index = 1)]
Unimplemented,
/// Origin Register does not contain a value value for a reserve transfer notification.
#[codec(index = 2)]
UntrustedReserveLocation,
/// Origin Register does not contain a value value for a teleport notification.
#[codec(index = 3)]
UntrustedTeleportLocation,
/// `MultiLocation` value too large to descend further.
#[codec(index = 4)]
LocationFull,
/// `MultiLocation` value ascend more parents than known ancestors of local location.
#[codec(index = 5)]
LocationNotInvertible,
/// The Origin Register does not contain a valid value for instruction.
#[codec(index = 6)]
BadOrigin,
/// The location parameter is not a valid value for the instruction.
#[codec(index = 7)]
InvalidLocation,
/// The given asset is not handled.
#[codec(index = 8)]
AssetNotFound,
/// An asset transaction (like withdraw or deposit) failed (typically due to type conversions).
#[codec(index = 9)]
FailedToTransactAsset(#[codec(skip)] &'static str),
/// An asset cannot be withdrawn, potentially due to lack of ownership, availability or rights.
#[codec(index = 10)]
NotWithdrawable,
/// An asset cannot be deposited under the ownership of a particular location.
#[codec(index = 11)]
LocationCannotHold,
/// Attempt to send a message greater than the maximum supported by the transport protocol.
#[codec(index = 12)]
ExceedsMaxMessageSize,
/// The given message cannot be translated into a format supported by the destination.
#[codec(index = 13)]
DestinationUnsupported,
/// Destination is routable, but there is some issue with the transport mechanism.
#[codec(index = 14)]
Transport(#[codec(skip)] &'static str),
/// Destination is known to be unroutable.
#[codec(index = 15)]
Unroutable,
/// Used by `ClaimAsset` when the given claim could not be recognized/found.
#[codec(index = 16)]
UnknownClaim,
/// Used by `Transact` when the functor cannot be decoded.
#[codec(index = 17)]
FailedToDecode,
/// Used by `Transact` to indicate that the given weight limit could be breached by the
/// functor.
#[codec(index = 18)]
MaxWeightInvalid,
/// Used by `BuyExecution` when the Holding Register does not contain payable fees.
#[codec(index = 19)]
NotHoldingFees,
/// Used by `BuyExecution` when the fees declared to purchase weight are insufficient.
#[codec(index = 20)]
TooExpensive,
/// Used by the `Trap` instruction to force an error intentionally. Its code is included.
#[codec(index = 21)]
Trap(u64),
/// Used by `ExpectAsset`, `ExpectError` and `ExpectOrigin` when the expectation was not true.
#[codec(index = 22)]
ExpectationFalse,
/// The provided pallet index was not found.
#[codec(index = 23)]
PalletNotFound,
/// The given pallet's name is different to that expected.
#[codec(index = 24)]
NameMismatch,
/// The given pallet's version has an incompatible version to that expected.
#[codec(index = 25)]
VersionIncompatible,
/// The given operation would lead to an overflow of the Holding Register.
#[codec(index = 26)]
HoldingWouldOverflow,
/// The message was unable to be exported.
#[codec(index = 27)]
ExportError,
/// `MultiLocation` value failed to be reanchored.
#[codec(index = 28)]
ReanchorFailed,
/// No deal is possible under the given constraints.
#[codec(index = 29)]
NoDeal,
/// Fees were required which the origin could not pay.
#[codec(index = 30)]
FeesNotMet,
/// Some other error with locking.
#[codec(index = 31)]
LockError,
/// The state was not in a condition where the operation was valid to make.
#[codec(index = 32)]
NoPermission,
/// The universal location of the local consensus is improper.
#[codec(index = 33)]
Unanchored,
/// An asset cannot be deposited, probably because (too much of) it already exists.
#[codec(index = 34)]
NotDepositable,
/// Too many assets matched the given asset filter.
#[codec(index = 35)]
TooManyAssets,
// Errors that happen prior to instructions being executed. These fall outside of the XCM
// spec.
/// XCM version not able to be handled.
UnhandledXcmVersion,
/// Execution of the XCM would potentially result in a greater weight used than weight limit.
WeightLimitReached(Weight),
/// The XCM did not pass the barrier condition for execution.
///
/// The barrier condition differs on different chains and in different circumstances, but
/// generally it means that the conditions surrounding the message were not such that the chain
/// considers the message worth spending time executing. Since most chains lift the barrier to
/// execution on appropriate payment, presentation of an NFT voucher, or based on the message
/// origin, it means that none of those were the case.
Barrier,
/// The weight of an XCM message is not computable ahead of execution.
WeightNotComputable,
/// Recursion stack limit reached
// TODO(https://github.com/pezkuwichain/pezkuwi-sdk/issues/148): This should have a fixed index since
// we use it in `FrameTransactionalProcessor` // which is used in instructions.
// Or we should create a different error for that.
ExceedsStackLimit,
}
impl TryFrom<OldError> for Error {
type Error = ();
fn try_from(old_error: OldError) -> result::Result<Error, ()> {
use OldError::*;
Ok(match old_error {
Overflow => Self::Overflow,
Unimplemented => Self::Unimplemented,
UntrustedReserveLocation => Self::UntrustedReserveLocation,
UntrustedTeleportLocation => Self::UntrustedTeleportLocation,
LocationFull => Self::LocationFull,
LocationNotInvertible => Self::LocationNotInvertible,
BadOrigin => Self::BadOrigin,
InvalidLocation => Self::InvalidLocation,
AssetNotFound => Self::AssetNotFound,
FailedToTransactAsset(s) => Self::FailedToTransactAsset(s),
NotWithdrawable => Self::NotWithdrawable,
LocationCannotHold => Self::LocationCannotHold,
ExceedsMaxMessageSize => Self::ExceedsMaxMessageSize,
DestinationUnsupported => Self::DestinationUnsupported,
Transport(s) => Self::Transport(s),
Unroutable => Self::Unroutable,
UnknownClaim => Self::UnknownClaim,
FailedToDecode => Self::FailedToDecode,
MaxWeightInvalid => Self::MaxWeightInvalid,
NotHoldingFees => Self::NotHoldingFees,
TooExpensive => Self::TooExpensive,
Trap(i) => Self::Trap(i),
ExpectationFalse => Self::ExpectationFalse,
PalletNotFound => Self::PalletNotFound,
NameMismatch => Self::NameMismatch,
VersionIncompatible => Self::VersionIncompatible,
HoldingWouldOverflow => Self::HoldingWouldOverflow,
ExportError => Self::ExportError,
ReanchorFailed => Self::ReanchorFailed,
NoDeal => Self::NoDeal,
FeesNotMet => Self::FeesNotMet,
LockError => Self::LockError,
NoPermission => Self::NoPermission,
Unanchored => Self::Unanchored,
NotDepositable => Self::NotDepositable,
UnhandledXcmVersion => Self::UnhandledXcmVersion,
WeightLimitReached(weight) => Self::WeightLimitReached(weight),
Barrier => Self::Barrier,
WeightNotComputable => Self::WeightNotComputable,
ExceedsStackLimit => Self::ExceedsStackLimit,
})
}
}
impl From<SendError> for Error {
fn from(e: SendError) -> Self {
match e {
SendError::NotApplicable | SendError::Unroutable | SendError::MissingArgument =>
Error::Unroutable,
SendError::Transport(s) => Error::Transport(s),
SendError::DestinationUnsupported => Error::DestinationUnsupported,
SendError::ExceedsMaxMessageSize => Error::ExceedsMaxMessageSize,
SendError::Fees => Error::FeesNotMet,
}
}
}
pub type Result = result::Result<(), Error>;
/// Outcome of an XCM execution.
#[derive(Clone, Encode, Decode, DecodeWithMemTracking, Eq, PartialEq, Debug, TypeInfo)]
pub enum Outcome {
/// Execution completed successfully; given weight was used.
Complete { used: Weight },
/// Execution started, but did not complete successfully due to`error` which occurred
/// on the `index`-th (top-level) instruction. Overall, total `weight` was used.
Incomplete { used: Weight, error: InstructionError },
/// Execution did not start due to an error. We use `InstructionError` since it's always
/// possible to isolate the problematic instruction that caused the error.
Error(InstructionError),
}
/// XCM error and the index of the instruction that caused it.
#[derive(Copy, Clone, Encode, Decode, DecodeWithMemTracking, Eq, PartialEq, Debug, TypeInfo)]
pub struct InstructionError {
/// The index of the intruction that caused the error.
pub index: InstructionIndex,
/// The XCM error itself.
pub error: Error,
}
impl Outcome {
pub fn ensure_complete(self) -> result::Result<(), InstructionError> {
match self {
Outcome::Complete { .. } => Ok(()),
Outcome::Incomplete { error, .. } => Err(error),
Outcome::Error(error) => Err(error),
}
}
pub fn ensure_execution(self) -> result::Result<Weight, InstructionError> {
match self {
Outcome::Complete { used, .. } => Ok(used),
Outcome::Incomplete { used, .. } => Ok(used),
Outcome::Error(error) => Err(error),
}
}
/// How much weight was used by the XCM execution attempt.
pub fn weight_used(&self) -> Weight {
match self {
Outcome::Complete { used, .. } => *used,
Outcome::Incomplete { used, .. } => *used,
Outcome::Error(_) => Weight::zero(),
}
}
}
impl From<Error> for Outcome {
fn from(error: Error) -> Self {
Self::Error(InstructionError { error, index: 0 })
}
}
pub trait PreparedMessage {
fn weight_of(&self) -> Weight;
}
/// The index of an instruction in an XCM.
pub type InstructionIndex = u8;
/// Type of XCM message executor.
pub trait ExecuteXcm<Call> {
type Prepared: PreparedMessage;
/// If it fails, returns the index of the problematic instruction.
fn prepare(
message: Xcm<Call>,
weight_limit: Weight,
) -> result::Result<Self::Prepared, InstructionError>;
fn execute(
origin: impl Into<Location>,
pre: Self::Prepared,
id: &mut XcmHash,
weight_credit: Weight,
) -> Outcome;
fn prepare_and_execute(
origin: impl Into<Location>,
message: Xcm<Call>,
id: &mut XcmHash,
weight_limit: Weight,
weight_credit: Weight,
) -> Outcome {
let pre = match Self::prepare(message, weight_limit) {
Ok(x) => x,
Err(error) => return Outcome::Error(error),
};
Self::execute(origin, pre, id, weight_credit)
}
/// Deduct some `fees` to the sovereign account of the given `location` and place them as per
/// the convention for fees.
fn charge_fees(location: impl Into<Location>, fees: Assets) -> Result;
}
pub enum Weightless {}
impl PreparedMessage for Weightless {
fn weight_of(&self) -> Weight {
unreachable!()
}
}
impl<C> ExecuteXcm<C> for () {
type Prepared = Weightless;
fn prepare(_: Xcm<C>, _: Weight) -> result::Result<Self::Prepared, InstructionError> {
Err(InstructionError { index: 0, error: Error::Unimplemented })
}
fn execute(_: impl Into<Location>, _: Self::Prepared, _: &mut XcmHash, _: Weight) -> Outcome {
unreachable!()
}
fn charge_fees(_location: impl Into<Location>, _fees: Assets) -> Result {
Err(Error::Unimplemented)
}
}
pub trait Reanchorable: Sized {
/// Type to return in case of an error.
type Error: Debug;
/// Mutate `self` so that it represents the same location from the point of view of `target`.
/// The context of `self` is provided as `context`.
///
/// Does not modify `self` in case of overflow.
fn reanchor(
&mut self,
target: &Location,
context: &InteriorLocation,
) -> core::result::Result<(), ()>;
/// Consume `self` and return a new value representing the same location from the point of view
/// of `target`. The context of `self` is provided as `context`.
///
/// Returns the original `self` in case of overflow.
fn reanchored(
self,
target: &Location,
context: &InteriorLocation,
) -> core::result::Result<Self, Self::Error>;
}
/// Result value when attempting to send an XCM message.
pub type SendResult<T> = result::Result<(T, Assets), SendError>;
/// Utility for sending an XCM message to a given location.
///
/// These can be amalgamated in tuples to form sophisticated routing systems. In tuple format, each
/// router might return `NotApplicable` to pass the execution to the next sender item. Note that
/// each `NotApplicable` might alter the destination and the XCM message for to the next router.
///
/// # Example
/// ```rust
/// # use codec::Encode;
/// # use staging_xcm::v5::{prelude::*, Weight};
/// # use staging_xcm::VersionedXcm;
/// # use std::convert::Infallible;
///
/// /// A sender that only passes the message through and does nothing.
/// struct Sender1;
/// impl SendXcm for Sender1 {
/// type Ticket = Infallible;
/// fn validate(_: &mut Option<Location>, _: &mut Option<Xcm<()>>) -> SendResult<Infallible> {
/// Err(SendError::NotApplicable)
/// }
/// fn deliver(_: Infallible) -> Result<XcmHash, SendError> {
/// unreachable!()
/// }
/// }
///
/// /// A sender that accepts a message that has two junctions, otherwise stops the routing.
/// struct Sender2;
/// impl SendXcm for Sender2 {
/// type Ticket = ();
/// fn validate(destination: &mut Option<Location>, message: &mut Option<Xcm<()>>) -> SendResult<()> {
/// match destination.as_ref().ok_or(SendError::MissingArgument)?.unpack() {
/// (0, [j1, j2]) => Ok(((), Assets::new())),
/// _ => Err(SendError::Unroutable),
/// }
/// }
/// fn deliver(_: ()) -> Result<XcmHash, SendError> {
/// Ok([0; 32])
/// }
/// }
///
/// /// A sender that accepts a message from a parent, passing through otherwise.
/// struct Sender3;
/// impl SendXcm for Sender3 {
/// type Ticket = ();
/// fn validate(destination: &mut Option<Location>, message: &mut Option<Xcm<()>>) -> SendResult<()> {
/// match destination.as_ref().ok_or(SendError::MissingArgument)?.unpack() {
/// (1, []) => Ok(((), Assets::new())),
/// _ => Err(SendError::NotApplicable),
/// }
/// }
/// fn deliver(_: ()) -> Result<XcmHash, SendError> {
/// Ok([0; 32])
/// }
/// }
///
/// // A call to send via XCM. We don't really care about this.
/// # fn main() {
/// let call: Vec<u8> = ().encode();
/// let message = Xcm(vec![Instruction::Transact {
/// origin_kind: OriginKind::Superuser,
/// call: call.into(),
/// fallback_max_weight: None,
/// }]);
/// let message_hash = message.using_encoded(sp_io::hashing::blake2_256);
///
/// // Sender2 will block this.
/// assert!(send_xcm::<(Sender1, Sender2, Sender3)>(Parent.into(), message.clone()).is_err());
///
/// // Sender3 will catch this.
/// assert!(send_xcm::<(Sender1, Sender3)>(Parent.into(), message.clone()).is_ok());
/// # }
/// ```
pub trait SendXcm {
/// Intermediate value which connects the two phases of the send operation.
type Ticket;
/// Check whether the given `_message` is deliverable to the given `_destination` and if so
/// determine the cost which will be paid by this chain to do so, returning a `Validated` token
/// which can be used to enact delivery.
///
/// The `destination` and `message` must be `Some` (or else an error will be returned) and they
/// may only be consumed if the `Err` is not `NotApplicable`.
///
/// If it is not a destination which can be reached with this type but possibly could by others,
/// then this *MUST* return `NotApplicable`. Any other error will cause the tuple
/// implementation to exit early without trying other type fields.
fn validate(
destination: &mut Option<Location>,
message: &mut Option<Xcm<()>>,
) -> SendResult<Self::Ticket>;
/// Actually carry out the delivery operation for a previously validated message sending.
fn deliver(ticket: Self::Ticket) -> result::Result<XcmHash, SendError>;
/// Ensure `[Self::delivery]` is successful for the given `location` when called in benchmarks.
#[cfg(feature = "runtime-benchmarks")]
fn ensure_successful_delivery(_location: Option<Location>) {}
}
#[impl_trait_for_tuples::impl_for_tuples(30)]
impl SendXcm for Tuple {
for_tuples! { type Ticket = (#( Option<Tuple::Ticket> ),* ); }
fn validate(
destination: &mut Option<Location>,
message: &mut Option<Xcm<()>>,
) -> SendResult<Self::Ticket> {
let mut maybe_cost: Option<Assets> = None;
let one_ticket: Self::Ticket = (for_tuples! { #(
if maybe_cost.is_some() {
None
} else {
match Tuple::validate(destination, message) {
Err(SendError::NotApplicable) => None,
Err(e) => { return Err(e) },
Ok((v, c)) => {
maybe_cost = Some(c);
Some(v)
},
}
}
),* });
if let Some(cost) = maybe_cost {
Ok((one_ticket, cost))
} else {
Err(SendError::NotApplicable)
}
}
fn deliver(one_ticket: Self::Ticket) -> result::Result<XcmHash, SendError> {
for_tuples!( #(
if let Some(validated) = one_ticket.Tuple {
return Tuple::deliver(validated);
}
)* );
Err(SendError::Unroutable)
}
#[cfg(feature = "runtime-benchmarks")]
fn ensure_successful_delivery(location: Option<Location>) {
for_tuples!( #(
return Tuple::ensure_successful_delivery(location.clone());
)* );
}
}
/// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps
/// both in `Some` before passing them as mutable references into `T::send_xcm`.
pub fn validate_send<T: SendXcm>(dest: Location, msg: Xcm<()>) -> SendResult<T::Ticket> {
T::validate(&mut Some(dest), &mut Some(msg))
}
/// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps
/// both in `Some` before passing them as mutable references into `T::send_xcm`.
///
/// Returns either `Ok` with the price of the delivery, or `Err` with the reason why the message
/// could not be sent.
///
/// Generally you'll want to validate and get the price first to ensure that the sender can pay it
/// before actually doing the delivery.
pub fn send_xcm<T: SendXcm>(
dest: Location,
msg: Xcm<()>,
) -> result::Result<(XcmHash, Assets), SendError> {
let (ticket, price) = T::validate(&mut Some(dest), &mut Some(msg))?;
let hash = T::deliver(ticket)?;
Ok((hash, price))
}
+90
View File
@@ -0,0 +1,90 @@
[package]
name = "staging-xcm-builder"
description = "Tools & types for building with XCM and its executor."
authors.workspace = true
edition.workspace = true
license.workspace = true
version = "7.0.0"
homepage.workspace = true
repository.workspace = true
[lints]
workspace = true
[dependencies]
codec = { features = ["derive"], workspace = true }
environmental = { workspace = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
impl-trait-for-tuples = { workspace = true }
pallet-asset-conversion = { workspace = true }
pallet-transaction-payment = { workspace = true }
scale-info = { features = ["derive"], workspace = true }
sp-arithmetic = { workspace = true }
sp-core = { workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
sp-weights = { workspace = true }
tracing = { workspace = true }
xcm = { workspace = true }
xcm-executor = { workspace = true }
# Pezkuwi dependencies
pezkuwi-teyrchain-primitives = { workspace = true }
[dev-dependencies]
pallet-assets = { workspace = true, default-features = true }
pallet-balances = { workspace = true, default-features = true }
pallet-salary = { workspace = true, default-features = true }
pallet-xcm = { workspace = true, default-features = true }
pezkuwi-primitives = { workspace = true, default-features = true }
pezkuwi-runtime-teyrchains = { workspace = true, default-features = true }
pezkuwi-test-runtime = { workspace = true }
primitive-types = { features = [
"codec",
"num-traits",
"scale-info",
], workspace = true }
sp-tracing = { workspace = true, default-features = true }
xcm-simulator = { workspace = true, default-features = true }
[features]
default = ["std"]
runtime-benchmarks = [
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-asset-conversion/runtime-benchmarks",
"pallet-assets/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"pallet-salary/runtime-benchmarks",
"pallet-transaction-payment/runtime-benchmarks",
"pallet-xcm/runtime-benchmarks",
"pezkuwi-primitives/runtime-benchmarks",
"pezkuwi-runtime-teyrchains/runtime-benchmarks",
"pezkuwi-test-runtime/runtime-benchmarks",
"pezkuwi-teyrchain-primitives/runtime-benchmarks",
"sp-io/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
"xcm-executor/runtime-benchmarks",
"xcm-simulator/runtime-benchmarks",
"xcm/runtime-benchmarks",
]
std = [
"codec/std",
"environmental/std",
"frame-support/std",
"frame-system/std",
"pallet-asset-conversion/std",
"pallet-transaction-payment/std",
"pezkuwi-teyrchain-primitives/std",
"primitive-types/std",
"scale-info/std",
"sp-arithmetic/std",
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
"sp-weights/std",
"tracing/std",
"xcm-executor/std",
"xcm/std",
]
@@ -0,0 +1,350 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Adapters to work with [`frame_support::traits::fungibles`] through XCM.
use core::{marker::PhantomData, result};
use frame_support::traits::{Contains, Get};
use sp_runtime::traits::MaybeEquivalence;
use xcm::latest::prelude::*;
use xcm_executor::traits::{
Error as MatchError, MatchesFungibles, MatchesInstance, MatchesNonFungible, MatchesNonFungibles,
};
/// Converter struct implementing `AssetIdConversion` converting a numeric asset ID (must be
/// `TryFrom/TryInto<u128>`) into a `GeneralIndex` junction, prefixed by some `Location` value.
/// The `Location` value will typically be a `PalletInstance` junction.
pub struct AsPrefixedGeneralIndex<Prefix, AssetId, ConvertAssetId, L = Location>(
PhantomData<(Prefix, AssetId, ConvertAssetId, L)>,
);
impl<
Prefix: Get<L>,
AssetId: Clone,
ConvertAssetId: MaybeEquivalence<u128, AssetId>,
L: TryInto<Location> + TryFrom<Location> + Clone,
> MaybeEquivalence<L, AssetId> for AsPrefixedGeneralIndex<Prefix, AssetId, ConvertAssetId, L>
{
fn convert(id: &L) -> Option<AssetId> {
let prefix = Prefix::get();
let latest_prefix: Location = prefix.try_into().ok()?;
let latest_id: Location = (*id).clone().try_into().ok()?;
if latest_prefix.parent_count() != latest_id.parent_count() ||
latest_prefix
.interior()
.iter()
.enumerate()
.any(|(index, junction)| latest_id.interior().at(index) != Some(junction))
{
return None;
}
match latest_id.interior().at(latest_prefix.interior().len()) {
Some(Junction::GeneralIndex(id)) => ConvertAssetId::convert(&id),
_ => None,
}
}
fn convert_back(what: &AssetId) -> Option<L> {
let location = Prefix::get();
let mut latest_location: Location = location.try_into().ok()?;
let id = ConvertAssetId::convert_back(what)?;
latest_location.push_interior(Junction::GeneralIndex(id)).ok()?;
latest_location.try_into().ok()
}
}
pub struct ConvertedConcreteId<AssetId, Balance, ConvertAssetId, ConvertOther>(
PhantomData<(AssetId, Balance, ConvertAssetId, ConvertOther)>,
);
impl<
AssetId: Clone,
Balance: Clone,
ConvertAssetId: MaybeEquivalence<Location, AssetId>,
ConvertBalance: MaybeEquivalence<u128, Balance>,
> MatchesFungibles<AssetId, Balance>
for ConvertedConcreteId<AssetId, Balance, ConvertAssetId, ConvertBalance>
{
fn matches_fungibles(a: &Asset) -> result::Result<(AssetId, Balance), MatchError> {
let (amount, id) = match (&a.fun, &a.id) {
(Fungible(ref amount), AssetId(ref id)) => (amount, id),
_ => return Err(MatchError::AssetNotHandled),
};
let what = ConvertAssetId::convert(id).ok_or(MatchError::AssetIdConversionFailed)?;
let amount =
ConvertBalance::convert(amount).ok_or(MatchError::AmountToBalanceConversionFailed)?;
Ok((what, amount))
}
}
impl<
ClassId: Clone,
InstanceId: Clone,
ConvertClassId: MaybeEquivalence<Location, ClassId>,
ConvertInstanceId: MaybeEquivalence<AssetInstance, InstanceId>,
> MatchesNonFungibles<ClassId, InstanceId>
for ConvertedConcreteId<ClassId, InstanceId, ConvertClassId, ConvertInstanceId>
{
fn matches_nonfungibles(a: &Asset) -> result::Result<(ClassId, InstanceId), MatchError> {
let (instance, class) = match (&a.fun, &a.id) {
(NonFungible(ref instance), AssetId(ref class)) => (instance, class),
_ => return Err(MatchError::AssetNotHandled),
};
let what = ConvertClassId::convert(class).ok_or(MatchError::AssetIdConversionFailed)?;
let instance =
ConvertInstanceId::convert(instance).ok_or(MatchError::InstanceConversionFailed)?;
Ok((what, instance))
}
}
#[deprecated = "Use `ConvertedConcreteId` instead"]
pub type ConvertedConcreteAssetId<A, B, C, O> = ConvertedConcreteId<A, B, C, O>;
pub struct MatchedConvertedConcreteId<AssetId, Balance, MatchAssetId, ConvertAssetId, ConvertOther>(
PhantomData<(AssetId, Balance, MatchAssetId, ConvertAssetId, ConvertOther)>,
);
impl<
AssetId: Clone,
Balance: Clone,
MatchAssetId: Contains<Location>,
ConvertAssetId: MaybeEquivalence<Location, AssetId>,
ConvertBalance: MaybeEquivalence<u128, Balance>,
> MatchesFungibles<AssetId, Balance>
for MatchedConvertedConcreteId<AssetId, Balance, MatchAssetId, ConvertAssetId, ConvertBalance>
{
fn matches_fungibles(a: &Asset) -> result::Result<(AssetId, Balance), MatchError> {
let (amount, id) = match (&a.fun, &a.id) {
(Fungible(ref amount), AssetId(ref id)) if MatchAssetId::contains(id) => (amount, id),
_ => return Err(MatchError::AssetNotHandled),
};
let what = ConvertAssetId::convert(id).ok_or(MatchError::AssetIdConversionFailed)?;
let amount =
ConvertBalance::convert(amount).ok_or(MatchError::AmountToBalanceConversionFailed)?;
Ok((what, amount))
}
}
impl<
ClassId: Clone,
InstanceId: Clone,
MatchClassId: Contains<Location>,
ConvertClassId: MaybeEquivalence<Location, ClassId>,
ConvertInstanceId: MaybeEquivalence<AssetInstance, InstanceId>,
> MatchesNonFungibles<ClassId, InstanceId>
for MatchedConvertedConcreteId<
ClassId,
InstanceId,
MatchClassId,
ConvertClassId,
ConvertInstanceId,
>
{
fn matches_nonfungibles(a: &Asset) -> result::Result<(ClassId, InstanceId), MatchError> {
let (instance, class) = match (&a.fun, &a.id) {
(NonFungible(ref instance), AssetId(ref class)) if MatchClassId::contains(class) =>
(instance, class),
_ => return Err(MatchError::AssetNotHandled),
};
let what = ConvertClassId::convert(class).ok_or(MatchError::AssetIdConversionFailed)?;
let instance =
ConvertInstanceId::convert(instance).ok_or(MatchError::InstanceConversionFailed)?;
Ok((what, instance))
}
}
/// An adapter that implements the unified unique instances matcher [`MatchesInstance`] trait
/// for the [`MatchesNonFungibles`].
/// The resulting matcher expects the instances to be part of some class (i.e., instance group,
/// such as an NFT collection).
///
/// * `ClassId` is the ID of an instance class (e.g., NFT collection ID),
/// * `InstanceId` is a class-scoped ID of a class member's unique instance (e.g., an NFT ID inside
/// a collection).
pub struct MatchInClassInstances<Matcher>(PhantomData<Matcher>);
impl<ClassId, InstanceId, Matcher: MatchesNonFungibles<ClassId, InstanceId>>
MatchesInstance<(ClassId, InstanceId)> for MatchInClassInstances<Matcher>
{
fn matches_instance(a: &Asset) -> result::Result<(ClassId, InstanceId), MatchError> {
Matcher::matches_nonfungibles(a)
}
}
/// An adapter that implements the unified unique instances matcher [`MatchesInstance`] trait
/// for the [`MatchesNonFungible`].
/// The resulting matcher expects the instances to be fully individual, not belonging to any group
/// (such as an NFT collection).
///
/// In practice, this typically means that the `InstanceId` is an indivisible ID (i.e., it is not
/// composed of multiple IDs).
pub struct MatchClasslessInstances<Matcher>(PhantomData<Matcher>);
impl<InstanceId, Matcher: MatchesNonFungible<InstanceId>> MatchesInstance<InstanceId>
for MatchClasslessInstances<Matcher>
{
fn matches_instance(a: &Asset) -> result::Result<InstanceId, MatchError> {
Matcher::matches_nonfungible(a).ok_or(MatchError::AssetNotHandled)
}
}
#[cfg(test)]
mod tests {
use super::*;
use xcm_executor::traits::JustTry;
struct OnlyParentZero;
impl Contains<Location> for OnlyParentZero {
fn contains(a: &Location) -> bool {
match a {
Location { parents: 0, .. } => true,
_ => false,
}
}
}
#[test]
fn matched_converted_concrete_id_for_fungibles_works() {
type AssetIdForTrustBackedAssets = u32;
type Balance = u128;
frame_support::parameter_types! {
pub TrustBackedAssetsPalletLocation: Location = PalletInstance(50).into();
}
// ConvertedConcreteId cfg
type Converter = MatchedConvertedConcreteId<
AssetIdForTrustBackedAssets,
Balance,
OnlyParentZero,
AsPrefixedGeneralIndex<
TrustBackedAssetsPalletLocation,
AssetIdForTrustBackedAssets,
JustTry,
>,
JustTry,
>;
assert_eq!(
TrustBackedAssetsPalletLocation::get(),
Location { parents: 0, interior: [PalletInstance(50)].into() }
);
// err - does not match
assert_eq!(
Converter::matches_fungibles(&Asset {
id: AssetId(Location::new(1, [PalletInstance(50), GeneralIndex(1)])),
fun: Fungible(12345),
}),
Err(MatchError::AssetNotHandled)
);
// err - matches, but convert fails
assert_eq!(
Converter::matches_fungibles(&Asset {
id: AssetId(Location::new(
0,
[PalletInstance(50), GeneralKey { length: 1, data: [1; 32] }]
)),
fun: Fungible(12345),
}),
Err(MatchError::AssetIdConversionFailed)
);
// err - matches, but NonFungible
assert_eq!(
Converter::matches_fungibles(&Asset {
id: AssetId(Location::new(0, [PalletInstance(50), GeneralIndex(1)])),
fun: NonFungible(Index(54321)),
}),
Err(MatchError::AssetNotHandled)
);
// ok
assert_eq!(
Converter::matches_fungibles(&Asset {
id: AssetId(Location::new(0, [PalletInstance(50), GeneralIndex(1)])),
fun: Fungible(12345),
}),
Ok((1, 12345))
);
}
#[test]
fn matched_converted_concrete_id_for_nonfungibles_works() {
type ClassId = u32;
type ClassInstanceId = u64;
frame_support::parameter_types! {
pub TrustBackedAssetsPalletLocation: Location = PalletInstance(50).into();
}
// ConvertedConcreteId cfg
struct ClassInstanceIdConverter;
impl MaybeEquivalence<AssetInstance, ClassInstanceId> for ClassInstanceIdConverter {
fn convert(value: &AssetInstance) -> Option<ClassInstanceId> {
(*value).try_into().ok()
}
fn convert_back(value: &ClassInstanceId) -> Option<AssetInstance> {
Some(AssetInstance::from(*value))
}
}
type Converter = MatchedConvertedConcreteId<
ClassId,
ClassInstanceId,
OnlyParentZero,
AsPrefixedGeneralIndex<TrustBackedAssetsPalletLocation, ClassId, JustTry>,
ClassInstanceIdConverter,
>;
assert_eq!(
TrustBackedAssetsPalletLocation::get(),
Location { parents: 0, interior: [PalletInstance(50)].into() }
);
// err - does not match
assert_eq!(
Converter::matches_nonfungibles(&Asset {
id: AssetId(Location::new(1, [PalletInstance(50), GeneralIndex(1)])),
fun: NonFungible(Index(54321)),
}),
Err(MatchError::AssetNotHandled)
);
// err - matches, but convert fails
assert_eq!(
Converter::matches_nonfungibles(&Asset {
id: AssetId(Location::new(
0,
[PalletInstance(50), GeneralKey { length: 1, data: [1; 32] }]
)),
fun: NonFungible(Index(54321)),
}),
Err(MatchError::AssetIdConversionFailed)
);
// err - matches, but Fungible vs NonFungible
assert_eq!(
Converter::matches_nonfungibles(&Asset {
id: AssetId(Location::new(0, [PalletInstance(50), GeneralIndex(1)])),
fun: Fungible(12345),
}),
Err(MatchError::AssetNotHandled)
);
// ok
assert_eq!(
Converter::matches_nonfungibles(&Asset {
id: AssetId(Location::new(0, [PalletInstance(50), GeneralIndex(1)])),
fun: NonFungible(Index(54321)),
}),
Ok((1, 54321))
);
}
}
@@ -0,0 +1,22 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Adapters for the AssetExchanger config item.
//!
//! E.g. types that implement the [`xcm_executor::traits::AssetExchange`] trait.
mod single_asset_adapter;
pub use single_asset_adapter::SingleAssetExchangeAdapter;
@@ -0,0 +1,210 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Single asset exchange adapter.
extern crate alloc;
use alloc::vec;
use core::marker::PhantomData;
use frame_support::{ensure, traits::tokens::fungibles};
use pallet_asset_conversion::{QuotePrice, SwapCredit};
use xcm::prelude::*;
use xcm_executor::{
traits::{AssetExchange, MatchesFungibles},
AssetsInHolding,
};
/// An adapter from [`pallet_asset_conversion::SwapCredit`] and
/// [`pallet_asset_conversion::QuotePrice`] to [`xcm_executor::traits::AssetExchange`].
///
/// This adapter takes just one fungible asset in `give` and allows only one fungible asset in
/// `want`. If you need to handle more assets in either `give` or `want`, then you should use
/// another type that implements [`xcm_executor::traits::AssetExchange`] or build your own.
///
/// This adapter also only works for fungible assets.
///
/// `exchange_asset` and `quote_exchange_price` will both return an error if there's
/// more than one asset in `give` or `want`.
pub struct SingleAssetExchangeAdapter<AssetConversion, Fungibles, Matcher, AccountId>(
PhantomData<(AssetConversion, Fungibles, Matcher, AccountId)>,
);
impl<AssetConversion, Fungibles, Matcher, AccountId> AssetExchange
for SingleAssetExchangeAdapter<AssetConversion, Fungibles, Matcher, AccountId>
where
AssetConversion: SwapCredit<
AccountId,
Balance = u128,
AssetKind = Fungibles::AssetId,
Credit = fungibles::Credit<AccountId, Fungibles>,
> + QuotePrice<Balance = u128, AssetKind = Fungibles::AssetId>,
Fungibles: fungibles::Balanced<AccountId, Balance = u128>,
Matcher: MatchesFungibles<Fungibles::AssetId, Fungibles::Balance>,
{
fn exchange_asset(
_: Option<&Location>,
give: AssetsInHolding,
want: &Assets,
maximal: bool,
) -> Result<AssetsInHolding, AssetsInHolding> {
let mut give_iter = give.fungible_assets_iter();
let give_asset = give_iter.next().ok_or_else(|| {
tracing::trace!(
target: "xcm::SingleAssetExchangeAdapter::exchange_asset",
?give, "No fungible asset was in `give`.",
);
give.clone()
})?;
ensure!(give_iter.next().is_none(), give.clone()); // We only support 1 asset in `give`.
ensure!(give.non_fungible_assets_iter().next().is_none(), give.clone()); // We don't allow non-fungible assets.
ensure!(want.len() == 1, give.clone()); // We only support 1 asset in `want`.
let want_asset = want.get(0).ok_or_else(|| give.clone())?;
let (give_asset_id, give_amount) =
Matcher::matches_fungibles(&give_asset).map_err(|error| {
tracing::trace!(
target: "xcm::SingleAssetExchangeAdapter::exchange_asset",
?give_asset,
?error,
"Could not map XCM asset give to FRAME asset.",
);
give.clone()
})?;
let (want_asset_id, want_amount) =
Matcher::matches_fungibles(&want_asset).map_err(|error| {
tracing::trace!(
target: "xcm::SingleAssetExchangeAdapter::exchange_asset",
?want_asset,
?error,
"Could not map XCM asset want to FRAME asset."
);
give.clone()
})?;
// We have to do this to convert the XCM assets into credit the pool can use.
let swap_asset = give_asset_id.clone().into();
let credit_in = Fungibles::issue(give_asset_id, give_amount);
// Do the swap.
let (credit_out, maybe_credit_change) = if maximal {
// If `maximal`, then we swap exactly `credit_in` to get as much of `want_asset_id` as
// we can, with a minimum of `want_amount`.
let credit_out = <AssetConversion as SwapCredit<_>>::swap_exact_tokens_for_tokens(
vec![swap_asset, want_asset_id],
credit_in,
Some(want_amount),
)
.map_err(|(credit_in, error)| {
tracing::debug!(
target: "xcm::SingleAssetExchangeAdapter::exchange_asset",
?error,
"Could not perform the swap"
);
drop(credit_in);
give.clone()
})?;
// We don't have leftover assets if exchange was maximal.
(credit_out, None)
} else {
// If `minimal`, then we swap as little of `credit_in` as we can to get exactly
// `want_amount` of `want_asset_id`.
let (credit_out, credit_change) =
<AssetConversion as SwapCredit<_>>::swap_tokens_for_exact_tokens(
vec![swap_asset, want_asset_id],
credit_in,
want_amount,
)
.map_err(|(credit_in, error)| {
tracing::debug!(
target: "xcm::SingleAssetExchangeAdapter::exchange_asset",
?error,
"Could not perform the swap",
);
drop(credit_in);
give.clone()
})?;
(credit_out, if credit_change.peek() > 0 { Some(credit_change) } else { None })
};
// We create an `AssetsInHolding` instance by putting in the resulting asset
// of the exchange.
let resulting_asset: Asset = (want_asset.id.clone(), credit_out.peek()).into();
let mut result: AssetsInHolding = resulting_asset.into();
// If we have some leftover assets from the exchange, also put them in the result.
if let Some(credit_change) = maybe_credit_change {
let leftover_asset: Asset = (give_asset.id.clone(), credit_change.peek()).into();
result.subsume(leftover_asset);
}
Ok(result.into())
}
fn quote_exchange_price(give: &Assets, want: &Assets, maximal: bool) -> Option<Assets> {
if give.len() != 1 || want.len() != 1 {
return None;
} // We only support 1 asset in `give` or `want`.
let give_asset = give.get(0)?;
let want_asset = want.get(0)?;
// We first match both XCM assets to the asset ID types `AssetConversion` can handle.
let (give_asset_id, give_amount) = Matcher::matches_fungibles(give_asset)
.map_err(|error| {
tracing::trace!(
target: "xcm::SingleAssetExchangeAdapter::quote_exchange_price",
?give_asset,
?error,
"Could not map XCM asset to FRAME asset."
);
()
})
.ok()?;
let (want_asset_id, want_amount) = Matcher::matches_fungibles(want_asset)
.map_err(|error| {
tracing::trace!(
target: "xcm::SingleAssetExchangeAdapter::quote_exchange_price",
?want_asset,
?error,
"Could not map XCM asset to FRAME asset"
);
()
})
.ok()?;
// We quote the price.
if maximal {
// The amount of `want` resulting from swapping `give`.
let resulting_want =
<AssetConversion as QuotePrice>::quote_price_exact_tokens_for_tokens(
give_asset_id,
want_asset_id,
give_amount,
true, // Include fee.
)?;
Some((want_asset.id.clone(), resulting_want).into())
} else {
// The `give` amount required to obtain `want`.
let necessary_give =
<AssetConversion as QuotePrice>::quote_price_tokens_for_exact_tokens(
give_asset_id,
want_asset_id,
want_amount,
true, // Include fee.
)?;
Some((give_asset.id.clone(), necessary_give).into())
}
}
}
@@ -0,0 +1,379 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Mock to test [`SingleAssetExchangeAdapter`].
use core::marker::PhantomData;
use frame_support::{
assert_ok, construct_runtime, derive_impl, ord_parameter_types, parameter_types,
traits::{
fungible::{self, NativeFromLeft, NativeOrWithId},
fungibles::Mutate,
tokens::imbalance::ResolveAssetTo,
AsEnsureOriginWithArg, Disabled, Equals, Everything, Nothing, OriginTrait,
PalletInfoAccess,
},
PalletId,
};
use sp_core::{ConstU128, ConstU32, Get};
use sp_runtime::{
traits::{AccountIdConversion, IdentityLookup, MaybeEquivalence, TryConvert, TryConvertInto},
BuildStorage, Permill,
};
use xcm::prelude::*;
use xcm_executor::{traits::ConvertLocation, XcmExecutor};
use crate::{FungibleAdapter, IsConcrete, MatchedConvertedConcreteId, StartsWith};
pub type Block = frame_system::mocking::MockBlock<Runtime>;
pub type AccountId = u64;
pub type Balance = u128;
construct_runtime! {
pub struct Runtime {
System: frame_system,
Balances: pallet_balances,
AssetsPallet: pallet_assets::<Instance1>,
PoolAssets: pallet_assets::<Instance2>,
XcmPallet: pallet_xcm,
AssetConversion: pallet_asset_conversion,
}
}
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Runtime {
type Block = Block;
type AccountId = AccountId;
type Lookup = IdentityLookup<AccountId>;
type AccountData = pallet_balances::AccountData<Balance>;
}
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
impl pallet_balances::Config for Runtime {
type Balance = Balance;
type AccountStore = System;
type ExistentialDeposit = ConstU128<1>;
}
pub type TrustBackedAssetsInstance = pallet_assets::Instance1;
pub type PoolAssetsInstance = pallet_assets::Instance2;
#[derive_impl(pallet_assets::config_preludes::TestDefaultConfig)]
impl pallet_assets::Config<TrustBackedAssetsInstance> for Runtime {
type Currency = Balances;
type Balance = Balance;
type AssetDeposit = ConstU128<1>;
type AssetAccountDeposit = ConstU128<10>;
type MetadataDepositBase = ConstU128<1>;
type MetadataDepositPerByte = ConstU128<1>;
type ApprovalDeposit = ConstU128<1>;
type CreateOrigin = AsEnsureOriginWithArg<frame_system::EnsureSigned<AccountId>>;
type ForceOrigin = frame_system::EnsureRoot<AccountId>;
type Freezer = ();
type Holder = ();
type CallbackHandle = ();
}
#[derive_impl(pallet_assets::config_preludes::TestDefaultConfig)]
impl pallet_assets::Config<PoolAssetsInstance> for Runtime {
type Currency = Balances;
type Balance = Balance;
type AssetDeposit = ConstU128<1>;
type AssetAccountDeposit = ConstU128<10>;
type MetadataDepositBase = ConstU128<1>;
type MetadataDepositPerByte = ConstU128<1>;
type ApprovalDeposit = ConstU128<1>;
type CreateOrigin = AsEnsureOriginWithArg<frame_system::EnsureSigned<AccountId>>;
type ForceOrigin = frame_system::EnsureRoot<AccountId>;
type Freezer = ();
type Holder = ();
type CallbackHandle = ();
}
/// Union fungibles implementation for `Assets` and `Balances`.
pub type NativeAndAssets =
fungible::UnionOf<Balances, AssetsPallet, NativeFromLeft, NativeOrWithId<u32>, AccountId>;
parameter_types! {
pub const AssetConversionPalletId: PalletId = PalletId(*b"py/ascon");
pub const Native: NativeOrWithId<u32> = NativeOrWithId::Native;
pub const LiquidityWithdrawalFee: Permill = Permill::from_percent(0);
}
ord_parameter_types! {
pub const AssetConversionOrigin: AccountId =
AccountIdConversion::<AccountId>::into_account_truncating(&AssetConversionPalletId::get());
}
pub type PoolIdToAccountId = pallet_asset_conversion::AccountIdConverter<
AssetConversionPalletId,
(NativeOrWithId<u32>, NativeOrWithId<u32>),
>;
impl pallet_asset_conversion::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type Balance = Balance;
type HigherPrecisionBalance = sp_core::U256;
type AssetKind = NativeOrWithId<u32>;
type Assets = NativeAndAssets;
type PoolId = (Self::AssetKind, Self::AssetKind);
type PoolLocator = pallet_asset_conversion::WithFirstAsset<
Native,
AccountId,
Self::AssetKind,
PoolIdToAccountId,
>;
type PoolAssetId = u32;
type PoolAssets = PoolAssets;
type PoolSetupFee = ConstU128<100>; // Asset class deposit fees are sufficient to prevent spam
type PoolSetupFeeAsset = Native;
type PoolSetupFeeTarget = ResolveAssetTo<AssetConversionOrigin, Self::Assets>;
type LiquidityWithdrawalFee = LiquidityWithdrawalFee;
type LPFee = ConstU32<3>;
type PalletId = AssetConversionPalletId;
type MaxSwapPathLength = ConstU32<3>;
type MintMinLiquidity = ConstU128<100>;
type WeightInfo = ();
#[cfg(feature = "runtime-benchmarks")]
type BenchmarkHelper = ();
}
/// We only alias local accounts.
pub type LocationToAccountId = AccountIndex64Aliases;
parameter_types! {
pub HereLocation: Location = Here.into_location();
pub WeightPerInstruction: Weight = Weight::from_parts(1, 1);
pub MaxInstructions: u32 = 100;
pub UniversalLocation: InteriorLocation = [GlobalConsensus(Pezkuwi), Teyrchain(1000)].into();
pub TrustBackedAssetsPalletIndex: u8 = <AssetsPallet as PalletInfoAccess>::index() as u8;
pub TrustBackedAssetsPalletLocation: Location = PalletInstance(TrustBackedAssetsPalletIndex::get()).into();
}
/// Adapter for the native token.
pub type FungibleTransactor = FungibleAdapter<
// Use this implementation of the `fungible::*` traits.
// `Balances` is the name given to the balances pallet
Balances,
// This transactor deals with the native token.
IsConcrete<HereLocation>,
// How to convert an XCM Location into a local account id.
// This is also something that's configured in the XCM executor.
LocationToAccountId,
// The type for account ids, only needed because `fungible` is generic over it.
AccountId,
// Not tracking teleports.
(),
>;
pub type Weigher = crate::FixedWeightBounds<WeightPerInstruction, RuntimeCall, MaxInstructions>;
pub struct LocationToAssetId;
impl MaybeEquivalence<Location, NativeOrWithId<u32>> for LocationToAssetId {
fn convert(location: &Location) -> Option<NativeOrWithId<u32>> {
let pallet_instance = TrustBackedAssetsPalletIndex::get();
match location.unpack() {
(0, [PalletInstance(instance), GeneralIndex(index)])
if *instance == pallet_instance =>
Some(NativeOrWithId::WithId(*index as u32)),
(0, []) => Some(NativeOrWithId::Native),
_ => None,
}
}
fn convert_back(asset_id: &NativeOrWithId<u32>) -> Option<Location> {
let pallet_instance = TrustBackedAssetsPalletIndex::get();
Some(match asset_id {
NativeOrWithId::WithId(id) =>
Location::new(0, [PalletInstance(pallet_instance), GeneralIndex((*id).into())]),
NativeOrWithId::Native => Location::new(0, []),
})
}
}
pub type PoolAssetsExchanger = crate::SingleAssetExchangeAdapter<
AssetConversion,
NativeAndAssets,
MatchedConvertedConcreteId<
NativeOrWithId<u32>,
Balance,
(StartsWith<TrustBackedAssetsPalletLocation>, Equals<HereLocation>),
LocationToAssetId,
TryConvertInto,
>,
AccountId,
>;
pub struct XcmConfig;
impl xcm_executor::Config for XcmConfig {
type RuntimeCall = RuntimeCall;
type XcmSender = ();
type XcmEventEmitter = ();
type AssetTransactor = FungibleTransactor;
type OriginConverter = ();
type IsReserve = ();
type IsTeleporter = ();
type UniversalLocation = UniversalLocation;
// This is not safe, you should use `crate::AllowTopLevelPaidExecutionFrom<T>` in a
// production chain
type Barrier = crate::AllowUnpaidExecutionFrom<Everything>;
type Weigher = Weigher;
type Trader = ();
type ResponseHandler = ();
type AssetTrap = ();
type AssetLocker = ();
type AssetExchanger = PoolAssetsExchanger;
type AssetClaims = ();
type SubscriptionService = ();
type PalletInstancesInfo = ();
type FeeManager = ();
type MaxAssetsIntoHolding = ConstU32<1>;
type MessageExporter = ();
type UniversalAliases = Nothing;
type CallDispatcher = RuntimeCall;
type SafeCallFilter = Everything;
type Aliasers = Nothing;
type TransactionalProcessor = crate::FrameTransactionalProcessor;
type HrmpNewChannelOpenRequestHandler = ();
type HrmpChannelAcceptedHandler = ();
type HrmpChannelClosingHandler = ();
type XcmRecorder = ();
}
/// Simple converter from a [`Location`] with an [`AccountIndex64`] junction and no parent to a
/// `u64`.
pub struct AccountIndex64Aliases;
impl ConvertLocation<AccountId> for AccountIndex64Aliases {
fn convert_location(location: &Location) -> Option<AccountId> {
let index = match location.unpack() {
(0, [AccountIndex64 { index, network: None }]) => index,
_ => return None,
};
Some((*index).into())
}
}
/// `Convert` implementation to convert from some a `Signed` (system) `Origin` into an
/// `AccountIndex64`.
///
/// Typically used when configuring `pallet-xcm` in tests to allow `u64` accounts to dispatch an XCM
/// from an `AccountIndex64` origin.
pub struct SignedToAccountIndex64<RuntimeOrigin, AccountId, Network>(
PhantomData<(RuntimeOrigin, AccountId, Network)>,
);
impl<RuntimeOrigin: OriginTrait + Clone, AccountId: Into<u64>, Network: Get<Option<NetworkId>>>
TryConvert<RuntimeOrigin, Location> for SignedToAccountIndex64<RuntimeOrigin, AccountId, Network>
where
RuntimeOrigin::PalletsOrigin: From<frame_system::RawOrigin<AccountId>>
+ TryInto<frame_system::RawOrigin<AccountId>, Error = RuntimeOrigin::PalletsOrigin>,
{
fn try_convert(o: RuntimeOrigin) -> Result<Location, RuntimeOrigin> {
o.try_with_caller(|caller| match caller.try_into() {
Ok(frame_system::RawOrigin::Signed(who)) =>
Ok(Junction::AccountIndex64 { network: Network::get(), index: who.into() }.into()),
Ok(other) => Err(other.into()),
Err(other) => Err(other),
})
}
}
parameter_types! {
pub const NoNetwork: Option<NetworkId> = None;
}
/// Converts a local signed origin into an XCM location. Forms the basis for local origins
/// sending/executing XCMs.
pub type LocalOriginToLocation = SignedToAccountIndex64<RuntimeOrigin, AccountId, NoNetwork>;
impl pallet_xcm::Config for Runtime {
// We turn off sending for these tests
type SendXcmOrigin = crate::EnsureXcmOrigin<RuntimeOrigin, ()>;
type XcmRouter = ();
// Anyone can execute XCM programs
type ExecuteXcmOrigin = crate::EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
// We execute any type of program
type XcmExecuteFilter = Everything;
// How we execute programs
type XcmExecutor = XcmExecutor<XcmConfig>;
// We don't allow teleports
type XcmTeleportFilter = Nothing;
// We don't allow reserve transfers
type XcmReserveTransferFilter = Nothing;
// Same weigher executor uses to weigh XCM programs
type Weigher = Weigher;
// Same universal location
type UniversalLocation = UniversalLocation;
// No version discovery needed
const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 0;
type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion;
type AdminOrigin = frame_system::EnsureRoot<AccountId>;
// No locking
type TrustedLockers = ();
type MaxLockers = frame_support::traits::ConstU32<0>;
type MaxRemoteLockConsumers = frame_support::traits::ConstU32<0>;
type RemoteLockConsumerIdentifier = ();
// How to turn locations into accounts
type SovereignAccountOf = LocationToAccountId;
// A currency to pay for things and its matcher, we are using the relay token
type Currency = Balances;
type CurrencyMatcher = crate::IsConcrete<HereLocation>;
// Pallet benchmarks, no need for this recipe
type WeightInfo = pallet_xcm::TestWeightInfo;
// Runtime types
type RuntimeOrigin = RuntimeOrigin;
type RuntimeCall = RuntimeCall;
type RuntimeEvent = RuntimeEvent;
// Aliasing is disabled: xcm_executor::Config::Aliasers is set to `Nothing`.
type AuthorizedAliasConsideration = Disabled;
}
pub const INITIAL_BALANCE: Balance = 1_000_000_000;
pub fn new_test_ext() -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
pallet_balances::GenesisConfig::<Runtime> {
balances: vec![(0, INITIAL_BALANCE), (1, INITIAL_BALANCE), (2, INITIAL_BALANCE)],
..Default::default()
}
.assimilate_storage(&mut t)
.unwrap();
let owner = 0;
let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(|| {
System::set_block_number(1);
assert_ok!(AssetsPallet::force_create(RuntimeOrigin::root(), 1, owner, false, 1,));
assert_ok!(AssetsPallet::mint_into(1, &owner, INITIAL_BALANCE,));
assert_ok!(AssetConversion::create_pool(
RuntimeOrigin::signed(owner),
Box::new(NativeOrWithId::Native),
Box::new(NativeOrWithId::WithId(1)),
));
assert_ok!(AssetConversion::add_liquidity(
RuntimeOrigin::signed(owner),
Box::new(NativeOrWithId::Native),
Box::new(NativeOrWithId::WithId(1)),
50_000_000,
100_000_000,
0,
0,
owner,
));
});
ext
}
@@ -0,0 +1,25 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! SingleAssetExchangeAdapter.
mod adapter;
pub use adapter::SingleAssetExchangeAdapter;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
@@ -0,0 +1,233 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Tests for the [`SingleAssetExchangeAdapter`] type.
use super::mock::*;
use xcm::prelude::*;
use xcm_executor::{traits::AssetExchange, AssetsInHolding};
// ========== Happy path ==========
/// Scenario:
/// Account #3 wants to use the local liquidity pool between two custom assets,
/// 1 and 2.
#[test]
fn maximal_exchange() {
new_test_ext().execute_with(|| {
let assets = PoolAssetsExchanger::exchange_asset(
None,
vec![([PalletInstance(2), GeneralIndex(1)], 10_000_000).into()].into(),
&vec![(Here, 2_000_000).into()].into(),
true, // Maximal
)
.unwrap();
let amount = get_amount_from_first_fungible(&assets);
assert_eq!(amount, 4_533_054);
});
}
#[test]
fn minimal_exchange() {
new_test_ext().execute_with(|| {
let assets = PoolAssetsExchanger::exchange_asset(
None,
vec![([PalletInstance(2), GeneralIndex(1)], 10_000_000).into()].into(),
&vec![(Here, 2_000_000).into()].into(),
false, // Minimal
)
.unwrap();
let (first_amount, second_amount) = get_amount_from_fungibles(&assets);
assert_eq!(first_amount, 2_000_000);
assert_eq!(second_amount, 5_820_795);
});
}
#[test]
fn maximal_quote() {
new_test_ext().execute_with(|| {
let assets = quote(
&([PalletInstance(2), GeneralIndex(1)], 10_000_000).into(),
&(Here, 2_000_000).into(),
true,
)
.unwrap();
let amount = get_amount_from_first_fungible(&assets.into());
// The amount of the native token resulting from swapping all `10_000_000` of the custom
// token.
assert_eq!(amount, 4_533_054);
});
}
#[test]
fn minimal_quote() {
new_test_ext().execute_with(|| {
let assets = quote(
&([PalletInstance(2), GeneralIndex(1)], 10_000_000).into(),
&(Here, 2_000_000).into(),
false,
)
.unwrap();
let amount = get_amount_from_first_fungible(&assets.into());
// The amount of the custom token needed to get `2_000_000` of the native token.
assert_eq!(amount, 4_179_205);
});
}
// ========== Unhappy path ==========
#[test]
fn no_asset_in_give() {
new_test_ext().execute_with(|| {
assert!(PoolAssetsExchanger::exchange_asset(
None,
vec![].into(),
&vec![(Here, 2_000_000).into()].into(),
true
)
.is_err());
});
}
#[test]
fn more_than_one_asset_in_give() {
new_test_ext().execute_with(|| {
assert!(PoolAssetsExchanger::exchange_asset(
None,
vec![([PalletInstance(2), GeneralIndex(1)], 1).into(), (Here, 2).into()].into(),
&vec![(Here, 2_000_000).into()].into(),
true
)
.is_err());
});
}
#[test]
fn no_asset_in_want() {
new_test_ext().execute_with(|| {
assert!(PoolAssetsExchanger::exchange_asset(
None,
vec![([PalletInstance(2), GeneralIndex(1)], 10_000_000).into()].into(),
&vec![].into(),
true
)
.is_err());
});
}
#[test]
fn more_than_one_asset_in_want() {
new_test_ext().execute_with(|| {
assert!(PoolAssetsExchanger::exchange_asset(
None,
vec![([PalletInstance(2), GeneralIndex(1)], 10_000_000).into()].into(),
&vec![(Here, 2_000_000).into(), ([PalletInstance(2), GeneralIndex(1)], 1).into()]
.into(),
true
)
.is_err());
});
}
#[test]
fn give_asset_does_not_match() {
new_test_ext().execute_with(|| {
let nonexistent_asset_id = 1000;
assert!(PoolAssetsExchanger::exchange_asset(
None,
vec![([PalletInstance(2), GeneralIndex(nonexistent_asset_id)], 10_000_000).into()]
.into(),
&vec![(Here, 2_000_000).into()].into(),
true
)
.is_err());
});
}
#[test]
fn want_asset_does_not_match() {
new_test_ext().execute_with(|| {
let nonexistent_asset_id = 1000;
assert!(PoolAssetsExchanger::exchange_asset(
None,
vec![(Here, 2_000_000).into()].into(),
&vec![([PalletInstance(2), GeneralIndex(nonexistent_asset_id)], 10_000_000).into()]
.into(),
true
)
.is_err());
});
}
#[test]
fn exchange_fails() {
new_test_ext().execute_with(|| {
assert!(PoolAssetsExchanger::exchange_asset(
None,
vec![([PalletInstance(2), GeneralIndex(1)], 10_000_000).into()].into(),
// We're asking for too much of the native token...
&vec![(Here, 200_000_000).into()].into(),
false, // Minimal
)
.is_err());
});
}
#[test]
fn non_fungible_asset_in_give() {
new_test_ext().execute_with(|| {
assert!(PoolAssetsExchanger::exchange_asset(
None,
// Using `u64` here will give us a non-fungible instead of a fungible.
vec![([PalletInstance(2), GeneralIndex(2)], 10_000_000u64).into()].into(),
&vec![(Here, 10_000_000).into()].into(),
false, // Minimal
)
.is_err());
});
}
// ========== Helper functions ==========
fn get_amount_from_first_fungible(assets: &AssetsInHolding) -> u128 {
let mut fungibles_iter = assets.fungible_assets_iter();
let first_fungible = fungibles_iter.next().unwrap();
let Fungible(amount) = first_fungible.fun else {
unreachable!("Asset should be fungible");
};
amount
}
fn get_amount_from_fungibles(assets: &AssetsInHolding) -> (u128, u128) {
let mut fungibles_iter = assets.fungible_assets_iter();
let first_fungible = fungibles_iter.next().unwrap();
let Fungible(first_amount) = first_fungible.fun else {
unreachable!("Asset should be fungible");
};
let second_fungible = fungibles_iter.next().unwrap();
let Fungible(second_amount) = second_fungible.fun else {
unreachable!("Asset should be fungible");
};
(first_amount, second_amount)
}
fn quote(asset_1: &Asset, asset_2: &Asset, maximal: bool) -> Option<Assets> {
PoolAssetsExchanger::quote_exchange_price(
&asset_1.clone().into(),
&asset_2.clone().into(),
maximal,
)
}
+680
View File
@@ -0,0 +1,680 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Various implementations for `ShouldExecute`.
use crate::{CreateMatcher, MatchXcm};
use core::{cell::Cell, marker::PhantomData, ops::ControlFlow, result::Result};
use frame_support::{
ensure,
traits::{Contains, ContainsPair, Get, Nothing, ProcessMessageError},
};
use pezkuwi_teyrchain_primitives::primitives::IsSystem;
use xcm::prelude::*;
use xcm_executor::traits::{CheckSuspension, DenyExecution, OnResponse, Properties, ShouldExecute};
/// Execution barrier that just takes `max_weight` from `properties.weight_credit`.
///
/// Useful to allow XCM execution by local chain users via extrinsics.
/// E.g. `pallet_xcm::reserve_asset_transfer` to transfer a reserve asset
/// out of the local chain to another one.
pub struct TakeWeightCredit;
impl ShouldExecute for TakeWeightCredit {
fn should_execute<RuntimeCall>(
origin: &Location,
instructions: &mut [Instruction<RuntimeCall>],
max_weight: Weight,
properties: &mut Properties,
) -> Result<(), ProcessMessageError> {
tracing::trace!(
target: "xcm::barriers",
?origin,
?instructions,
?max_weight,
?properties,
"TakeWeightCredit"
);
properties.weight_credit = properties
.weight_credit
.checked_sub(&max_weight)
.ok_or(ProcessMessageError::Overweight(max_weight))?;
Ok(())
}
}
const MAX_ASSETS_FOR_BUY_EXECUTION: usize = 2;
/// Allows execution from `origin` if it is contained in `T` (i.e. `T::Contains(origin)`) taking
/// payments into account.
///
/// Only allows for `WithdrawAsset`, `ReceiveTeleportedAsset`, `ReserveAssetDeposited` and
/// `ClaimAsset` XCMs because they are the only ones that place assets in the Holding Register to
/// pay for execution.
pub struct AllowTopLevelPaidExecutionFrom<T>(PhantomData<T>);
impl<T: Contains<Location>> ShouldExecute for AllowTopLevelPaidExecutionFrom<T> {
fn should_execute<RuntimeCall>(
origin: &Location,
instructions: &mut [Instruction<RuntimeCall>],
max_weight: Weight,
properties: &mut Properties,
) -> Result<(), ProcessMessageError> {
tracing::trace!(
target: "xcm::barriers",
?origin,
?instructions,
?max_weight,
?properties,
"AllowTopLevelPaidExecutionFrom",
);
ensure!(T::contains(origin), ProcessMessageError::Unsupported);
// We will read up to 5 instructions. This allows up to 3 `ClearOrigin` instructions. We
// allow for more than one since anything beyond the first is a no-op and it's conceivable
// that composition of operations might result in more than one being appended.
let end = instructions.len().min(5);
instructions[..end]
.matcher()
.match_next_inst(|inst| match inst {
WithdrawAsset(ref assets) |
ReceiveTeleportedAsset(ref assets) |
ReserveAssetDeposited(ref assets) |
ClaimAsset { ref assets, .. } =>
if assets.len() <= MAX_ASSETS_FOR_BUY_EXECUTION {
Ok(())
} else {
Err(ProcessMessageError::BadFormat)
},
_ => Err(ProcessMessageError::BadFormat),
})?
.skip_inst_while(|inst| {
matches!(inst, ClearOrigin | AliasOrigin(..)) ||
matches!(inst, DescendOrigin(child) if child != &Here) ||
matches!(inst, SetHints { .. })
})?
.match_next_inst(|inst| match inst {
BuyExecution { weight_limit: Limited(ref mut weight), .. }
if weight.all_gte(max_weight) =>
{
*weight = max_weight;
Ok(())
},
BuyExecution { ref mut weight_limit, .. } if weight_limit == &Unlimited => {
*weight_limit = Limited(max_weight);
Ok(())
},
PayFees { .. } => Ok(()),
_ => Err(ProcessMessageError::Overweight(max_weight)),
})?;
Ok(())
}
}
/// A derivative barrier, which scans the first `MaxPrefixes` instructions for origin-alterers and
/// then evaluates `should_execute` of the `InnerBarrier` based on the remaining instructions and
/// the newly computed origin.
///
/// This effectively allows for the possibility of distinguishing an origin which is acting as a
/// router for its derivative locations (or as a bridge for a remote location) and an origin which
/// is actually trying to send a message for itself. In the former case, the message will be
/// prefixed with origin-mutating instructions.
///
/// Any barriers which should be interpreted based on the computed origin rather than the original
/// message origin should be subject to this. This is the case for most barriers since the
/// effective origin is generally more important than the routing origin. Any other barriers, and
/// especially those which should be interpreted only the routing origin should not be subject to
/// this.
///
/// E.g.
/// ```nocompile
/// type MyBarrier = (
/// TakeWeightCredit,
/// AllowTopLevelPaidExecutionFrom<DirectCustomerLocations>,
/// WithComputedOrigin<(
/// AllowTopLevelPaidExecutionFrom<DerivativeCustomerLocations>,
/// AllowUnpaidExecutionFrom<ParentLocation>,
/// AllowSubscriptionsFrom<AllowedSubscribers>,
/// AllowKnownQueryResponses<TheResponseHandler>,
/// )>,
/// );
/// ```
///
/// In the above example, `AllowUnpaidExecutionFrom` appears once underneath
/// `WithComputedOrigin`. This is in order to distinguish between messages which are notionally
/// from a derivative location of `ParentLocation` but that just happened to be sent via
/// `ParentLocation` rather than messages that were sent by the parent.
///
/// Similarly `AllowTopLevelPaidExecutionFrom` appears twice: once inside of `WithComputedOrigin`
/// where we provide the list of origins which are derivative origins, and then secondly outside
/// of `WithComputedOrigin` where we provide the list of locations which are direct origins. It's
/// reasonable for these lists to be merged into one and that used both inside and out.
///
/// Finally, we see `AllowSubscriptionsFrom` and `AllowKnownQueryResponses` are both inside of
/// `WithComputedOrigin`. This means that if a message begins with origin-mutating instructions,
/// then it must be the finally computed origin which we accept subscriptions or expect a query
/// response from. For example, even if an origin appeared in the `AllowedSubscribers` list, we
/// would ignore this rule if it began with origin mutators and they changed the origin to something
/// which was not on the list.
pub struct WithComputedOrigin<InnerBarrier, LocalUniversal, MaxPrefixes>(
PhantomData<(InnerBarrier, LocalUniversal, MaxPrefixes)>,
);
impl<InnerBarrier: ShouldExecute, LocalUniversal: Get<InteriorLocation>, MaxPrefixes: Get<u32>>
ShouldExecute for WithComputedOrigin<InnerBarrier, LocalUniversal, MaxPrefixes>
{
fn should_execute<Call>(
origin: &Location,
instructions: &mut [Instruction<Call>],
max_weight: Weight,
properties: &mut Properties,
) -> Result<(), ProcessMessageError> {
tracing::trace!(
target: "xcm::barriers",
?origin,
?instructions,
?max_weight,
?properties,
"WithComputedOrigin"
);
let mut actual_origin = origin.clone();
let skipped = Cell::new(0usize);
// NOTE: We do not check the validity of `UniversalOrigin` here, meaning that a malicious
// origin could place a `UniversalOrigin` in order to spoof some location which gets free
// execution. This technical could get it past the barrier condition, but the execution
// would instantly fail since the first instruction would cause an error with the
// invalid UniversalOrigin.
instructions.matcher().match_next_inst_while(
|_| skipped.get() < MaxPrefixes::get() as usize,
|inst| {
match inst {
UniversalOrigin(new_global) => {
// Note the origin is *relative to local consensus*! So we need to escape
// local consensus with the `parents` before diving in into the
// `universal_location`.
actual_origin =
Junctions::from([*new_global]).relative_to(&LocalUniversal::get());
},
DescendOrigin(j) => {
let Ok(_) = actual_origin.append_with(j.clone()) else {
return Err(ProcessMessageError::Unsupported);
};
},
_ => return Ok(ControlFlow::Break(())),
};
skipped.set(skipped.get() + 1);
Ok(ControlFlow::Continue(()))
},
)?;
InnerBarrier::should_execute(
&actual_origin,
&mut instructions[skipped.get()..],
max_weight,
properties,
)
}
}
/// Sets the message ID to `t` using a `SetTopic(t)` in the last position if present.
///
/// Note that the message ID does not necessarily have to be unique; it is the
/// sender's responsibility to ensure uniqueness.
///
/// Requires some inner barrier to pass on the rest of the message.
pub struct TrailingSetTopicAsId<InnerBarrier>(PhantomData<InnerBarrier>);
impl<InnerBarrier: ShouldExecute> ShouldExecute for TrailingSetTopicAsId<InnerBarrier> {
fn should_execute<Call>(
origin: &Location,
instructions: &mut [Instruction<Call>],
max_weight: Weight,
properties: &mut Properties,
) -> Result<(), ProcessMessageError> {
tracing::trace!(
target: "xcm::barriers",
?origin,
?instructions,
?max_weight,
?properties,
"TrailingSetTopicAsId"
);
let until = if let Some(SetTopic(t)) = instructions.last() {
properties.message_id = Some(*t);
instructions.len() - 1
} else {
instructions.len()
};
InnerBarrier::should_execute(&origin, &mut instructions[..until], max_weight, properties)
}
}
/// Barrier condition that allows for a `SuspensionChecker` that controls whether or not the XCM
/// executor will be suspended from executing the given XCM.
pub struct RespectSuspension<Inner, SuspensionChecker>(PhantomData<(Inner, SuspensionChecker)>);
impl<Inner, SuspensionChecker> ShouldExecute for RespectSuspension<Inner, SuspensionChecker>
where
Inner: ShouldExecute,
SuspensionChecker: CheckSuspension,
{
fn should_execute<Call>(
origin: &Location,
instructions: &mut [Instruction<Call>],
max_weight: Weight,
properties: &mut Properties,
) -> Result<(), ProcessMessageError> {
if SuspensionChecker::is_suspended(origin, instructions, max_weight, properties) {
Err(ProcessMessageError::Yield)
} else {
Inner::should_execute(origin, instructions, max_weight, properties)
}
}
}
/// Allows execution from any origin that is contained in `T` (i.e. `T::Contains(origin)`).
///
/// Use only for executions from completely trusted origins, from which no permissionless messages
/// can be sent.
pub struct AllowUnpaidExecutionFrom<T>(PhantomData<T>);
impl<T: Contains<Location>> ShouldExecute for AllowUnpaidExecutionFrom<T> {
fn should_execute<RuntimeCall>(
origin: &Location,
instructions: &mut [Instruction<RuntimeCall>],
max_weight: Weight,
properties: &mut Properties,
) -> Result<(), ProcessMessageError> {
tracing::trace!(
target: "xcm::barriers",
?origin, ?instructions, ?max_weight, ?properties,
"AllowUnpaidExecutionFrom"
);
ensure!(T::contains(origin), ProcessMessageError::Unsupported);
Ok(())
}
}
/// Allows execution from any origin that is contained in `T` (i.e. `T::Contains(origin)`) if the
/// message explicitly includes the `UnpaidExecution` instruction.
///
/// Use only for executions from trusted origin groups.
///
/// Allows for the message to receive teleports or reserve asset transfers and altering
/// the origin before indicating `UnpaidExecution`.
///
/// Origin altering instructions are executed so the barrier can more accurately reject messages
/// whose effective origin at the time of calling `UnpaidExecution` is not allowed.
/// This means `T` will be checked against the actual origin _after_ being modified by prior
/// instructions.
///
/// In order to execute the `AliasOrigin` instruction, the `Aliasers` type should be set to the same
/// `Aliasers` item in the XCM configuration. If it isn't, then all messages with an `AliasOrigin`
/// instruction will be rejected.
pub struct AllowExplicitUnpaidExecutionFrom<T, Aliasers = Nothing>(PhantomData<(T, Aliasers)>);
impl<T: Contains<Location>, Aliasers: ContainsPair<Location, Location>> ShouldExecute
for AllowExplicitUnpaidExecutionFrom<T, Aliasers>
{
fn should_execute<Call>(
origin: &Location,
instructions: &mut [Instruction<Call>],
max_weight: Weight,
properties: &mut Properties,
) -> Result<(), ProcessMessageError> {
tracing::trace!(
target: "xcm::barriers",
?origin, ?instructions, ?max_weight, ?properties,
"AllowExplicitUnpaidExecutionFrom",
);
// We will read up to 5 instructions before `UnpaidExecution`.
// This allows up to 3 asset transfer instructions, thus covering all possible transfer
// types, followed by a potential origin altering instruction, and a potential `SetHints`.
let mut actual_origin = origin.clone();
let processed = Cell::new(0usize);
let instructions_to_process = 5;
instructions
.matcher()
// We skip set hints and all types of asset transfer instructions.
.match_next_inst_while(
|inst| {
processed.get() < instructions_to_process &&
matches!(
inst,
ReceiveTeleportedAsset(_) |
ReserveAssetDeposited(_) | WithdrawAsset(_) |
SetHints { .. }
)
},
|_| {
processed.set(processed.get() + 1);
Ok(ControlFlow::Continue(()))
},
)?
// Then we go through all origin altering instructions and we
// alter the original origin.
.match_next_inst_while(
|_| processed.get() < instructions_to_process,
|inst| {
match inst {
ClearOrigin => {
// We don't support the `ClearOrigin` instruction since we always need
// to know the origin to know if it's allowed unpaid execution.
return Err(ProcessMessageError::Unsupported);
},
AliasOrigin(target) =>
if Aliasers::contains(&actual_origin, &target) {
actual_origin = target.clone();
} else {
return Err(ProcessMessageError::Unsupported);
},
DescendOrigin(child) if child != &Here => {
let Ok(_) = actual_origin.append_with(child.clone()) else {
return Err(ProcessMessageError::Unsupported);
};
},
_ => return Ok(ControlFlow::Break(())),
};
processed.set(processed.get() + 1);
Ok(ControlFlow::Continue(()))
},
)?
// We finally match on the required `UnpaidExecution` instruction.
.match_next_inst(|inst| match inst {
UnpaidExecution { weight_limit: Limited(m), .. } if m.all_gte(max_weight) => Ok(()),
UnpaidExecution { weight_limit: Unlimited, .. } => Ok(()),
_ => Err(ProcessMessageError::Overweight(max_weight)),
})?;
// After processing all the instructions, `actual_origin` was modified and we
// check if it's allowed to have unpaid execution.
ensure!(T::contains(&actual_origin), ProcessMessageError::Unsupported);
Ok(())
}
}
/// Allows a message only if it is from a system-level child teyrchain.
pub struct IsChildSystemTeyrchain<ParaId>(PhantomData<ParaId>);
impl<ParaId: IsSystem + From<u32>> Contains<Location> for IsChildSystemTeyrchain<ParaId> {
fn contains(l: &Location) -> bool {
matches!(
l.interior().as_slice(),
[Junction::Teyrchain(id)]
if ParaId::from(*id).is_system() && l.parent_count() == 0,
)
}
}
/// Matches if the given location is a system-level sibling teyrchain.
pub struct IsSiblingSystemTeyrchain<ParaId, SelfParaId>(PhantomData<(ParaId, SelfParaId)>);
impl<ParaId: IsSystem + From<u32> + Eq, SelfParaId: Get<ParaId>> Contains<Location>
for IsSiblingSystemTeyrchain<ParaId, SelfParaId>
{
fn contains(l: &Location) -> bool {
matches!(
l.unpack(),
(1, [Junction::Teyrchain(id)])
if SelfParaId::get() != ParaId::from(*id) && ParaId::from(*id).is_system(),
)
}
}
/// Matches if the given location contains only the specified amount of parents and no interior
/// junctions.
pub struct IsParentsOnly<Count>(PhantomData<Count>);
impl<Count: Get<u8>> Contains<Location> for IsParentsOnly<Count> {
fn contains(t: &Location) -> bool {
t.contains_parents_only(Count::get())
}
}
/// Allows only messages if the generic `ResponseHandler` expects them via `expecting_response`.
pub struct AllowKnownQueryResponses<ResponseHandler>(PhantomData<ResponseHandler>);
impl<ResponseHandler: OnResponse> ShouldExecute for AllowKnownQueryResponses<ResponseHandler> {
fn should_execute<RuntimeCall>(
origin: &Location,
instructions: &mut [Instruction<RuntimeCall>],
max_weight: Weight,
properties: &mut Properties,
) -> Result<(), ProcessMessageError> {
tracing::trace!(
target: "xcm::barriers",
?origin, ?instructions, ?max_weight, ?properties,
"AllowKnownQueryResponses"
);
instructions
.matcher()
.assert_remaining_insts(1)?
.match_next_inst(|inst| match inst {
QueryResponse { query_id, querier, .. }
if ResponseHandler::expecting_response(origin, *query_id, querier.as_ref()) =>
Ok(()),
_ => Err(ProcessMessageError::BadFormat),
})?;
Ok(())
}
}
/// Allows execution from `origin` if it is just a straight `SubscribeVersion` or
/// `UnsubscribeVersion` instruction.
pub struct AllowSubscriptionsFrom<T>(PhantomData<T>);
impl<T: Contains<Location>> ShouldExecute for AllowSubscriptionsFrom<T> {
fn should_execute<RuntimeCall>(
origin: &Location,
instructions: &mut [Instruction<RuntimeCall>],
max_weight: Weight,
properties: &mut Properties,
) -> Result<(), ProcessMessageError> {
tracing::trace!(
target: "xcm::barriers",
?origin, ?instructions, ?max_weight, ?properties,
"AllowSubscriptionsFrom",
);
ensure!(T::contains(origin), ProcessMessageError::Unsupported);
instructions
.matcher()
.assert_remaining_insts(1)?
.match_next_inst(|inst| match inst {
SubscribeVersion { .. } | UnsubscribeVersion => Ok(()),
_ => Err(ProcessMessageError::BadFormat),
})?;
Ok(())
}
}
/// Allows execution for the Relay Chain origin (represented as `Location::parent()`) if it is just
/// a straight `HrmpNewChannelOpenRequest`, `HrmpChannelAccepted`, or `HrmpChannelClosing`
/// instruction.
///
/// Note: This barrier fulfills safety recommendations for the mentioned instructions - see their
/// documentation.
pub struct AllowHrmpNotificationsFromRelayChain;
impl ShouldExecute for AllowHrmpNotificationsFromRelayChain {
fn should_execute<RuntimeCall>(
origin: &Location,
instructions: &mut [Instruction<RuntimeCall>],
max_weight: Weight,
properties: &mut Properties,
) -> Result<(), ProcessMessageError> {
tracing::trace!(
target: "xcm::barriers",
?origin, ?instructions, ?max_weight, ?properties,
"AllowHrmpNotificationsFromRelayChain"
);
// accept only the Relay Chain
ensure!(matches!(origin.unpack(), (1, [])), ProcessMessageError::Unsupported);
// accept only HRMP notifications and nothing else
instructions
.matcher()
.assert_remaining_insts(1)?
.match_next_inst(|inst| match inst {
HrmpNewChannelOpenRequest { .. } |
HrmpChannelAccepted { .. } |
HrmpChannelClosing { .. } => Ok(()),
_ => Err(ProcessMessageError::BadFormat),
})?;
Ok(())
}
}
/// Deny executing the XCM if it matches any of the Deny filter regardless of anything else.
/// If it passes the Deny, and matches one of the Allow cases then it is let through.
pub struct DenyThenTry<Deny, Allow>(PhantomData<Deny>, PhantomData<Allow>)
where
Deny: DenyExecution,
Allow: ShouldExecute;
impl<Deny, Allow> ShouldExecute for DenyThenTry<Deny, Allow>
where
Deny: DenyExecution,
Allow: ShouldExecute,
{
fn should_execute<RuntimeCall>(
origin: &Location,
message: &mut [Instruction<RuntimeCall>],
max_weight: Weight,
properties: &mut Properties,
) -> Result<(), ProcessMessageError> {
Deny::deny_execution(origin, message, max_weight, properties)?;
Allow::should_execute(origin, message, max_weight, properties)
}
}
// See issue <https://github.com/paritytech/polkadot/issues/5233>
pub struct DenyReserveTransferToRelayChain;
impl DenyExecution for DenyReserveTransferToRelayChain {
fn deny_execution<RuntimeCall>(
origin: &Location,
message: &mut [Instruction<RuntimeCall>],
_max_weight: Weight,
_properties: &mut Properties,
) -> Result<(), ProcessMessageError> {
message.matcher().match_next_inst_while(
|_| true,
|inst| match inst {
InitiateReserveWithdraw {
reserve: Location { parents: 1, interior: Here },
..
} |
DepositReserveAsset { dest: Location { parents: 1, interior: Here }, .. } |
TransferReserveAsset { dest: Location { parents: 1, interior: Here }, .. } => {
Err(ProcessMessageError::Unsupported) // Deny
},
// An unexpected reserve transfer has arrived from the Relay Chain. Generally,
// `IsReserve` should not allow this, but we just log it here.
ReserveAssetDeposited { .. }
if matches!(origin, Location { parents: 1, interior: Here }) =>
{
tracing::debug!(
target: "xcm::barriers",
"Unexpected ReserveAssetDeposited from the Relay Chain",
);
Ok(ControlFlow::Continue(()))
},
_ => Ok(ControlFlow::Continue(())),
},
)?;
Ok(())
}
}
environmental::environmental!(recursion_count: u8);
/// Denies execution if the XCM contains instructions not meant to run on this chain,
/// first checking at the top-level and then **recursively**.
///
/// This barrier only applies to **locally executed** XCM instructions (`SetAppendix`,
/// `SetErrorHandler`, and `ExecuteWithOrigin`). Remote parts of the XCM are expected to be
/// validated by the receiving chain's barrier.
///
/// Note: Ensures that restricted instructions do not execute on the local chain, enforcing stricter
/// execution policies while allowing remote chains to enforce their own rules.
pub struct DenyRecursively<Inner>(PhantomData<Inner>);
impl<Inner: DenyExecution> DenyRecursively<Inner> {
/// Recursively applies the deny filter to a nested XCM.
///
/// Ensures that restricted instructions are blocked at any depth within the XCM.
/// Uses a **recursion counter** to prevent stack overflows from deep nesting.
fn deny_recursively<RuntimeCall>(
origin: &Location,
xcm: &mut Xcm<RuntimeCall>,
max_weight: Weight,
properties: &mut Properties,
) -> Result<ControlFlow<()>, ProcessMessageError> {
// Initialise recursion counter for this execution context.
recursion_count::using_once(&mut 1, || {
// Prevent stack overflow by enforcing a recursion depth limit.
recursion_count::with(|count| {
if *count > xcm_executor::RECURSION_LIMIT {
tracing::debug!(
target: "xcm::barriers",
"Recursion limit exceeded (count: {count}), origin: {:?}, xcm: {:?}, max_weight: {:?}, properties: {:?}",
origin, xcm, max_weight, properties
);
return None;
}
*count = count.saturating_add(1);
Some(())
}).flatten().ok_or(ProcessMessageError::StackLimitReached)?;
// Ensure the counter is decremented even if an early return occurs.
sp_core::defer! {
recursion_count::with(|count| {
*count = count.saturating_sub(1);
});
}
// Recursively check the nested XCM instructions.
Self::deny_execution(origin, xcm.inner_mut(), max_weight, properties)
})?;
Ok(ControlFlow::Continue(()))
}
}
impl<Inner: DenyExecution> DenyExecution for DenyRecursively<Inner> {
/// Denies execution of restricted local nested XCM instructions.
///
/// This checks for `SetAppendix`, `SetErrorHandler`, and `ExecuteWithOrigin` instruction
/// applying the deny filter **recursively** to any nested XCMs found.
fn deny_execution<RuntimeCall>(
origin: &Location,
instructions: &mut [Instruction<RuntimeCall>],
max_weight: Weight,
properties: &mut Properties,
) -> Result<(), ProcessMessageError> {
// First, check if the top-level message should be denied.
Inner::deny_execution(origin, instructions, max_weight, properties).inspect_err(|e| {
tracing::debug!(
target: "xcm::barriers",
"DenyRecursively::Inner denied execution, origin: {:?}, instructions: {:?}, max_weight: {:?}, properties: {:?}, error: {:?}",
origin, instructions, max_weight, properties, e
);
})?;
// If the top-level check passes, check nested instructions recursively.
instructions.matcher().match_next_inst_while(
|_| true,
|inst| match inst {
SetAppendix(nested_xcm) |
SetErrorHandler(nested_xcm) |
ExecuteWithOrigin { xcm: nested_xcm, .. } => Self::deny_recursively::<RuntimeCall>(
origin, nested_xcm, max_weight, properties,
),
_ => Ok(ControlFlow::Continue(())),
},
)?;
// Permit everything else
Ok(())
}
}
+192
View File
@@ -0,0 +1,192 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! A set of traits that define how a pallet interface with XCM.
//! Controller traits defined in this module are high-level traits that will rely on other traits
//! from `xcm-executor` to perform their tasks.
use alloc::boxed::Box;
use frame_support::{
dispatch::{DispatchErrorWithPostInfo, WithPostDispatchInfo},
pallet_prelude::DispatchError,
};
use xcm::prelude::*;
pub use xcm_executor::traits::QueryHandler;
/// Umbrella trait for all Controller traits.
pub trait Controller<Origin, RuntimeCall, Timeout>:
ExecuteController<Origin, RuntimeCall> + SendController<Origin> + QueryController<Origin, Timeout>
{
}
impl<T, Origin, RuntimeCall, Timeout> Controller<Origin, RuntimeCall, Timeout> for T where
T: ExecuteController<Origin, RuntimeCall>
+ SendController<Origin>
+ QueryController<Origin, Timeout>
{
}
/// Weight functions needed for [`ExecuteController`].
pub trait ExecuteControllerWeightInfo {
/// Weight for [`ExecuteController::execute`]
fn execute() -> Weight;
}
/// Execute an XCM locally, for a given origin.
///
/// An implementation of that trait will handle the low-level details of the execution, such as:
/// - Validating and Converting the origin to a Location.
/// - Handling versioning.
/// - Calling the internal executor, which implements [`ExecuteXcm`].
pub trait ExecuteController<Origin, RuntimeCall> {
/// Weight information for ExecuteController functions.
type WeightInfo: ExecuteControllerWeightInfo;
/// Attempt to execute an XCM locally, returns Ok with the weight consumed if the execution
/// complete successfully, Err otherwise.
///
/// # Parameters
///
/// - `origin`: the origin of the call.
/// - `message`: the XCM program to be executed.
/// - `max_weight`: the maximum weight that can be consumed by the execution.
fn execute(
origin: Origin,
message: Box<VersionedXcm<RuntimeCall>>,
max_weight: Weight,
) -> Result<Weight, DispatchErrorWithPostInfo>;
}
/// Weight functions needed for [`SendController`].
pub trait SendControllerWeightInfo {
/// Weight for [`SendController::send`]
fn send() -> Weight;
}
/// Send an XCM from a given origin.
///
/// An implementation of that trait will handle the low-level details of dispatching an XCM, such
/// as:
/// - Validating and Converting the origin to an interior location.
/// - Handling versioning.
/// - Calling the internal router, which implements [`SendXcm`].
pub trait SendController<Origin> {
/// Weight information for SendController functions.
type WeightInfo: SendControllerWeightInfo;
/// Send an XCM to be executed by a remote location.
///
/// # Parameters
///
/// - `origin`: the origin of the call.
/// - `dest`: the destination of the message.
/// - `msg`: the XCM to be sent.
fn send(
origin: Origin,
dest: Box<VersionedLocation>,
message: Box<VersionedXcm<()>>,
) -> Result<XcmHash, DispatchError>;
}
/// Weight functions needed for [`QueryController`].
pub trait QueryControllerWeightInfo {
/// Weight for [`QueryController::query`]
fn query() -> Weight;
/// Weight for [`QueryHandler::take_response`]
fn take_response() -> Weight;
}
/// Query a remote location, from a given origin.
///
/// An implementation of that trait will handle the low-level details of querying a remote location,
/// such as:
/// - Validating and Converting the origin to an interior location.
/// - Handling versioning.
/// - Calling the [`QueryHandler`] to register the query.
pub trait QueryController<Origin, Timeout>: QueryHandler {
/// Weight information for QueryController functions.
type WeightInfo: QueryControllerWeightInfo;
/// Query a remote location.
///
/// # Parameters
///
/// - `origin`: the origin of the call, used to determine the responder.
/// - `timeout`: the maximum block number that the query should be responded to.
/// - `match_querier`: the querier that the query should be responded to.
fn query(
origin: Origin,
timeout: Timeout,
match_querier: VersionedLocation,
) -> Result<QueryId, DispatchError>;
}
impl<Origin, RuntimeCall> ExecuteController<Origin, RuntimeCall> for () {
type WeightInfo = ();
fn execute(
_origin: Origin,
_message: Box<VersionedXcm<RuntimeCall>>,
_max_weight: Weight,
) -> Result<Weight, DispatchErrorWithPostInfo> {
Err(DispatchError::Other("ExecuteController::execute not implemented")
.with_weight(Weight::zero()))
}
}
impl ExecuteControllerWeightInfo for () {
fn execute() -> Weight {
Weight::zero()
}
}
impl<Origin> SendController<Origin> for () {
type WeightInfo = ();
fn send(
_origin: Origin,
_dest: Box<VersionedLocation>,
_message: Box<VersionedXcm<()>>,
) -> Result<XcmHash, DispatchError> {
Ok(Default::default())
}
}
impl SendControllerWeightInfo for () {
fn send() -> Weight {
Weight::zero()
}
}
impl QueryControllerWeightInfo for () {
fn query() -> Weight {
Weight::zero()
}
fn take_response() -> Weight {
Weight::zero()
}
}
impl<Origin, Timeout> QueryController<Origin, Timeout> for () {
type WeightInfo = ();
fn query(
_origin: Origin,
_timeout: Timeout,
_match_querier: VersionedLocation,
) -> Result<QueryId, DispatchError> {
Ok(Default::default())
}
}
@@ -0,0 +1,247 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Adapters to work with `frame_support::traits::Currency` through XCM.
#![allow(deprecated)]
use super::MintLocation;
use core::{fmt::Debug, marker::PhantomData, result};
use frame_support::traits::{ExistenceRequirement::AllowDeath, Get, WithdrawReasons};
use sp_runtime::traits::CheckedSub;
use xcm::latest::{Asset, Error as XcmError, Location, Result, XcmContext};
use xcm_executor::{
traits::{ConvertLocation, MatchesFungible, TransactAsset},
AssetsInHolding,
};
/// Asset transaction errors.
enum Error {
/// The given asset is not handled. (According to [`XcmError::AssetNotFound`])
AssetNotHandled,
/// `Location` to `AccountId` conversion failed.
AccountIdConversionFailed,
}
impl From<Error> for XcmError {
fn from(e: Error) -> Self {
use XcmError::FailedToTransactAsset;
match e {
Error::AssetNotHandled => XcmError::AssetNotFound,
Error::AccountIdConversionFailed => FailedToTransactAsset("AccountIdConversionFailed"),
}
}
}
/// Simple adapter to use a currency as asset transactor. This type can be used as `type
/// AssetTransactor` in `xcm::Config`.
///
/// # Example
/// ```
/// use codec::Decode;
/// use frame_support::{parameter_types, PalletId};
/// use sp_runtime::traits::{AccountIdConversion, TrailingZeroInput};
/// use xcm::latest::prelude::*;
/// use staging_xcm_builder::{ParentIsPreset, CurrencyAdapter, IsConcrete};
///
/// /// Our chain's account id.
/// type AccountId = sp_runtime::AccountId32;
///
/// /// Our relay chain's location.
/// parameter_types! {
/// pub RelayChain: Location = Parent.into();
/// pub CheckingAccount: AccountId = PalletId(*b"checking").into_account_truncating();
/// }
///
/// /// Some items that implement `ConvertLocation<AccountId>`. Can be more, but for now we just assume we accept
/// /// messages from the parent (relay chain).
/// pub type LocationConverter = (ParentIsPreset<AccountId>);
///
/// /// Just a dummy implementation of `Currency`. Normally this would be `Balances`.
/// pub type CurrencyImpl = ();
///
/// /// Final currency adapter. This can be used in `xcm::Config` to specify how asset related transactions happen.
/// pub type AssetTransactor = CurrencyAdapter<
/// // Use this `Currency` impl instance:
/// CurrencyImpl,
/// // The matcher: use the currency when the asset is a concrete asset in our relay chain.
/// IsConcrete<RelayChain>,
/// // The local converter: default account of the parent relay chain.
/// LocationConverter,
/// // Our chain's account ID type.
/// AccountId,
/// // The checking account. Can be any deterministic inaccessible account.
/// CheckingAccount,
/// >;
/// ```
#[deprecated = "Use `FungibleAdapter` instead"]
pub struct CurrencyAdapter<Currency, Matcher, AccountIdConverter, AccountId, CheckedAccount>(
PhantomData<(Currency, Matcher, AccountIdConverter, AccountId, CheckedAccount)>,
);
impl<
Currency: frame_support::traits::Currency<AccountId>,
Matcher: MatchesFungible<Currency::Balance>,
AccountIdConverter: ConvertLocation<AccountId>,
AccountId: Clone, // can't get away without it since Currency is generic over it.
CheckedAccount: Get<Option<(AccountId, MintLocation)>>,
> CurrencyAdapter<Currency, Matcher, AccountIdConverter, AccountId, CheckedAccount>
{
fn can_accrue_checked(_checked_account: AccountId, _amount: Currency::Balance) -> Result {
Ok(())
}
fn can_reduce_checked(checked_account: AccountId, amount: Currency::Balance) -> Result {
let new_balance = Currency::free_balance(&checked_account)
.checked_sub(&amount)
.ok_or(XcmError::NotWithdrawable)?;
Currency::ensure_can_withdraw(
&checked_account,
amount,
WithdrawReasons::TRANSFER,
new_balance,
)
.map_err(|error| {
tracing::debug!(target: "xcm::currency_adapter", ?error, "Failed to ensure can withdraw");
XcmError::NotWithdrawable
})
}
fn accrue_checked(checked_account: AccountId, amount: Currency::Balance) {
let _ = Currency::deposit_creating(&checked_account, amount);
Currency::deactivate(amount);
}
fn reduce_checked(checked_account: AccountId, amount: Currency::Balance) {
let ok =
Currency::withdraw(&checked_account, amount, WithdrawReasons::TRANSFER, AllowDeath)
.is_ok();
if ok {
Currency::reactivate(amount);
} else {
frame_support::defensive!(
"`can_check_in` must have returned `true` immediately prior; qed"
);
}
}
}
impl<
Currency: frame_support::traits::Currency<AccountId>,
Matcher: MatchesFungible<Currency::Balance>,
AccountIdConverter: ConvertLocation<AccountId>,
AccountId: Clone + Debug, // can't get away without it since Currency is generic over it.
CheckedAccount: Get<Option<(AccountId, MintLocation)>>,
> TransactAsset
for CurrencyAdapter<Currency, Matcher, AccountIdConverter, AccountId, CheckedAccount>
{
fn can_check_in(origin: &Location, what: &Asset, _context: &XcmContext) -> Result {
tracing::trace!(target: "xcm::currency_adapter", ?origin, ?what, "can_check_in origin");
// Check we handle this asset.
let amount: Currency::Balance =
Matcher::matches_fungible(what).ok_or(Error::AssetNotHandled)?;
match CheckedAccount::get() {
Some((checked_account, MintLocation::Local)) =>
Self::can_reduce_checked(checked_account, amount),
Some((checked_account, MintLocation::NonLocal)) =>
Self::can_accrue_checked(checked_account, amount),
None => Ok(()),
}
}
fn check_in(origin: &Location, what: &Asset, _context: &XcmContext) {
tracing::trace!(target: "xcm::currency_adapter", ?origin, ?what, "check_in origin");
if let Some(amount) = Matcher::matches_fungible(what) {
match CheckedAccount::get() {
Some((checked_account, MintLocation::Local)) =>
Self::reduce_checked(checked_account, amount),
Some((checked_account, MintLocation::NonLocal)) =>
Self::accrue_checked(checked_account, amount),
None => (),
}
}
}
fn can_check_out(dest: &Location, what: &Asset, _context: &XcmContext) -> Result {
tracing::trace!(target: "xcm::currency_adapter", ?dest, ?what, "can_check_out");
let amount = Matcher::matches_fungible(what).ok_or(Error::AssetNotHandled)?;
match CheckedAccount::get() {
Some((checked_account, MintLocation::Local)) =>
Self::can_accrue_checked(checked_account, amount),
Some((checked_account, MintLocation::NonLocal)) =>
Self::can_reduce_checked(checked_account, amount),
None => Ok(()),
}
}
fn check_out(dest: &Location, what: &Asset, _context: &XcmContext) {
tracing::trace!(target: "xcm::currency_adapter", ?dest, ?what, "check_out");
if let Some(amount) = Matcher::matches_fungible(what) {
match CheckedAccount::get() {
Some((checked_account, MintLocation::Local)) =>
Self::accrue_checked(checked_account, amount),
Some((checked_account, MintLocation::NonLocal)) =>
Self::reduce_checked(checked_account, amount),
None => (),
}
}
}
fn deposit_asset(what: &Asset, who: &Location, _context: Option<&XcmContext>) -> Result {
tracing::trace!(target: "xcm::currency_adapter", ?what, ?who, "deposit_asset");
// Check we handle this asset.
let amount = Matcher::matches_fungible(&what).ok_or(Error::AssetNotHandled)?;
let who =
AccountIdConverter::convert_location(who).ok_or(Error::AccountIdConversionFailed)?;
let _imbalance = Currency::deposit_creating(&who, amount);
Ok(())
}
fn withdraw_asset(
what: &Asset,
who: &Location,
_maybe_context: Option<&XcmContext>,
) -> result::Result<AssetsInHolding, XcmError> {
tracing::trace!(target: "xcm::currency_adapter", ?what, ?who, "withdraw_asset");
// Check we handle this asset.
let amount = Matcher::matches_fungible(what).ok_or(Error::AssetNotHandled)?;
let who =
AccountIdConverter::convert_location(who).ok_or(Error::AccountIdConversionFailed)?;
let _ = Currency::withdraw(&who, amount, WithdrawReasons::TRANSFER, AllowDeath).map_err(
|error| {
tracing::debug!(target: "xcm::currency_adapter", ?error, ?who, ?amount, "Failed to withdraw asset");
XcmError::FailedToTransactAsset(error.into())
},
)?;
Ok(what.clone().into())
}
fn internal_transfer_asset(
asset: &Asset,
from: &Location,
to: &Location,
_context: &XcmContext,
) -> result::Result<AssetsInHolding, XcmError> {
tracing::trace!(target: "xcm::currency_adapter", ?asset, ?from, ?to, "internal_transfer_asset");
let amount = Matcher::matches_fungible(asset).ok_or(Error::AssetNotHandled)?;
let from =
AccountIdConverter::convert_location(from).ok_or(Error::AccountIdConversionFailed)?;
let to =
AccountIdConverter::convert_location(to).ok_or(Error::AccountIdConversionFailed)?;
Currency::transfer(&from, &to, amount, AllowDeath).map_err(|error| {
tracing::debug!(target: "xcm::currency_adapter", ?error, ?from, ?to, ?amount, "Failed to transfer asset");
XcmError::FailedToTransactAsset(error.into())
})?;
Ok(asset.clone().into())
}
}
+138
View File
@@ -0,0 +1,138 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
use core::marker::PhantomData;
use frame_support::traits::{Contains, Get};
use xcm::prelude::*;
use xcm_executor::traits::{FeeManager, FeeReason, TransactAsset};
/// Handles the fees that are taken by certain XCM instructions.
pub trait HandleFee {
/// Do something with the fee which has been paid. Doing nothing here silently burns the
/// fees.
///
/// Returns any part of the fee that wasn't consumed.
fn handle_fee(fee: Assets, context: Option<&XcmContext>, reason: FeeReason) -> Assets;
}
// Default `HandleFee` implementation that just burns the fee.
impl HandleFee for () {
fn handle_fee(_: Assets, _: Option<&XcmContext>, _: FeeReason) -> Assets {
Assets::new()
}
}
#[impl_trait_for_tuples::impl_for_tuples(1, 30)]
impl HandleFee for Tuple {
fn handle_fee(fee: Assets, context: Option<&XcmContext>, reason: FeeReason) -> Assets {
let mut unconsumed_fee = fee;
for_tuples!( #(
unconsumed_fee = Tuple::handle_fee(unconsumed_fee, context, reason.clone());
if unconsumed_fee.is_none() {
return unconsumed_fee;
}
)* );
unconsumed_fee
}
}
/// A `FeeManager` implementation that permits the specified `WaivedLocations` to not pay for fees
/// and that uses the provided `HandleFee` implementation otherwise.
pub struct XcmFeeManagerFromComponents<WaivedLocations, HandleFee>(
PhantomData<(WaivedLocations, HandleFee)>,
);
impl<WaivedLocations: Contains<Location>, FeeHandler: HandleFee> FeeManager
for XcmFeeManagerFromComponents<WaivedLocations, FeeHandler>
{
fn is_waived(origin: Option<&Location>, _: FeeReason) -> bool {
let Some(loc) = origin else { return false };
WaivedLocations::contains(loc)
}
fn handle_fee(fee: Assets, context: Option<&XcmContext>, reason: FeeReason) {
FeeHandler::handle_fee(fee, context, reason);
}
}
/// A `HandleFee` implementation that simply deposits the fees into a specific on-chain
/// `ReceiverAccount`.
///
/// It reuses the `AssetTransactor` configured on the XCM executor to deposit fee assets. If
/// the `AssetTransactor` returns an error while calling `deposit_asset`, then a warning will be
/// logged and the fee burned.
#[deprecated(
note = "`XcmFeeToAccount` will be removed in January 2025. Use `SendXcmFeeToAccount` instead."
)]
#[allow(dead_code)]
pub struct XcmFeeToAccount<AssetTransactor, AccountId, ReceiverAccount>(
PhantomData<(AssetTransactor, AccountId, ReceiverAccount)>,
);
#[allow(deprecated)]
impl<
AssetTransactor: TransactAsset,
AccountId: Clone + Into<[u8; 32]>,
ReceiverAccount: Get<AccountId>,
> HandleFee for XcmFeeToAccount<AssetTransactor, AccountId, ReceiverAccount>
{
fn handle_fee(fee: Assets, context: Option<&XcmContext>, _reason: FeeReason) -> Assets {
let dest = AccountId32 { network: None, id: ReceiverAccount::get().into() }.into();
deposit_or_burn_fee::<AssetTransactor>(fee, context, dest);
Assets::new()
}
}
/// A `HandleFee` implementation that simply deposits the fees into a specific on-chain
/// `ReceiverAccount`.
///
/// It reuses the `AssetTransactor` configured on the XCM executor to deposit fee assets. If
/// the `AssetTransactor` returns an error while calling `deposit_asset`, then a warning will be
/// logged and the fee burned.
///
/// `ReceiverAccount` should implement `Get<Location>`.
pub struct SendXcmFeeToAccount<AssetTransactor, ReceiverAccount>(
PhantomData<(AssetTransactor, ReceiverAccount)>,
);
impl<AssetTransactor: TransactAsset, ReceiverAccount: Get<Location>> HandleFee
for SendXcmFeeToAccount<AssetTransactor, ReceiverAccount>
{
fn handle_fee(fee: Assets, context: Option<&XcmContext>, _reason: FeeReason) -> Assets {
deposit_or_burn_fee::<AssetTransactor>(fee, context, ReceiverAccount::get());
Assets::new()
}
}
/// Try to deposit the given fee in the specified account.
/// Burns the fee in case of a failure.
pub fn deposit_or_burn_fee<AssetTransactor: TransactAsset>(
fee: Assets,
context: Option<&XcmContext>,
dest: Location,
) {
for asset in fee.into_inner() {
if let Err(e) = AssetTransactor::deposit_asset(&asset, &dest, context) {
tracing::trace!(
target: "xcm::fees",
"`AssetTransactor::deposit_asset` returned error: {e:?}. Burning fee: {asset:?}. \
They might be burned.",
);
}
}
}
@@ -0,0 +1,210 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Various implementations of `ContainsPair<Asset, Location>` or
//! `Contains<(Location, Vec<Asset>)>`.
use alloc::vec::Vec;
use core::marker::PhantomData;
use frame_support::traits::{Contains, ContainsPair, Get};
use xcm::latest::{Asset, AssetFilter, AssetId, Location, WildAsset};
/// Accepts an asset iff it is a native asset.
pub struct NativeAsset;
impl ContainsPair<Asset, Location> for NativeAsset {
fn contains(asset: &Asset, origin: &Location) -> bool {
tracing::trace!(target: "xcm::contains", ?asset, ?origin, "NativeAsset");
matches!(asset.id, AssetId(ref id) if id == origin)
}
}
/// Accepts an asset if it is contained in the given `T`'s `Get` implementation.
pub struct Case<T>(PhantomData<T>);
impl<T: Get<(AssetFilter, Location)>> ContainsPair<Asset, Location> for Case<T> {
fn contains(asset: &Asset, origin: &Location) -> bool {
tracing::trace!(target: "xcm::contains", ?asset, ?origin, "Case asset");
let (a, o) = T::get();
a.matches(asset) && &o == origin
}
}
/// Accepts a tuple `(location, assets)` if the `location` is contained in the `Contains`
/// implementation of the given `Location` and if every asset from `assets` matches at least one of
/// the `AssetFilter` instances provided by the `Get` implementation of `AssetFilters`.
pub struct LocationWithAssetFilters<LocationFilter, AssetFilters>(
core::marker::PhantomData<(LocationFilter, AssetFilters)>,
);
impl<LocationFilter: Contains<Location>, AssetFilters: Get<Vec<AssetFilter>>>
Contains<(Location, Vec<Asset>)> for LocationWithAssetFilters<LocationFilter, AssetFilters>
{
fn contains((location, assets): &(Location, Vec<Asset>)) -> bool {
tracing::trace!(target: "xcm::contains", ?location, ?assets, "LocationWithAssetFilters");
// `location` must match the `Location` filter.
if !LocationFilter::contains(location) {
return false;
}
// All `assets` must match at least one of the `AssetFilters`.
let filters = AssetFilters::get();
assets.iter().all(|asset| {
for filter in &filters {
if filter.matches(asset) {
return true;
}
}
false
})
}
}
/// Implementation of `Get<Vec<AssetFilter>>` which accepts every asset.
/// (For example, it can be used with `LocationWithAssetFilters`).
pub struct AllAssets;
impl Get<Vec<AssetFilter>> for AllAssets {
fn get() -> Vec<AssetFilter> {
alloc::vec![AssetFilter::Wild(WildAsset::All)]
}
}
#[cfg(test)]
mod tests {
use super::*;
use frame_support::traits::Equals;
use xcm::latest::prelude::*;
#[test]
fn location_with_asset_filters_works() {
frame_support::parameter_types! {
pub ParaA: Location = Location::new(1, [Teyrchain(1001)]);
pub ParaB: Location = Location::new(1, [Teyrchain(1002)]);
pub ParaC: Location = Location::new(1, [Teyrchain(1003)]);
pub AssetXLocation: Location = Location::new(1, [GeneralIndex(1111)]);
pub AssetYLocation: Location = Location::new(1, [GeneralIndex(2222)]);
pub AssetZLocation: Location = Location::new(1, [GeneralIndex(3333)]);
pub OnlyAssetXOrAssetY: alloc::vec::Vec<AssetFilter> = alloc::vec![
Wild(AllOf { fun: WildFungible, id: AssetId(AssetXLocation::get()) }),
Wild(AllOf { fun: WildFungible, id: AssetId(AssetYLocation::get()) }),
];
pub OnlyAssetZ: alloc::vec::Vec<AssetFilter> = alloc::vec![
Wild(AllOf { fun: WildFungible, id: AssetId(AssetZLocation::get()) })
];
}
let test_data: Vec<(Location, Vec<Asset>, bool)> = vec![
(ParaA::get(), vec![(AssetXLocation::get(), 1).into()], true),
(ParaA::get(), vec![(AssetYLocation::get(), 1).into()], true),
(ParaA::get(), vec![(AssetZLocation::get(), 1).into()], false),
(
ParaA::get(),
vec![(AssetXLocation::get(), 1).into(), (AssetYLocation::get(), 1).into()],
true,
),
(
ParaA::get(),
vec![(AssetXLocation::get(), 1).into(), (AssetZLocation::get(), 1).into()],
false,
),
(
ParaA::get(),
vec![(AssetYLocation::get(), 1).into(), (AssetZLocation::get(), 1).into()],
false,
),
(
ParaA::get(),
vec![
(AssetXLocation::get(), 1).into(),
(AssetYLocation::get(), 1).into(),
(AssetZLocation::get(), 1).into(),
],
false,
),
(ParaB::get(), vec![(AssetXLocation::get(), 1).into()], false),
(ParaB::get(), vec![(AssetYLocation::get(), 1).into()], false),
(ParaB::get(), vec![(AssetZLocation::get(), 1).into()], true),
(
ParaB::get(),
vec![(AssetXLocation::get(), 1).into(), (AssetYLocation::get(), 1).into()],
false,
),
(
ParaB::get(),
vec![(AssetXLocation::get(), 1).into(), (AssetZLocation::get(), 1).into()],
false,
),
(
ParaB::get(),
vec![(AssetYLocation::get(), 1).into(), (AssetZLocation::get(), 1).into()],
false,
),
(
ParaB::get(),
vec![
(AssetXLocation::get(), 1).into(),
(AssetYLocation::get(), 1).into(),
(AssetZLocation::get(), 1).into(),
],
false,
),
(ParaC::get(), vec![(AssetXLocation::get(), 1).into()], true),
(ParaC::get(), vec![(AssetYLocation::get(), 1).into()], true),
(ParaC::get(), vec![(AssetZLocation::get(), 1).into()], true),
(
ParaC::get(),
vec![(AssetXLocation::get(), 1).into(), (AssetYLocation::get(), 1).into()],
true,
),
(
ParaC::get(),
vec![(AssetXLocation::get(), 1).into(), (AssetZLocation::get(), 1).into()],
true,
),
(
ParaC::get(),
vec![(AssetYLocation::get(), 1).into(), (AssetZLocation::get(), 1).into()],
true,
),
(
ParaC::get(),
vec![
(AssetXLocation::get(), 1).into(),
(AssetYLocation::get(), 1).into(),
(AssetZLocation::get(), 1).into(),
],
true,
),
];
type Filter = (
// For ParaA accept only asset X and Y.
LocationWithAssetFilters<Equals<ParaA>, OnlyAssetXOrAssetY>,
// For ParaB accept only asset Z.
LocationWithAssetFilters<Equals<ParaB>, OnlyAssetZ>,
// For ParaC accept all assets.
LocationWithAssetFilters<Equals<ParaC>, AllAssets>,
);
for (location, assets, expected_result) in test_data {
assert_eq!(
Filter::contains(&(location.clone(), assets.clone())),
expected_result,
"expected_result: {expected_result} not matched for (location, assets): ({:?}, {:?})!", location, assets,
)
}
}
}
@@ -0,0 +1,335 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Adapters to work with [`frame_support::traits::fungible`] through XCM.
use super::MintLocation;
use core::{fmt::Debug, marker::PhantomData, result};
use frame_support::traits::{
tokens::{
fungible, Fortitude::Polite, Precision::Exact, Preservation::Expendable, Provenance::Minted,
},
Get,
};
use xcm::latest::prelude::*;
use xcm_executor::{
traits::{ConvertLocation, Error as MatchError, MatchesFungible, TransactAsset},
AssetsInHolding,
};
/// [`TransactAsset`] implementation that allows the use of a [`fungible`] implementation for
/// handling an asset in the XCM executor.
/// Only works for transfers.
pub struct FungibleTransferAdapter<Fungible, Matcher, AccountIdConverter, AccountId>(
PhantomData<(Fungible, Matcher, AccountIdConverter, AccountId)>,
);
impl<
Fungible: fungible::Mutate<AccountId>,
Matcher: MatchesFungible<Fungible::Balance>,
AccountIdConverter: ConvertLocation<AccountId>,
AccountId: Eq + Clone + Debug,
> TransactAsset for FungibleTransferAdapter<Fungible, Matcher, AccountIdConverter, AccountId>
{
fn internal_transfer_asset(
what: &Asset,
from: &Location,
to: &Location,
_context: &XcmContext,
) -> result::Result<AssetsInHolding, XcmError> {
tracing::trace!(
target: "xcm::fungible_adapter",
?what, ?from, ?to,
"internal_transfer_asset",
);
// Check we handle the asset
let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?;
let source = AccountIdConverter::convert_location(from)
.ok_or(MatchError::AccountIdConversionFailed)?;
let dest = AccountIdConverter::convert_location(to)
.ok_or(MatchError::AccountIdConversionFailed)?;
Fungible::transfer(&source, &dest, amount, Expendable).map_err(|error| {
tracing::debug!(
target: "xcm::fungible_adapter", ?error, ?source, ?dest, ?amount,
"Failed to transfer asset",
);
XcmError::FailedToTransactAsset(error.into())
})?;
Ok(what.clone().into())
}
}
/// [`TransactAsset`] implementation that allows the use of a [`fungible`] implementation for
/// handling an asset in the XCM executor.
/// Works for everything but transfers.
pub struct FungibleMutateAdapter<Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount>(
PhantomData<(Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount)>,
);
impl<
Fungible: fungible::Mutate<AccountId>,
Matcher: MatchesFungible<Fungible::Balance>,
AccountIdConverter: ConvertLocation<AccountId>,
AccountId: Eq + Clone + Debug,
CheckingAccount: Get<Option<(AccountId, MintLocation)>>,
> FungibleMutateAdapter<Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount>
{
fn can_accrue_checked(checking_account: AccountId, amount: Fungible::Balance) -> XcmResult {
Fungible::can_deposit(&checking_account, amount, Minted)
.into_result()
.map_err(|error| {
tracing::debug!(
target: "xcm::fungible_adapter", ?error, ?checking_account, ?amount,
"Failed to deposit funds into account",
);
XcmError::NotDepositable
})
}
fn can_reduce_checked(checking_account: AccountId, amount: Fungible::Balance) -> XcmResult {
Fungible::can_withdraw(&checking_account, amount)
.into_result(false)
.map_err(|error| {
tracing::debug!(
target: "xcm::fungible_adapter", ?error, ?checking_account, ?amount,
"Failed to withdraw funds from account",
);
XcmError::NotWithdrawable
})
.map(|_| ())
}
fn accrue_checked(checking_account: AccountId, amount: Fungible::Balance) {
let ok = Fungible::mint_into(&checking_account, amount).is_ok();
debug_assert!(ok, "`can_accrue_checked` must have returned `true` immediately prior; qed");
}
fn reduce_checked(checking_account: AccountId, amount: Fungible::Balance) {
let ok = Fungible::burn_from(&checking_account, amount, Expendable, Exact, Polite).is_ok();
debug_assert!(ok, "`can_reduce_checked` must have returned `true` immediately prior; qed");
}
}
impl<
Fungible: fungible::Mutate<AccountId>,
Matcher: MatchesFungible<Fungible::Balance>,
AccountIdConverter: ConvertLocation<AccountId>,
AccountId: Eq + Clone + Debug,
CheckingAccount: Get<Option<(AccountId, MintLocation)>>,
> TransactAsset
for FungibleMutateAdapter<Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount>
{
fn can_check_in(origin: &Location, what: &Asset, _context: &XcmContext) -> XcmResult {
tracing::trace!(
target: "xcm::fungible_adapter",
?origin, ?what,
"can_check_in origin",
);
// Check we handle this asset
let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?;
match CheckingAccount::get() {
Some((checking_account, MintLocation::Local)) =>
Self::can_reduce_checked(checking_account, amount),
Some((checking_account, MintLocation::NonLocal)) =>
Self::can_accrue_checked(checking_account, amount),
None => Ok(()),
}
}
fn check_in(origin: &Location, what: &Asset, _context: &XcmContext) {
tracing::trace!(
target: "xcm::fungible_adapter",
?origin, ?what,
"check_in origin",
);
if let Some(amount) = Matcher::matches_fungible(what) {
match CheckingAccount::get() {
Some((checking_account, MintLocation::Local)) =>
Self::reduce_checked(checking_account, amount),
Some((checking_account, MintLocation::NonLocal)) =>
Self::accrue_checked(checking_account, amount),
None => (),
}
}
}
fn can_check_out(dest: &Location, what: &Asset, _context: &XcmContext) -> XcmResult {
tracing::trace!(
target: "xcm::fungible_adapter",
?dest,
?what,
"can_check_out",
);
let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?;
match CheckingAccount::get() {
Some((checking_account, MintLocation::Local)) =>
Self::can_accrue_checked(checking_account, amount),
Some((checking_account, MintLocation::NonLocal)) =>
Self::can_reduce_checked(checking_account, amount),
None => Ok(()),
}
}
fn check_out(dest: &Location, what: &Asset, _context: &XcmContext) {
tracing::trace!(
target: "xcm::fungible_adapter",
?dest,
?what,
"check_out",
);
if let Some(amount) = Matcher::matches_fungible(what) {
match CheckingAccount::get() {
Some((checking_account, MintLocation::Local)) =>
Self::accrue_checked(checking_account, amount),
Some((checking_account, MintLocation::NonLocal)) =>
Self::reduce_checked(checking_account, amount),
None => (),
}
}
}
fn deposit_asset(what: &Asset, who: &Location, _context: Option<&XcmContext>) -> XcmResult {
tracing::trace!(
target: "xcm::fungible_adapter",
?what, ?who,
"deposit_asset",
);
let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?;
let who = AccountIdConverter::convert_location(who)
.ok_or(MatchError::AccountIdConversionFailed)?;
Fungible::mint_into(&who, amount).map_err(|error| {
tracing::debug!(
target: "xcm::fungible_adapter", ?error, ?who, ?amount,
"Failed to deposit assets",
);
XcmError::FailedToTransactAsset(error.into())
})?;
Ok(())
}
fn withdraw_asset(
what: &Asset,
who: &Location,
_context: Option<&XcmContext>,
) -> result::Result<AssetsInHolding, XcmError> {
tracing::trace!(
target: "xcm::fungible_adapter",
?what, ?who,
"withdraw_asset",
);
let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?;
let who = AccountIdConverter::convert_location(who)
.ok_or(MatchError::AccountIdConversionFailed)?;
Fungible::burn_from(&who, amount, Expendable, Exact, Polite).map_err(|error| {
tracing::debug!(
target: "xcm::fungible_adapter", ?error, ?who, ?amount,
"Failed to withdraw assets",
);
XcmError::FailedToTransactAsset(error.into())
})?;
Ok(what.clone().into())
}
}
/// [`TransactAsset`] implementation that allows the use of a [`fungible`] implementation for
/// handling an asset in the XCM executor.
/// Works for everything, transfers and teleport bookkeeping.
pub struct FungibleAdapter<Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount>(
PhantomData<(Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount)>,
);
impl<
Fungible: fungible::Mutate<AccountId>,
Matcher: MatchesFungible<Fungible::Balance>,
AccountIdConverter: ConvertLocation<AccountId>,
AccountId: Eq + Clone + Debug,
CheckingAccount: Get<Option<(AccountId, MintLocation)>>,
> TransactAsset
for FungibleAdapter<Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount>
{
fn can_check_in(origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
FungibleMutateAdapter::<
Fungible,
Matcher,
AccountIdConverter,
AccountId,
CheckingAccount,
>::can_check_in(origin, what, context)
}
fn check_in(origin: &Location, what: &Asset, context: &XcmContext) {
FungibleMutateAdapter::<
Fungible,
Matcher,
AccountIdConverter,
AccountId,
CheckingAccount,
>::check_in(origin, what, context)
}
fn can_check_out(dest: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
FungibleMutateAdapter::<
Fungible,
Matcher,
AccountIdConverter,
AccountId,
CheckingAccount,
>::can_check_out(dest, what, context)
}
fn check_out(dest: &Location, what: &Asset, context: &XcmContext) {
FungibleMutateAdapter::<
Fungible,
Matcher,
AccountIdConverter,
AccountId,
CheckingAccount,
>::check_out(dest, what, context)
}
fn deposit_asset(what: &Asset, who: &Location, context: Option<&XcmContext>) -> XcmResult {
FungibleMutateAdapter::<
Fungible,
Matcher,
AccountIdConverter,
AccountId,
CheckingAccount,
>::deposit_asset(what, who, context)
}
fn withdraw_asset(
what: &Asset,
who: &Location,
maybe_context: Option<&XcmContext>,
) -> result::Result<AssetsInHolding, XcmError> {
FungibleMutateAdapter::<
Fungible,
Matcher,
AccountIdConverter,
AccountId,
CheckingAccount,
>::withdraw_asset(what, who, maybe_context)
}
fn internal_transfer_asset(
what: &Asset,
from: &Location,
to: &Location,
context: &XcmContext,
) -> result::Result<AssetsInHolding, XcmError> {
FungibleTransferAdapter::<Fungible, Matcher, AccountIdConverter, AccountId>::internal_transfer_asset(
what, from, to, context
)
}
}
@@ -0,0 +1,426 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Adapters to work with [`frame_support::traits::fungibles`] through XCM.
use core::{fmt::Debug, marker::PhantomData, result};
use frame_support::traits::{
tokens::{
fungibles, Fortitude::Polite, Precision::Exact, Preservation::Expendable,
Provenance::Minted,
},
Contains, Get,
};
use xcm::latest::prelude::*;
use xcm_executor::traits::{ConvertLocation, Error as MatchError, MatchesFungibles, TransactAsset};
/// `TransactAsset` implementation to convert a `fungibles` implementation to become usable in XCM.
pub struct FungiblesTransferAdapter<Assets, Matcher, AccountIdConverter, AccountId>(
PhantomData<(Assets, Matcher, AccountIdConverter, AccountId)>,
);
impl<
Assets: fungibles::Mutate<AccountId>,
Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
AccountIdConverter: ConvertLocation<AccountId>,
AccountId: Eq + Clone + Debug, /* can't get away without it since Currency is generic
* over it. */
> TransactAsset for FungiblesTransferAdapter<Assets, Matcher, AccountIdConverter, AccountId>
{
fn internal_transfer_asset(
what: &Asset,
from: &Location,
to: &Location,
_context: &XcmContext,
) -> result::Result<xcm_executor::AssetsInHolding, XcmError> {
tracing::trace!(
target: "xcm::fungibles_adapter",
?what, ?from, ?to,
"internal_transfer_asset"
);
// Check we handle this asset.
let (asset_id, amount) = Matcher::matches_fungibles(what)?;
let source = AccountIdConverter::convert_location(from)
.ok_or(MatchError::AccountIdConversionFailed)?;
let dest = AccountIdConverter::convert_location(to)
.ok_or(MatchError::AccountIdConversionFailed)?;
Assets::transfer(asset_id.clone(), &source, &dest, amount, Expendable).map_err(|e| {
tracing::debug!(target: "xcm::fungibles_adapter", error = ?e, ?asset_id, ?source, ?dest, ?amount, "Failed internal transfer asset");
XcmError::FailedToTransactAsset(e.into())
})?;
Ok(what.clone().into())
}
}
/// The location which is allowed to mint a particular asset.
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum MintLocation {
/// This chain is allowed to mint the asset. When we track teleports of the asset we ensure
/// that no more of the asset returns back to the chain than has been sent out.
Local,
/// This chain is not allowed to mint the asset. When we track teleports of the asset we ensure
/// that no more of the asset is sent out from the chain than has been previously received.
NonLocal,
}
/// Simple trait to indicate whether an asset is subject to having its teleportation into and out of
/// this chain recorded and if so in what `MintLocation`.
///
/// The overall purpose of asset-checking is to ensure either no more assets are teleported into a
/// chain than the outstanding balance of assets which were previously teleported out (as in the
/// case of locally-minted assets); or that no more assets are teleported out of a chain than the
/// outstanding balance of assets which have previously been teleported in (as in the case of chains
/// where the `asset` is not minted locally).
pub trait AssetChecking<AssetId> {
/// Return the teleportation asset-checking policy for the given `asset`. `None` implies no
/// checking. Otherwise the policy detailed by the inner `MintLocation` should be respected by
/// teleportation.
fn asset_checking(asset: &AssetId) -> Option<MintLocation>;
}
/// Implementation of `AssetChecking` which subjects no assets to having their teleportations
/// recorded.
pub struct NoChecking;
impl<AssetId> AssetChecking<AssetId> for NoChecking {
fn asset_checking(_: &AssetId) -> Option<MintLocation> {
None
}
}
/// Implementation of `AssetChecking` which subjects a given set of assets `T` to having their
/// teleportations recorded with a `MintLocation::Local`.
pub struct LocalMint<T>(core::marker::PhantomData<T>);
impl<AssetId, T: Contains<AssetId>> AssetChecking<AssetId> for LocalMint<T> {
fn asset_checking(asset: &AssetId) -> Option<MintLocation> {
match T::contains(asset) {
true => Some(MintLocation::Local),
false => None,
}
}
}
/// Implementation of `AssetChecking` which subjects a given set of assets `T` to having their
/// teleportations recorded with a `MintLocation::NonLocal`.
pub struct NonLocalMint<T>(core::marker::PhantomData<T>);
impl<AssetId, T: Contains<AssetId>> AssetChecking<AssetId> for NonLocalMint<T> {
fn asset_checking(asset: &AssetId) -> Option<MintLocation> {
match T::contains(asset) {
true => Some(MintLocation::NonLocal),
false => None,
}
}
}
/// Implementation of `AssetChecking` which subjects a given set of assets `L` to having their
/// teleportations recorded with a `MintLocation::Local` and a second set of assets `R` to having
/// their teleportations recorded with a `MintLocation::NonLocal`.
pub struct DualMint<L, R>(core::marker::PhantomData<(L, R)>);
impl<AssetId, L: Contains<AssetId>, R: Contains<AssetId>> AssetChecking<AssetId>
for DualMint<L, R>
{
fn asset_checking(asset: &AssetId) -> Option<MintLocation> {
if L::contains(asset) {
Some(MintLocation::Local)
} else if R::contains(asset) {
Some(MintLocation::NonLocal)
} else {
None
}
}
}
pub struct FungiblesMutateAdapter<
Assets,
Matcher,
AccountIdConverter,
AccountId,
CheckAsset,
CheckingAccount,
>(PhantomData<(Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount)>);
impl<
Assets: fungibles::Mutate<AccountId>,
Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
AccountIdConverter: ConvertLocation<AccountId>,
AccountId: Eq + Clone + Debug, /* can't get away without it since Currency is generic
* over it. */
CheckAsset: AssetChecking<Assets::AssetId>,
CheckingAccount: Get<AccountId>,
>
FungiblesMutateAdapter<Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount>
{
fn can_accrue_checked(asset_id: Assets::AssetId, amount: Assets::Balance) -> XcmResult {
let checking_account = CheckingAccount::get();
Assets::can_deposit(asset_id, &checking_account, amount, Minted)
.into_result()
.map_err(|error| {
tracing::debug!(
target: "xcm::fungibles_adapter", ?error, ?checking_account, ?amount,
"Failed to check if asset can be accrued"
);
XcmError::NotDepositable
})
}
fn can_reduce_checked(asset_id: Assets::AssetId, amount: Assets::Balance) -> XcmResult {
let checking_account = CheckingAccount::get();
Assets::can_withdraw(asset_id, &checking_account, amount)
.into_result(false)
.map_err(|error| {
tracing::debug!(
target: "xcm::fungibles_adapter", ?error, ?checking_account, ?amount,
"Failed to check if asset can be reduced"
);
XcmError::NotWithdrawable
})
.map(|_| ())
}
fn accrue_checked(asset_id: Assets::AssetId, amount: Assets::Balance) {
let checking_account = CheckingAccount::get();
let ok = Assets::mint_into(asset_id, &checking_account, amount).is_ok();
debug_assert!(ok, "`can_accrue_checked` must have returned `true` immediately prior; qed");
}
fn reduce_checked(asset_id: Assets::AssetId, amount: Assets::Balance) {
let checking_account = CheckingAccount::get();
let ok = Assets::burn_from(asset_id, &checking_account, amount, Expendable, Exact, Polite)
.is_ok();
debug_assert!(ok, "`can_reduce_checked` must have returned `true` immediately prior; qed");
}
}
impl<
Assets: fungibles::Mutate<AccountId>,
Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
AccountIdConverter: ConvertLocation<AccountId>,
AccountId: Eq + Clone + Debug, /* can't get away without it since Currency is generic
* over it. */
CheckAsset: AssetChecking<Assets::AssetId>,
CheckingAccount: Get<AccountId>,
> TransactAsset
for FungiblesMutateAdapter<
Assets,
Matcher,
AccountIdConverter,
AccountId,
CheckAsset,
CheckingAccount,
>
{
fn can_check_in(origin: &Location, what: &Asset, _context: &XcmContext) -> XcmResult {
tracing::trace!(
target: "xcm::fungibles_adapter",
?origin, ?what,
"can_check_in"
);
// Check we handle this asset.
let (asset_id, amount) = Matcher::matches_fungibles(what)?;
match CheckAsset::asset_checking(&asset_id) {
// We track this asset's teleports to ensure no more come in than have gone out.
Some(MintLocation::Local) => Self::can_reduce_checked(asset_id, amount),
// We track this asset's teleports to ensure no more go out than have come in.
Some(MintLocation::NonLocal) => Self::can_accrue_checked(asset_id, amount),
_ => Ok(()),
}
}
fn check_in(origin: &Location, what: &Asset, _context: &XcmContext) {
tracing::trace!(
target: "xcm::fungibles_adapter",
?origin, ?what,
"check_in"
);
if let Ok((asset_id, amount)) = Matcher::matches_fungibles(what) {
match CheckAsset::asset_checking(&asset_id) {
// We track this asset's teleports to ensure no more come in than have gone out.
Some(MintLocation::Local) => Self::reduce_checked(asset_id, amount),
// We track this asset's teleports to ensure no more go out than have come in.
Some(MintLocation::NonLocal) => Self::accrue_checked(asset_id, amount),
_ => (),
}
}
}
fn can_check_out(origin: &Location, what: &Asset, _context: &XcmContext) -> XcmResult {
tracing::trace!(
target: "xcm::fungibles_adapter",
?origin, ?what,
"can_check_out"
);
// Check we handle this asset.
let (asset_id, amount) = Matcher::matches_fungibles(what)?;
match CheckAsset::asset_checking(&asset_id) {
// We track this asset's teleports to ensure no more come in than have gone out.
Some(MintLocation::Local) => Self::can_accrue_checked(asset_id, amount),
// We track this asset's teleports to ensure no more go out than have come in.
Some(MintLocation::NonLocal) => Self::can_reduce_checked(asset_id, amount),
_ => Ok(()),
}
}
fn check_out(dest: &Location, what: &Asset, _context: &XcmContext) {
tracing::trace!(
target: "xcm::fungibles_adapter",
?dest, ?what,
"check_out"
);
if let Ok((asset_id, amount)) = Matcher::matches_fungibles(what) {
match CheckAsset::asset_checking(&asset_id) {
// We track this asset's teleports to ensure no more come in than have gone out.
Some(MintLocation::Local) => Self::accrue_checked(asset_id, amount),
// We track this asset's teleports to ensure no more go out than have come in.
Some(MintLocation::NonLocal) => Self::reduce_checked(asset_id, amount),
_ => (),
}
}
}
fn deposit_asset(what: &Asset, who: &Location, _context: Option<&XcmContext>) -> XcmResult {
tracing::trace!(
target: "xcm::fungibles_adapter",
?what, ?who,
"deposit_asset"
);
// Check we handle this asset.
let (asset_id, amount) = Matcher::matches_fungibles(what)?;
let who = AccountIdConverter::convert_location(who)
.ok_or(MatchError::AccountIdConversionFailed)?;
Assets::mint_into(asset_id, &who, amount).map_err(|error| {
tracing::debug!(target: "xcm::fungibles_adapter", ?error, ?who, ?amount, "Failed to deposit asset");
XcmError::FailedToTransactAsset(error.into())
})?;
Ok(())
}
fn withdraw_asset(
what: &Asset,
who: &Location,
_maybe_context: Option<&XcmContext>,
) -> result::Result<xcm_executor::AssetsInHolding, XcmError> {
tracing::trace!(
target: "xcm::fungibles_adapter",
?what, ?who,
"withdraw_asset"
);
// Check we handle this asset.
let (asset_id, amount) = Matcher::matches_fungibles(what)?;
let who = AccountIdConverter::convert_location(who)
.ok_or(MatchError::AccountIdConversionFailed)?;
Assets::burn_from(asset_id, &who, amount, Expendable, Exact, Polite).map_err(|error| {
tracing::debug!(target: "xcm::fungibles_adapter", ?error, ?who, ?amount, "Failed to withdraw asset");
XcmError::FailedToTransactAsset(error.into())
})?;
Ok(what.clone().into())
}
}
pub struct FungiblesAdapter<
Assets,
Matcher,
AccountIdConverter,
AccountId,
CheckAsset,
CheckingAccount,
>(PhantomData<(Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount)>);
impl<
Assets: fungibles::Mutate<AccountId>,
Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
AccountIdConverter: ConvertLocation<AccountId>,
AccountId: Eq + Clone + Debug, /* can't get away without it since Currency is generic
* over it. */
CheckAsset: AssetChecking<Assets::AssetId>,
CheckingAccount: Get<AccountId>,
> TransactAsset
for FungiblesAdapter<Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount>
{
fn can_check_in(origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
FungiblesMutateAdapter::<
Assets,
Matcher,
AccountIdConverter,
AccountId,
CheckAsset,
CheckingAccount,
>::can_check_in(origin, what, context)
}
fn check_in(origin: &Location, what: &Asset, context: &XcmContext) {
FungiblesMutateAdapter::<
Assets,
Matcher,
AccountIdConverter,
AccountId,
CheckAsset,
CheckingAccount,
>::check_in(origin, what, context)
}
fn can_check_out(dest: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
FungiblesMutateAdapter::<
Assets,
Matcher,
AccountIdConverter,
AccountId,
CheckAsset,
CheckingAccount,
>::can_check_out(dest, what, context)
}
fn check_out(dest: &Location, what: &Asset, context: &XcmContext) {
FungiblesMutateAdapter::<
Assets,
Matcher,
AccountIdConverter,
AccountId,
CheckAsset,
CheckingAccount,
>::check_out(dest, what, context)
}
fn deposit_asset(what: &Asset, who: &Location, context: Option<&XcmContext>) -> XcmResult {
FungiblesMutateAdapter::<
Assets,
Matcher,
AccountIdConverter,
AccountId,
CheckAsset,
CheckingAccount,
>::deposit_asset(what, who, context)
}
fn withdraw_asset(
what: &Asset,
who: &Location,
maybe_context: Option<&XcmContext>,
) -> result::Result<xcm_executor::AssetsInHolding, XcmError> {
FungiblesMutateAdapter::<
Assets,
Matcher,
AccountIdConverter,
AccountId,
CheckAsset,
CheckingAccount,
>::withdraw_asset(what, who, maybe_context)
}
fn internal_transfer_asset(
what: &Asset,
from: &Location,
to: &Location,
context: &XcmContext,
) -> result::Result<xcm_executor::AssetsInHolding, XcmError> {
FungiblesTransferAdapter::<Assets, Matcher, AccountIdConverter, AccountId>::internal_transfer_asset(
what, from, to, context
)
}
}
+158
View File
@@ -0,0 +1,158 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! # XCM-Builder
//!
//! Types and helpers for *building* XCM configuration.
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
extern crate core;
#[cfg(test)]
mod tests;
#[cfg(feature = "std")]
pub mod test_utils;
mod asset_conversion;
#[allow(deprecated)]
pub use asset_conversion::ConvertedConcreteAssetId;
pub use asset_conversion::{
AsPrefixedGeneralIndex, ConvertedConcreteId, MatchClasslessInstances, MatchInClassInstances,
MatchedConvertedConcreteId,
};
mod asset_exchange;
pub use asset_exchange::SingleAssetExchangeAdapter;
mod barriers;
pub use barriers::{
AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain,
AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom,
AllowUnpaidExecutionFrom, DenyRecursively, DenyReserveTransferToRelayChain, DenyThenTry,
IsChildSystemTeyrchain, IsParentsOnly, IsSiblingSystemTeyrchain, RespectSuspension,
TakeWeightCredit, TrailingSetTopicAsId, WithComputedOrigin,
};
mod controller;
pub use controller::{
Controller, ExecuteController, ExecuteControllerWeightInfo, QueryController,
QueryControllerWeightInfo, QueryHandler, SendController, SendControllerWeightInfo,
};
mod currency_adapter;
#[allow(deprecated)]
pub use currency_adapter::CurrencyAdapter;
mod fee_handling;
pub use fee_handling::{
deposit_or_burn_fee, HandleFee, SendXcmFeeToAccount, XcmFeeManagerFromComponents,
};
mod filter_asset_location;
pub use filter_asset_location::{AllAssets, Case, LocationWithAssetFilters, NativeAsset};
mod fungible_adapter;
pub use fungible_adapter::{FungibleAdapter, FungibleMutateAdapter, FungibleTransferAdapter};
mod fungibles_adapter;
pub use fungibles_adapter::{
AssetChecking, DualMint, FungiblesAdapter, FungiblesMutateAdapter, FungiblesTransferAdapter,
LocalMint, MintLocation, NoChecking, NonLocalMint,
};
mod location_conversion;
#[allow(deprecated)]
pub use location_conversion::ForeignChainAliasAccount;
pub use location_conversion::{
Account32Hash, AccountId32Aliases, AccountKey20Aliases, AliasesIntoAccountId32,
ChildTeyrchainConvertsVia, DescribeAccountId32Terminal, DescribeAccountIdTerminal,
DescribeAccountKey20Terminal, DescribeAllTerminal, DescribeBodyTerminal, DescribeFamily,
DescribeLocation, DescribePalletTerminal, DescribeTerminus, DescribeTreasuryVoiceTerminal,
ExternalConsensusLocationsConverterFor, GlobalConsensusConvertsFor,
GlobalConsensusTeyrchainConvertsFor, HashedDescription, LocalTreasuryVoiceConvertsVia,
ParentIsPreset, SiblingTeyrchainConvertsVia,
};
mod matches_location;
pub use matches_location::{
StartsWith, StartsWithExplicitGlobalConsensus, WithLatestLocationConverter,
};
mod matches_token;
pub use matches_token::IsConcrete;
mod matcher;
pub use matcher::{CreateMatcher, MatchXcm, Matcher};
pub mod unique_instances;
mod nonfungibles_adapter;
pub use nonfungibles_adapter::{
NonFungiblesAdapter, NonFungiblesMutateAdapter, NonFungiblesTransferAdapter,
};
mod nonfungible_adapter;
pub use nonfungible_adapter::{
NonFungibleAdapter, NonFungibleMutateAdapter, NonFungibleTransferAdapter,
};
mod origin_aliases;
pub use origin_aliases::*;
mod origin_conversion;
pub use origin_conversion::{
BackingToPlurality, ChildSystemTeyrchainAsSuperuser, ChildTeyrchainAsNative, EnsureXcmOrigin,
LocationAsSuperuser, OriginToPluralityVoice, ParentAsSuperuser, RelayChainAsNative,
SiblingSystemTeyrchainAsSuperuser, SiblingTeyrchainAsNative, SignedAccountId32AsNative,
SignedAccountKey20AsNative, SignedToAccountId32, SovereignSignedViaLocation,
};
mod pay;
pub use pay::{
FixedLocation, LocatableAssetId, PayAccountId32OnChainOverXcm, PayOverXcm, PayOverXcmWithHelper,
};
mod process_xcm_message;
pub use process_xcm_message::ProcessXcmMessage;
mod routing;
pub use routing::{
EnsureDecodableXcm, EnsureDelivery, InspectMessageQueues, WithTopicSource, WithUniqueTopic,
};
mod transactional;
pub use transactional::FrameTransactionalProcessor;
#[allow(deprecated)]
pub use universal_exports::UnpaidLocalExporter;
mod transfer;
pub use transfer::{Transfer, TransferOverXcm, TransferOverXcmHelper, TransferStatus};
mod universal_exports;
pub use universal_exports::{
ensure_is_remote, BridgeBlobDispatcher, BridgeMessage, DispatchBlob, DispatchBlobError,
ExporterFor, HaulBlob, HaulBlobError, HaulBlobExporter, LocalExporter, NetworkExportTable,
NetworkExportTableItem, SovereignPaidRemoteExporter, UnpaidRemoteExporter,
};
mod weight;
pub use weight::{
FixedRateOfFungible, FixedWeightBounds, TakeRevenue, UsingComponents, WeightInfoBounds,
};

Some files were not shown because too many files have changed in this diff Show More