feat: initialize Kurdistan SDK - independent fork of Polkadot SDK
This commit is contained in:
@@ -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",
|
||||
]
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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}}
|
||||
}
|
||||
@@ -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());
|
||||
});
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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)).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)).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
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)).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)).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
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)).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)).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
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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"]);
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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,
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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))
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user