mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 20:21:03 +00:00
XCM v3 (#4097)
* cargo fmt * Create benchmarks for XCM instructions introduced in v3 (#4564) * Create benchmarks for BurnAsset and ExpectAsset * Add benchmarks for ExpectOrigin and ExpectError * Add benchmarks for QueryPallet and ExpectPallet * Add benchmarks for ReportTransactStatus and ClearTransactStatus * cargo fmt * Use AllPalletsWithSystem in mocks * Update XCM generic benchmarks for westend * Remove default impls for some XCM weight functions * Fix compilation error * Add weight_args helper attribute * Remove manually written XcmWeightInfo * Parse trailing comma * Revert "Add weight_args helper attribute" This reverts commit 3b7c47a6182e1b9227036c38b406d494c3fcf6fd. * Fixes * Fixes * XCM v3: Introduce querier field into `QueryReponse` (#4732) * Introduce querier field into QueryReponse * Convert &Option<MultiLocation> to Option<&MultiLocation> &Option<T> is almost always never quite useful, most of the time it still gets converted to an Option<&T> via `as_ref`, so we should simply make functions that accept Option<&T> instead. * Fix tests * cargo fmt * Fix benchmarks * Appease spellchecker * Fix test * Fix tests * Fix test * Fix mock * Fixes * Fix tests * Add test for response queriers * Update xcm/pallet-xcm/src/lib.rs * Test for non-existence of querier Co-authored-by: Keith Yeung <kungfukeith11@gmail.com> * Fixes * Fixes * Add `starts_with` function to `MultiLocation` and `Junctions` (#4835) * add matches_prefix function to MultiLocation and Junctions * rename matches_prefix to starts_with * remove unnecessary main in doc comment Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Make use of starts_with in match_and_split Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> Co-authored-by: Keith Yeung <kungfukeith11@gmail.com> * XCM v3: Bridge infrastructure (#4681) * XCM bridge infrastructure * Missing bit of cherry-pick * Revamped XCM proc macros; new NetworkIds * Fixes * Formatting * ExportMessage instruction and config type * Add MessageExporter definitions * Formatting * Missing files * Fixes * Initial bridging config API * Allow for two-stage XCM execution * Update xcm/src/v3/mod.rs Co-authored-by: Keith Yeung <kungfukeith11@gmail.com> * XCM crate building again * Initial bridging primitive * Docs * Docs * More work * More work * Merge branch 'gav-xcm-v3' into gav-xcm-v3-bridging * Make build * WithComputedOrigin and SovereignPaidRemoteExporter * Remove TODOs * Slim bridge API and tests. * Fixes * More work * First bridge test passing * Formatting * Another test * Next round of bridging tests * Repot tests * Cleanups * Paid bridging * Formatting * Tests * Spelling * Formatting * Fees and refactoring * Fixes * Formatting * Refactor SendXcm to become two-phase * Fix tests * Refactoring of SendXcm and ExportXcm complete * Formatting * Rename CannotReachDestination -> NotApplicable * Remove XCM v0 * Minor grumbles * Formatting * Formatting * Fixes * Fixes * Cleanup XCM config * Fee handling * Fixes * Formatting * Fixes * Bump Co-authored-by: Keith Yeung <kungfukeith11@gmail.com> * Bump Substrate * XCM v3: `ExchangeAsset` and Remote-locking (#4945) * Asset Exchange and Locks * Make sure XCM typers impl MaxEncodedLen * Basic implementation for locks * Bump Substrate * Missing files * Use new API * Introduce instruction * Big refactor * Docs * Remove deprecated struct * Remove deprecated struct * Repot XCM builder tests * ExchangeAsset test * Exchange tests * Locking tests * Locking tests * Fixes and tests * Fixes * Formatting * Spelling * Add simulator test for remote locking * Fix tests * Bump * XCM v3: Support for non-fungibles (#4950) * NFT support and a test * New files. * Integration tests for sending NFTs * Formatting * Broken Cargo features * Use 2021 edition * Fixes * Formatting * Formatting * Update xcm/xcm-builder/src/asset_conversion.rs Co-authored-by: Keith Yeung <kungfukeith11@gmail.com> * Update xcm/xcm-builder/src/nonfungibles_adapter.rs Co-authored-by: Keith Yeung <kungfukeith11@gmail.com> * Update xcm/xcm-executor/src/lib.rs Co-authored-by: Keith Yeung <kungfukeith11@gmail.com> * Fixes * Fixes * Fixes * Formatting * Fixes Co-authored-by: Bastian Köcher <info@kchr.de> Co-authored-by: Keith Yeung <kungfukeith11@gmail.com> * XCM v3: Context & ID hash (#4756) * send_xcm returns message hash * cargo fmt * Create topic register and instructions * Fix weights * Use tabs * Sketch out XcmContext * Fix doc test * Add the XCM context as a parameter to executor trait fns * Fixes * Add XcmContext parameter * Revert adding context as an arg to SendXcm trait methods * Revert adding context argument to ConvertOrigin trait methods * cargo fmt * Do not change the API of XcmExecutor::execute * Fixes * Fixes * Fixes * Fixes * Remove convenience method * Fixes * Fixes * cargo fmt * Fixes * Add benchmarks for XCM topic instructions * cargo run --quiet --profile=production --features=runtime-benchmarks -- benchmark --chain=westend-dev --steps=50 --repeat=20 --pallet=pallet_xcm_benchmarks::generic --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --header=./file_header.txt --template=./xcm/pallet-xcm-benchmarks/template.hbs --output=./runtime/westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs * Remove context argument on FilterAssetLocation * Fixes * Remove unused import * Fixes * Fixes * Fixes * Accept XCM hash parameter in ExecuteXcm trait methods * cargo fmt * Properly enable sp-io/std * Fixes * default-features = false * Fixes * Fixes * Fixes * Make XcmContext optional in withdraw_asset * Fixes * Fixes * Fixes * Modify tests to check for the correct XCM hash * Small refactor * cargo fmt * Check for expected hash in xcm-builder unit tests * Add doc comment for the optionality of the XCM context in withdraw_asset * Update xcm/src/v3/traits.rs * Update xcm/src/v3/traits.rs * Store XcmContext and avoid rebuilding * Use ref for XcmContext * Formatting * Fix incorrect hash CC @KiChjang * Refactor and make clear fake hashes * Fixes * Fixes * Fixes * Fix broken hashing * Docs * Fixes * Fixes * Fixes * Formatting * Fixes * Fixes * Fixes * Remove unknowable hash * Formatting * Use message hash for greater identifiability * Formatting * Fixes * Formatting Co-authored-by: Keith Yeung <kungfukeith11@gmail.com> Co-authored-by: Parity Bot <admin@parity.io> * Fixes * Fixes * Fixes * Fixes * Formatting * Fixes * Formatting * Fixes * Fixes * Formatting * Formatting * Remove horrible names * Bump * Remove InvertLocation trait (#5092) * Remove InvertLocation trait * Remove unneeded functions * Formatting * Fixes * Remove XCMv1 (#5094) * Remove XCMv1 * Remove XCMv1 * Formatting * Fixes * Fixes * Formatting * derive serialize/deserialize for xcm primitives (#5036) * derive serialize/deserialize for xcm primitives * derive serialize/deserialize for xcm primitives * update v3 * update v2 Co-authored-by: Gav Wood <gavin@parity.io> * Update lock * Fixes * Add benchmarks for the ExchangeAsset instruction * `AliasOrigin` instruction stub (#5122) * AliasOrigin instruction stub * Fixes * Fixes * Update substrate * Fixes * Ensure same array length before using copy_from_slice * Fixes * Add benchmarks for the UniversalOrigin instruction * Remove unused import * Remove unused import * Add benchmarks for SetFeesMode instruction * Add benchmarks for asset (un)locking instructions * Leave AliasOrigin unbenchmarked * Fixes after merge * cargo fmt * Fixes * Fixes * Set TrustedReserves to None on both Kusama and Westend * Remove extraneous reserve_asset_deposited benchmark * Fix universal_origin benchmark * cargo run --quiet --profile=production --features=runtime-benchmarks -- benchmark pallet --chain=westend-dev --steps=50 --repeat=20 --pallet=pallet_xcm_benchmarks::generic --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --header=./file_header.txt --template=./xcm/pallet-xcm-benchmarks/template.hbs --output=./runtime/westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs * Don't rely on skipped benchmark functions * Fixes * cargo run --quiet --profile=production --features=runtime-benchmarks -- benchmark pallet --chain=kusama-dev --steps=50 --repeat=20 --pallet=pallet_xcm_benchmarks::generic --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --header=./file_header.txt --template=./xcm/pallet-xcm-benchmarks/template.hbs --output=./runtime/kusama/src/weights/xcm/pallet_xcm_benchmarks_generic.rs * Fix unused variables * Fixes * Spelling * Fixes * Fix codec index of VersionedXcm * Allows to customize how calls are dispatched from XCM (#5657) * CallDispatcher trait * fmt * unused import * fix test-runtime * remove JustDispatch type * fix typo in test-runtime * missing CallDispatcher * more missing CallDispatcher * Update comment `NoteAssetLocked` -> `NoteUnlockable` * Fixes * Fixes * Adjust MultiAssets weights based on new wild card variants * Fixes * Fixes * Fixes * Fixes * Fixes * Some late fixes for XCMv3 (#5237) * Maximise chances that trapped assets can be reclaimed * Do origin check as part of ExportMessage for security * Formatting * Fixes * Cleanup export XCM APIs * Formatting * Update xcm/src/v3/junctions.rs * UnpaidExecution instruction and associated barrier. * Tighten barriers (ClearOrigin/QueryResponse) * Allow only 1 ClearOrigin instruction in AllowTopLevelPaidExecutionFrom * Bi-directional teleport accounting * Revert other fix * Build fixes] * Tests build * Benchmark fixes Co-authored-by: Keith Yeung <kungfukeith11@gmail.com> * Update Substrate * Re-export `pub` stuff from universal_exports.rs + removed unecessary clone (#6145) * Re-export `pub` stuff from universal_exports.rs * Removed unnecessary clone * Use 2D weights in XCM v3 (#6134) * Depend upon sp-core instead of sp-runtime * Make sp-io a dev-dependency * Use 2D weights in XCM v3 * cargo fmt * Add XCM pallet migration to runtimes * Use from_parts * cargo fmt * Fixes * cargo fmt * Remove XCMWeight import * Fixes * Fixes * Fixes * Fixes * Use translate in migration * Increase max upward message size in tests * Fix doc test * Remove most uses of from_ref_time * cargo fmt * Fixes * Fixes * Add extrinsic benchmarking to XCM pallet * cargo fmt * Fixes * Use old syntax * cargo fmt * Fixes * Remove hardcoded weights * Add XCM pallet to benchmarks * Use successful origin * Fix weird type parameter compilation issue * Fixes * ".git/.scripts/bench-bot.sh" runtime westend-dev pallet_xcm * ".git/.scripts/bench-bot.sh" runtime rococo-dev pallet_xcm * ".git/.scripts/bench-bot.sh" runtime kusama-dev pallet_xcm * ".git/.scripts/bench-bot.sh" runtime polkadot-dev pallet_xcm * Use benchmarked XCM pallet weights * Fixes * Fixes * Use override instead of skip * Fixes * Fixes * Fixes * Fixes * ".git/.scripts/bench-bot.sh" runtime polkadot-dev pallet_xcm * Fixes * ".git/.scripts/bench-bot.sh" runtime polkadot-dev pallet_xcm * ".git/.scripts/bench-bot.sh" runtime westend-dev pallet_xcm Co-authored-by: command-bot <> * Replace Weight::MAX with 100b weight units * Add test to ensure all_gte in barriers is correct * Update xcm/src/v3/junction.rs Co-authored-by: asynchronous rob <rphmeier@gmail.com> * Add more weight tests * cargo fmt * Create thread_local in XCM executor to limit recursion depth (#6304) * Create thread_local in XCM executor to limit recursion depth * Add unit test for recursion limit * Fix statefulness in tests * Remove panic * Use defer and environmental macro * Fix the implementation * Use nicer interface * Change ThisNetwork to AnyNetwork * Move recursion check up to top level * cargo fmt * Update comment Co-authored-by: Bastian Köcher <info@kchr.de> * Add upper limit on the number of overweight messages in the queue (#6298) * Add upper limit on the number of ovwerweight messages in the queue * Add newline * Introduce whitelist for Transact and limit UMP processing to 10 messages per block (#6280) * Add SafeCallFilter to XcmConfig * Limit UMP to receive 10 messages every block * Place 10 message limit on processing instead of receiving * Always increment the message_processed count whenever a message is processed * Add as_derivative to the Transact whitelist * cargo fmt * Fixes * Update xcm/xcm-builder/src/universal_exports.rs Co-authored-by: Branislav Kontur <bkontur@gmail.com> * Fixes * Fixes * Remove topic register and instead use the topic field in XcmContext * Derive some common traits for DispatchBlobError * Fixes * cargo fmt * Fixes * Fixes * Fix comments * Fixes * Introduce WithOriginFilter and apply it as the CallDispatcher for runtimes * Fixes * Appease clippy and fixes * Fixes * Fix more clippy issues * Fixes * ".git/.scripts/bench-bot.sh" runtime polkadot-dev pallet_xcm * ".git/.scripts/bench-bot.sh" runtime westend-dev pallet_xcm * ".git/.scripts/bench-bot.sh" runtime westend-dev pallet_xcm * Add benchmark function for ExportMessage * Fix comment * Add upper limit to DownwardMessageQueues size * Add max size check for queue in can_queue_downward_message * Fixes * Make Transact runtime call configurable * Return Weight::MAX when there is no successful send XCM origin * Update substrate * Fixes * Fixes * Remove ExportMessage benchmark * Remove assertion on Transact instruction benchmark * Make reachable destination configurable in XCM pallet benchmarks * Fixes * Fixes * Remove cfg attribute in fuzzer * Fixes * Remove cfg attribute for XCM pallet in test runtime * Fixes * Use ReachableDest where possible * Fixes * Add benchmark for UnpaidExecution * Update substrate * Ensure benchmark functions pass filters * Add runtime-benchmarks feature to fuzzer * Ensure FixedRateOfFungible accounts for proof size weights * cargo fmt * Whitelist remark_with_event when runtime-benchmarks feature is enabled * Use remark_with_event for Transact benchmarks * Fix Cargo.lock * Allow up to 3 DescendOrigin instructions before UnpaidExecution * cargo fmt * Edit code comment * Check check_origin for unpaid execution privilege * Fixes * Small nits for xcm-v3 (#6408) * Add possibility to skip benchmark for export_message * ".git/.scripts/bench-bot.sh" xcm westend-dev pallet_xcm_benchmarks::generic * Revert * ".git/.scripts/bench-bot.sh" xcm westend-dev pallet_xcm_benchmarks::generic * Add HaulBlobError to `fn haul_blob` * ".git/.scripts/bench-bot.sh" xcm westend-dev pallet_xcm_benchmarks::generic Co-authored-by: command-bot <> * Revert changes to UnpaidExecution * Change AllowUnpaidExecutionFrom to be explicit * Fix log text * cargo fmt * Add benchmarks for XCM pallet version migration (#6448) * Add benchmarks for XCM pallet version migration * cargo fmt * Fixes * Fixes * Fixes * ".git/.scripts/bench-bot.sh" runtime westend-dev pallet_xcm * ".git/.scripts/bench-bot.sh" runtime kusama-dev pallet_xcm * ".git/.scripts/bench-bot.sh" runtime rococo-dev pallet_xcm * ".git/.scripts/bench-bot.sh" runtime polkadot-dev pallet_xcm * Fix benchmarks * Fix benchmarks * ".git/.scripts/bench-bot.sh" runtime westend-dev pallet_xcm * ".git/.scripts/bench-bot.sh" runtime kusama-dev pallet_xcm * ".git/.scripts/bench-bot.sh" runtime rococo-dev pallet_xcm * ".git/.scripts/bench-bot.sh" runtime polkadot-dev pallet_xcm Co-authored-by: command-bot <> * Merge remote-tracking branch 'origin/master' into gav-xcm-v3 * Fixes * Fix comments (#6470) * Specify Ethereum networks by their chain id (#6286) Co-authored-by: Squirrel <gilescope@gmail.com> * Use for Kusama * Use WithComputedOrigin for Polkadot, Rococo and Westend * Update lock * Fix warning * Update xcm/pallet-xcm/src/tests.rs Co-authored-by: Squirrel <gilescope@gmail.com> * Update runtime/parachains/src/ump/migration.rs Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Update xcm/pallet-xcm/src/migration.rs Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Fixes * cargo fmt * Typo * Update xcm/src/v3/mod.rs Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Docs * Docs * Docs * Docs * Docs * Update xcm/src/v3/multiasset.rs Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Add tests for MultiAssets::from_sorted_and_deduplicated * Fail gracefully when same instance NFTs are detected during push * Update Substrate to fix benchmarks * Apply suggestions from code review * Update runtime/kusama/src/xcm_config.rs * Rename arguments * Attempt to fix benchmark * ".git/.scripts/commands/bench/bench.sh" runtime polkadot-dev runtime_parachains::ump * Use actual weights for UMP pallet in Polkadot * ".git/.scripts/commands/bench/bench.sh" runtime kusama-dev runtime_parachains::ump * ".git/.scripts/commands/bench/bench.sh" runtime westend-dev runtime_parachains::ump * ".git/.scripts/commands/bench/bench.sh" runtime rococo-dev runtime_parachains::ump Co-authored-by: Keith Yeung <kungfukeith11@gmail.com> Co-authored-by: Alexander Popiak <alexander.popiak@parity.io> Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> Co-authored-by: Bastian Köcher <info@kchr.de> Co-authored-by: Parity Bot <admin@parity.io> Co-authored-by: stanly-johnson <stanlyjohnson@outlook.com> Co-authored-by: nanocryk <6422796+nanocryk@users.noreply.github.com> Co-authored-by: Branislav Kontur <bkontur@gmail.com> Co-authored-by: asynchronous rob <rphmeier@gmail.com> Co-authored-by: command-bot <> Co-authored-by: Vincent Geddes <vincent.geddes@hey.com> Co-authored-by: Squirrel <gilescope@gmail.com> Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
// Copyright 2022 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Adapters to work with `frame_support::traits::tokens::fungibles` through XCM.
|
||||
|
||||
use frame_support::traits::Get;
|
||||
use sp_std::{borrow::Borrow, marker::PhantomData, prelude::*, result};
|
||||
use xcm::latest::prelude::*;
|
||||
use xcm_executor::traits::{Convert, Error as MatchError, MatchesFungibles, MatchesNonFungibles};
|
||||
|
||||
/// Converter struct implementing `AssetIdConversion` converting a numeric asset ID (must be `TryFrom/TryInto<u128>`) into
|
||||
/// a `GeneralIndex` junction, prefixed by some `MultiLocation` value. The `MultiLocation` value will typically be a
|
||||
/// `PalletInstance` junction.
|
||||
pub struct AsPrefixedGeneralIndex<Prefix, AssetId, ConvertAssetId>(
|
||||
PhantomData<(Prefix, AssetId, ConvertAssetId)>,
|
||||
);
|
||||
impl<Prefix: Get<MultiLocation>, AssetId: Clone, ConvertAssetId: Convert<u128, AssetId>>
|
||||
Convert<MultiLocation, AssetId> for AsPrefixedGeneralIndex<Prefix, AssetId, ConvertAssetId>
|
||||
{
|
||||
fn convert_ref(id: impl Borrow<MultiLocation>) -> result::Result<AssetId, ()> {
|
||||
let prefix = Prefix::get();
|
||||
let id = id.borrow();
|
||||
if prefix.parent_count() != id.parent_count() ||
|
||||
prefix
|
||||
.interior()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.any(|(index, junction)| id.interior().at(index) != Some(junction))
|
||||
{
|
||||
return Err(())
|
||||
}
|
||||
match id.interior().at(prefix.interior().len()) {
|
||||
Some(Junction::GeneralIndex(id)) => ConvertAssetId::convert_ref(id),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
fn reverse_ref(what: impl Borrow<AssetId>) -> result::Result<MultiLocation, ()> {
|
||||
let mut location = Prefix::get();
|
||||
let id = ConvertAssetId::reverse_ref(what)?;
|
||||
location.push_interior(Junction::GeneralIndex(id)).map_err(|_| ())?;
|
||||
Ok(location)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConvertedConcreteId<AssetId, Balance, ConvertAssetId, ConvertOther>(
|
||||
PhantomData<(AssetId, Balance, ConvertAssetId, ConvertOther)>,
|
||||
);
|
||||
impl<
|
||||
AssetId: Clone,
|
||||
Balance: Clone,
|
||||
ConvertAssetId: Convert<MultiLocation, AssetId>,
|
||||
ConvertBalance: Convert<u128, Balance>,
|
||||
> MatchesFungibles<AssetId, Balance>
|
||||
for ConvertedConcreteId<AssetId, Balance, ConvertAssetId, ConvertBalance>
|
||||
{
|
||||
fn matches_fungibles(a: &MultiAsset) -> result::Result<(AssetId, Balance), MatchError> {
|
||||
let (amount, id) = match (&a.fun, &a.id) {
|
||||
(Fungible(ref amount), Concrete(ref id)) => (amount, id),
|
||||
_ => return Err(MatchError::AssetNotFound),
|
||||
};
|
||||
let what =
|
||||
ConvertAssetId::convert_ref(id).map_err(|_| MatchError::AssetIdConversionFailed)?;
|
||||
let amount = ConvertBalance::convert_ref(amount)
|
||||
.map_err(|_| MatchError::AmountToBalanceConversionFailed)?;
|
||||
Ok((what, amount))
|
||||
}
|
||||
}
|
||||
impl<
|
||||
ClassId: Clone,
|
||||
InstanceId: Clone,
|
||||
ConvertClassId: Convert<MultiLocation, ClassId>,
|
||||
ConvertInstanceId: Convert<AssetInstance, InstanceId>,
|
||||
> MatchesNonFungibles<ClassId, InstanceId>
|
||||
for ConvertedConcreteId<ClassId, InstanceId, ConvertClassId, ConvertInstanceId>
|
||||
{
|
||||
fn matches_nonfungibles(a: &MultiAsset) -> result::Result<(ClassId, InstanceId), MatchError> {
|
||||
let (instance, class) = match (&a.fun, &a.id) {
|
||||
(NonFungible(ref instance), Concrete(ref class)) => (instance, class),
|
||||
_ => return Err(MatchError::AssetNotFound),
|
||||
};
|
||||
let what =
|
||||
ConvertClassId::convert_ref(class).map_err(|_| MatchError::AssetIdConversionFailed)?;
|
||||
let instance = ConvertInstanceId::convert_ref(instance)
|
||||
.map_err(|_| MatchError::InstanceConversionFailed)?;
|
||||
Ok((what, instance))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConvertedAbstractId<AssetId, Balance, ConvertAssetId, ConvertOther>(
|
||||
PhantomData<(AssetId, Balance, ConvertAssetId, ConvertOther)>,
|
||||
);
|
||||
impl<
|
||||
AssetId: Clone,
|
||||
Balance: Clone,
|
||||
ConvertAssetId: Convert<[u8; 32], AssetId>,
|
||||
ConvertBalance: Convert<u128, Balance>,
|
||||
> MatchesFungibles<AssetId, Balance>
|
||||
for ConvertedAbstractId<AssetId, Balance, ConvertAssetId, ConvertBalance>
|
||||
{
|
||||
fn matches_fungibles(a: &MultiAsset) -> result::Result<(AssetId, Balance), MatchError> {
|
||||
let (amount, id) = match (&a.fun, &a.id) {
|
||||
(Fungible(ref amount), Abstract(ref id)) => (amount, id),
|
||||
_ => return Err(MatchError::AssetNotFound),
|
||||
};
|
||||
let what =
|
||||
ConvertAssetId::convert_ref(id).map_err(|_| MatchError::AssetIdConversionFailed)?;
|
||||
let amount = ConvertBalance::convert_ref(amount)
|
||||
.map_err(|_| MatchError::AmountToBalanceConversionFailed)?;
|
||||
Ok((what, amount))
|
||||
}
|
||||
}
|
||||
impl<
|
||||
ClassId: Clone,
|
||||
InstanceId: Clone,
|
||||
ConvertClassId: Convert<[u8; 32], ClassId>,
|
||||
ConvertInstanceId: Convert<AssetInstance, InstanceId>,
|
||||
> MatchesNonFungibles<ClassId, InstanceId>
|
||||
for ConvertedAbstractId<ClassId, InstanceId, ConvertClassId, ConvertInstanceId>
|
||||
{
|
||||
fn matches_nonfungibles(a: &MultiAsset) -> result::Result<(ClassId, InstanceId), MatchError> {
|
||||
let (instance, class) = match (&a.fun, &a.id) {
|
||||
(NonFungible(ref instance), Abstract(ref class)) => (instance, class),
|
||||
_ => return Err(MatchError::AssetNotFound),
|
||||
};
|
||||
let what =
|
||||
ConvertClassId::convert_ref(class).map_err(|_| MatchError::AssetIdConversionFailed)?;
|
||||
let instance = ConvertInstanceId::convert_ref(instance)
|
||||
.map_err(|_| MatchError::InstanceConversionFailed)?;
|
||||
Ok((what, instance))
|
||||
}
|
||||
}
|
||||
|
||||
#[deprecated = "Use `ConvertedConcreteId` instead"]
|
||||
pub type ConvertedConcreteAssetId<A, B, C, O> = ConvertedConcreteId<A, B, C, O>;
|
||||
#[deprecated = "Use `ConvertedAbstractId` instead"]
|
||||
pub type ConvertedAbstractAssetId<A, B, C, O> = ConvertedAbstractId<A, B, C, O>;
|
||||
@@ -16,11 +16,18 @@
|
||||
|
||||
//! Various implementations for `ShouldExecute`.
|
||||
|
||||
use frame_support::{ensure, traits::Contains};
|
||||
use frame_support::{
|
||||
ensure,
|
||||
traits::{Contains, Get},
|
||||
};
|
||||
use polkadot_parachain::primitives::IsSystem;
|
||||
use sp_std::{marker::PhantomData, result::Result};
|
||||
use xcm::latest::{
|
||||
Instruction::*, Junction, Junctions, MultiLocation, Weight, WeightLimit::*, Xcm,
|
||||
Instruction::{self, *},
|
||||
InteriorMultiLocation, Junction, Junctions,
|
||||
Junctions::X1,
|
||||
MultiLocation, Weight,
|
||||
WeightLimit::*,
|
||||
};
|
||||
use xcm_executor::traits::{OnResponse, ShouldExecute};
|
||||
|
||||
@@ -33,16 +40,16 @@ pub struct TakeWeightCredit;
|
||||
impl ShouldExecute for TakeWeightCredit {
|
||||
fn should_execute<RuntimeCall>(
|
||||
_origin: &MultiLocation,
|
||||
_message: &mut Xcm<RuntimeCall>,
|
||||
_instructions: &mut [Instruction<RuntimeCall>],
|
||||
max_weight: Weight,
|
||||
weight_credit: &mut Weight,
|
||||
) -> Result<(), ()> {
|
||||
log::trace!(
|
||||
target: "xcm::barriers",
|
||||
"TakeWeightCredit origin: {:?}, message: {:?}, max_weight: {:?}, weight_credit: {:?}",
|
||||
_origin, _message, max_weight, weight_credit,
|
||||
"TakeWeightCredit origin: {:?}, instructions: {:?}, max_weight: {:?}, weight_credit: {:?}",
|
||||
_origin, _instructions, max_weight, weight_credit,
|
||||
);
|
||||
*weight_credit = weight_credit.checked_sub(max_weight).ok_or(())?;
|
||||
*weight_credit = weight_credit.checked_sub(&max_weight).ok_or(())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -56,17 +63,21 @@ pub struct AllowTopLevelPaidExecutionFrom<T>(PhantomData<T>);
|
||||
impl<T: Contains<MultiLocation>> ShouldExecute for AllowTopLevelPaidExecutionFrom<T> {
|
||||
fn should_execute<RuntimeCall>(
|
||||
origin: &MultiLocation,
|
||||
message: &mut Xcm<RuntimeCall>,
|
||||
instructions: &mut [Instruction<RuntimeCall>],
|
||||
max_weight: Weight,
|
||||
_weight_credit: &mut Weight,
|
||||
) -> Result<(), ()> {
|
||||
log::trace!(
|
||||
target: "xcm::barriers",
|
||||
"AllowTopLevelPaidExecutionFrom origin: {:?}, message: {:?}, max_weight: {:?}, weight_credit: {:?}",
|
||||
origin, message, max_weight, _weight_credit,
|
||||
"AllowTopLevelPaidExecutionFrom origin: {:?}, instructions: {:?}, max_weight: {:?}, weight_credit: {:?}",
|
||||
origin, instructions, max_weight, _weight_credit,
|
||||
);
|
||||
|
||||
ensure!(T::contains(origin), ());
|
||||
let mut iter = message.0.iter_mut();
|
||||
// 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 mut iter = instructions.iter_mut().take(5);
|
||||
let i = iter.next().ok_or(())?;
|
||||
match i {
|
||||
ReceiveTeleportedAsset(..) |
|
||||
@@ -80,8 +91,10 @@ impl<T: Contains<MultiLocation>> ShouldExecute for AllowTopLevelPaidExecutionFro
|
||||
i = iter.next().ok_or(())?;
|
||||
}
|
||||
match i {
|
||||
BuyExecution { weight_limit: Limited(ref mut weight), .. } if *weight >= max_weight => {
|
||||
*weight = max_weight;
|
||||
BuyExecution { weight_limit: Limited(ref mut weight), .. }
|
||||
if weight.all_gte(max_weight) =>
|
||||
{
|
||||
*weight = weight.max(max_weight);
|
||||
Ok(())
|
||||
},
|
||||
BuyExecution { ref mut weight_limit, .. } if weight_limit == &Unlimited => {
|
||||
@@ -93,26 +106,150 @@ impl<T: Contains<MultiLocation>> ShouldExecute for AllowTopLevelPaidExecutionFro
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows execution from any origin that is contained in `T` (i.e. `T::Contains(origin)`) without any payments.
|
||||
/// Use only for executions from trusted origin groups.
|
||||
/// 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
|
||||
/// `ParentLocaction` 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<InteriorMultiLocation>,
|
||||
MaxPrefixes: Get<u32>,
|
||||
> ShouldExecute for WithComputedOrigin<InnerBarrier, LocalUniversal, MaxPrefixes>
|
||||
{
|
||||
fn should_execute<Call>(
|
||||
origin: &MultiLocation,
|
||||
instructions: &mut [Instruction<Call>],
|
||||
max_weight: Weight,
|
||||
weight_credit: &mut Weight,
|
||||
) -> Result<(), ()> {
|
||||
log::trace!(
|
||||
target: "xcm::barriers",
|
||||
"WithComputedOrigin origin: {:?}, instructions: {:?}, max_weight: {:?}, weight_credit: {:?}",
|
||||
origin, instructions, max_weight, weight_credit,
|
||||
);
|
||||
let mut actual_origin = *origin;
|
||||
let mut skipped = 0;
|
||||
// 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.
|
||||
while skipped < MaxPrefixes::get() as usize {
|
||||
match instructions.get(skipped) {
|
||||
Some(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 = X1(*new_global).relative_to(&LocalUniversal::get());
|
||||
},
|
||||
Some(DescendOrigin(j)) => {
|
||||
actual_origin.append_with(*j).map_err(|_| ())?;
|
||||
},
|
||||
_ => break,
|
||||
}
|
||||
skipped += 1;
|
||||
}
|
||||
InnerBarrier::should_execute(
|
||||
&actual_origin,
|
||||
&mut instructions[skipped..],
|
||||
max_weight,
|
||||
weight_credit,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 unpermissioned messages
|
||||
/// can be sent.
|
||||
pub struct AllowUnpaidExecutionFrom<T>(PhantomData<T>);
|
||||
impl<T: Contains<MultiLocation>> ShouldExecute for AllowUnpaidExecutionFrom<T> {
|
||||
fn should_execute<RuntimeCall>(
|
||||
origin: &MultiLocation,
|
||||
_message: &mut Xcm<RuntimeCall>,
|
||||
instructions: &mut [Instruction<RuntimeCall>],
|
||||
_max_weight: Weight,
|
||||
_weight_credit: &mut Weight,
|
||||
) -> Result<(), ()> {
|
||||
log::trace!(
|
||||
target: "xcm::barriers",
|
||||
"AllowUnpaidExecutionFrom origin: {:?}, message: {:?}, max_weight: {:?}, weight_credit: {:?}",
|
||||
origin, _message, _max_weight, _weight_credit,
|
||||
"AllowUnpaidExecutionFrom origin: {:?}, instructions: {:?}, max_weight: {:?}, weight_credit: {:?}",
|
||||
origin, instructions, _max_weight, _weight_credit,
|
||||
);
|
||||
ensure!(T::contains(origin), ());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows execution from any origin that is contained in `T` (i.e. `T::Contains(origin)`) if the
|
||||
/// message begins with the instruction `UnpaidExecution`.
|
||||
///
|
||||
/// Use only for executions from trusted origin groups.
|
||||
pub struct AllowExplicitUnpaidExecutionFrom<T>(PhantomData<T>);
|
||||
impl<T: Contains<MultiLocation>> ShouldExecute for AllowExplicitUnpaidExecutionFrom<T> {
|
||||
fn should_execute<Call>(
|
||||
origin: &MultiLocation,
|
||||
instructions: &mut [Instruction<Call>],
|
||||
max_weight: Weight,
|
||||
_weight_credit: &mut Weight,
|
||||
) -> Result<(), ()> {
|
||||
log::trace!(
|
||||
target: "xcm::barriers",
|
||||
"AllowExplicitUnpaidExecutionFrom origin: {:?}, instructions: {:?}, max_weight: {:?}, weight_credit: {:?}",
|
||||
origin, instructions, max_weight, _weight_credit,
|
||||
);
|
||||
ensure!(T::contains(origin), ());
|
||||
match instructions.first() {
|
||||
Some(UnpaidExecution { weight_limit: Limited(m), .. }) if m.all_gte(max_weight) =>
|
||||
Ok(()),
|
||||
Some(UnpaidExecution { weight_limit: Unlimited, .. }) => Ok(()),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows a message only if it is from a system-level child parachain.
|
||||
pub struct IsChildSystemParachain<ParaId>(PhantomData<ParaId>);
|
||||
impl<ParaId: IsSystem + From<u32>> Contains<MultiLocation> for IsChildSystemParachain<ParaId> {
|
||||
@@ -130,42 +267,43 @@ pub struct AllowKnownQueryResponses<ResponseHandler>(PhantomData<ResponseHandler
|
||||
impl<ResponseHandler: OnResponse> ShouldExecute for AllowKnownQueryResponses<ResponseHandler> {
|
||||
fn should_execute<RuntimeCall>(
|
||||
origin: &MultiLocation,
|
||||
message: &mut Xcm<RuntimeCall>,
|
||||
instructions: &mut [Instruction<RuntimeCall>],
|
||||
_max_weight: Weight,
|
||||
_weight_credit: &mut Weight,
|
||||
) -> Result<(), ()> {
|
||||
log::trace!(
|
||||
target: "xcm::barriers",
|
||||
"AllowKnownQueryResponses origin: {:?}, message: {:?}, max_weight: {:?}, weight_credit: {:?}",
|
||||
origin, message, _max_weight, _weight_credit,
|
||||
"AllowKnownQueryResponses origin: {:?}, instructions: {:?}, max_weight: {:?}, weight_credit: {:?}",
|
||||
origin, instructions, _max_weight, _weight_credit,
|
||||
);
|
||||
match message.0.first() {
|
||||
Some(QueryResponse { query_id, .. })
|
||||
if ResponseHandler::expecting_response(origin, *query_id) =>
|
||||
ensure!(instructions.len() == 1, ());
|
||||
match instructions.first() {
|
||||
Some(QueryResponse { query_id, querier, .. })
|
||||
if ResponseHandler::expecting_response(origin, *query_id, querier.as_ref()) =>
|
||||
Ok(()),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows execution from `origin` if it is just a straight `SubscribeVerison` or
|
||||
/// Allows execution from `origin` if it is just a straight `SubscribeVersion` or
|
||||
/// `UnsubscribeVersion` instruction.
|
||||
pub struct AllowSubscriptionsFrom<T>(PhantomData<T>);
|
||||
impl<T: Contains<MultiLocation>> ShouldExecute for AllowSubscriptionsFrom<T> {
|
||||
fn should_execute<RuntimeCall>(
|
||||
origin: &MultiLocation,
|
||||
message: &mut Xcm<RuntimeCall>,
|
||||
instructions: &mut [Instruction<RuntimeCall>],
|
||||
_max_weight: Weight,
|
||||
_weight_credit: &mut Weight,
|
||||
) -> Result<(), ()> {
|
||||
log::trace!(
|
||||
target: "xcm::barriers",
|
||||
"AllowSubscriptionsFrom origin: {:?}, message: {:?}, max_weight: {:?}, weight_credit: {:?}",
|
||||
origin, message, _max_weight, _weight_credit,
|
||||
"AllowSubscriptionsFrom origin: {:?}, instructions: {:?}, max_weight: {:?}, weight_credit: {:?}",
|
||||
origin, instructions, _max_weight, _weight_credit,
|
||||
);
|
||||
ensure!(T::contains(origin), ());
|
||||
match (message.0.len(), message.0.first()) {
|
||||
(1, Some(SubscribeVersion { .. })) | (1, Some(UnsubscribeVersion)) => Ok(()),
|
||||
match instructions {
|
||||
&mut [SubscribeVersion { .. } | UnsubscribeVersion] => Ok(()),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,11 @@
|
||||
|
||||
//! Adapters to work with `frame_support::traits::Currency` through XCM.
|
||||
|
||||
use super::MintLocation;
|
||||
use frame_support::traits::{ExistenceRequirement::AllowDeath, Get, WithdrawReasons};
|
||||
use sp_runtime::traits::CheckedSub;
|
||||
use sp_std::{marker::PhantomData, result};
|
||||
use xcm::latest::{Error as XcmError, MultiAsset, MultiLocation, Result};
|
||||
use xcm::latest::{Error as XcmError, MultiAsset, MultiLocation, Result, XcmContext};
|
||||
use xcm_executor::{
|
||||
traits::{Convert, MatchesFungible, TransactAsset},
|
||||
Assets,
|
||||
@@ -67,10 +68,13 @@ impl From<Error> for XcmError {
|
||||
/// /// 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 balance type:
|
||||
/// u128,
|
||||
/// // 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.
|
||||
@@ -86,67 +90,108 @@ pub struct CurrencyAdapter<Currency, Matcher, AccountIdConverter, AccountId, Che
|
||||
);
|
||||
|
||||
impl<
|
||||
Currency: frame_support::traits::Currency<AccountId>,
|
||||
Matcher: MatchesFungible<Currency::Balance>,
|
||||
AccountIdConverter: Convert<MultiLocation, AccountId>,
|
||||
Currency: frame_support::traits::Currency<AccountId>,
|
||||
AccountId: Clone, // can't get away without it since Currency is generic over it.
|
||||
CheckedAccount: Get<Option<AccountId>>,
|
||||
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(|_| XcmError::NotWithdrawable)
|
||||
}
|
||||
fn accrue_checked(checked_account: AccountId, amount: Currency::Balance) {
|
||||
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: Convert<MultiLocation, AccountId>,
|
||||
AccountId: Clone, // 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: &MultiLocation, what: &MultiAsset) -> Result {
|
||||
fn can_check_in(_origin: &MultiLocation, what: &MultiAsset, _context: &XcmContext) -> Result {
|
||||
log::trace!(target: "xcm::currency_adapter", "can_check_in origin: {:?}, what: {:?}", _origin, what);
|
||||
// Check we handle this asset.
|
||||
let amount: Currency::Balance =
|
||||
Matcher::matches_fungible(what).ok_or(Error::AssetNotFound)?;
|
||||
if let Some(checked_account) = CheckedAccount::get() {
|
||||
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(|_| XcmError::NotWithdrawable)?;
|
||||
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(()),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_in(_origin: &MultiLocation, what: &MultiAsset) {
|
||||
fn check_in(_origin: &MultiLocation, what: &MultiAsset, _context: &XcmContext) {
|
||||
log::trace!(target: "xcm::currency_adapter", "check_in origin: {:?}, what: {:?}", _origin, what);
|
||||
if let Some(amount) = Matcher::matches_fungible(what) {
|
||||
if let Some(checked_account) = CheckedAccount::get() {
|
||||
let ok = Currency::withdraw(
|
||||
&checked_account,
|
||||
amount,
|
||||
WithdrawReasons::TRANSFER,
|
||||
AllowDeath,
|
||||
)
|
||||
.is_ok();
|
||||
if ok {
|
||||
Currency::reactivate(amount);
|
||||
}
|
||||
debug_assert!(
|
||||
ok,
|
||||
"`can_check_in` must have returned `true` immediately prior; qed"
|
||||
);
|
||||
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 check_out(_dest: &MultiLocation, what: &MultiAsset) {
|
||||
fn can_check_out(_dest: &MultiLocation, what: &MultiAsset, _context: &XcmContext) -> Result {
|
||||
log::trace!(target: "xcm::currency_adapter", "check_out dest: {:?}, what: {:?}", _dest, what);
|
||||
let amount = Matcher::matches_fungible(what).ok_or(Error::AssetNotFound)?;
|
||||
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: &MultiLocation, what: &MultiAsset, _context: &XcmContext) {
|
||||
log::trace!(target: "xcm::currency_adapter", "check_out dest: {:?}, what: {:?}", _dest, what);
|
||||
if let Some(amount) = Matcher::matches_fungible(what) {
|
||||
if let Some(checked_account) = CheckedAccount::get() {
|
||||
Currency::deposit_creating(&checked_account, amount);
|
||||
Currency::deactivate(amount);
|
||||
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: &MultiAsset, who: &MultiLocation) -> Result {
|
||||
fn deposit_asset(what: &MultiAsset, who: &MultiLocation, _context: &XcmContext) -> Result {
|
||||
log::trace!(target: "xcm::currency_adapter", "deposit_asset what: {:?}, who: {:?}", what, who);
|
||||
// Check we handle this asset.
|
||||
let amount = Matcher::matches_fungible(&what).ok_or(Error::AssetNotFound)?;
|
||||
@@ -156,7 +201,11 @@ impl<
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn withdraw_asset(what: &MultiAsset, who: &MultiLocation) -> result::Result<Assets, XcmError> {
|
||||
fn withdraw_asset(
|
||||
what: &MultiAsset,
|
||||
who: &MultiLocation,
|
||||
_maybe_context: Option<&XcmContext>,
|
||||
) -> result::Result<Assets, XcmError> {
|
||||
log::trace!(target: "xcm::currency_adapter", "withdraw_asset what: {:?}, who: {:?}", what, who);
|
||||
// Check we handle this asset.
|
||||
let amount = Matcher::matches_fungible(what).ok_or(Error::AssetNotFound)?;
|
||||
@@ -171,6 +220,7 @@ impl<
|
||||
asset: &MultiAsset,
|
||||
from: &MultiLocation,
|
||||
to: &MultiLocation,
|
||||
_context: &XcmContext,
|
||||
) -> result::Result<Assets, XcmError> {
|
||||
log::trace!(target: "xcm::currency_adapter", "internal_transfer_asset asset: {:?}, from: {:?}, to: {:?}", asset, from, to);
|
||||
let amount = Matcher::matches_fungible(asset).ok_or(Error::AssetNotFound)?;
|
||||
|
||||
@@ -14,28 +14,29 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Various implementations of `FilterAssetLocation`.
|
||||
//! Various implementations of `ContainsPair<MultiAsset, MultiLocation>`.
|
||||
|
||||
use frame_support::traits::Get;
|
||||
use frame_support::traits::{ContainsPair, Get};
|
||||
use sp_std::marker::PhantomData;
|
||||
use xcm::latest::{AssetId::Concrete, MultiAsset, MultiAssetFilter, MultiLocation};
|
||||
use xcm_executor::traits::FilterAssetLocation;
|
||||
|
||||
/// Accepts an asset iff it is a native asset.
|
||||
pub struct NativeAsset;
|
||||
impl FilterAssetLocation for NativeAsset {
|
||||
fn filter_asset_location(asset: &MultiAsset, origin: &MultiLocation) -> bool {
|
||||
log::trace!(target: "xcm::filter_asset_location", "NativeAsset asset: {:?}, origin: {:?}", asset, origin);
|
||||
impl ContainsPair<MultiAsset, MultiLocation> for NativeAsset {
|
||||
fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool {
|
||||
log::trace!(target: "xcm::contains", "NativeAsset asset: {:?}, origin: {:?}", asset, origin);
|
||||
matches!(asset.id, Concrete(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<(MultiAssetFilter, MultiLocation)>> FilterAssetLocation for Case<T> {
|
||||
fn filter_asset_location(asset: &MultiAsset, origin: &MultiLocation) -> bool {
|
||||
log::trace!(target: "xcm::filter_asset_location", "Case asset: {:?}, origin: {:?}", asset, origin);
|
||||
impl<T: Get<(MultiAssetFilter, MultiLocation)>> ContainsPair<MultiAsset, MultiLocation>
|
||||
for Case<T>
|
||||
{
|
||||
fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool {
|
||||
log::trace!(target: "xcm::contains", "Case asset: {:?}, origin: {:?}", asset, origin);
|
||||
let (a, o) = T::get();
|
||||
a.contains(asset) && &o == origin
|
||||
a.matches(asset) && &o == origin
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,97 +17,11 @@
|
||||
//! Adapters to work with `frame_support::traits::tokens::fungibles` through XCM.
|
||||
|
||||
use frame_support::traits::{tokens::fungibles, Contains, Get};
|
||||
use sp_std::{borrow::Borrow, marker::PhantomData, prelude::*, result};
|
||||
use xcm::latest::{
|
||||
AssetId::{Abstract, Concrete},
|
||||
Error as XcmError,
|
||||
Fungibility::Fungible,
|
||||
Junction, MultiAsset, MultiLocation, Result,
|
||||
};
|
||||
use sp_std::{marker::PhantomData, prelude::*, result};
|
||||
use xcm::latest::prelude::*;
|
||||
use xcm_executor::traits::{Convert, Error as MatchError, MatchesFungibles, TransactAsset};
|
||||
|
||||
/// Converter struct implementing `AssetIdConversion` converting a numeric asset ID (must be `TryFrom/TryInto<u128>`) into
|
||||
/// a `GeneralIndex` junction, prefixed by some `MultiLocation` value. The `MultiLocation` value will typically be a
|
||||
/// `PalletInstance` junction.
|
||||
pub struct AsPrefixedGeneralIndex<Prefix, AssetId, ConvertAssetId>(
|
||||
PhantomData<(Prefix, AssetId, ConvertAssetId)>,
|
||||
);
|
||||
impl<Prefix: Get<MultiLocation>, AssetId: Clone, ConvertAssetId: Convert<u128, AssetId>>
|
||||
Convert<MultiLocation, AssetId> for AsPrefixedGeneralIndex<Prefix, AssetId, ConvertAssetId>
|
||||
{
|
||||
fn convert_ref(id: impl Borrow<MultiLocation>) -> result::Result<AssetId, ()> {
|
||||
let prefix = Prefix::get();
|
||||
let id = id.borrow();
|
||||
if prefix.parent_count() != id.parent_count() ||
|
||||
prefix
|
||||
.interior()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.any(|(index, junction)| id.interior().at(index) != Some(junction))
|
||||
{
|
||||
return Err(())
|
||||
}
|
||||
match id.interior().at(prefix.interior().len()) {
|
||||
Some(Junction::GeneralIndex(id)) => ConvertAssetId::convert_ref(id),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
fn reverse_ref(what: impl Borrow<AssetId>) -> result::Result<MultiLocation, ()> {
|
||||
let mut location = Prefix::get();
|
||||
let id = ConvertAssetId::reverse_ref(what)?;
|
||||
location.push_interior(Junction::GeneralIndex(id)).map_err(|_| ())?;
|
||||
Ok(location)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConvertedConcreteAssetId<AssetId, Balance, ConvertAssetId, ConvertBalance>(
|
||||
PhantomData<(AssetId, Balance, ConvertAssetId, ConvertBalance)>,
|
||||
);
|
||||
impl<
|
||||
AssetId: Clone,
|
||||
Balance: Clone,
|
||||
ConvertAssetId: Convert<MultiLocation, AssetId>,
|
||||
ConvertBalance: Convert<u128, Balance>,
|
||||
> MatchesFungibles<AssetId, Balance>
|
||||
for ConvertedConcreteAssetId<AssetId, Balance, ConvertAssetId, ConvertBalance>
|
||||
{
|
||||
fn matches_fungibles(a: &MultiAsset) -> result::Result<(AssetId, Balance), MatchError> {
|
||||
let (amount, id) = match (&a.fun, &a.id) {
|
||||
(Fungible(ref amount), Concrete(ref id)) => (amount, id),
|
||||
_ => return Err(MatchError::AssetNotFound),
|
||||
};
|
||||
let what =
|
||||
ConvertAssetId::convert_ref(id).map_err(|_| MatchError::AssetIdConversionFailed)?;
|
||||
let amount = ConvertBalance::convert_ref(amount)
|
||||
.map_err(|_| MatchError::AmountToBalanceConversionFailed)?;
|
||||
Ok((what, amount))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConvertedAbstractAssetId<AssetId, Balance, ConvertAssetId, ConvertBalance>(
|
||||
PhantomData<(AssetId, Balance, ConvertAssetId, ConvertBalance)>,
|
||||
);
|
||||
impl<
|
||||
AssetId: Clone,
|
||||
Balance: Clone,
|
||||
ConvertAssetId: Convert<Vec<u8>, AssetId>,
|
||||
ConvertBalance: Convert<u128, Balance>,
|
||||
> MatchesFungibles<AssetId, Balance>
|
||||
for ConvertedAbstractAssetId<AssetId, Balance, ConvertAssetId, ConvertBalance>
|
||||
{
|
||||
fn matches_fungibles(a: &MultiAsset) -> result::Result<(AssetId, Balance), MatchError> {
|
||||
let (amount, id) = match (&a.fun, &a.id) {
|
||||
(Fungible(ref amount), Abstract(ref id)) => (amount, id),
|
||||
_ => return Err(MatchError::AssetNotFound),
|
||||
};
|
||||
let what =
|
||||
ConvertAssetId::convert_ref(id).map_err(|_| MatchError::AssetIdConversionFailed)?;
|
||||
let amount = ConvertBalance::convert_ref(amount)
|
||||
.map_err(|_| MatchError::AmountToBalanceConversionFailed)?;
|
||||
Ok((what, amount))
|
||||
}
|
||||
}
|
||||
|
||||
/// `TransactAsset` implementation to convert a `fungibles` implementation to become usable in XCM.
|
||||
pub struct FungiblesTransferAdapter<Assets, Matcher, AccountIdConverter, AccountId>(
|
||||
PhantomData<(Assets, Matcher, AccountIdConverter, AccountId)>,
|
||||
);
|
||||
@@ -122,6 +36,7 @@ impl<
|
||||
what: &MultiAsset,
|
||||
from: &MultiLocation,
|
||||
to: &MultiLocation,
|
||||
_context: &XcmContext,
|
||||
) -> result::Result<xcm_executor::Assets, XcmError> {
|
||||
log::trace!(
|
||||
target: "xcm::fungibles_adapter",
|
||||
@@ -140,6 +55,83 @@ impl<
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>(sp_std::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>(sp_std::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>(sp_std::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,
|
||||
@@ -148,12 +140,48 @@ pub struct FungiblesMutateAdapter<
|
||||
CheckAsset,
|
||||
CheckingAccount,
|
||||
>(PhantomData<(Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount)>);
|
||||
|
||||
impl<
|
||||
Assets: fungibles::Mutate<AccountId>,
|
||||
Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
|
||||
AccountIdConverter: Convert<MultiLocation, AccountId>,
|
||||
AccountId: Clone, // can't get away without it since Currency is generic over it.
|
||||
CheckAsset: Contains<Assets::AssetId>,
|
||||
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, true)
|
||||
.into_result()
|
||||
.map_err(|_| 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()
|
||||
.map_err(|_| 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).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: Convert<MultiLocation, AccountId>,
|
||||
AccountId: Clone, // can't get away without it since Currency is generic over it.
|
||||
CheckAsset: AssetChecking<Assets::AssetId>,
|
||||
CheckingAccount: Get<AccountId>,
|
||||
> TransactAsset
|
||||
for FungiblesMutateAdapter<
|
||||
@@ -165,7 +193,11 @@ impl<
|
||||
CheckingAccount,
|
||||
>
|
||||
{
|
||||
fn can_check_in(_origin: &MultiLocation, what: &MultiAsset) -> Result {
|
||||
fn can_check_in(
|
||||
_origin: &MultiLocation,
|
||||
what: &MultiAsset,
|
||||
_context: &XcmContext,
|
||||
) -> XcmResult {
|
||||
log::trace!(
|
||||
target: "xcm::fungibles_adapter",
|
||||
"can_check_in origin: {:?}, what: {:?}",
|
||||
@@ -173,50 +205,71 @@ impl<
|
||||
);
|
||||
// Check we handle this asset.
|
||||
let (asset_id, amount) = Matcher::matches_fungibles(what)?;
|
||||
if CheckAsset::contains(&asset_id) {
|
||||
// This is an asset whose teleports we track.
|
||||
let checking_account = CheckingAccount::get();
|
||||
Assets::can_withdraw(asset_id, &checking_account, amount)
|
||||
.into_result()
|
||||
.map_err(|_| XcmError::NotWithdrawable)?;
|
||||
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(()),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_in(_origin: &MultiLocation, what: &MultiAsset) {
|
||||
fn check_in(_origin: &MultiLocation, what: &MultiAsset, _context: &XcmContext) {
|
||||
log::trace!(
|
||||
target: "xcm::fungibles_adapter",
|
||||
"check_in origin: {:?}, what: {:?}",
|
||||
_origin, what
|
||||
);
|
||||
if let Ok((asset_id, amount)) = Matcher::matches_fungibles(what) {
|
||||
if CheckAsset::contains(&asset_id) {
|
||||
let checking_account = CheckingAccount::get();
|
||||
let ok = Assets::burn_from(asset_id, &checking_account, amount).is_ok();
|
||||
debug_assert!(
|
||||
ok,
|
||||
"`can_check_in` must have returned `true` immediately prior; qed"
|
||||
);
|
||||
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 check_out(_dest: &MultiLocation, what: &MultiAsset) {
|
||||
fn can_check_out(
|
||||
_origin: &MultiLocation,
|
||||
what: &MultiAsset,
|
||||
_context: &XcmContext,
|
||||
) -> XcmResult {
|
||||
log::trace!(
|
||||
target: "xcm::fungibles_adapter",
|
||||
"can_check_in origin: {:?}, what: {:?}",
|
||||
_origin, what
|
||||
);
|
||||
// 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: &MultiLocation, what: &MultiAsset, _context: &XcmContext) {
|
||||
log::trace!(
|
||||
target: "xcm::fungibles_adapter",
|
||||
"check_out dest: {:?}, what: {:?}",
|
||||
_dest, what
|
||||
);
|
||||
if let Ok((asset_id, amount)) = Matcher::matches_fungibles(what) {
|
||||
if CheckAsset::contains(&asset_id) {
|
||||
let checking_account = CheckingAccount::get();
|
||||
let ok = Assets::mint_into(asset_id, &checking_account, amount).is_ok();
|
||||
debug_assert!(ok, "`mint_into` cannot generally fail; qed");
|
||||
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: &MultiAsset, who: &MultiLocation) -> Result {
|
||||
fn deposit_asset(what: &MultiAsset, who: &MultiLocation, _context: &XcmContext) -> XcmResult {
|
||||
log::trace!(
|
||||
target: "xcm::fungibles_adapter",
|
||||
"deposit_asset what: {:?}, who: {:?}",
|
||||
@@ -233,6 +286,7 @@ impl<
|
||||
fn withdraw_asset(
|
||||
what: &MultiAsset,
|
||||
who: &MultiLocation,
|
||||
_maybe_context: Option<&XcmContext>,
|
||||
) -> result::Result<xcm_executor::Assets, XcmError> {
|
||||
log::trace!(
|
||||
target: "xcm::fungibles_adapter",
|
||||
@@ -262,12 +316,12 @@ impl<
|
||||
Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
|
||||
AccountIdConverter: Convert<MultiLocation, AccountId>,
|
||||
AccountId: Clone, // can't get away without it since Currency is generic over it.
|
||||
CheckAsset: Contains<Assets::AssetId>,
|
||||
CheckAsset: AssetChecking<Assets::AssetId>,
|
||||
CheckingAccount: Get<AccountId>,
|
||||
> TransactAsset
|
||||
for FungiblesAdapter<Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount>
|
||||
{
|
||||
fn can_check_in(origin: &MultiLocation, what: &MultiAsset) -> Result {
|
||||
fn can_check_in(origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult {
|
||||
FungiblesMutateAdapter::<
|
||||
Assets,
|
||||
Matcher,
|
||||
@@ -275,10 +329,10 @@ impl<
|
||||
AccountId,
|
||||
CheckAsset,
|
||||
CheckingAccount,
|
||||
>::can_check_in(origin, what)
|
||||
>::can_check_in(origin, what, context)
|
||||
}
|
||||
|
||||
fn check_in(origin: &MultiLocation, what: &MultiAsset) {
|
||||
fn check_in(origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) {
|
||||
FungiblesMutateAdapter::<
|
||||
Assets,
|
||||
Matcher,
|
||||
@@ -286,10 +340,10 @@ impl<
|
||||
AccountId,
|
||||
CheckAsset,
|
||||
CheckingAccount,
|
||||
>::check_in(origin, what)
|
||||
>::check_in(origin, what, context)
|
||||
}
|
||||
|
||||
fn check_out(dest: &MultiLocation, what: &MultiAsset) {
|
||||
fn can_check_out(dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult {
|
||||
FungiblesMutateAdapter::<
|
||||
Assets,
|
||||
Matcher,
|
||||
@@ -297,10 +351,10 @@ impl<
|
||||
AccountId,
|
||||
CheckAsset,
|
||||
CheckingAccount,
|
||||
>::check_out(dest, what)
|
||||
>::can_check_out(dest, what, context)
|
||||
}
|
||||
|
||||
fn deposit_asset(what: &MultiAsset, who: &MultiLocation) -> Result {
|
||||
fn check_out(dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) {
|
||||
FungiblesMutateAdapter::<
|
||||
Assets,
|
||||
Matcher,
|
||||
@@ -308,12 +362,24 @@ impl<
|
||||
AccountId,
|
||||
CheckAsset,
|
||||
CheckingAccount,
|
||||
>::deposit_asset(what, who)
|
||||
>::check_out(dest, what, context)
|
||||
}
|
||||
|
||||
fn deposit_asset(what: &MultiAsset, who: &MultiLocation, context: &XcmContext) -> XcmResult {
|
||||
FungiblesMutateAdapter::<
|
||||
Assets,
|
||||
Matcher,
|
||||
AccountIdConverter,
|
||||
AccountId,
|
||||
CheckAsset,
|
||||
CheckingAccount,
|
||||
>::deposit_asset(what, who, context)
|
||||
}
|
||||
|
||||
fn withdraw_asset(
|
||||
what: &MultiAsset,
|
||||
who: &MultiLocation,
|
||||
maybe_context: Option<&XcmContext>,
|
||||
) -> result::Result<xcm_executor::Assets, XcmError> {
|
||||
FungiblesMutateAdapter::<
|
||||
Assets,
|
||||
@@ -322,16 +388,17 @@ impl<
|
||||
AccountId,
|
||||
CheckAsset,
|
||||
CheckingAccount,
|
||||
>::withdraw_asset(what, who)
|
||||
>::withdraw_asset(what, who, maybe_context)
|
||||
}
|
||||
|
||||
fn internal_transfer_asset(
|
||||
what: &MultiAsset,
|
||||
from: &MultiLocation,
|
||||
to: &MultiLocation,
|
||||
context: &XcmContext,
|
||||
) -> result::Result<xcm_executor::Assets, XcmError> {
|
||||
FungiblesTransferAdapter::<Assets, Matcher, AccountIdConverter, AccountId>::internal_transfer_asset(
|
||||
what, from, to,
|
||||
what, from, to, context
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,6 @@
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
@@ -31,7 +29,7 @@ pub mod test_utils;
|
||||
mod location_conversion;
|
||||
pub use location_conversion::{
|
||||
Account32Hash, AccountId32Aliases, AccountKey20Aliases, ChildParachainConvertsVia,
|
||||
LocationInverter, ParentIsPreset, SiblingParachainConvertsVia,
|
||||
ParentIsPreset, SiblingParachainConvertsVia,
|
||||
};
|
||||
|
||||
mod origin_conversion;
|
||||
@@ -42,10 +40,16 @@ pub use origin_conversion::{
|
||||
SignedToAccountId32, SovereignSignedViaLocation,
|
||||
};
|
||||
|
||||
mod asset_conversion;
|
||||
pub use asset_conversion::{AsPrefixedGeneralIndex, ConvertedAbstractId, ConvertedConcreteId};
|
||||
#[allow(deprecated)]
|
||||
pub use asset_conversion::{ConvertedAbstractAssetId, ConvertedConcreteAssetId};
|
||||
|
||||
mod barriers;
|
||||
pub use barriers::{
|
||||
AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom,
|
||||
AllowUnpaidExecutionFrom, IsChildSystemParachain, TakeWeightCredit,
|
||||
AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom,
|
||||
AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, IsChildSystemParachain,
|
||||
TakeWeightCredit, WithComputedOrigin,
|
||||
};
|
||||
|
||||
mod currency_adapter;
|
||||
@@ -53,19 +57,29 @@ pub use currency_adapter::CurrencyAdapter;
|
||||
|
||||
mod fungibles_adapter;
|
||||
pub use fungibles_adapter::{
|
||||
AsPrefixedGeneralIndex, ConvertedAbstractAssetId, ConvertedConcreteAssetId, FungiblesAdapter,
|
||||
FungiblesMutateAdapter, FungiblesTransferAdapter,
|
||||
AssetChecking, DualMint, FungiblesAdapter, FungiblesMutateAdapter, FungiblesTransferAdapter,
|
||||
LocalMint, MintLocation, NoChecking, NonLocalMint,
|
||||
};
|
||||
|
||||
mod nonfungibles_adapter;
|
||||
pub use nonfungibles_adapter::{
|
||||
NonFungiblesAdapter, NonFungiblesMutateAdapter, NonFungiblesTransferAdapter,
|
||||
};
|
||||
|
||||
mod weight;
|
||||
#[allow(deprecated)]
|
||||
pub use weight::FixedRateOfConcreteFungible;
|
||||
pub use weight::{
|
||||
FixedRateOfFungible, FixedWeightBounds, TakeRevenue, UsingComponents, WeightInfoBounds,
|
||||
};
|
||||
|
||||
mod matches_fungible;
|
||||
pub use matches_fungible::{IsAbstract, IsConcrete};
|
||||
mod matches_token;
|
||||
pub use matches_token::{IsAbstract, IsConcrete};
|
||||
|
||||
mod filter_asset_location;
|
||||
pub use filter_asset_location::{Case, NativeAsset};
|
||||
|
||||
mod universal_exports;
|
||||
pub use universal_exports::{
|
||||
BridgeBlobDispatcher, BridgeMessage, DispatchBlob, DispatchBlobError, ExporterFor, HaulBlob,
|
||||
HaulBlobExporter, NetworkExportTable, SovereignPaidRemoteExporter, UnpaidLocalExporter,
|
||||
UnpaidRemoteExporter,
|
||||
};
|
||||
|
||||
@@ -19,11 +19,11 @@ use parity_scale_codec::{Decode, Encode};
|
||||
use sp_io::hashing::blake2_256;
|
||||
use sp_runtime::traits::{AccountIdConversion, TrailingZeroInput};
|
||||
use sp_std::{borrow::Borrow, marker::PhantomData};
|
||||
use xcm::latest::{Junction::*, Junctions::*, MultiLocation, NetworkId, Parent};
|
||||
use xcm_executor::traits::{Convert, InvertLocation};
|
||||
use xcm::latest::prelude::*;
|
||||
use xcm_executor::traits::Convert;
|
||||
|
||||
pub struct Account32Hash<Network, AccountId>(PhantomData<(Network, AccountId)>);
|
||||
impl<Network: Get<NetworkId>, AccountId: From<[u8; 32]> + Into<[u8; 32]> + Clone>
|
||||
impl<Network: Get<Option<NetworkId>>, AccountId: From<[u8; 32]> + Into<[u8; 32]> + Clone>
|
||||
Convert<MultiLocation, AccountId> for Account32Hash<Network, AccountId>
|
||||
{
|
||||
fn convert_ref(location: impl Borrow<MultiLocation>) -> Result<AccountId, ()> {
|
||||
@@ -107,15 +107,12 @@ impl<ParaId: From<u32> + Into<u32> + AccountIdConversion<AccountId>, AccountId:
|
||||
|
||||
/// Extracts the `AccountId32` from the passed `location` if the network matches.
|
||||
pub struct AccountId32Aliases<Network, AccountId>(PhantomData<(Network, AccountId)>);
|
||||
impl<Network: Get<NetworkId>, AccountId: From<[u8; 32]> + Into<[u8; 32]> + Clone>
|
||||
impl<Network: Get<Option<NetworkId>>, AccountId: From<[u8; 32]> + Into<[u8; 32]> + Clone>
|
||||
Convert<MultiLocation, AccountId> for AccountId32Aliases<Network, AccountId>
|
||||
{
|
||||
fn convert(location: MultiLocation) -> Result<AccountId, MultiLocation> {
|
||||
let id = match location {
|
||||
MultiLocation {
|
||||
parents: 0,
|
||||
interior: X1(AccountId32 { id, network: NetworkId::Any }),
|
||||
} => id,
|
||||
MultiLocation { parents: 0, interior: X1(AccountId32 { id, network: None }) } => id,
|
||||
MultiLocation { parents: 0, interior: X1(AccountId32 { id, network }) }
|
||||
if network == Network::get() =>
|
||||
id,
|
||||
@@ -130,15 +127,12 @@ impl<Network: Get<NetworkId>, AccountId: From<[u8; 32]> + Into<[u8; 32]> + Clone
|
||||
}
|
||||
|
||||
pub struct AccountKey20Aliases<Network, AccountId>(PhantomData<(Network, AccountId)>);
|
||||
impl<Network: Get<NetworkId>, AccountId: From<[u8; 20]> + Into<[u8; 20]> + Clone>
|
||||
impl<Network: Get<Option<NetworkId>>, AccountId: From<[u8; 20]> + Into<[u8; 20]> + Clone>
|
||||
Convert<MultiLocation, AccountId> for AccountKey20Aliases<Network, AccountId>
|
||||
{
|
||||
fn convert(location: MultiLocation) -> Result<AccountId, MultiLocation> {
|
||||
let key = match location {
|
||||
MultiLocation {
|
||||
parents: 0,
|
||||
interior: X1(AccountKey20 { key, network: NetworkId::Any }),
|
||||
} => key,
|
||||
MultiLocation { parents: 0, interior: X1(AccountKey20 { key, network: None }) } => key,
|
||||
MultiLocation { parents: 0, interior: X1(AccountKey20 { key, network }) }
|
||||
if network == Network::get() =>
|
||||
key,
|
||||
@@ -153,69 +147,19 @@ impl<Network: Get<NetworkId>, AccountId: From<[u8; 20]> + Into<[u8; 20]> + Clone
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple location inverter; give it this location's ancestry and it'll figure out the inverted
|
||||
/// location.
|
||||
///
|
||||
/// # Example
|
||||
/// ## Network Topology
|
||||
/// ```txt
|
||||
/// v Source
|
||||
/// Relay -> Para 1 -> Account20
|
||||
/// -> Para 2 -> Account32
|
||||
/// ^ Target
|
||||
/// ```
|
||||
/// ```rust
|
||||
/// # use frame_support::parameter_types;
|
||||
/// # use xcm::latest::{MultiLocation, Junction::*, Junctions::{self, *}, NetworkId::Any};
|
||||
/// # use xcm_builder::LocationInverter;
|
||||
/// # use xcm_executor::traits::InvertLocation;
|
||||
/// # fn main() {
|
||||
/// parameter_types!{
|
||||
/// pub Ancestry: MultiLocation = X2(
|
||||
/// Parachain(1),
|
||||
/// AccountKey20 { network: Any, key: Default::default() },
|
||||
/// ).into();
|
||||
/// }
|
||||
///
|
||||
/// let input = MultiLocation::new(2, X2(Parachain(2), AccountId32 { network: Any, id: Default::default() }));
|
||||
/// let inverted = LocationInverter::<Ancestry>::invert_location(&input);
|
||||
/// assert_eq!(inverted, Ok(MultiLocation::new(
|
||||
/// 2,
|
||||
/// X2(Parachain(1), AccountKey20 { network: Any, key: Default::default() }),
|
||||
/// )));
|
||||
/// # }
|
||||
/// ```
|
||||
pub struct LocationInverter<Ancestry>(PhantomData<Ancestry>);
|
||||
impl<Ancestry: Get<MultiLocation>> InvertLocation for LocationInverter<Ancestry> {
|
||||
fn ancestry() -> MultiLocation {
|
||||
Ancestry::get()
|
||||
}
|
||||
fn invert_location(location: &MultiLocation) -> Result<MultiLocation, ()> {
|
||||
let mut ancestry = Ancestry::get();
|
||||
let mut junctions = Here;
|
||||
for _ in 0..location.parent_count() {
|
||||
junctions = junctions
|
||||
.pushed_with(ancestry.take_first_interior().unwrap_or(OnlyChild))
|
||||
.map_err(|_| ())?;
|
||||
}
|
||||
let parents = location.interior().len() as u8;
|
||||
Ok(MultiLocation::new(parents, junctions))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use frame_support::parameter_types;
|
||||
use xcm::latest::{Junction, NetworkId::Any};
|
||||
use xcm::latest::Junction;
|
||||
|
||||
fn account20() -> Junction {
|
||||
AccountKey20 { network: Any, key: Default::default() }
|
||||
AccountKey20 { network: None, key: Default::default() }
|
||||
}
|
||||
|
||||
fn account32() -> Junction {
|
||||
AccountId32 { network: Any, id: Default::default() }
|
||||
AccountId32 { network: None, id: Default::default() }
|
||||
}
|
||||
|
||||
// Network Topology
|
||||
@@ -227,17 +171,17 @@ mod tests {
|
||||
// Inputs and outputs written as file paths:
|
||||
//
|
||||
// input location (source to target): ../../../para_2/account32_default
|
||||
// ancestry (root to source): para_1/account20_default/account20_default
|
||||
// context (root to source): para_1/account20_default/account20_default
|
||||
// =>
|
||||
// output (target to source): ../../para_1/account20_default/account20_default
|
||||
#[test]
|
||||
fn inverter_works_in_tree() {
|
||||
parameter_types! {
|
||||
pub Ancestry: MultiLocation = X3(Parachain(1), account20(), account20()).into();
|
||||
pub UniversalLocation: InteriorMultiLocation = X3(Parachain(1), account20(), account20());
|
||||
}
|
||||
|
||||
let input = MultiLocation::new(3, X2(Parachain(2), account32()));
|
||||
let inverted = LocationInverter::<Ancestry>::invert_location(&input).unwrap();
|
||||
let inverted = UniversalLocation::get().invert_target(&input).unwrap();
|
||||
assert_eq!(inverted, MultiLocation::new(2, X3(Parachain(1), account20(), account20())));
|
||||
}
|
||||
|
||||
@@ -246,13 +190,13 @@ mod tests {
|
||||
// Relay -> Para 1 -> SmartContract -> Account
|
||||
// ^ Target
|
||||
#[test]
|
||||
fn inverter_uses_ancestry_as_inverted_location() {
|
||||
fn inverter_uses_context_as_inverted_location() {
|
||||
parameter_types! {
|
||||
pub Ancestry: MultiLocation = X2(account20(), account20()).into();
|
||||
pub UniversalLocation: InteriorMultiLocation = X2(account20(), account20());
|
||||
}
|
||||
|
||||
let input = MultiLocation::grandparent();
|
||||
let inverted = LocationInverter::<Ancestry>::invert_location(&input).unwrap();
|
||||
let inverted = UniversalLocation::get().invert_target(&input).unwrap();
|
||||
assert_eq!(inverted, X2(account20(), account20()).into());
|
||||
}
|
||||
|
||||
@@ -261,24 +205,24 @@ mod tests {
|
||||
// Relay -> Para 1 -> CollectivePallet -> Plurality
|
||||
// ^ Target
|
||||
#[test]
|
||||
fn inverter_uses_only_child_on_missing_ancestry() {
|
||||
fn inverter_uses_only_child_on_missing_context() {
|
||||
parameter_types! {
|
||||
pub Ancestry: MultiLocation = X1(PalletInstance(5)).into();
|
||||
pub UniversalLocation: InteriorMultiLocation = PalletInstance(5).into();
|
||||
}
|
||||
|
||||
let input = MultiLocation::grandparent();
|
||||
let inverted = LocationInverter::<Ancestry>::invert_location(&input).unwrap();
|
||||
assert_eq!(inverted, X2(PalletInstance(5), OnlyChild).into());
|
||||
let inverted = UniversalLocation::get().invert_target(&input).unwrap();
|
||||
assert_eq!(inverted, (OnlyChild, PalletInstance(5)).into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inverter_errors_when_location_is_too_large() {
|
||||
parameter_types! {
|
||||
pub Ancestry: MultiLocation = Here.into();
|
||||
pub UniversalLocation: InteriorMultiLocation = Here;
|
||||
}
|
||||
|
||||
let input = MultiLocation { parents: 99, interior: X1(Parachain(88)) };
|
||||
let inverted = LocationInverter::<Ancestry>::invert_location(&input);
|
||||
let inverted = UniversalLocation::get().invert_target(&input);
|
||||
assert_eq!(inverted, Err(()));
|
||||
}
|
||||
}
|
||||
|
||||
+31
-10
@@ -17,14 +17,14 @@
|
||||
//! Various implementations for the `MatchesFungible` trait.
|
||||
|
||||
use frame_support::traits::Get;
|
||||
use sp_runtime::traits::CheckedConversion;
|
||||
use sp_std::marker::PhantomData;
|
||||
use xcm::latest::{
|
||||
AssetId::{Abstract, Concrete},
|
||||
Fungibility::Fungible,
|
||||
AssetInstance,
|
||||
Fungibility::{Fungible, NonFungible},
|
||||
MultiAsset, MultiLocation,
|
||||
};
|
||||
use xcm_executor::traits::MatchesFungible;
|
||||
use xcm_executor::traits::{MatchesFungible, MatchesNonFungible};
|
||||
|
||||
/// Converts a `MultiAsset` into balance `B` if it is a concrete fungible with an id equal to that
|
||||
/// given by `T`'s `Get`.
|
||||
@@ -51,7 +51,15 @@ impl<T: Get<MultiLocation>, B: TryFrom<u128>> MatchesFungible<B> for IsConcrete<
|
||||
fn matches_fungible(a: &MultiAsset) -> Option<B> {
|
||||
match (&a.id, &a.fun) {
|
||||
(Concrete(ref id), Fungible(ref amount)) if id == &T::get() =>
|
||||
CheckedConversion::checked_from(*amount),
|
||||
(*amount).try_into().ok(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T: Get<MultiLocation>, I: TryFrom<AssetInstance>> MatchesNonFungible<I> for IsConcrete<T> {
|
||||
fn matches_nonfungible(a: &MultiAsset) -> Option<I> {
|
||||
match (&a.id, &a.fun) {
|
||||
(Concrete(id), NonFungible(instance)) if id == &T::get() => (*instance).try_into().ok(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -64,24 +72,37 @@ impl<T: Get<MultiLocation>, B: TryFrom<u128>> MatchesFungible<B> for IsConcrete<
|
||||
/// ```
|
||||
/// use xcm::latest::prelude::*;
|
||||
/// use xcm_builder::IsAbstract;
|
||||
/// use xcm_executor::traits::MatchesFungible;
|
||||
/// use xcm_executor::traits::{MatchesFungible, MatchesNonFungible};
|
||||
///
|
||||
/// frame_support::parameter_types! {
|
||||
/// pub TargetLocation: &'static [u8] = &[7u8];
|
||||
/// pub TargetLocation: [u8; 32] = [7u8; 32];
|
||||
/// }
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let asset = (vec![7u8], 999).into();
|
||||
/// // match `asset` if it is a concrete asset in `TargetLocation`.
|
||||
/// let asset = ([7u8; 32], 999u128).into();
|
||||
/// // match `asset` if it is an abstract asset in `TargetLocation`.
|
||||
/// assert_eq!(<IsAbstract<TargetLocation> as MatchesFungible<u128>>::matches_fungible(&asset), Some(999));
|
||||
/// let nft = ([7u8; 32], [42u8; 4]).into();
|
||||
/// assert_eq!(
|
||||
/// <IsAbstract<TargetLocation> as MatchesNonFungible<[u8; 4]>>::matches_nonfungible(&nft),
|
||||
/// Some([42u8; 4])
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub struct IsAbstract<T>(PhantomData<T>);
|
||||
impl<T: Get<&'static [u8]>, B: TryFrom<u128>> MatchesFungible<B> for IsAbstract<T> {
|
||||
impl<T: Get<[u8; 32]>, B: TryFrom<u128>> MatchesFungible<B> for IsAbstract<T> {
|
||||
fn matches_fungible(a: &MultiAsset) -> Option<B> {
|
||||
match (&a.id, &a.fun) {
|
||||
(Abstract(ref id), Fungible(ref amount)) if id == &T::get() =>
|
||||
CheckedConversion::checked_from(*amount),
|
||||
(*amount).try_into().ok(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T: Get<[u8; 32]>, B: TryFrom<AssetInstance>> MatchesNonFungible<B> for IsAbstract<T> {
|
||||
fn matches_nonfungible(a: &MultiAsset) -> Option<B> {
|
||||
match (&a.id, &a.fun) {
|
||||
(Abstract(id), NonFungible(instance)) if id == &T::get() => (*instance).try_into().ok(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -1,296 +0,0 @@
|
||||
// Copyright 2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{barriers::AllowSubscriptionsFrom, test_utils::*};
|
||||
pub use crate::{
|
||||
AllowKnownQueryResponses, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom,
|
||||
FixedRateOfFungible, FixedWeightBounds, LocationInverter, TakeWeightCredit,
|
||||
};
|
||||
pub use frame_support::{
|
||||
dispatch::{
|
||||
DispatchError, DispatchInfo, DispatchResultWithPostInfo, Dispatchable, GetDispatchInfo,
|
||||
Parameter, PostDispatchInfo,
|
||||
},
|
||||
ensure, parameter_types,
|
||||
sp_runtime::DispatchErrorWithPostInfo,
|
||||
traits::{Contains, Get, IsInVec},
|
||||
};
|
||||
pub use parity_scale_codec::{Decode, Encode};
|
||||
pub use sp_std::{
|
||||
cell::RefCell,
|
||||
collections::{btree_map::BTreeMap, btree_set::BTreeSet},
|
||||
fmt::Debug,
|
||||
marker::PhantomData,
|
||||
};
|
||||
pub use xcm::latest::{prelude::*, Weight};
|
||||
pub use xcm_executor::{
|
||||
traits::{ConvertOrigin, FilterAssetLocation, InvertLocation, OnResponse, TransactAsset},
|
||||
Assets, Config,
|
||||
};
|
||||
|
||||
pub enum TestOrigin {
|
||||
Root,
|
||||
Relay,
|
||||
Signed(u64),
|
||||
Parachain(u32),
|
||||
}
|
||||
|
||||
/// A dummy call.
|
||||
///
|
||||
/// Each item contains the amount of weight that it *wants* to consume as the first item, and the actual amount (if
|
||||
/// different from the former) in the second option.
|
||||
#[derive(Debug, Encode, Decode, Eq, PartialEq, Clone, Copy, scale_info::TypeInfo)]
|
||||
pub enum TestCall {
|
||||
OnlyRoot(Weight, Option<Weight>),
|
||||
OnlyParachain(Weight, Option<Weight>, Option<u32>),
|
||||
OnlySigned(Weight, Option<Weight>, Option<u64>),
|
||||
Any(Weight, Option<Weight>),
|
||||
}
|
||||
impl Dispatchable for TestCall {
|
||||
type RuntimeOrigin = TestOrigin;
|
||||
type Config = ();
|
||||
type Info = ();
|
||||
type PostInfo = PostDispatchInfo;
|
||||
fn dispatch(self, origin: Self::RuntimeOrigin) -> DispatchResultWithPostInfo {
|
||||
let mut post_info = PostDispatchInfo::default();
|
||||
let maybe_actual = match self {
|
||||
TestCall::OnlyRoot(_, maybe_actual) |
|
||||
TestCall::OnlySigned(_, maybe_actual, _) |
|
||||
TestCall::OnlyParachain(_, maybe_actual, _) |
|
||||
TestCall::Any(_, maybe_actual) => maybe_actual,
|
||||
};
|
||||
post_info.actual_weight =
|
||||
maybe_actual.map(|x| frame_support::weights::Weight::from_ref_time(x));
|
||||
if match (&origin, &self) {
|
||||
(TestOrigin::Parachain(i), TestCall::OnlyParachain(_, _, Some(j))) => i == j,
|
||||
(TestOrigin::Signed(i), TestCall::OnlySigned(_, _, Some(j))) => i == j,
|
||||
(TestOrigin::Root, TestCall::OnlyRoot(..)) |
|
||||
(TestOrigin::Parachain(_), TestCall::OnlyParachain(_, _, None)) |
|
||||
(TestOrigin::Signed(_), TestCall::OnlySigned(_, _, None)) |
|
||||
(_, TestCall::Any(..)) => true,
|
||||
_ => false,
|
||||
} {
|
||||
Ok(post_info)
|
||||
} else {
|
||||
Err(DispatchErrorWithPostInfo { error: DispatchError::BadOrigin, post_info })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GetDispatchInfo for TestCall {
|
||||
fn get_dispatch_info(&self) -> DispatchInfo {
|
||||
let weight = *match self {
|
||||
TestCall::OnlyRoot(estimate, ..) |
|
||||
TestCall::OnlyParachain(estimate, ..) |
|
||||
TestCall::OnlySigned(estimate, ..) |
|
||||
TestCall::Any(estimate, ..) => estimate,
|
||||
};
|
||||
DispatchInfo {
|
||||
weight: frame_support::weights::Weight::from_ref_time(weight),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
pub static SENT_XCM: RefCell<Vec<(MultiLocation, opaque::Xcm)>> = RefCell::new(Vec::new());
|
||||
}
|
||||
pub fn sent_xcm() -> Vec<(MultiLocation, opaque::Xcm)> {
|
||||
SENT_XCM.with(|q| (*q.borrow()).clone())
|
||||
}
|
||||
pub struct TestSendXcm;
|
||||
impl SendXcm for TestSendXcm {
|
||||
fn send_xcm(dest: impl Into<MultiLocation>, msg: opaque::Xcm) -> SendResult {
|
||||
SENT_XCM.with(|q| q.borrow_mut().push((dest.into(), msg)));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
pub static ASSETS: RefCell<BTreeMap<u64, Assets>> = RefCell::new(BTreeMap::new());
|
||||
}
|
||||
pub fn assets(who: u64) -> Vec<MultiAsset> {
|
||||
ASSETS.with(|a| a.borrow().get(&who).map_or(vec![], |a| a.clone().into()))
|
||||
}
|
||||
pub fn add_asset<AssetArg: Into<MultiAsset>>(who: u64, what: AssetArg) {
|
||||
ASSETS.with(|a| a.borrow_mut().entry(who).or_insert(Assets::new()).subsume(what.into()));
|
||||
}
|
||||
|
||||
pub struct TestAssetTransactor;
|
||||
impl TransactAsset for TestAssetTransactor {
|
||||
fn deposit_asset(what: &MultiAsset, who: &MultiLocation) -> Result<(), XcmError> {
|
||||
let who = to_account(who.clone()).map_err(|_| XcmError::LocationCannotHold)?;
|
||||
add_asset(who, what.clone());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn withdraw_asset(what: &MultiAsset, who: &MultiLocation) -> Result<Assets, XcmError> {
|
||||
let who = to_account(who.clone()).map_err(|_| XcmError::LocationCannotHold)?;
|
||||
ASSETS.with(|a| {
|
||||
a.borrow_mut()
|
||||
.get_mut(&who)
|
||||
.ok_or(XcmError::NotWithdrawable)?
|
||||
.try_take(what.clone().into())
|
||||
.map_err(|_| XcmError::NotWithdrawable)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_account(l: MultiLocation) -> Result<u64, MultiLocation> {
|
||||
Ok(match l {
|
||||
// Siblings at 2000+id
|
||||
MultiLocation { parents: 1, interior: X1(Parachain(id)) } => 2000 + id as u64,
|
||||
// Accounts are their number
|
||||
MultiLocation { parents: 0, interior: X1(AccountIndex64 { index, .. }) } => index,
|
||||
// Children at 1000+id
|
||||
MultiLocation { parents: 0, interior: X1(Parachain(id)) } => 1000 + id as u64,
|
||||
// Self at 3000
|
||||
MultiLocation { parents: 0, interior: Here } => 3000,
|
||||
// Parent at 3001
|
||||
MultiLocation { parents: 1, interior: Here } => 3001,
|
||||
_ => return Err(l),
|
||||
})
|
||||
}
|
||||
|
||||
pub struct TestOriginConverter;
|
||||
impl ConvertOrigin<TestOrigin> for TestOriginConverter {
|
||||
fn convert_origin(
|
||||
origin: impl Into<MultiLocation>,
|
||||
kind: OriginKind,
|
||||
) -> Result<TestOrigin, MultiLocation> {
|
||||
use OriginKind::*;
|
||||
match (kind, origin.into()) {
|
||||
(Superuser, _) => Ok(TestOrigin::Root),
|
||||
(SovereignAccount, l) => Ok(TestOrigin::Signed(to_account(l)?)),
|
||||
(Native, MultiLocation { parents: 0, interior: X1(Parachain(id)) }) =>
|
||||
Ok(TestOrigin::Parachain(id)),
|
||||
(Native, MultiLocation { parents: 1, interior: Here }) => Ok(TestOrigin::Relay),
|
||||
(Native, MultiLocation { parents: 0, interior: X1(AccountIndex64 { index, .. }) }) =>
|
||||
Ok(TestOrigin::Signed(index)),
|
||||
(_, origin) => Err(origin),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
pub static IS_RESERVE: RefCell<BTreeMap<MultiLocation, Vec<MultiAssetFilter>>> = RefCell::new(BTreeMap::new());
|
||||
pub static IS_TELEPORTER: RefCell<BTreeMap<MultiLocation, Vec<MultiAssetFilter>>> = RefCell::new(BTreeMap::new());
|
||||
}
|
||||
pub fn add_reserve(from: MultiLocation, asset: MultiAssetFilter) {
|
||||
IS_RESERVE.with(|r| r.borrow_mut().entry(from).or_default().push(asset));
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn add_teleporter(from: MultiLocation, asset: MultiAssetFilter) {
|
||||
IS_TELEPORTER.with(|r| r.borrow_mut().entry(from).or_default().push(asset));
|
||||
}
|
||||
pub struct TestIsReserve;
|
||||
impl FilterAssetLocation for TestIsReserve {
|
||||
fn filter_asset_location(asset: &MultiAsset, origin: &MultiLocation) -> bool {
|
||||
IS_RESERVE
|
||||
.with(|r| r.borrow().get(origin).map_or(false, |v| v.iter().any(|a| a.contains(asset))))
|
||||
}
|
||||
}
|
||||
pub struct TestIsTeleporter;
|
||||
impl FilterAssetLocation for TestIsTeleporter {
|
||||
fn filter_asset_location(asset: &MultiAsset, origin: &MultiLocation) -> bool {
|
||||
IS_TELEPORTER
|
||||
.with(|r| r.borrow().get(origin).map_or(false, |v| v.iter().any(|a| a.contains(asset))))
|
||||
}
|
||||
}
|
||||
|
||||
use xcm::latest::Response;
|
||||
pub enum ResponseSlot {
|
||||
Expecting(MultiLocation),
|
||||
Received(Response),
|
||||
}
|
||||
thread_local! {
|
||||
pub static QUERIES: RefCell<BTreeMap<u64, ResponseSlot>> = RefCell::new(BTreeMap::new());
|
||||
}
|
||||
pub struct TestResponseHandler;
|
||||
impl OnResponse for TestResponseHandler {
|
||||
fn expecting_response(origin: &MultiLocation, query_id: u64) -> bool {
|
||||
QUERIES.with(|q| match q.borrow().get(&query_id) {
|
||||
Some(ResponseSlot::Expecting(ref l)) => l == origin,
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
fn on_response(
|
||||
_origin: &MultiLocation,
|
||||
query_id: u64,
|
||||
response: xcm::latest::Response,
|
||||
_max_weight: Weight,
|
||||
) -> Weight {
|
||||
QUERIES.with(|q| {
|
||||
q.borrow_mut().entry(query_id).and_modify(|v| {
|
||||
if matches!(*v, ResponseSlot::Expecting(..)) {
|
||||
*v = ResponseSlot::Received(response);
|
||||
}
|
||||
});
|
||||
});
|
||||
10
|
||||
}
|
||||
}
|
||||
pub fn expect_response(query_id: u64, from: MultiLocation) {
|
||||
QUERIES.with(|q| q.borrow_mut().insert(query_id, ResponseSlot::Expecting(from)));
|
||||
}
|
||||
pub fn response(query_id: u64) -> Option<Response> {
|
||||
QUERIES.with(|q| {
|
||||
q.borrow().get(&query_id).and_then(|v| match v {
|
||||
ResponseSlot::Received(r) => Some(r.clone()),
|
||||
_ => None,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub TestAncestry: MultiLocation = X1(Parachain(42)).into();
|
||||
pub UnitWeightCost: u64 = 10;
|
||||
}
|
||||
parameter_types! {
|
||||
// Nothing is allowed to be paid/unpaid by default.
|
||||
pub static AllowUnpaidFrom: Vec<MultiLocation> = vec![];
|
||||
pub static AllowPaidFrom: Vec<MultiLocation> = vec![];
|
||||
pub static AllowSubsFrom: Vec<MultiLocation> = vec![];
|
||||
// 1_000_000_000_000 => 1 unit of asset for 1 unit of Weight.
|
||||
pub static WeightPrice: (AssetId, u128) = (From::from(Here), 1_000_000_000_000);
|
||||
pub static MaxInstructions: u32 = 100;
|
||||
}
|
||||
|
||||
pub type TestBarrier = (
|
||||
TakeWeightCredit,
|
||||
AllowKnownQueryResponses<TestResponseHandler>,
|
||||
AllowTopLevelPaidExecutionFrom<IsInVec<AllowPaidFrom>>,
|
||||
AllowUnpaidExecutionFrom<IsInVec<AllowUnpaidFrom>>,
|
||||
AllowSubscriptionsFrom<IsInVec<AllowSubsFrom>>,
|
||||
);
|
||||
|
||||
pub struct TestConfig;
|
||||
impl Config for TestConfig {
|
||||
type RuntimeCall = TestCall;
|
||||
type XcmSender = TestSendXcm;
|
||||
type AssetTransactor = TestAssetTransactor;
|
||||
type OriginConverter = TestOriginConverter;
|
||||
type IsReserve = TestIsReserve;
|
||||
type IsTeleporter = TestIsTeleporter;
|
||||
type LocationInverter = LocationInverter<TestAncestry>;
|
||||
type Barrier = TestBarrier;
|
||||
type Weigher = FixedWeightBounds<UnitWeightCost, TestCall, MaxInstructions>;
|
||||
type Trader = FixedRateOfFungible<WeightPrice, ()>;
|
||||
type ResponseHandler = TestResponseHandler;
|
||||
type AssetTrap = TestAssetTrap;
|
||||
type AssetClaims = TestAssetTrap;
|
||||
type SubscriptionService = TestSubscriptionService;
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
// Copyright 2022 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Adapters to work with `frame_support::traits::tokens::fungibles` through XCM.
|
||||
|
||||
use crate::{AssetChecking, MintLocation};
|
||||
use frame_support::{
|
||||
ensure,
|
||||
traits::{tokens::nonfungibles, Get},
|
||||
};
|
||||
use sp_std::{marker::PhantomData, prelude::*, result};
|
||||
use xcm::latest::prelude::*;
|
||||
use xcm_executor::traits::{Convert, Error as MatchError, MatchesNonFungibles, TransactAsset};
|
||||
|
||||
pub struct NonFungiblesTransferAdapter<Assets, Matcher, AccountIdConverter, AccountId>(
|
||||
PhantomData<(Assets, Matcher, AccountIdConverter, AccountId)>,
|
||||
);
|
||||
impl<
|
||||
Assets: nonfungibles::Transfer<AccountId>,
|
||||
Matcher: MatchesNonFungibles<Assets::CollectionId, Assets::ItemId>,
|
||||
AccountIdConverter: Convert<MultiLocation, AccountId>,
|
||||
AccountId: Clone, // can't get away without it since Currency is generic over it.
|
||||
> TransactAsset for NonFungiblesTransferAdapter<Assets, Matcher, AccountIdConverter, AccountId>
|
||||
{
|
||||
fn transfer_asset(
|
||||
what: &MultiAsset,
|
||||
from: &MultiLocation,
|
||||
to: &MultiLocation,
|
||||
context: &XcmContext,
|
||||
) -> result::Result<xcm_executor::Assets, XcmError> {
|
||||
log::trace!(
|
||||
target: "xcm::non_fungibles_adapter",
|
||||
"transfer_asset what: {:?}, from: {:?}, to: {:?}, context: {:?}",
|
||||
what, from, to, context,
|
||||
);
|
||||
// Check we handle this asset.
|
||||
let (class, instance) = Matcher::matches_nonfungibles(what)?;
|
||||
let destination = AccountIdConverter::convert_ref(to)
|
||||
.map_err(|()| MatchError::AccountIdConversionFailed)?;
|
||||
Assets::transfer(&class, &instance, &destination)
|
||||
.map_err(|e| XcmError::FailedToTransactAsset(e.into()))?;
|
||||
Ok(what.clone().into())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NonFungiblesMutateAdapter<
|
||||
Assets,
|
||||
Matcher,
|
||||
AccountIdConverter,
|
||||
AccountId,
|
||||
CheckAsset,
|
||||
CheckingAccount,
|
||||
>(PhantomData<(Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount)>);
|
||||
|
||||
impl<
|
||||
Assets: nonfungibles::Mutate<AccountId>,
|
||||
Matcher: MatchesNonFungibles<Assets::CollectionId, Assets::ItemId>,
|
||||
AccountIdConverter: Convert<MultiLocation, AccountId>,
|
||||
AccountId: Clone + Eq, // can't get away without it since Currency is generic over it.
|
||||
CheckAsset: AssetChecking<Assets::CollectionId>,
|
||||
CheckingAccount: Get<Option<AccountId>>,
|
||||
>
|
||||
NonFungiblesMutateAdapter<
|
||||
Assets,
|
||||
Matcher,
|
||||
AccountIdConverter,
|
||||
AccountId,
|
||||
CheckAsset,
|
||||
CheckingAccount,
|
||||
>
|
||||
{
|
||||
fn can_accrue_checked(class: Assets::CollectionId, instance: Assets::ItemId) -> XcmResult {
|
||||
ensure!(Assets::owner(&class, &instance).is_none(), XcmError::NotDepositable);
|
||||
Ok(())
|
||||
}
|
||||
fn can_reduce_checked(class: Assets::CollectionId, instance: Assets::ItemId) -> XcmResult {
|
||||
if let Some(checking_account) = CheckingAccount::get() {
|
||||
// This is an asset whose teleports we track.
|
||||
let owner = Assets::owner(&class, &instance);
|
||||
ensure!(owner == Some(checking_account), XcmError::NotWithdrawable);
|
||||
ensure!(Assets::can_transfer(&class, &instance), XcmError::NotWithdrawable);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn accrue_checked(class: Assets::CollectionId, instance: Assets::ItemId) {
|
||||
if let Some(checking_account) = CheckingAccount::get() {
|
||||
let ok = Assets::mint_into(&class, &instance, &checking_account).is_ok();
|
||||
debug_assert!(ok, "`mint_into` cannot generally fail; qed");
|
||||
}
|
||||
}
|
||||
fn reduce_checked(class: Assets::CollectionId, instance: Assets::ItemId) {
|
||||
let ok = Assets::burn(&class, &instance, None).is_ok();
|
||||
debug_assert!(ok, "`can_check_in` must have returned `true` immediately prior; qed");
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
Assets: nonfungibles::Mutate<AccountId>,
|
||||
Matcher: MatchesNonFungibles<Assets::CollectionId, Assets::ItemId>,
|
||||
AccountIdConverter: Convert<MultiLocation, AccountId>,
|
||||
AccountId: Clone + Eq, // can't get away without it since Currency is generic over it.
|
||||
CheckAsset: AssetChecking<Assets::CollectionId>,
|
||||
CheckingAccount: Get<Option<AccountId>>,
|
||||
> TransactAsset
|
||||
for NonFungiblesMutateAdapter<
|
||||
Assets,
|
||||
Matcher,
|
||||
AccountIdConverter,
|
||||
AccountId,
|
||||
CheckAsset,
|
||||
CheckingAccount,
|
||||
>
|
||||
{
|
||||
fn can_check_in(_origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult {
|
||||
log::trace!(
|
||||
target: "xcm::fungibles_adapter",
|
||||
"can_check_in origin: {:?}, what: {:?}, context: {:?}",
|
||||
_origin, what, context,
|
||||
);
|
||||
// Check we handle this asset.
|
||||
let (class, instance) = Matcher::matches_nonfungibles(what)?;
|
||||
match CheckAsset::asset_checking(&class) {
|
||||
// We track this asset's teleports to ensure no more come in than have gone out.
|
||||
Some(MintLocation::Local) => Self::can_reduce_checked(class, instance),
|
||||
// We track this asset's teleports to ensure no more go out than have come in.
|
||||
Some(MintLocation::NonLocal) => Self::can_accrue_checked(class, instance),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_in(_origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) {
|
||||
log::trace!(
|
||||
target: "xcm::fungibles_adapter",
|
||||
"check_in origin: {:?}, what: {:?}, context: {:?}",
|
||||
_origin, what, context,
|
||||
);
|
||||
if let Ok((class, instance)) = Matcher::matches_nonfungibles(what) {
|
||||
match CheckAsset::asset_checking(&class) {
|
||||
// We track this asset's teleports to ensure no more come in than have gone out.
|
||||
Some(MintLocation::Local) => Self::reduce_checked(class, instance),
|
||||
// We track this asset's teleports to ensure no more go out than have come in.
|
||||
Some(MintLocation::NonLocal) => Self::accrue_checked(class, instance),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn can_check_out(_dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult {
|
||||
log::trace!(
|
||||
target: "xcm::fungibles_adapter",
|
||||
"can_check_out dest: {:?}, what: {:?}, context: {:?}",
|
||||
_dest, what, context,
|
||||
);
|
||||
// Check we handle this asset.
|
||||
let (class, instance) = Matcher::matches_nonfungibles(what)?;
|
||||
match CheckAsset::asset_checking(&class) {
|
||||
// We track this asset's teleports to ensure no more come in than have gone out.
|
||||
Some(MintLocation::Local) => Self::can_accrue_checked(class, instance),
|
||||
// We track this asset's teleports to ensure no more go out than have come in.
|
||||
Some(MintLocation::NonLocal) => Self::can_reduce_checked(class, instance),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_out(_dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) {
|
||||
log::trace!(
|
||||
target: "xcm::fungibles_adapter",
|
||||
"check_out dest: {:?}, what: {:?}, context: {:?}",
|
||||
_dest, what, context,
|
||||
);
|
||||
if let Ok((class, instance)) = Matcher::matches_nonfungibles(what) {
|
||||
match CheckAsset::asset_checking(&class) {
|
||||
// We track this asset's teleports to ensure no more come in than have gone out.
|
||||
Some(MintLocation::Local) => Self::accrue_checked(class, instance),
|
||||
// We track this asset's teleports to ensure no more go out than have come in.
|
||||
Some(MintLocation::NonLocal) => Self::reduce_checked(class, instance),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn deposit_asset(what: &MultiAsset, who: &MultiLocation, context: &XcmContext) -> XcmResult {
|
||||
log::trace!(
|
||||
target: "xcm::fungibles_adapter",
|
||||
"deposit_asset what: {:?}, who: {:?}, context: {:?}",
|
||||
what, who, context,
|
||||
);
|
||||
// Check we handle this asset.
|
||||
let (class, instance) = Matcher::matches_nonfungibles(what)?;
|
||||
let who = AccountIdConverter::convert_ref(who)
|
||||
.map_err(|()| MatchError::AccountIdConversionFailed)?;
|
||||
Assets::mint_into(&class, &instance, &who)
|
||||
.map_err(|e| XcmError::FailedToTransactAsset(e.into()))
|
||||
}
|
||||
|
||||
fn withdraw_asset(
|
||||
what: &MultiAsset,
|
||||
who: &MultiLocation,
|
||||
maybe_context: Option<&XcmContext>,
|
||||
) -> result::Result<xcm_executor::Assets, XcmError> {
|
||||
log::trace!(
|
||||
target: "xcm::fungibles_adapter",
|
||||
"withdraw_asset what: {:?}, who: {:?}, maybe_context: {:?}",
|
||||
what, who, maybe_context,
|
||||
);
|
||||
// Check we handle this asset.
|
||||
let who = AccountIdConverter::convert_ref(who)
|
||||
.map_err(|()| MatchError::AccountIdConversionFailed)?;
|
||||
let (class, instance) = Matcher::matches_nonfungibles(what)?;
|
||||
Assets::burn(&class, &instance, Some(&who))
|
||||
.map_err(|e| XcmError::FailedToTransactAsset(e.into()))?;
|
||||
Ok(what.clone().into())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NonFungiblesAdapter<
|
||||
Assets,
|
||||
Matcher,
|
||||
AccountIdConverter,
|
||||
AccountId,
|
||||
CheckAsset,
|
||||
CheckingAccount,
|
||||
>(PhantomData<(Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount)>);
|
||||
impl<
|
||||
Assets: nonfungibles::Mutate<AccountId> + nonfungibles::Transfer<AccountId>,
|
||||
Matcher: MatchesNonFungibles<Assets::CollectionId, Assets::ItemId>,
|
||||
AccountIdConverter: Convert<MultiLocation, AccountId>,
|
||||
AccountId: Clone + Eq, // can't get away without it since Currency is generic over it.
|
||||
CheckAsset: AssetChecking<Assets::CollectionId>,
|
||||
CheckingAccount: Get<Option<AccountId>>,
|
||||
> TransactAsset
|
||||
for NonFungiblesAdapter<Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount>
|
||||
{
|
||||
fn can_check_in(origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult {
|
||||
NonFungiblesMutateAdapter::<
|
||||
Assets,
|
||||
Matcher,
|
||||
AccountIdConverter,
|
||||
AccountId,
|
||||
CheckAsset,
|
||||
CheckingAccount,
|
||||
>::can_check_in(origin, what, context)
|
||||
}
|
||||
|
||||
fn check_in(origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) {
|
||||
NonFungiblesMutateAdapter::<
|
||||
Assets,
|
||||
Matcher,
|
||||
AccountIdConverter,
|
||||
AccountId,
|
||||
CheckAsset,
|
||||
CheckingAccount,
|
||||
>::check_in(origin, what, context)
|
||||
}
|
||||
|
||||
fn can_check_out(dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult {
|
||||
NonFungiblesMutateAdapter::<
|
||||
Assets,
|
||||
Matcher,
|
||||
AccountIdConverter,
|
||||
AccountId,
|
||||
CheckAsset,
|
||||
CheckingAccount,
|
||||
>::can_check_out(dest, what, context)
|
||||
}
|
||||
|
||||
fn check_out(dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) {
|
||||
NonFungiblesMutateAdapter::<
|
||||
Assets,
|
||||
Matcher,
|
||||
AccountIdConverter,
|
||||
AccountId,
|
||||
CheckAsset,
|
||||
CheckingAccount,
|
||||
>::check_out(dest, what, context)
|
||||
}
|
||||
|
||||
fn deposit_asset(what: &MultiAsset, who: &MultiLocation, context: &XcmContext) -> XcmResult {
|
||||
NonFungiblesMutateAdapter::<
|
||||
Assets,
|
||||
Matcher,
|
||||
AccountIdConverter,
|
||||
AccountId,
|
||||
CheckAsset,
|
||||
CheckingAccount,
|
||||
>::deposit_asset(what, who, context)
|
||||
}
|
||||
|
||||
fn withdraw_asset(
|
||||
what: &MultiAsset,
|
||||
who: &MultiLocation,
|
||||
maybe_context: Option<&XcmContext>,
|
||||
) -> result::Result<xcm_executor::Assets, XcmError> {
|
||||
NonFungiblesMutateAdapter::<
|
||||
Assets,
|
||||
Matcher,
|
||||
AccountIdConverter,
|
||||
AccountId,
|
||||
CheckAsset,
|
||||
CheckingAccount,
|
||||
>::withdraw_asset(what, who, maybe_context)
|
||||
}
|
||||
|
||||
fn transfer_asset(
|
||||
what: &MultiAsset,
|
||||
from: &MultiLocation,
|
||||
to: &MultiLocation,
|
||||
context: &XcmContext,
|
||||
) -> result::Result<xcm_executor::Assets, XcmError> {
|
||||
NonFungiblesTransferAdapter::<Assets, Matcher, AccountIdConverter, AccountId>::transfer_asset(
|
||||
what, from, to, context,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -187,7 +187,7 @@ impl<RelayOrigin: Get<RuntimeOrigin>, RuntimeOrigin> ConvertOrigin<RuntimeOrigin
|
||||
}
|
||||
|
||||
pub struct SignedAccountId32AsNative<Network, RuntimeOrigin>(PhantomData<(Network, RuntimeOrigin)>);
|
||||
impl<Network: Get<NetworkId>, RuntimeOrigin: OriginTrait> ConvertOrigin<RuntimeOrigin>
|
||||
impl<Network: Get<Option<NetworkId>>, RuntimeOrigin: OriginTrait> ConvertOrigin<RuntimeOrigin>
|
||||
for SignedAccountId32AsNative<Network, RuntimeOrigin>
|
||||
where
|
||||
RuntimeOrigin::AccountId: From<[u8; 32]>,
|
||||
@@ -206,7 +206,7 @@ where
|
||||
(
|
||||
OriginKind::Native,
|
||||
MultiLocation { parents: 0, interior: X1(Junction::AccountId32 { id, network }) },
|
||||
) if matches!(network, NetworkId::Any) || network == Network::get() =>
|
||||
) if matches!(network, None) || network == Network::get() =>
|
||||
Ok(RuntimeOrigin::signed(id.into())),
|
||||
(_, origin) => Err(origin),
|
||||
}
|
||||
@@ -216,7 +216,7 @@ where
|
||||
pub struct SignedAccountKey20AsNative<Network, RuntimeOrigin>(
|
||||
PhantomData<(Network, RuntimeOrigin)>,
|
||||
);
|
||||
impl<Network: Get<NetworkId>, RuntimeOrigin: OriginTrait> ConvertOrigin<RuntimeOrigin>
|
||||
impl<Network: Get<Option<NetworkId>>, RuntimeOrigin: OriginTrait> ConvertOrigin<RuntimeOrigin>
|
||||
for SignedAccountKey20AsNative<Network, RuntimeOrigin>
|
||||
where
|
||||
RuntimeOrigin::AccountId: From<[u8; 20]>,
|
||||
@@ -235,7 +235,7 @@ where
|
||||
(
|
||||
OriginKind::Native,
|
||||
MultiLocation { parents: 0, interior: X1(Junction::AccountKey20 { key, network }) },
|
||||
) if (matches!(network, NetworkId::Any) || network == Network::get()) =>
|
||||
) if (matches!(network, None) || network == Network::get()) =>
|
||||
Ok(RuntimeOrigin::signed(key.into())),
|
||||
(_, origin) => Err(origin),
|
||||
}
|
||||
@@ -277,8 +277,11 @@ where
|
||||
pub struct SignedToAccountId32<RuntimeOrigin, AccountId, Network>(
|
||||
PhantomData<(RuntimeOrigin, AccountId, Network)>,
|
||||
);
|
||||
impl<RuntimeOrigin: OriginTrait + Clone, AccountId: Into<[u8; 32]>, Network: Get<NetworkId>>
|
||||
Convert<RuntimeOrigin, MultiLocation> for SignedToAccountId32<RuntimeOrigin, AccountId, Network>
|
||||
impl<
|
||||
RuntimeOrigin: OriginTrait + Clone,
|
||||
AccountId: Into<[u8; 32]>,
|
||||
Network: Get<Option<NetworkId>>,
|
||||
> Convert<RuntimeOrigin, MultiLocation> for SignedToAccountId32<RuntimeOrigin, AccountId, Network>
|
||||
where
|
||||
RuntimeOrigin::PalletsOrigin: From<SystemRawOrigin<AccountId>>
|
||||
+ TryInto<SystemRawOrigin<AccountId>, Error = RuntimeOrigin::PalletsOrigin>,
|
||||
|
||||
@@ -16,32 +16,43 @@
|
||||
|
||||
// Shared test utilities and implementations for the XCM Builder.
|
||||
|
||||
use frame_support::parameter_types;
|
||||
use frame_support::{
|
||||
parameter_types,
|
||||
traits::{Contains, CrateVersion, PalletInfoData, PalletsInfoAccess},
|
||||
};
|
||||
use sp_std::vec::Vec;
|
||||
pub use xcm::latest::{prelude::*, Weight};
|
||||
use xcm_executor::traits::{ClaimAssets, DropAssets, VersionChangeNotifier};
|
||||
pub use xcm_executor::{
|
||||
traits::{ConvertOrigin, FilterAssetLocation, InvertLocation, OnResponse, TransactAsset},
|
||||
traits::{
|
||||
AssetExchange, AssetLock, ConvertOrigin, Enact, LockError, OnResponse, TransactAsset,
|
||||
},
|
||||
Assets, Config,
|
||||
};
|
||||
|
||||
parameter_types! {
|
||||
pub static SubscriptionRequests: Vec<(MultiLocation, Option<(QueryId, u64)>)> = vec![];
|
||||
pub static SubscriptionRequests: Vec<(MultiLocation, Option<(QueryId, Weight)>)> = vec![];
|
||||
pub static MaxAssetsIntoHolding: u32 = 4;
|
||||
}
|
||||
|
||||
pub struct TestSubscriptionService;
|
||||
|
||||
impl VersionChangeNotifier for TestSubscriptionService {
|
||||
fn start(location: &MultiLocation, query_id: QueryId, max_weight: u64) -> XcmResult {
|
||||
fn start(
|
||||
location: &MultiLocation,
|
||||
query_id: QueryId,
|
||||
max_weight: Weight,
|
||||
_context: &XcmContext,
|
||||
) -> XcmResult {
|
||||
let mut r = SubscriptionRequests::get();
|
||||
r.push((location.clone(), Some((query_id, max_weight))));
|
||||
r.push((*location, Some((query_id, max_weight))));
|
||||
SubscriptionRequests::set(r);
|
||||
Ok(())
|
||||
}
|
||||
fn stop(location: &MultiLocation) -> XcmResult {
|
||||
fn stop(location: &MultiLocation, _context: &XcmContext) -> XcmResult {
|
||||
let mut r = SubscriptionRequests::get();
|
||||
r.retain(|(l, _q)| l != location);
|
||||
r.push((location.clone(), None));
|
||||
r.push((*location, None));
|
||||
SubscriptionRequests::set(r);
|
||||
Ok(())
|
||||
}
|
||||
@@ -58,16 +69,21 @@ parameter_types! {
|
||||
pub struct TestAssetTrap;
|
||||
|
||||
impl DropAssets for TestAssetTrap {
|
||||
fn drop_assets(origin: &MultiLocation, assets: Assets) -> Weight {
|
||||
fn drop_assets(origin: &MultiLocation, assets: Assets, _context: &XcmContext) -> Weight {
|
||||
let mut t: Vec<(MultiLocation, MultiAssets)> = TrappedAssets::get();
|
||||
t.push((origin.clone(), assets.into()));
|
||||
t.push((*origin, assets.into()));
|
||||
TrappedAssets::set(t);
|
||||
5
|
||||
Weight::from_parts(5, 5)
|
||||
}
|
||||
}
|
||||
|
||||
impl ClaimAssets for TestAssetTrap {
|
||||
fn claim_assets(origin: &MultiLocation, ticket: &MultiLocation, what: &MultiAssets) -> bool {
|
||||
fn claim_assets(
|
||||
origin: &MultiLocation,
|
||||
ticket: &MultiLocation,
|
||||
what: &MultiAssets,
|
||||
_context: &XcmContext,
|
||||
) -> bool {
|
||||
let mut t: Vec<(MultiLocation, MultiAssets)> = TrappedAssets::get();
|
||||
if let (0, X1(GeneralIndex(i))) = (ticket.parents, &ticket.interior) {
|
||||
if let Some((l, a)) = t.get(*i as usize) {
|
||||
@@ -81,3 +97,120 @@ impl ClaimAssets for TestAssetTrap {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestAssetExchanger;
|
||||
|
||||
impl AssetExchange for TestAssetExchanger {
|
||||
fn exchange_asset(
|
||||
_origin: Option<&MultiLocation>,
|
||||
_give: Assets,
|
||||
want: &MultiAssets,
|
||||
_maximal: bool,
|
||||
) -> Result<Assets, Assets> {
|
||||
Ok(want.clone().into())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestPalletsInfo;
|
||||
impl PalletsInfoAccess for TestPalletsInfo {
|
||||
fn count() -> usize {
|
||||
2
|
||||
}
|
||||
fn infos() -> Vec<PalletInfoData> {
|
||||
vec![
|
||||
PalletInfoData {
|
||||
index: 0,
|
||||
name: "System",
|
||||
module_name: "pallet_system",
|
||||
crate_version: CrateVersion { major: 1, minor: 10, patch: 1 },
|
||||
},
|
||||
PalletInfoData {
|
||||
index: 1,
|
||||
name: "Balances",
|
||||
module_name: "pallet_balances",
|
||||
crate_version: CrateVersion { major: 1, minor: 42, patch: 69 },
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestUniversalAliases;
|
||||
impl Contains<(MultiLocation, Junction)> for TestUniversalAliases {
|
||||
fn contains(aliases: &(MultiLocation, Junction)) -> bool {
|
||||
&aliases.0 == &Here.into_location() && &aliases.1 == &GlobalConsensus(ByGenesis([0; 32]))
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static LockedAssets: Vec<(MultiLocation, MultiAsset)> = vec![];
|
||||
}
|
||||
|
||||
pub struct TestLockTicket(MultiLocation, MultiAsset);
|
||||
impl Enact for TestLockTicket {
|
||||
fn enact(self) -> Result<(), LockError> {
|
||||
let mut locked_assets = LockedAssets::get();
|
||||
locked_assets.push((self.0, self.1));
|
||||
LockedAssets::set(locked_assets);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
pub struct TestUnlockTicket(MultiLocation, MultiAsset);
|
||||
impl Enact for TestUnlockTicket {
|
||||
fn enact(self) -> Result<(), LockError> {
|
||||
let mut locked_assets = LockedAssets::get();
|
||||
if let Some((idx, _)) = locked_assets
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, (origin, asset))| origin == &self.0 && asset == &self.1)
|
||||
{
|
||||
locked_assets.remove(idx);
|
||||
}
|
||||
LockedAssets::set(locked_assets);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
pub struct TestReduceTicket;
|
||||
impl Enact for TestReduceTicket {
|
||||
fn enact(self) -> Result<(), LockError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestAssetLocker;
|
||||
impl AssetLock for TestAssetLocker {
|
||||
type LockTicket = TestLockTicket;
|
||||
type UnlockTicket = TestUnlockTicket;
|
||||
type ReduceTicket = TestReduceTicket;
|
||||
|
||||
fn prepare_lock(
|
||||
unlocker: MultiLocation,
|
||||
asset: MultiAsset,
|
||||
_owner: MultiLocation,
|
||||
) -> Result<TestLockTicket, LockError> {
|
||||
Ok(TestLockTicket(unlocker, asset))
|
||||
}
|
||||
|
||||
fn prepare_unlock(
|
||||
unlocker: MultiLocation,
|
||||
asset: MultiAsset,
|
||||
_owner: MultiLocation,
|
||||
) -> Result<TestUnlockTicket, LockError> {
|
||||
Ok(TestUnlockTicket(unlocker, asset))
|
||||
}
|
||||
|
||||
fn note_unlockable(
|
||||
_locker: MultiLocation,
|
||||
_asset: MultiAsset,
|
||||
_owner: MultiLocation,
|
||||
) -> Result<(), LockError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn prepare_reduce_unlockable(
|
||||
_locker: MultiLocation,
|
||||
_asset: MultiAsset,
|
||||
_owner: MultiLocation,
|
||||
) -> Result<TestReduceTicket, LockError> {
|
||||
Ok(TestReduceTicket)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,716 +0,0 @@
|
||||
// Copyright 2020 Parity Technologies query_id: (), max_response_weight: () query_id: (), max_response_weight: () (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::{mock::*, test_utils::*, *};
|
||||
use frame_support::{assert_err, weights::constants::WEIGHT_REF_TIME_PER_SECOND};
|
||||
use xcm::latest::prelude::*;
|
||||
use xcm_executor::{traits::*, Config, XcmExecutor};
|
||||
|
||||
#[test]
|
||||
fn basic_setup_works() {
|
||||
add_reserve(Parent.into(), Wild((Parent, WildFungible).into()));
|
||||
assert!(<TestConfig as Config>::IsReserve::filter_asset_location(
|
||||
&(Parent, 100).into(),
|
||||
&Parent.into(),
|
||||
));
|
||||
|
||||
assert_eq!(to_account(X1(Parachain(1)).into()), Ok(1001));
|
||||
assert_eq!(to_account(X1(Parachain(50)).into()), Ok(1050));
|
||||
assert_eq!(to_account(MultiLocation::new(1, X1(Parachain(1)))), Ok(2001));
|
||||
assert_eq!(to_account(MultiLocation::new(1, X1(Parachain(50)))), Ok(2050));
|
||||
assert_eq!(
|
||||
to_account(MultiLocation::new(0, X1(AccountIndex64 { index: 1, network: Any }))),
|
||||
Ok(1),
|
||||
);
|
||||
assert_eq!(
|
||||
to_account(MultiLocation::new(0, X1(AccountIndex64 { index: 42, network: Any }))),
|
||||
Ok(42),
|
||||
);
|
||||
assert_eq!(to_account(Here.into()), Ok(3000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn weigher_should_work() {
|
||||
let mut message = Xcm(vec![
|
||||
ReserveAssetDeposited((Parent, 100).into()),
|
||||
BuyExecution { fees: (Parent, 1).into(), weight_limit: Limited(30) },
|
||||
DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here.into() },
|
||||
]);
|
||||
assert_eq!(<TestConfig as Config>::Weigher::weight(&mut message), Ok(30));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn take_weight_credit_barrier_should_work() {
|
||||
let mut message =
|
||||
Xcm::<()>(vec![TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }]);
|
||||
let mut weight_credit = 10;
|
||||
let r = TakeWeightCredit::should_execute(&Parent.into(), &mut message, 10, &mut weight_credit);
|
||||
assert_eq!(r, Ok(()));
|
||||
assert_eq!(weight_credit, 0);
|
||||
|
||||
let r = TakeWeightCredit::should_execute(&Parent.into(), &mut message, 10, &mut weight_credit);
|
||||
assert_eq!(r, Err(()));
|
||||
assert_eq!(weight_credit, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn allow_unpaid_should_work() {
|
||||
let mut message =
|
||||
Xcm::<()>(vec![TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }]);
|
||||
|
||||
AllowUnpaidFrom::set(vec![Parent.into()]);
|
||||
|
||||
let r = AllowUnpaidExecutionFrom::<IsInVec<AllowUnpaidFrom>>::should_execute(
|
||||
&Parachain(1).into(),
|
||||
&mut message,
|
||||
10,
|
||||
&mut 0,
|
||||
);
|
||||
assert_eq!(r, Err(()));
|
||||
|
||||
let r = AllowUnpaidExecutionFrom::<IsInVec<AllowUnpaidFrom>>::should_execute(
|
||||
&Parent.into(),
|
||||
&mut message,
|
||||
10,
|
||||
&mut 0,
|
||||
);
|
||||
assert_eq!(r, Ok(()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn allow_paid_should_work() {
|
||||
AllowPaidFrom::set(vec![Parent.into()]);
|
||||
|
||||
let mut message =
|
||||
Xcm::<()>(vec![TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }]);
|
||||
|
||||
let r = AllowTopLevelPaidExecutionFrom::<IsInVec<AllowPaidFrom>>::should_execute(
|
||||
&Parachain(1).into(),
|
||||
&mut message,
|
||||
10,
|
||||
&mut 0,
|
||||
);
|
||||
assert_eq!(r, Err(()));
|
||||
|
||||
let fees = (Parent, 1).into();
|
||||
let mut underpaying_message = Xcm::<()>(vec![
|
||||
ReserveAssetDeposited((Parent, 100).into()),
|
||||
BuyExecution { fees, weight_limit: Limited(20) },
|
||||
DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here.into() },
|
||||
]);
|
||||
|
||||
let r = AllowTopLevelPaidExecutionFrom::<IsInVec<AllowPaidFrom>>::should_execute(
|
||||
&Parent.into(),
|
||||
&mut underpaying_message,
|
||||
30,
|
||||
&mut 0,
|
||||
);
|
||||
assert_eq!(r, Err(()));
|
||||
|
||||
let fees = (Parent, 1).into();
|
||||
let mut paying_message = Xcm::<()>(vec![
|
||||
ReserveAssetDeposited((Parent, 100).into()),
|
||||
BuyExecution { fees, weight_limit: Limited(30) },
|
||||
DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here.into() },
|
||||
]);
|
||||
|
||||
let r = AllowTopLevelPaidExecutionFrom::<IsInVec<AllowPaidFrom>>::should_execute(
|
||||
&Parachain(1).into(),
|
||||
&mut paying_message,
|
||||
30,
|
||||
&mut 0,
|
||||
);
|
||||
assert_eq!(r, Err(()));
|
||||
|
||||
let r = AllowTopLevelPaidExecutionFrom::<IsInVec<AllowPaidFrom>>::should_execute(
|
||||
&Parent.into(),
|
||||
&mut paying_message,
|
||||
30,
|
||||
&mut 0,
|
||||
);
|
||||
assert_eq!(r, Ok(()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn paying_reserve_deposit_should_work() {
|
||||
AllowPaidFrom::set(vec![Parent.into()]);
|
||||
add_reserve(Parent.into(), (Parent, WildFungible).into());
|
||||
WeightPrice::set((Parent.into(), 1_000_000_000_000));
|
||||
|
||||
let fees = (Parent, 30).into();
|
||||
let message = Xcm(vec![
|
||||
ReserveAssetDeposited((Parent, 100).into()),
|
||||
BuyExecution { fees, weight_limit: Limited(30) },
|
||||
DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here.into() },
|
||||
]);
|
||||
let weight_limit = 50;
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Parent, message, weight_limit);
|
||||
assert_eq!(r, Outcome::Complete(30));
|
||||
assert_eq!(assets(3000), vec![(Parent, 70).into()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transfer_should_work() {
|
||||
// we'll let them have message execution for free.
|
||||
AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]);
|
||||
// Child parachain #1 owns 1000 tokens held by us in reserve.
|
||||
add_asset(1001, (Here, 1000));
|
||||
// They want to transfer 100 of them to their sibling parachain #2
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
Xcm(vec![TransferAsset {
|
||||
assets: (Here, 100).into(),
|
||||
beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(),
|
||||
}]),
|
||||
50,
|
||||
);
|
||||
assert_eq!(r, Outcome::Complete(10));
|
||||
assert_eq!(assets(3), vec![(Here, 100).into()]);
|
||||
assert_eq!(assets(1001), vec![(Here, 900).into()]);
|
||||
assert_eq!(sent_xcm(), vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_asset_trap_should_work() {
|
||||
// we'll let them have message execution for free.
|
||||
AllowUnpaidFrom::set(vec![X1(Parachain(1)).into(), X1(Parachain(2)).into()]);
|
||||
|
||||
// Child parachain #1 owns 1000 tokens held by us in reserve.
|
||||
add_asset(1001, (Here, 1000));
|
||||
// They want to transfer 100 of them to their sibling parachain #2 but have a problem
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1).into(),
|
||||
Xcm(vec![
|
||||
WithdrawAsset((Here, 100).into()),
|
||||
DepositAsset {
|
||||
assets: Wild(All),
|
||||
max_assets: 0, //< Whoops!
|
||||
beneficiary: AccountIndex64 { index: 3, network: Any }.into(),
|
||||
},
|
||||
]),
|
||||
20,
|
||||
);
|
||||
assert_eq!(r, Outcome::Complete(25));
|
||||
assert_eq!(assets(1001), vec![(Here, 900).into()]);
|
||||
assert_eq!(assets(3), vec![]);
|
||||
|
||||
// Incorrect ticket doesn't work.
|
||||
let old_trapped_assets = TrappedAssets::get();
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1).into(),
|
||||
Xcm(vec![
|
||||
ClaimAsset { assets: (Here, 100).into(), ticket: GeneralIndex(1).into() },
|
||||
DepositAsset {
|
||||
assets: Wild(All),
|
||||
max_assets: 1,
|
||||
beneficiary: AccountIndex64 { index: 3, network: Any }.into(),
|
||||
},
|
||||
]),
|
||||
20,
|
||||
);
|
||||
assert_eq!(r, Outcome::Incomplete(10, XcmError::UnknownClaim));
|
||||
assert_eq!(assets(1001), vec![(Here, 900).into()]);
|
||||
assert_eq!(assets(3), vec![]);
|
||||
assert_eq!(old_trapped_assets, TrappedAssets::get());
|
||||
|
||||
// Incorrect origin doesn't work.
|
||||
let old_trapped_assets = TrappedAssets::get();
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(2).into(),
|
||||
Xcm(vec![
|
||||
ClaimAsset { assets: (Here, 100).into(), ticket: GeneralIndex(0).into() },
|
||||
DepositAsset {
|
||||
assets: Wild(All),
|
||||
max_assets: 1,
|
||||
beneficiary: AccountIndex64 { index: 3, network: Any }.into(),
|
||||
},
|
||||
]),
|
||||
20,
|
||||
);
|
||||
assert_eq!(r, Outcome::Incomplete(10, XcmError::UnknownClaim));
|
||||
assert_eq!(assets(1001), vec![(Here, 900).into()]);
|
||||
assert_eq!(assets(3), vec![]);
|
||||
assert_eq!(old_trapped_assets, TrappedAssets::get());
|
||||
|
||||
// Incorrect assets doesn't work.
|
||||
let old_trapped_assets = TrappedAssets::get();
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1).into(),
|
||||
Xcm(vec![
|
||||
ClaimAsset { assets: (Here, 101).into(), ticket: GeneralIndex(0).into() },
|
||||
DepositAsset {
|
||||
assets: Wild(All),
|
||||
max_assets: 1,
|
||||
beneficiary: AccountIndex64 { index: 3, network: Any }.into(),
|
||||
},
|
||||
]),
|
||||
20,
|
||||
);
|
||||
assert_eq!(r, Outcome::Incomplete(10, XcmError::UnknownClaim));
|
||||
assert_eq!(assets(1001), vec![(Here, 900).into()]);
|
||||
assert_eq!(assets(3), vec![]);
|
||||
assert_eq!(old_trapped_assets, TrappedAssets::get());
|
||||
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1).into(),
|
||||
Xcm(vec![
|
||||
ClaimAsset { assets: (Here, 100).into(), ticket: GeneralIndex(0).into() },
|
||||
DepositAsset {
|
||||
assets: Wild(All),
|
||||
max_assets: 1,
|
||||
beneficiary: AccountIndex64 { index: 3, network: Any }.into(),
|
||||
},
|
||||
]),
|
||||
20,
|
||||
);
|
||||
assert_eq!(r, Outcome::Complete(20));
|
||||
assert_eq!(assets(1001), vec![(Here, 900).into()]);
|
||||
assert_eq!(assets(3), vec![(Here, 100).into()]);
|
||||
|
||||
// Same again doesn't work :-)
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1).into(),
|
||||
Xcm(vec![
|
||||
ClaimAsset { assets: (Here, 100).into(), ticket: GeneralIndex(0).into() },
|
||||
DepositAsset {
|
||||
assets: Wild(All),
|
||||
max_assets: 1,
|
||||
beneficiary: AccountIndex64 { index: 3, network: Any }.into(),
|
||||
},
|
||||
]),
|
||||
20,
|
||||
);
|
||||
assert_eq!(r, Outcome::Incomplete(10, XcmError::UnknownClaim));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors_should_return_unused_weight() {
|
||||
// we'll let them have message execution for free.
|
||||
AllowUnpaidFrom::set(vec![Here.into()]);
|
||||
// We own 1000 of our tokens.
|
||||
add_asset(3000, (Here, 11));
|
||||
let mut message = Xcm(vec![
|
||||
// First xfer results in an error on the last message only
|
||||
TransferAsset {
|
||||
assets: (Here, 1).into(),
|
||||
beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(),
|
||||
},
|
||||
// Second xfer results in error third message and after
|
||||
TransferAsset {
|
||||
assets: (Here, 2).into(),
|
||||
beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(),
|
||||
},
|
||||
// Third xfer results in error second message and after
|
||||
TransferAsset {
|
||||
assets: (Here, 4).into(),
|
||||
beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(),
|
||||
},
|
||||
]);
|
||||
// Weight limit of 70 is needed.
|
||||
let limit = <TestConfig as Config>::Weigher::weight(&mut message).unwrap();
|
||||
assert_eq!(limit, 30);
|
||||
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Here.into(), message.clone(), limit);
|
||||
assert_eq!(r, Outcome::Complete(30));
|
||||
assert_eq!(assets(3), vec![(Here, 7).into()]);
|
||||
assert_eq!(assets(3000), vec![(Here, 4).into()]);
|
||||
assert_eq!(sent_xcm(), vec![]);
|
||||
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Here.into(), message.clone(), limit);
|
||||
assert_eq!(r, Outcome::Incomplete(30, XcmError::NotWithdrawable));
|
||||
assert_eq!(assets(3), vec![(Here, 10).into()]);
|
||||
assert_eq!(assets(3000), vec![(Here, 1).into()]);
|
||||
assert_eq!(sent_xcm(), vec![]);
|
||||
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Here.into(), message.clone(), limit);
|
||||
assert_eq!(r, Outcome::Incomplete(20, XcmError::NotWithdrawable));
|
||||
assert_eq!(assets(3), vec![(Here, 11).into()]);
|
||||
assert_eq!(assets(3000), vec![]);
|
||||
assert_eq!(sent_xcm(), vec![]);
|
||||
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Here.into(), message, limit);
|
||||
assert_eq!(r, Outcome::Incomplete(10, XcmError::NotWithdrawable));
|
||||
assert_eq!(assets(3), vec![(Here, 11).into()]);
|
||||
assert_eq!(assets(3000), vec![]);
|
||||
assert_eq!(sent_xcm(), vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn weight_bounds_should_respect_instructions_limit() {
|
||||
MaxInstructions::set(3);
|
||||
let mut message = Xcm(vec![ClearOrigin; 4]);
|
||||
// 4 instructions are too many.
|
||||
assert_eq!(<TestConfig as Config>::Weigher::weight(&mut message), Err(()));
|
||||
|
||||
let mut message =
|
||||
Xcm(vec![SetErrorHandler(Xcm(vec![ClearOrigin])), SetAppendix(Xcm(vec![ClearOrigin]))]);
|
||||
// 4 instructions are too many, even when hidden within 2.
|
||||
assert_eq!(<TestConfig as Config>::Weigher::weight(&mut message), Err(()));
|
||||
|
||||
let mut message =
|
||||
Xcm(vec![SetErrorHandler(Xcm(vec![SetErrorHandler(Xcm(vec![SetErrorHandler(Xcm(
|
||||
vec![ClearOrigin],
|
||||
))]))]))]);
|
||||
// 4 instructions are too many, even when it's just one that's 3 levels deep.
|
||||
assert_eq!(<TestConfig as Config>::Weigher::weight(&mut message), Err(()));
|
||||
|
||||
let mut message =
|
||||
Xcm(vec![SetErrorHandler(Xcm(vec![SetErrorHandler(Xcm(vec![ClearOrigin]))]))]);
|
||||
// 3 instructions are OK.
|
||||
assert_eq!(<TestConfig as Config>::Weigher::weight(&mut message), Ok(30));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn code_registers_should_work() {
|
||||
// we'll let them have message execution for free.
|
||||
AllowUnpaidFrom::set(vec![Here.into()]);
|
||||
// We own 1000 of our tokens.
|
||||
add_asset(3000, (Here, 21));
|
||||
let mut message = Xcm(vec![
|
||||
// Set our error handler - this will fire only on the second message, when there's an error
|
||||
SetErrorHandler(Xcm(vec![
|
||||
TransferAsset {
|
||||
assets: (Here, 2).into(),
|
||||
beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(),
|
||||
},
|
||||
// It was handled fine.
|
||||
ClearError,
|
||||
])),
|
||||
// Set the appendix - this will always fire.
|
||||
SetAppendix(Xcm(vec![TransferAsset {
|
||||
assets: (Here, 4).into(),
|
||||
beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(),
|
||||
}])),
|
||||
// First xfer always works ok
|
||||
TransferAsset {
|
||||
assets: (Here, 1).into(),
|
||||
beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(),
|
||||
},
|
||||
// Second xfer results in error on the second message - our error handler will fire.
|
||||
TransferAsset {
|
||||
assets: (Here, 8).into(),
|
||||
beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(),
|
||||
},
|
||||
]);
|
||||
// Weight limit of 70 is needed.
|
||||
let limit = <TestConfig as Config>::Weigher::weight(&mut message).unwrap();
|
||||
assert_eq!(limit, 70);
|
||||
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Here.into(), message.clone(), limit);
|
||||
assert_eq!(r, Outcome::Complete(50)); // We don't pay the 20 weight for the error handler.
|
||||
assert_eq!(assets(3), vec![(Here, 13).into()]);
|
||||
assert_eq!(assets(3000), vec![(Here, 8).into()]);
|
||||
assert_eq!(sent_xcm(), vec![]);
|
||||
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Here.into(), message, limit);
|
||||
assert_eq!(r, Outcome::Complete(70)); // We pay the full weight here.
|
||||
assert_eq!(assets(3), vec![(Here, 20).into()]);
|
||||
assert_eq!(assets(3000), vec![(Here, 1).into()]);
|
||||
assert_eq!(sent_xcm(), vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reserve_transfer_should_work() {
|
||||
AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]);
|
||||
// Child parachain #1 owns 1000 tokens held by us in reserve.
|
||||
add_asset(1001, (Here, 1000));
|
||||
// The remote account owned by gav.
|
||||
let three: MultiLocation = X1(AccountIndex64 { index: 3, network: Any }).into();
|
||||
|
||||
// They want to transfer 100 of our native asset from sovereign account of parachain #1 into #2
|
||||
// and let them know to hand it to account #3.
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
Xcm(vec![TransferReserveAsset {
|
||||
assets: (Here, 100).into(),
|
||||
dest: Parachain(2).into(),
|
||||
xcm: Xcm::<()>(vec![DepositAsset {
|
||||
assets: All.into(),
|
||||
max_assets: 1,
|
||||
beneficiary: three.clone(),
|
||||
}]),
|
||||
}]),
|
||||
50,
|
||||
);
|
||||
assert_eq!(r, Outcome::Complete(10));
|
||||
|
||||
assert_eq!(assets(1002), vec![(Here, 100).into()]);
|
||||
assert_eq!(
|
||||
sent_xcm(),
|
||||
vec![(
|
||||
Parachain(2).into(),
|
||||
Xcm::<()>(vec![
|
||||
ReserveAssetDeposited((Parent, 100).into()),
|
||||
ClearOrigin,
|
||||
DepositAsset { assets: All.into(), max_assets: 1, beneficiary: three },
|
||||
]),
|
||||
)]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_version_subscriptions_should_work() {
|
||||
AllowSubsFrom::set(vec![Parent.into()]);
|
||||
|
||||
let origin = Parachain(1000).into();
|
||||
let message = Xcm::<TestCall>(vec![
|
||||
SetAppendix(Xcm(vec![])),
|
||||
SubscribeVersion { query_id: 42, max_response_weight: 5000 },
|
||||
]);
|
||||
let weight_limit = 20;
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(origin, message, weight_limit);
|
||||
assert_eq!(r, Outcome::Error(XcmError::Barrier));
|
||||
|
||||
let origin = Parachain(1000).into();
|
||||
let message =
|
||||
Xcm::<TestCall>(vec![SubscribeVersion { query_id: 42, max_response_weight: 5000 }]);
|
||||
let weight_limit = 10;
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(origin, message.clone(), weight_limit);
|
||||
assert_eq!(r, Outcome::Error(XcmError::Barrier));
|
||||
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Parent, message, weight_limit);
|
||||
assert_eq!(r, Outcome::Complete(10));
|
||||
|
||||
assert_eq!(SubscriptionRequests::get(), vec![(Parent.into(), Some((42, 5000)))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_subscription_instruction_should_work() {
|
||||
let origin = Parachain(1000).into();
|
||||
let message = Xcm::<TestCall>(vec![
|
||||
DescendOrigin(X1(AccountIndex64 { index: 1, network: Any })),
|
||||
SubscribeVersion { query_id: 42, max_response_weight: 5000 },
|
||||
]);
|
||||
let weight_limit = 20;
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm_in_credit(
|
||||
origin.clone(),
|
||||
message.clone(),
|
||||
weight_limit,
|
||||
weight_limit,
|
||||
);
|
||||
assert_eq!(r, Outcome::Incomplete(20, XcmError::BadOrigin));
|
||||
|
||||
let message = Xcm::<TestCall>(vec![
|
||||
SetAppendix(Xcm(vec![])),
|
||||
SubscribeVersion { query_id: 42, max_response_weight: 5000 },
|
||||
]);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm_in_credit(
|
||||
origin,
|
||||
message.clone(),
|
||||
weight_limit,
|
||||
weight_limit,
|
||||
);
|
||||
assert_eq!(r, Outcome::Complete(20));
|
||||
|
||||
assert_eq!(SubscriptionRequests::get(), vec![(Parachain(1000).into(), Some((42, 5000)))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_version_unsubscriptions_should_work() {
|
||||
AllowSubsFrom::set(vec![Parent.into()]);
|
||||
|
||||
let origin = Parachain(1000).into();
|
||||
let message = Xcm::<TestCall>(vec![SetAppendix(Xcm(vec![])), UnsubscribeVersion]);
|
||||
let weight_limit = 20;
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(origin, message, weight_limit);
|
||||
assert_eq!(r, Outcome::Error(XcmError::Barrier));
|
||||
|
||||
let origin = Parachain(1000).into();
|
||||
let message = Xcm::<TestCall>(vec![UnsubscribeVersion]);
|
||||
let weight_limit = 10;
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(origin, message.clone(), weight_limit);
|
||||
assert_eq!(r, Outcome::Error(XcmError::Barrier));
|
||||
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Parent, message, weight_limit);
|
||||
assert_eq!(r, Outcome::Complete(10));
|
||||
|
||||
assert_eq!(SubscriptionRequests::get(), vec![(Parent.into(), None)]);
|
||||
assert_eq!(sent_xcm(), vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_unsubscription_instruction_should_work() {
|
||||
let origin = Parachain(1000).into();
|
||||
|
||||
// Not allowed to do it when origin has been changed.
|
||||
let message = Xcm::<TestCall>(vec![
|
||||
DescendOrigin(X1(AccountIndex64 { index: 1, network: Any })),
|
||||
UnsubscribeVersion,
|
||||
]);
|
||||
let weight_limit = 20;
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm_in_credit(
|
||||
origin.clone(),
|
||||
message.clone(),
|
||||
weight_limit,
|
||||
weight_limit,
|
||||
);
|
||||
assert_eq!(r, Outcome::Incomplete(20, XcmError::BadOrigin));
|
||||
|
||||
// Fine to do it when origin is untouched.
|
||||
let message = Xcm::<TestCall>(vec![SetAppendix(Xcm(vec![])), UnsubscribeVersion]);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm_in_credit(
|
||||
origin,
|
||||
message.clone(),
|
||||
weight_limit,
|
||||
weight_limit,
|
||||
);
|
||||
assert_eq!(r, Outcome::Complete(20));
|
||||
|
||||
assert_eq!(SubscriptionRequests::get(), vec![(Parachain(1000).into(), None)]);
|
||||
assert_eq!(sent_xcm(), vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transacting_should_work() {
|
||||
AllowUnpaidFrom::set(vec![Parent.into()]);
|
||||
|
||||
let message = Xcm::<TestCall>(vec![Transact {
|
||||
origin_type: OriginKind::Native,
|
||||
require_weight_at_most: 50,
|
||||
call: TestCall::Any(50, None).encode().into(),
|
||||
}]);
|
||||
let weight_limit = 60;
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Parent, message, weight_limit);
|
||||
assert_eq!(r, Outcome::Complete(60));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transacting_should_respect_max_weight_requirement() {
|
||||
AllowUnpaidFrom::set(vec![Parent.into()]);
|
||||
|
||||
let message = Xcm::<TestCall>(vec![Transact {
|
||||
origin_type: OriginKind::Native,
|
||||
require_weight_at_most: 40,
|
||||
call: TestCall::Any(50, None).encode().into(),
|
||||
}]);
|
||||
let weight_limit = 60;
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Parent, message, weight_limit);
|
||||
assert_eq!(r, Outcome::Incomplete(50, XcmError::MaxWeightInvalid));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transacting_should_refund_weight() {
|
||||
AllowUnpaidFrom::set(vec![Parent.into()]);
|
||||
|
||||
let message = Xcm::<TestCall>(vec![Transact {
|
||||
origin_type: OriginKind::Native,
|
||||
require_weight_at_most: 50,
|
||||
call: TestCall::Any(50, Some(30)).encode().into(),
|
||||
}]);
|
||||
let weight_limit = 60;
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Parent, message, weight_limit);
|
||||
assert_eq!(r, Outcome::Complete(40));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn paid_transacting_should_refund_payment_for_unused_weight() {
|
||||
let one: MultiLocation = X1(AccountIndex64 { index: 1, network: Any }).into();
|
||||
AllowPaidFrom::set(vec![one.clone()]);
|
||||
add_asset(1, (Parent, 100));
|
||||
WeightPrice::set((Parent.into(), 1_000_000_000_000));
|
||||
|
||||
let origin = one.clone();
|
||||
let fees = (Parent, 100).into();
|
||||
let message = Xcm::<TestCall>(vec![
|
||||
WithdrawAsset((Parent, 100).into()), // enough for 100 units of weight.
|
||||
BuyExecution { fees, weight_limit: Limited(100) },
|
||||
Transact {
|
||||
origin_type: OriginKind::Native,
|
||||
require_weight_at_most: 50,
|
||||
// call estimated at 50 but only takes 10.
|
||||
call: TestCall::Any(50, Some(10)).encode().into(),
|
||||
},
|
||||
RefundSurplus,
|
||||
DepositAsset { assets: All.into(), max_assets: 1, beneficiary: one.clone() },
|
||||
]);
|
||||
let weight_limit = 100;
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(origin, message, weight_limit);
|
||||
assert_eq!(r, Outcome::Complete(60));
|
||||
assert_eq!(assets(1), vec![(Parent, 40).into()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prepaid_result_of_query_should_get_free_execution() {
|
||||
let query_id = 33;
|
||||
// We put this in manually here, but normally this would be done at the point of crafting the message.
|
||||
expect_response(query_id, Parent.into());
|
||||
|
||||
let the_response = Response::Assets((Parent, 100).into());
|
||||
let message = Xcm::<TestCall>(vec![QueryResponse {
|
||||
query_id,
|
||||
response: the_response.clone(),
|
||||
max_weight: 10,
|
||||
}]);
|
||||
let weight_limit = 10;
|
||||
|
||||
// First time the response gets through since we're expecting it...
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Parent, message.clone(), weight_limit);
|
||||
assert_eq!(r, Outcome::Complete(10));
|
||||
assert_eq!(response(query_id).unwrap(), the_response);
|
||||
|
||||
// Second time it doesn't, since we're not.
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Parent, message.clone(), weight_limit);
|
||||
assert_eq!(r, Outcome::Error(XcmError::Barrier));
|
||||
}
|
||||
|
||||
fn fungible_multi_asset(location: MultiLocation, amount: u128) -> MultiAsset {
|
||||
(AssetId::from(location), Fungibility::Fungible(amount)).into()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn weight_trader_tuple_should_work() {
|
||||
pub const PARA_1: MultiLocation = X1(Parachain(1)).into();
|
||||
pub const PARA_2: MultiLocation = X1(Parachain(2)).into();
|
||||
|
||||
parameter_types! {
|
||||
pub static HereWeightPrice: (AssetId, u128) = (Here.into().into(), WEIGHT_REF_TIME_PER_SECOND.into());
|
||||
pub static PARA1WeightPrice: (AssetId, u128) = (PARA_1.into(), WEIGHT_REF_TIME_PER_SECOND.into());
|
||||
}
|
||||
|
||||
type Traders = (
|
||||
// trader one
|
||||
FixedRateOfFungible<HereWeightPrice, ()>,
|
||||
// trader two
|
||||
FixedRateOfFungible<PARA1WeightPrice, ()>,
|
||||
);
|
||||
|
||||
let mut traders = Traders::new();
|
||||
// trader one buys weight
|
||||
assert_eq!(
|
||||
traders.buy_weight(5, fungible_multi_asset(Here.into(), 10).into()),
|
||||
Ok(fungible_multi_asset(Here.into(), 5).into()),
|
||||
);
|
||||
// trader one refunds
|
||||
assert_eq!(traders.refund_weight(2), Some(fungible_multi_asset(Here.into(), 2)));
|
||||
|
||||
let mut traders = Traders::new();
|
||||
// trader one failed; trader two buys weight
|
||||
assert_eq!(
|
||||
traders.buy_weight(5, fungible_multi_asset(PARA_1, 10).into()),
|
||||
Ok(fungible_multi_asset(PARA_1, 5).into()),
|
||||
);
|
||||
// trader two refunds
|
||||
assert_eq!(traders.refund_weight(2), Some(fungible_multi_asset(PARA_1, 2)));
|
||||
|
||||
let mut traders = Traders::new();
|
||||
// all traders fails
|
||||
assert_err!(
|
||||
traders.buy_weight(5, fungible_multi_asset(PARA_2, 10).into()),
|
||||
XcmError::TooExpensive,
|
||||
);
|
||||
// and no refund
|
||||
assert_eq!(traders.refund_weight(2), None);
|
||||
}
|
||||
@@ -0,0 +1,469 @@
|
||||
// Copyright 2022 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn exchange_asset_should_work() {
|
||||
AllowUnpaidFrom::set(vec![Parent.into()]);
|
||||
add_asset(Parent, (Parent, 1000u128));
|
||||
set_exchange_assets(vec![(Here, 100u128).into()]);
|
||||
let message = Xcm(vec![
|
||||
WithdrawAsset((Parent, 100u128).into()),
|
||||
SetAppendix(
|
||||
vec![DepositAsset { assets: AllCounted(2).into(), beneficiary: Parent.into() }].into(),
|
||||
),
|
||||
ExchangeAsset {
|
||||
give: Definite((Parent, 50u128).into()),
|
||||
want: (Here, 50u128).into(),
|
||||
maximal: true,
|
||||
},
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r =
|
||||
XcmExecutor::<TestConfig>::execute_xcm(Parent, message, hash, Weight::from_parts(50, 50));
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(40, 40)));
|
||||
assert_eq!(asset_list(Parent), vec![(Here, 100u128).into(), (Parent, 950u128).into()]);
|
||||
assert_eq!(exchange_assets(), vec![(Parent, 50u128).into()].into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exchange_asset_without_maximal_should_work() {
|
||||
AllowUnpaidFrom::set(vec![Parent.into()]);
|
||||
add_asset(Parent, (Parent, 1000u128));
|
||||
set_exchange_assets(vec![(Here, 100u128).into()]);
|
||||
let message = Xcm(vec![
|
||||
WithdrawAsset((Parent, 100u128).into()),
|
||||
SetAppendix(
|
||||
vec![DepositAsset { assets: AllCounted(2).into(), beneficiary: Parent.into() }].into(),
|
||||
),
|
||||
ExchangeAsset {
|
||||
give: Definite((Parent, 50).into()),
|
||||
want: (Here, 50u128).into(),
|
||||
maximal: false,
|
||||
},
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r =
|
||||
XcmExecutor::<TestConfig>::execute_xcm(Parent, message, hash, Weight::from_parts(50, 50));
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(40, 40)));
|
||||
assert_eq!(asset_list(Parent), vec![(Here, 50u128).into(), (Parent, 950u128).into()]);
|
||||
assert_eq!(exchange_assets(), vec![(Here, 50u128).into(), (Parent, 50u128).into()].into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exchange_asset_should_fail_when_no_deal_possible() {
|
||||
AllowUnpaidFrom::set(vec![Parent.into()]);
|
||||
add_asset(Parent, (Parent, 1000u128));
|
||||
set_exchange_assets(vec![(Here, 100u128).into()]);
|
||||
let message = Xcm(vec![
|
||||
WithdrawAsset((Parent, 150u128).into()),
|
||||
SetAppendix(
|
||||
vec![DepositAsset { assets: AllCounted(2).into(), beneficiary: Parent.into() }].into(),
|
||||
),
|
||||
ExchangeAsset {
|
||||
give: Definite((Parent, 150u128).into()),
|
||||
want: (Here, 150u128).into(),
|
||||
maximal: false,
|
||||
},
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r =
|
||||
XcmExecutor::<TestConfig>::execute_xcm(Parent, message, hash, Weight::from_parts(50, 50));
|
||||
assert_eq!(r, Outcome::Incomplete(Weight::from_parts(40, 40), XcmError::NoDeal));
|
||||
assert_eq!(asset_list(Parent), vec![(Parent, 1000u128).into()]);
|
||||
assert_eq!(exchange_assets(), vec![(Here, 100u128).into()].into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn paying_reserve_deposit_should_work() {
|
||||
AllowPaidFrom::set(vec![Parent.into()]);
|
||||
add_reserve(Parent.into(), (Parent, WildFungible).into());
|
||||
WeightPrice::set((Parent.into(), 1_000_000_000_000, 1024 * 1024));
|
||||
|
||||
let fees = (Parent, 60u128).into();
|
||||
let message = Xcm(vec![
|
||||
ReserveAssetDeposited((Parent, 100u128).into()),
|
||||
BuyExecution { fees, weight_limit: Limited(Weight::from_parts(30, 30)) },
|
||||
DepositAsset { assets: AllCounted(1).into(), beneficiary: Here.into() },
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let weight_limit = Weight::from_parts(50, 50);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Parent, message, hash, weight_limit);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(30, 30)));
|
||||
assert_eq!(asset_list(Here), vec![(Parent, 40u128).into()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transfer_should_work() {
|
||||
// we'll let them have message execution for free.
|
||||
AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]);
|
||||
// Child parachain #1 owns 1000 tokens held by us in reserve.
|
||||
add_asset(Parachain(1), (Here, 1000));
|
||||
// They want to transfer 100 of them to their sibling parachain #2
|
||||
let message = Xcm(vec![TransferAsset {
|
||||
assets: (Here, 100u128).into(),
|
||||
beneficiary: X1(AccountIndex64 { index: 3, network: None }).into(),
|
||||
}]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(50, 50),
|
||||
);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10)));
|
||||
assert_eq!(
|
||||
asset_list(AccountIndex64 { index: 3, network: None }),
|
||||
vec![(Here, 100u128).into()]
|
||||
);
|
||||
assert_eq!(asset_list(Parachain(1)), vec![(Here, 900u128).into()]);
|
||||
assert_eq!(sent_xcm(), vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reserve_transfer_should_work() {
|
||||
AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]);
|
||||
// Child parachain #1 owns 1000 tokens held by us in reserve.
|
||||
add_asset(Parachain(1), (Here, 1000));
|
||||
// The remote account owned by gav.
|
||||
let three: MultiLocation = X1(AccountIndex64 { index: 3, network: None }).into();
|
||||
|
||||
// They want to transfer 100 of our native asset from sovereign account of parachain #1 into #2
|
||||
// and let them know to hand it to account #3.
|
||||
let message = Xcm(vec![TransferReserveAsset {
|
||||
assets: (Here, 100u128).into(),
|
||||
dest: Parachain(2).into(),
|
||||
xcm: Xcm::<()>(vec![DepositAsset {
|
||||
assets: AllCounted(1).into(),
|
||||
beneficiary: three.clone(),
|
||||
}]),
|
||||
}]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(50, 50),
|
||||
);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10)));
|
||||
|
||||
let expected_msg = Xcm::<()>(vec![
|
||||
ReserveAssetDeposited((Parent, 100u128).into()),
|
||||
ClearOrigin,
|
||||
DepositAsset { assets: AllCounted(1).into(), beneficiary: three },
|
||||
]);
|
||||
let expected_hash = fake_message_hash(&expected_msg);
|
||||
assert_eq!(asset_list(Parachain(2)), vec![(Here, 100).into()]);
|
||||
assert_eq!(sent_xcm(), vec![(Parachain(2).into(), expected_msg, expected_hash)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn burn_should_work() {
|
||||
// we'll let them have message execution for free.
|
||||
AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]);
|
||||
// Child parachain #1 owns 1000 tokens held by us in reserve.
|
||||
add_asset(Parachain(1), (Here, 1000));
|
||||
// They want to burn 100 of them
|
||||
let message = Xcm(vec![
|
||||
WithdrawAsset((Here, 1000u128).into()),
|
||||
BurnAsset((Here, 100u128).into()),
|
||||
DepositAsset { assets: Wild(AllCounted(1)), beneficiary: Parachain(1).into() },
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(50, 50),
|
||||
);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(30, 30)));
|
||||
assert_eq!(asset_list(Parachain(1)), vec![(Here, 900u128).into()]);
|
||||
assert_eq!(sent_xcm(), vec![]);
|
||||
|
||||
// Now they want to burn 1000 of them, which will actually only burn 900.
|
||||
let message = Xcm(vec![
|
||||
WithdrawAsset((Here, 900u128).into()),
|
||||
BurnAsset((Here, 1000u128).into()),
|
||||
DepositAsset { assets: Wild(AllCounted(1)), beneficiary: Parachain(1).into() },
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(50, 50),
|
||||
);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(30, 30)));
|
||||
assert_eq!(asset_list(Parachain(1)), vec![]);
|
||||
assert_eq!(sent_xcm(), vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_asset_trap_should_work() {
|
||||
// we'll let them have message execution for free.
|
||||
AllowUnpaidFrom::set(vec![X1(Parachain(1)).into(), X1(Parachain(2)).into()]);
|
||||
|
||||
// Child parachain #1 owns 1000 tokens held by us in reserve.
|
||||
add_asset(Parachain(1), (Here, 1000));
|
||||
// They want to transfer 100 of them to their sibling parachain #2 but have a problem
|
||||
let message = Xcm(vec![
|
||||
WithdrawAsset((Here, 100u128).into()),
|
||||
DepositAsset {
|
||||
assets: Wild(AllCounted(0)), // <<< 0 is an error.
|
||||
beneficiary: AccountIndex64 { index: 3, network: None }.into(),
|
||||
},
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(20, 20),
|
||||
);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(25, 25)));
|
||||
assert_eq!(asset_list(Parachain(1)), vec![(Here, 900u128).into()]);
|
||||
assert_eq!(asset_list(AccountIndex64 { index: 3, network: None }), vec![]);
|
||||
|
||||
// Incorrect ticket doesn't work.
|
||||
let message = Xcm(vec![
|
||||
ClaimAsset { assets: (Here, 100u128).into(), ticket: GeneralIndex(1).into() },
|
||||
DepositAsset {
|
||||
assets: Wild(AllCounted(1)),
|
||||
beneficiary: AccountIndex64 { index: 3, network: None }.into(),
|
||||
},
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let old_trapped_assets = TrappedAssets::get();
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(20, 20),
|
||||
);
|
||||
assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::UnknownClaim));
|
||||
assert_eq!(asset_list(Parachain(1)), vec![(Here, 900u128).into()]);
|
||||
assert_eq!(asset_list(AccountIndex64 { index: 3, network: None }), vec![]);
|
||||
assert_eq!(old_trapped_assets, TrappedAssets::get());
|
||||
|
||||
// Incorrect origin doesn't work.
|
||||
let message = Xcm(vec![
|
||||
ClaimAsset { assets: (Here, 100u128).into(), ticket: GeneralIndex(0).into() },
|
||||
DepositAsset {
|
||||
assets: Wild(AllCounted(1)),
|
||||
beneficiary: AccountIndex64 { index: 3, network: None }.into(),
|
||||
},
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let old_trapped_assets = TrappedAssets::get();
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(2),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(20, 20),
|
||||
);
|
||||
assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::UnknownClaim));
|
||||
assert_eq!(asset_list(Parachain(1)), vec![(Here, 900u128).into()]);
|
||||
assert_eq!(asset_list(AccountIndex64 { index: 3, network: None }), vec![]);
|
||||
assert_eq!(old_trapped_assets, TrappedAssets::get());
|
||||
|
||||
// Incorrect assets doesn't work.
|
||||
let message = Xcm(vec![
|
||||
ClaimAsset { assets: (Here, 101u128).into(), ticket: GeneralIndex(0).into() },
|
||||
DepositAsset {
|
||||
assets: Wild(AllCounted(1)),
|
||||
beneficiary: AccountIndex64 { index: 3, network: None }.into(),
|
||||
},
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let old_trapped_assets = TrappedAssets::get();
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(20, 20),
|
||||
);
|
||||
assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::UnknownClaim));
|
||||
assert_eq!(asset_list(Parachain(1)), vec![(Here, 900u128).into()]);
|
||||
assert_eq!(asset_list(AccountIndex64 { index: 3, network: None }), vec![]);
|
||||
assert_eq!(old_trapped_assets, TrappedAssets::get());
|
||||
|
||||
let message = Xcm(vec![
|
||||
ClaimAsset { assets: (Here, 100u128).into(), ticket: GeneralIndex(0).into() },
|
||||
DepositAsset {
|
||||
assets: Wild(AllCounted(1)),
|
||||
beneficiary: AccountIndex64 { index: 3, network: None }.into(),
|
||||
},
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(20, 20),
|
||||
);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(20, 20)));
|
||||
assert_eq!(asset_list(Parachain(1)), vec![(Here, 900u128).into()]);
|
||||
assert_eq!(
|
||||
asset_list(AccountIndex64 { index: 3, network: None }),
|
||||
vec![(Here, 100u128).into()]
|
||||
);
|
||||
|
||||
// Same again doesn't work :-)
|
||||
let message = Xcm(vec![
|
||||
ClaimAsset { assets: (Here, 100u128).into(), ticket: GeneralIndex(0).into() },
|
||||
DepositAsset {
|
||||
assets: Wild(AllCounted(1)),
|
||||
beneficiary: AccountIndex64 { index: 3, network: None }.into(),
|
||||
},
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(20, 20),
|
||||
);
|
||||
assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::UnknownClaim));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_assets_limit_should_work() {
|
||||
// we'll let them have message execution for free.
|
||||
AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]);
|
||||
// Child parachain #1 owns 1000 tokens held by us in reserve.
|
||||
add_asset(Parachain(1), ([1u8; 32], 1000u128));
|
||||
add_asset(Parachain(1), ([2u8; 32], 1000u128));
|
||||
add_asset(Parachain(1), ([3u8; 32], 1000u128));
|
||||
add_asset(Parachain(1), ([4u8; 32], 1000u128));
|
||||
add_asset(Parachain(1), ([5u8; 32], 1000u128));
|
||||
add_asset(Parachain(1), ([6u8; 32], 1000u128));
|
||||
add_asset(Parachain(1), ([7u8; 32], 1000u128));
|
||||
add_asset(Parachain(1), ([8u8; 32], 1000u128));
|
||||
add_asset(Parachain(1), ([9u8; 32], 1000u128));
|
||||
|
||||
// Attempt to withdraw 8 (=2x4)different assets. This will succeed.
|
||||
let message = Xcm(vec![
|
||||
WithdrawAsset(([1u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([2u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([3u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([4u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([5u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([6u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([7u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([8u8; 32], 100u128).into()),
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(100, 100),
|
||||
);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(85, 85)));
|
||||
|
||||
// Attempt to withdraw 9 different assets will fail.
|
||||
let message = Xcm(vec![
|
||||
WithdrawAsset(([1u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([2u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([3u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([4u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([5u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([6u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([7u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([8u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([9u8; 32], 100u128).into()),
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(100, 100),
|
||||
);
|
||||
assert_eq!(r, Outcome::Incomplete(Weight::from_parts(95, 95), XcmError::HoldingWouldOverflow));
|
||||
|
||||
// Attempt to withdraw 4 different assets and then the same 4 and then a different 4 will succeed.
|
||||
let message = Xcm(vec![
|
||||
WithdrawAsset(([1u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([2u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([3u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([4u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([1u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([2u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([3u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([4u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([5u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([6u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([7u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([8u8; 32], 100u128).into()),
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(200, 200),
|
||||
);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(125, 125)));
|
||||
|
||||
// Attempt to withdraw 4 different assets and then a different 4 and then the same 4 will fail.
|
||||
let message = Xcm(vec![
|
||||
WithdrawAsset(([1u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([2u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([3u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([4u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([5u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([6u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([7u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([8u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([1u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([2u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([3u8; 32], 100u128).into()),
|
||||
WithdrawAsset(([4u8; 32], 100u128).into()),
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(200, 200),
|
||||
);
|
||||
assert_eq!(r, Outcome::Incomplete(Weight::from_parts(95, 95), XcmError::HoldingWouldOverflow));
|
||||
|
||||
// Attempt to withdraw 4 different assets and then a different 4 and then the same 4 will fail.
|
||||
let message = Xcm(vec![
|
||||
WithdrawAsset(MultiAssets::from(vec![
|
||||
([1u8; 32], 100u128).into(),
|
||||
([2u8; 32], 100u128).into(),
|
||||
([3u8; 32], 100u128).into(),
|
||||
([4u8; 32], 100u128).into(),
|
||||
([5u8; 32], 100u128).into(),
|
||||
([6u8; 32], 100u128).into(),
|
||||
([7u8; 32], 100u128).into(),
|
||||
([8u8; 32], 100u128).into(),
|
||||
])),
|
||||
WithdrawAsset(([1u8; 32], 100u128).into()),
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(200, 200),
|
||||
);
|
||||
assert_eq!(r, Outcome::Incomplete(Weight::from_parts(25, 25), XcmError::HoldingWouldOverflow));
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
// Copyright 2022 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn take_weight_credit_barrier_should_work() {
|
||||
let mut message =
|
||||
Xcm::<()>(vec![TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }]);
|
||||
let mut weight_credit = Weight::from_parts(10, 10);
|
||||
let r = TakeWeightCredit::should_execute(
|
||||
&Parent.into(),
|
||||
message.inner_mut(),
|
||||
Weight::from_parts(10, 10),
|
||||
&mut weight_credit,
|
||||
);
|
||||
assert_eq!(r, Ok(()));
|
||||
assert_eq!(weight_credit, Weight::zero());
|
||||
|
||||
let r = TakeWeightCredit::should_execute(
|
||||
&Parent.into(),
|
||||
message.inner_mut(),
|
||||
Weight::from_parts(10, 10),
|
||||
&mut weight_credit,
|
||||
);
|
||||
assert_eq!(r, Err(()));
|
||||
assert_eq!(weight_credit, Weight::zero());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn computed_origin_should_work() {
|
||||
let mut message = Xcm::<()>(vec![
|
||||
UniversalOrigin(GlobalConsensus(Kusama)),
|
||||
DescendOrigin(Parachain(100).into()),
|
||||
DescendOrigin(PalletInstance(69).into()),
|
||||
WithdrawAsset((Parent, 100).into()),
|
||||
BuyExecution {
|
||||
fees: (Parent, 100).into(),
|
||||
weight_limit: Limited(Weight::from_parts(100, 100)),
|
||||
},
|
||||
TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() },
|
||||
]);
|
||||
|
||||
AllowPaidFrom::set(vec![(
|
||||
Parent,
|
||||
Parent,
|
||||
GlobalConsensus(Kusama),
|
||||
Parachain(100),
|
||||
PalletInstance(69),
|
||||
)
|
||||
.into()]);
|
||||
|
||||
let r = AllowTopLevelPaidExecutionFrom::<IsInVec<AllowPaidFrom>>::should_execute(
|
||||
&Parent.into(),
|
||||
message.inner_mut(),
|
||||
Weight::from_parts(100, 100),
|
||||
&mut Weight::zero(),
|
||||
);
|
||||
assert_eq!(r, Err(()));
|
||||
|
||||
let r = WithComputedOrigin::<
|
||||
AllowTopLevelPaidExecutionFrom<IsInVec<AllowPaidFrom>>,
|
||||
ExecutorUniversalLocation,
|
||||
ConstU32<2>,
|
||||
>::should_execute(
|
||||
&Parent.into(),
|
||||
message.inner_mut(),
|
||||
Weight::from_parts(100, 100),
|
||||
&mut Weight::zero(),
|
||||
);
|
||||
assert_eq!(r, Err(()));
|
||||
|
||||
let r = WithComputedOrigin::<
|
||||
AllowTopLevelPaidExecutionFrom<IsInVec<AllowPaidFrom>>,
|
||||
ExecutorUniversalLocation,
|
||||
ConstU32<5>,
|
||||
>::should_execute(
|
||||
&Parent.into(),
|
||||
message.inner_mut(),
|
||||
Weight::from_parts(100, 100),
|
||||
&mut Weight::zero(),
|
||||
);
|
||||
assert_eq!(r, Ok(()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn allow_unpaid_should_work() {
|
||||
let mut message =
|
||||
Xcm::<()>(vec![TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }]);
|
||||
|
||||
AllowUnpaidFrom::set(vec![Parent.into()]);
|
||||
|
||||
let r = AllowUnpaidExecutionFrom::<IsInVec<AllowUnpaidFrom>>::should_execute(
|
||||
&Parachain(1).into(),
|
||||
message.inner_mut(),
|
||||
Weight::from_parts(10, 10),
|
||||
&mut Weight::zero(),
|
||||
);
|
||||
assert_eq!(r, Err(()));
|
||||
|
||||
let r = AllowUnpaidExecutionFrom::<IsInVec<AllowUnpaidFrom>>::should_execute(
|
||||
&Parent.into(),
|
||||
message.inner_mut(),
|
||||
Weight::from_parts(10, 10),
|
||||
&mut Weight::zero(),
|
||||
);
|
||||
assert_eq!(r, Ok(()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn allow_explicit_unpaid_should_work() {
|
||||
let mut bad_message1 =
|
||||
Xcm::<()>(vec![TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }]);
|
||||
|
||||
let mut bad_message2 = Xcm::<()>(vec![
|
||||
UnpaidExecution {
|
||||
weight_limit: Limited(Weight::from_parts(10, 10)),
|
||||
check_origin: Some(Parent.into()),
|
||||
},
|
||||
TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() },
|
||||
]);
|
||||
|
||||
let mut good_message = Xcm::<()>(vec![
|
||||
UnpaidExecution {
|
||||
weight_limit: Limited(Weight::from_parts(20, 20)),
|
||||
check_origin: Some(Parent.into()),
|
||||
},
|
||||
TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() },
|
||||
]);
|
||||
|
||||
AllowExplicitUnpaidFrom::set(vec![Parent.into()]);
|
||||
|
||||
let r = AllowExplicitUnpaidExecutionFrom::<IsInVec<AllowExplicitUnpaidFrom>>::should_execute(
|
||||
&Parachain(1).into(),
|
||||
good_message.inner_mut(),
|
||||
Weight::from_parts(20, 20),
|
||||
&mut Weight::zero(),
|
||||
);
|
||||
assert_eq!(r, Err(()));
|
||||
|
||||
let r = AllowExplicitUnpaidExecutionFrom::<IsInVec<AllowExplicitUnpaidFrom>>::should_execute(
|
||||
&Parent.into(),
|
||||
bad_message1.inner_mut(),
|
||||
Weight::from_parts(20, 20),
|
||||
&mut Weight::zero(),
|
||||
);
|
||||
assert_eq!(r, Err(()));
|
||||
|
||||
let r = AllowExplicitUnpaidExecutionFrom::<IsInVec<AllowExplicitUnpaidFrom>>::should_execute(
|
||||
&Parent.into(),
|
||||
bad_message2.inner_mut(),
|
||||
Weight::from_parts(20, 20),
|
||||
&mut Weight::zero(),
|
||||
);
|
||||
assert_eq!(r, Err(()));
|
||||
|
||||
let r = AllowExplicitUnpaidExecutionFrom::<IsInVec<AllowExplicitUnpaidFrom>>::should_execute(
|
||||
&Parent.into(),
|
||||
good_message.inner_mut(),
|
||||
Weight::from_parts(20, 20),
|
||||
&mut Weight::zero(),
|
||||
);
|
||||
assert_eq!(r, Ok(()));
|
||||
|
||||
let mut message_with_different_weight_parts = Xcm::<()>(vec![
|
||||
UnpaidExecution {
|
||||
weight_limit: Limited(Weight::from_parts(20, 10)),
|
||||
check_origin: Some(Parent.into()),
|
||||
},
|
||||
TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() },
|
||||
]);
|
||||
|
||||
let r = AllowExplicitUnpaidExecutionFrom::<IsInVec<AllowExplicitUnpaidFrom>>::should_execute(
|
||||
&Parent.into(),
|
||||
message_with_different_weight_parts.inner_mut(),
|
||||
Weight::from_parts(20, 20),
|
||||
&mut Weight::zero(),
|
||||
);
|
||||
assert_eq!(r, Err(()));
|
||||
|
||||
let r = AllowExplicitUnpaidExecutionFrom::<IsInVec<AllowExplicitUnpaidFrom>>::should_execute(
|
||||
&Parent.into(),
|
||||
message_with_different_weight_parts.inner_mut(),
|
||||
Weight::from_parts(10, 10),
|
||||
&mut Weight::zero(),
|
||||
);
|
||||
assert_eq!(r, Ok(()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn allow_paid_should_work() {
|
||||
AllowPaidFrom::set(vec![Parent.into()]);
|
||||
|
||||
let mut message =
|
||||
Xcm::<()>(vec![TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }]);
|
||||
|
||||
let r = AllowTopLevelPaidExecutionFrom::<IsInVec<AllowPaidFrom>>::should_execute(
|
||||
&Parachain(1).into(),
|
||||
message.inner_mut(),
|
||||
Weight::from_parts(10, 10),
|
||||
&mut Weight::zero(),
|
||||
);
|
||||
assert_eq!(r, Err(()));
|
||||
|
||||
let fees = (Parent, 1).into();
|
||||
let mut underpaying_message = Xcm::<()>(vec![
|
||||
ReserveAssetDeposited((Parent, 100).into()),
|
||||
BuyExecution { fees, weight_limit: Limited(Weight::from_parts(20, 20)) },
|
||||
DepositAsset { assets: AllCounted(1).into(), beneficiary: Here.into() },
|
||||
]);
|
||||
|
||||
let r = AllowTopLevelPaidExecutionFrom::<IsInVec<AllowPaidFrom>>::should_execute(
|
||||
&Parent.into(),
|
||||
underpaying_message.inner_mut(),
|
||||
Weight::from_parts(30, 30),
|
||||
&mut Weight::zero(),
|
||||
);
|
||||
assert_eq!(r, Err(()));
|
||||
|
||||
let fees = (Parent, 1).into();
|
||||
let mut paying_message = Xcm::<()>(vec![
|
||||
ReserveAssetDeposited((Parent, 100).into()),
|
||||
BuyExecution { fees, weight_limit: Limited(Weight::from_parts(30, 30)) },
|
||||
DepositAsset { assets: AllCounted(1).into(), beneficiary: Here.into() },
|
||||
]);
|
||||
|
||||
let r = AllowTopLevelPaidExecutionFrom::<IsInVec<AllowPaidFrom>>::should_execute(
|
||||
&Parachain(1).into(),
|
||||
paying_message.inner_mut(),
|
||||
Weight::from_parts(30, 30),
|
||||
&mut Weight::zero(),
|
||||
);
|
||||
assert_eq!(r, Err(()));
|
||||
|
||||
let r = AllowTopLevelPaidExecutionFrom::<IsInVec<AllowPaidFrom>>::should_execute(
|
||||
&Parent.into(),
|
||||
paying_message.inner_mut(),
|
||||
Weight::from_parts(30, 30),
|
||||
&mut Weight::zero(),
|
||||
);
|
||||
assert_eq!(r, Ok(()));
|
||||
|
||||
let fees = (Parent, 1).into();
|
||||
let mut paying_message_with_different_weight_parts = Xcm::<()>(vec![
|
||||
WithdrawAsset((Parent, 100).into()),
|
||||
BuyExecution { fees, weight_limit: Limited(Weight::from_parts(20, 10)) },
|
||||
DepositAsset { assets: AllCounted(1).into(), beneficiary: Here.into() },
|
||||
]);
|
||||
|
||||
let r = AllowTopLevelPaidExecutionFrom::<IsInVec<AllowPaidFrom>>::should_execute(
|
||||
&Parent.into(),
|
||||
paying_message_with_different_weight_parts.inner_mut(),
|
||||
Weight::from_parts(20, 20),
|
||||
&mut Weight::zero(),
|
||||
);
|
||||
assert_eq!(r, Err(()));
|
||||
|
||||
let r = AllowTopLevelPaidExecutionFrom::<IsInVec<AllowPaidFrom>>::should_execute(
|
||||
&Parent.into(),
|
||||
paying_message_with_different_weight_parts.inner_mut(),
|
||||
Weight::from_parts(10, 10),
|
||||
&mut Weight::zero(),
|
||||
);
|
||||
assert_eq!(r, Ok(()))
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
// Copyright 2022 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn basic_setup_works() {
|
||||
add_reserve(Parent.into(), Wild((Parent, WildFungible).into()));
|
||||
assert!(
|
||||
<TestConfig as Config>::IsReserve::contains(&(Parent, 100u128).into(), &Parent.into(),)
|
||||
);
|
||||
|
||||
assert_eq!(to_account(Parachain(1)), Ok(1001));
|
||||
assert_eq!(to_account(Parachain(50)), Ok(1050));
|
||||
assert_eq!(to_account((Parent, Parachain(1))), Ok(2001));
|
||||
assert_eq!(to_account((Parent, Parachain(50))), Ok(2050));
|
||||
assert_eq!(
|
||||
to_account(MultiLocation::new(0, X1(AccountIndex64 { index: 1, network: None }))),
|
||||
Ok(1),
|
||||
);
|
||||
assert_eq!(
|
||||
to_account(MultiLocation::new(0, X1(AccountIndex64 { index: 42, network: None }))),
|
||||
Ok(42),
|
||||
);
|
||||
assert_eq!(to_account(Here), Ok(3000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn weigher_should_work() {
|
||||
let mut message = Xcm(vec![
|
||||
ReserveAssetDeposited((Parent, 100u128).into()),
|
||||
BuyExecution {
|
||||
fees: (Parent, 1u128).into(),
|
||||
weight_limit: Limited(Weight::from_parts(30, 30)),
|
||||
},
|
||||
DepositAsset { assets: AllCounted(1).into(), beneficiary: Here.into() },
|
||||
]);
|
||||
assert_eq!(
|
||||
<TestConfig as Config>::Weigher::weight(&mut message),
|
||||
Ok(Weight::from_parts(30, 30))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn code_registers_should_work() {
|
||||
// we'll let them have message execution for free.
|
||||
AllowUnpaidFrom::set(vec![Here.into()]);
|
||||
// We own 1000 of our tokens.
|
||||
add_asset(Here, (Here, 21u128));
|
||||
let mut message = Xcm(vec![
|
||||
// Set our error handler - this will fire only on the second message, when there's an error
|
||||
SetErrorHandler(Xcm(vec![
|
||||
TransferAsset {
|
||||
assets: (Here, 2u128).into(),
|
||||
beneficiary: X1(AccountIndex64 { index: 3, network: None }).into(),
|
||||
},
|
||||
// It was handled fine.
|
||||
ClearError,
|
||||
])),
|
||||
// Set the appendix - this will always fire.
|
||||
SetAppendix(Xcm(vec![TransferAsset {
|
||||
assets: (Here, 4u128).into(),
|
||||
beneficiary: X1(AccountIndex64 { index: 3, network: None }).into(),
|
||||
}])),
|
||||
// First xfer always works ok
|
||||
TransferAsset {
|
||||
assets: (Here, 1u128).into(),
|
||||
beneficiary: X1(AccountIndex64 { index: 3, network: None }).into(),
|
||||
},
|
||||
// Second xfer results in error on the second message - our error handler will fire.
|
||||
TransferAsset {
|
||||
assets: (Here, 8u128).into(),
|
||||
beneficiary: X1(AccountIndex64 { index: 3, network: None }).into(),
|
||||
},
|
||||
]);
|
||||
// Weight limit of 70 is needed.
|
||||
let limit = <TestConfig as Config>::Weigher::weight(&mut message).unwrap();
|
||||
assert_eq!(limit, Weight::from_parts(70, 70));
|
||||
|
||||
let hash = fake_message_hash(&message);
|
||||
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Here, message.clone(), hash, limit);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(50, 50))); // We don't pay the 20 weight for the error handler.
|
||||
assert_eq!(asset_list(AccountIndex64 { index: 3, network: None }), vec![(Here, 13u128).into()]);
|
||||
assert_eq!(asset_list(Here), vec![(Here, 8u128).into()]);
|
||||
assert_eq!(sent_xcm(), vec![]);
|
||||
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Here, message, hash, limit);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(70, 70))); // We pay the full weight here.
|
||||
assert_eq!(asset_list(AccountIndex64 { index: 3, network: None }), vec![(Here, 20u128).into()]);
|
||||
assert_eq!(asset_list(Here), vec![(Here, 1u128).into()]);
|
||||
assert_eq!(sent_xcm(), vec![]);
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright 2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! This test is when we're sending an XCM from a parachain which hosts a bridge to another
|
||||
//! network's bridge parachain. The destination of the XCM is within the global consensus of the
|
||||
//! remote side of the bridge.
|
||||
|
||||
use super::*;
|
||||
|
||||
parameter_types! {
|
||||
pub UniversalLocation: Junctions = X2(GlobalConsensus(Local::get()), Parachain(1));
|
||||
pub RemoteUniversalLocation: Junctions = X2(GlobalConsensus(Remote::get()), Parachain(1));
|
||||
}
|
||||
type TheBridge =
|
||||
TestBridge<BridgeBlobDispatcher<TestRemoteIncomingRouter, RemoteUniversalLocation>>;
|
||||
type Router = UnpaidLocalExporter<HaulBlobExporter<TheBridge, Remote, Price>, UniversalLocation>;
|
||||
|
||||
/// ```nocompile
|
||||
/// local | remote
|
||||
/// |
|
||||
/// GlobalConsensus(Local::get()) | GlobalConsensus(Remote::get())
|
||||
/// |
|
||||
/// |
|
||||
/// |
|
||||
/// |
|
||||
/// Parachain(1) ===> Parachain(1)
|
||||
/// ```
|
||||
#[test]
|
||||
fn sending_to_bridged_chain_works() {
|
||||
let msg = Xcm(vec![Trap(1)]);
|
||||
let dest = (Parent, Parent, Remote::get(), Parachain(1)).into();
|
||||
assert_eq!(send_xcm::<Router>(dest, msg).unwrap().1, (Here, 100).into());
|
||||
assert_eq!(TheBridge::service(), 1);
|
||||
assert_eq!(
|
||||
take_received_remote_messages(),
|
||||
vec![(
|
||||
Here.into(),
|
||||
Xcm(vec![
|
||||
UniversalOrigin(Local::get().into()),
|
||||
DescendOrigin(Parachain(1).into()),
|
||||
Trap(1),
|
||||
])
|
||||
)]
|
||||
);
|
||||
}
|
||||
|
||||
/// ```nocompile
|
||||
/// local | remote
|
||||
/// |
|
||||
/// GlobalConsensus(Local::get()) | GlobalConsensus(Remote::get())
|
||||
/// |
|
||||
/// |
|
||||
/// |
|
||||
/// |
|
||||
/// Parachain(1) ===> Parachain(1) ==> Parachain(1000)
|
||||
/// ```
|
||||
#[test]
|
||||
fn sending_to_parachain_of_bridged_chain_works() {
|
||||
let msg = Xcm(vec![Trap(1)]);
|
||||
let dest = (Parent, Parent, Remote::get(), Parachain(1000)).into();
|
||||
assert_eq!(send_xcm::<Router>(dest, msg).unwrap().1, (Here, 100).into());
|
||||
assert_eq!(TheBridge::service(), 1);
|
||||
let expected = vec![(
|
||||
(Parent, Parachain(1000)).into(),
|
||||
Xcm(vec![
|
||||
UniversalOrigin(Local::get().into()),
|
||||
DescendOrigin(Parachain(1).into()),
|
||||
Trap(1),
|
||||
]),
|
||||
)];
|
||||
assert_eq!(take_received_remote_messages(), expected);
|
||||
}
|
||||
|
||||
/// ```nocompile
|
||||
/// local | remote
|
||||
/// |
|
||||
/// GlobalConsensus(Local::get()) | GlobalConsensus(Remote::get())
|
||||
/// | /\
|
||||
/// | ||
|
||||
/// | ||
|
||||
/// | ||
|
||||
/// Parachain(1) ===> Parachain(1)
|
||||
/// ```
|
||||
#[test]
|
||||
fn sending_to_relay_chain_of_bridged_chain_works() {
|
||||
let msg = Xcm(vec![Trap(1)]);
|
||||
let dest = (Parent, Parent, Remote::get()).into();
|
||||
assert_eq!(send_xcm::<Router>(dest, msg).unwrap().1, (Here, 100).into());
|
||||
assert_eq!(TheBridge::service(), 1);
|
||||
let expected = vec![(
|
||||
Parent.into(),
|
||||
Xcm(vec![
|
||||
UniversalOrigin(Local::get().into()),
|
||||
DescendOrigin(Parachain(1).into()),
|
||||
Trap(1),
|
||||
]),
|
||||
)];
|
||||
assert_eq!(take_received_remote_messages(), expected);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright 2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! This test is when we're sending an XCM from a relay-chain which hosts a bridge to another
|
||||
//! relay-chain. The destination of the XCM is within the global consensus of the
|
||||
//! remote side of the bridge.
|
||||
|
||||
use super::*;
|
||||
|
||||
parameter_types! {
|
||||
pub UniversalLocation: Junctions = X1(GlobalConsensus(Local::get()));
|
||||
pub RemoteUniversalLocation: Junctions = X1(GlobalConsensus(Remote::get()));
|
||||
}
|
||||
type TheBridge =
|
||||
TestBridge<BridgeBlobDispatcher<TestRemoteIncomingRouter, RemoteUniversalLocation>>;
|
||||
type Router = UnpaidLocalExporter<HaulBlobExporter<TheBridge, Remote, Price>, UniversalLocation>;
|
||||
|
||||
/// ```nocompile
|
||||
/// local | remote
|
||||
/// |
|
||||
/// GlobalConsensus(Local::get()) ========> GlobalConsensus(Remote::get())
|
||||
/// |
|
||||
/// ```
|
||||
#[test]
|
||||
fn sending_to_bridged_chain_works() {
|
||||
let msg = Xcm(vec![Trap(1)]);
|
||||
assert_eq!(
|
||||
send_xcm::<Router>((Parent, Remote::get()).into(), msg).unwrap().1,
|
||||
(Here, 100).into()
|
||||
);
|
||||
assert_eq!(TheBridge::service(), 1);
|
||||
assert_eq!(
|
||||
take_received_remote_messages(),
|
||||
vec![(Here.into(), Xcm(vec![UniversalOrigin(Local::get().into()), Trap(1)]))]
|
||||
);
|
||||
}
|
||||
|
||||
/// ```nocompile
|
||||
/// local | remote
|
||||
/// |
|
||||
/// GlobalConsensus(Local::get()) ========> GlobalConsensus(Remote::get())
|
||||
/// | ||
|
||||
/// | ||
|
||||
/// | ||
|
||||
/// | \/
|
||||
/// | Parachain(1000)
|
||||
/// ```
|
||||
#[test]
|
||||
fn sending_to_parachain_of_bridged_chain_works() {
|
||||
let msg = Xcm(vec![Trap(1)]);
|
||||
let dest = (Parent, Remote::get(), Parachain(1000)).into();
|
||||
assert_eq!(send_xcm::<Router>(dest, msg).unwrap().1, (Here, 100).into());
|
||||
assert_eq!(TheBridge::service(), 1);
|
||||
let expected =
|
||||
vec![(Parachain(1000).into(), Xcm(vec![UniversalOrigin(Local::get().into()), Trap(1)]))];
|
||||
assert_eq!(take_received_remote_messages(), expected);
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
// Copyright 2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Tests specific to the bridging primitives
|
||||
|
||||
use super::mock::*;
|
||||
use crate::universal_exports::*;
|
||||
use frame_support::{parameter_types, traits::Get};
|
||||
use std::{cell::RefCell, marker::PhantomData};
|
||||
use xcm_executor::{
|
||||
traits::{export_xcm, validate_export},
|
||||
XcmExecutor,
|
||||
};
|
||||
use SendError::*;
|
||||
|
||||
mod local_para_para;
|
||||
mod local_relay_relay;
|
||||
mod paid_remote_relay_relay;
|
||||
mod remote_para_para;
|
||||
mod remote_para_para_via_relay;
|
||||
mod remote_relay_relay;
|
||||
|
||||
parameter_types! {
|
||||
pub Local: NetworkId = ByGenesis([0; 32]);
|
||||
pub Remote: NetworkId = ByGenesis([1; 32]);
|
||||
pub Price: MultiAssets = MultiAssets::from((Here, 100u128));
|
||||
}
|
||||
|
||||
std::thread_local! {
|
||||
static BRIDGE_TRAFFIC: RefCell<Vec<Vec<u8>>> = RefCell::new(Vec::new());
|
||||
}
|
||||
|
||||
struct TestBridge<D>(PhantomData<D>);
|
||||
impl<D: DispatchBlob> TestBridge<D> {
|
||||
fn service() -> u64 {
|
||||
BRIDGE_TRAFFIC
|
||||
.with(|t| t.borrow_mut().drain(..).map(|b| D::dispatch_blob(b).map_or(0, |()| 1)).sum())
|
||||
}
|
||||
}
|
||||
impl<D: DispatchBlob> HaulBlob for TestBridge<D> {
|
||||
fn haul_blob(blob: Vec<u8>) -> Result<(), HaulBlobError> {
|
||||
BRIDGE_TRAFFIC.with(|t| t.borrow_mut().push(blob));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
std::thread_local! {
|
||||
static REMOTE_INCOMING_XCM: RefCell<Vec<(MultiLocation, Xcm<()>)>> = RefCell::new(Vec::new());
|
||||
}
|
||||
struct TestRemoteIncomingRouter;
|
||||
impl SendXcm for TestRemoteIncomingRouter {
|
||||
type Ticket = (MultiLocation, Xcm<()>);
|
||||
fn validate(
|
||||
dest: &mut Option<MultiLocation>,
|
||||
msg: &mut Option<Xcm<()>>,
|
||||
) -> SendResult<(MultiLocation, Xcm<()>)> {
|
||||
let pair = (dest.take().unwrap(), msg.take().unwrap());
|
||||
Ok((pair, MultiAssets::new()))
|
||||
}
|
||||
fn deliver(pair: (MultiLocation, Xcm<()>)) -> Result<XcmHash, SendError> {
|
||||
let hash = fake_message_hash(&pair.1);
|
||||
REMOTE_INCOMING_XCM.with(|q| q.borrow_mut().push(pair));
|
||||
Ok(hash)
|
||||
}
|
||||
}
|
||||
|
||||
fn take_received_remote_messages() -> Vec<(MultiLocation, Xcm<()>)> {
|
||||
REMOTE_INCOMING_XCM.with(|r| r.replace(vec![]))
|
||||
}
|
||||
|
||||
/// This is a dummy router which accepts messages destined for `Remote` from `Local`
|
||||
/// and then executes them for free in a context simulated to be like that of our `Remote`.
|
||||
struct UnpaidExecutingRouter<Local, Remote, RemoteExporter>(
|
||||
PhantomData<(Local, Remote, RemoteExporter)>,
|
||||
);
|
||||
|
||||
fn price<RemoteExporter: ExportXcm>(
|
||||
n: NetworkId,
|
||||
c: u32,
|
||||
s: &InteriorMultiLocation,
|
||||
d: &InteriorMultiLocation,
|
||||
m: &Xcm<()>,
|
||||
) -> Result<MultiAssets, SendError> {
|
||||
Ok(validate_export::<RemoteExporter>(n, c, s.clone(), d.clone(), m.clone())?.1)
|
||||
}
|
||||
|
||||
fn deliver<RemoteExporter: ExportXcm>(
|
||||
n: NetworkId,
|
||||
c: u32,
|
||||
s: InteriorMultiLocation,
|
||||
d: InteriorMultiLocation,
|
||||
m: Xcm<()>,
|
||||
) -> Result<XcmHash, SendError> {
|
||||
export_xcm::<RemoteExporter>(n, c, s, d, m).map(|(hash, _)| hash)
|
||||
}
|
||||
|
||||
impl<Local: Get<Junctions>, Remote: Get<Junctions>, RemoteExporter: ExportXcm> SendXcm
|
||||
for UnpaidExecutingRouter<Local, Remote, RemoteExporter>
|
||||
{
|
||||
type Ticket = Xcm<()>;
|
||||
|
||||
fn validate(
|
||||
destination: &mut Option<MultiLocation>,
|
||||
message: &mut Option<Xcm<()>>,
|
||||
) -> SendResult<Xcm<()>> {
|
||||
let expect_dest = Remote::get().relative_to(&Local::get());
|
||||
if destination.as_ref().ok_or(MissingArgument)? != &expect_dest {
|
||||
return Err(NotApplicable)
|
||||
}
|
||||
let message = message.take().ok_or(MissingArgument)?;
|
||||
Ok((message, MultiAssets::new()))
|
||||
}
|
||||
|
||||
fn deliver(message: Xcm<()>) -> Result<XcmHash, SendError> {
|
||||
// We now pretend that the message was delivered from `Local` to `Remote`, and execute
|
||||
// so we need to ensure that the `TestConfig` is set up properly for executing as
|
||||
// though it is `Remote`.
|
||||
ExecutorUniversalLocation::set(Remote::get());
|
||||
let origin = Local::get().relative_to(&Remote::get());
|
||||
AllowUnpaidFrom::set(vec![origin.clone()]);
|
||||
set_exporter_override(price::<RemoteExporter>, deliver::<RemoteExporter>);
|
||||
// The we execute it:
|
||||
let hash = fake_message_hash(&message);
|
||||
let outcome = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
origin,
|
||||
message.into(),
|
||||
hash,
|
||||
Weight::from_parts(2_000_000_000_000, 2_000_000_000_000),
|
||||
);
|
||||
match outcome {
|
||||
Outcome::Complete(..) => Ok(hash),
|
||||
Outcome::Incomplete(..) => Err(Transport("Error executing")),
|
||||
Outcome::Error(..) => Err(Transport("Unable to execute")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This is a dummy router which accepts messages destined for `Remote` from `Local`
|
||||
/// and then executes them in a context simulated to be like that of our `Remote`. Payment is
|
||||
/// needed.
|
||||
struct ExecutingRouter<Local, Remote, RemoteExporter>(PhantomData<(Local, Remote, RemoteExporter)>);
|
||||
impl<Local: Get<Junctions>, Remote: Get<Junctions>, RemoteExporter: ExportXcm> SendXcm
|
||||
for ExecutingRouter<Local, Remote, RemoteExporter>
|
||||
{
|
||||
type Ticket = Xcm<()>;
|
||||
|
||||
fn validate(
|
||||
destination: &mut Option<MultiLocation>,
|
||||
message: &mut Option<Xcm<()>>,
|
||||
) -> SendResult<Xcm<()>> {
|
||||
let expect_dest = Remote::get().relative_to(&Local::get());
|
||||
if destination.as_ref().ok_or(MissingArgument)? != &expect_dest {
|
||||
return Err(NotApplicable)
|
||||
}
|
||||
let message = message.take().ok_or(MissingArgument)?;
|
||||
Ok((message, MultiAssets::new()))
|
||||
}
|
||||
|
||||
fn deliver(message: Xcm<()>) -> Result<XcmHash, SendError> {
|
||||
// We now pretend that the message was delivered from `Local` to `Remote`, and execute
|
||||
// so we need to ensure that the `TestConfig` is set up properly for executing as
|
||||
// though it is `Remote`.
|
||||
ExecutorUniversalLocation::set(Remote::get());
|
||||
let origin = Local::get().relative_to(&Remote::get());
|
||||
AllowPaidFrom::set(vec![origin.clone()]);
|
||||
set_exporter_override(price::<RemoteExporter>, deliver::<RemoteExporter>);
|
||||
// Then we execute it:
|
||||
let hash = fake_message_hash(&message);
|
||||
let outcome = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
origin,
|
||||
message.into(),
|
||||
hash,
|
||||
Weight::from_parts(2_000_000_000_000, 2_000_000_000_000),
|
||||
);
|
||||
match outcome {
|
||||
Outcome::Complete(..) => Ok(hash),
|
||||
Outcome::Incomplete(..) => Err(Transport("Error executing")),
|
||||
Outcome::Error(..) => Err(Transport("Unable to execute")),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright 2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! This test is when we're sending an XCM from a parachain whose relay-chain hosts a bridge to
|
||||
//! another relay-chain. The destination of the XCM is within the global consensus of the
|
||||
//! remote side of the bridge.
|
||||
//!
|
||||
//! The Relay-chain here requires payment by the parachain for use of the bridge. This is expressed
|
||||
//! under the standard XCM weight and the weight pricing.
|
||||
|
||||
use super::*;
|
||||
|
||||
parameter_types! {
|
||||
pub UniversalLocation: Junctions = X2(GlobalConsensus(Local::get()), Parachain(100));
|
||||
pub RelayUniversalLocation: Junctions = X1(GlobalConsensus(Local::get()));
|
||||
pub RemoteUniversalLocation: Junctions = X1(GlobalConsensus(Remote::get()));
|
||||
pub static BridgeTable: Vec<(NetworkId, MultiLocation, Option<MultiAsset>)>
|
||||
= vec![(Remote::get(), MultiLocation::parent(), Some((Parent, 200u128).into()))];
|
||||
// ^^^ 100 to use the bridge (export) and 100 for the remote execution weight (5 instructions
|
||||
// x (10 + 10) weight each).
|
||||
}
|
||||
type TheBridge =
|
||||
TestBridge<BridgeBlobDispatcher<TestRemoteIncomingRouter, RemoteUniversalLocation>>;
|
||||
type RelayExporter = HaulBlobExporter<TheBridge, Remote, Price>;
|
||||
type LocalInnerRouter = ExecutingRouter<UniversalLocation, RelayUniversalLocation, RelayExporter>;
|
||||
type LocalBridgeRouter = SovereignPaidRemoteExporter<
|
||||
NetworkExportTable<BridgeTable>,
|
||||
LocalInnerRouter,
|
||||
UniversalLocation,
|
||||
>;
|
||||
type LocalRouter = (LocalInnerRouter, LocalBridgeRouter);
|
||||
|
||||
/// ```nocompile
|
||||
/// local | remote
|
||||
/// |
|
||||
/// GlobalConsensus(Local::get()) ========> GlobalConsensus(Remote::get())
|
||||
/// /\ |
|
||||
/// || |
|
||||
/// || |
|
||||
/// || |
|
||||
/// Parachain(100) |
|
||||
/// ```
|
||||
#[test]
|
||||
fn sending_to_bridged_chain_works() {
|
||||
let dest: MultiLocation = (Parent, Parent, Remote::get()).into();
|
||||
// Routing won't work if we don't have enough funds.
|
||||
assert_eq!(
|
||||
send_xcm::<LocalRouter>(dest.clone(), Xcm(vec![Trap(1)])),
|
||||
Err(SendError::Transport("Error executing")),
|
||||
);
|
||||
|
||||
// Initialize the local relay so that our parachain has funds to pay for export.
|
||||
add_asset(Parachain(100), (Here, 1000u128));
|
||||
|
||||
let msg = Xcm(vec![Trap(1)]);
|
||||
assert_eq!(send_xcm::<LocalRouter>(dest, msg).unwrap().1, (Parent, 200u128).into());
|
||||
assert_eq!(TheBridge::service(), 1);
|
||||
assert_eq!(
|
||||
take_received_remote_messages(),
|
||||
vec![(
|
||||
Here.into(),
|
||||
Xcm(vec![
|
||||
UniversalOrigin(Local::get().into()),
|
||||
DescendOrigin(Parachain(100).into()),
|
||||
Trap(1),
|
||||
])
|
||||
)]
|
||||
);
|
||||
|
||||
// The export cost 50 ref time and 50 proof size weight units (and thus 100 units of balance).
|
||||
assert_eq!(asset_list(Parachain(100)), vec![(Here, 800u128).into()]);
|
||||
}
|
||||
|
||||
/// ```nocompile
|
||||
/// local | remote
|
||||
/// |
|
||||
/// GlobalConsensus(Local::get()) ========> GlobalConsensus(Remote::get())
|
||||
/// /\ | ||
|
||||
/// || | ||
|
||||
/// || | ||
|
||||
/// || | \/
|
||||
/// Parachain(100) | Parachain(100)
|
||||
/// ```
|
||||
#[test]
|
||||
fn sending_to_parachain_of_bridged_chain_works() {
|
||||
let dest: MultiLocation = (Parent, Parent, Remote::get(), Parachain(100)).into();
|
||||
// Routing won't work if we don't have enough funds.
|
||||
assert_eq!(
|
||||
send_xcm::<LocalRouter>(dest.clone(), Xcm(vec![Trap(1)])),
|
||||
Err(SendError::Transport("Error executing")),
|
||||
);
|
||||
|
||||
// Initialize the local relay so that our parachain has funds to pay for export.
|
||||
add_asset(Parachain(100), (Here, 1000u128));
|
||||
|
||||
let msg = Xcm(vec![Trap(1)]);
|
||||
assert_eq!(send_xcm::<LocalRouter>(dest, msg).unwrap().1, (Parent, 200u128).into());
|
||||
assert_eq!(TheBridge::service(), 1);
|
||||
let expected = vec![(
|
||||
Parachain(100).into(),
|
||||
Xcm(vec![
|
||||
UniversalOrigin(Local::get().into()),
|
||||
DescendOrigin(Parachain(100).into()),
|
||||
Trap(1),
|
||||
]),
|
||||
)];
|
||||
assert_eq!(take_received_remote_messages(), expected);
|
||||
|
||||
// The export cost 50 ref time and 50 proof size weight units (and thus 100 units of balance).
|
||||
assert_eq!(asset_list(Parachain(100)), vec![(Here, 800u128).into()]);
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright 2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! This test is when we're sending an XCM from a parachain whose sibling parachain hosts a
|
||||
//! bridge to a parachain from another global consensus. The destination of the XCM is within
|
||||
//! the global consensus of the remote side of the bridge.
|
||||
|
||||
use super::*;
|
||||
|
||||
parameter_types! {
|
||||
pub UniversalLocation: Junctions = X2(GlobalConsensus(Local::get()), Parachain(1000));
|
||||
pub ParaBridgeUniversalLocation: Junctions = X2(GlobalConsensus(Local::get()), Parachain(1));
|
||||
pub RemoteParaBridgeUniversalLocation: Junctions = X2(GlobalConsensus(Remote::get()), Parachain(1));
|
||||
pub BridgeTable: Vec<(NetworkId, MultiLocation, Option<MultiAsset>)>
|
||||
= vec![(Remote::get(), (Parent, Parachain(1)).into(), None)];
|
||||
}
|
||||
type TheBridge =
|
||||
TestBridge<BridgeBlobDispatcher<TestRemoteIncomingRouter, RemoteParaBridgeUniversalLocation>>;
|
||||
type RelayExporter = HaulBlobExporter<TheBridge, Remote, ()>;
|
||||
type LocalInnerRouter =
|
||||
UnpaidExecutingRouter<UniversalLocation, ParaBridgeUniversalLocation, RelayExporter>;
|
||||
type LocalBridgingRouter =
|
||||
UnpaidRemoteExporter<NetworkExportTable<BridgeTable>, LocalInnerRouter, UniversalLocation>;
|
||||
type LocalRouter = (LocalInnerRouter, LocalBridgingRouter);
|
||||
|
||||
/// ```nocompile
|
||||
/// local | remote
|
||||
/// |
|
||||
/// GlobalConsensus(Local::get()) | GlobalConsensus(Remote::get())
|
||||
/// |
|
||||
/// |
|
||||
/// |
|
||||
/// |
|
||||
/// Parachain(1000) ===> Parachain(1) ===> Parachain(1)
|
||||
/// ```
|
||||
#[test]
|
||||
fn sending_to_bridged_chain_works() {
|
||||
let msg = Xcm(vec![Trap(1)]);
|
||||
assert_eq!(
|
||||
send_xcm::<LocalRouter>((Parent, Parent, Remote::get(), Parachain(1)).into(), msg)
|
||||
.unwrap()
|
||||
.1,
|
||||
MultiAssets::new()
|
||||
);
|
||||
assert_eq!(TheBridge::service(), 1);
|
||||
assert_eq!(
|
||||
take_received_remote_messages(),
|
||||
vec![(
|
||||
Here.into(),
|
||||
Xcm(vec![
|
||||
UniversalOrigin(Local::get().into()),
|
||||
DescendOrigin(Parachain(1000).into()),
|
||||
Trap(1)
|
||||
])
|
||||
)]
|
||||
);
|
||||
}
|
||||
|
||||
/// ```nocompile
|
||||
/// local | remote
|
||||
/// |
|
||||
/// GlobalConsensus(Local::get()) | GlobalConsensus(Remote::get())
|
||||
/// |
|
||||
/// |
|
||||
/// |
|
||||
/// |
|
||||
/// Parachain(1000) ===> Parachain(1) ===> Parachain(1) ===> Parachain(1000)
|
||||
/// ```
|
||||
#[test]
|
||||
fn sending_to_sibling_of_bridged_chain_works() {
|
||||
let msg = Xcm(vec![Trap(1)]);
|
||||
let dest = (Parent, Parent, Remote::get(), Parachain(1000)).into();
|
||||
assert_eq!(send_xcm::<LocalRouter>(dest, msg).unwrap().1, MultiAssets::new());
|
||||
assert_eq!(TheBridge::service(), 1);
|
||||
let expected = vec![(
|
||||
(Parent, Parachain(1000)).into(),
|
||||
Xcm(vec![
|
||||
UniversalOrigin(Local::get().into()),
|
||||
DescendOrigin(Parachain(1000).into()),
|
||||
Trap(1),
|
||||
]),
|
||||
)];
|
||||
assert_eq!(take_received_remote_messages(), expected);
|
||||
}
|
||||
|
||||
/// ```nocompile
|
||||
/// local | remote
|
||||
/// |
|
||||
/// GlobalConsensus(Local::get()) | GlobalConsensus(Remote::get())
|
||||
/// | /\
|
||||
/// | ||
|
||||
/// | ||
|
||||
/// | ||
|
||||
/// Parachain(1000) ===> Parachain(1) ===> Parachain(1)
|
||||
/// ```
|
||||
#[test]
|
||||
fn sending_to_relay_of_bridged_chain_works() {
|
||||
let msg = Xcm(vec![Trap(1)]);
|
||||
let dest = (Parent, Parent, Remote::get()).into();
|
||||
assert_eq!(send_xcm::<LocalRouter>(dest, msg).unwrap().1, MultiAssets::new());
|
||||
assert_eq!(TheBridge::service(), 1);
|
||||
let expected = vec![(
|
||||
Parent.into(),
|
||||
Xcm(vec![
|
||||
UniversalOrigin(Local::get().into()),
|
||||
DescendOrigin(Parachain(1000).into()),
|
||||
Trap(1),
|
||||
]),
|
||||
)];
|
||||
assert_eq!(take_received_remote_messages(), expected);
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
// Copyright 2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! This test is when we're sending an XCM from a relay-chain whose child parachain hosts a
|
||||
//! bridge to a parachain from another global consensus. The destination of the XCM is within
|
||||
//! the global consensus of the remote side of the bridge.
|
||||
|
||||
use super::*;
|
||||
|
||||
parameter_types! {
|
||||
pub UniversalLocation: Junctions = X1(GlobalConsensus(Local::get()));
|
||||
pub ParaBridgeUniversalLocation: Junctions = X2(GlobalConsensus(Local::get()), Parachain(1));
|
||||
pub RemoteParaBridgeUniversalLocation: Junctions = X2(GlobalConsensus(Remote::get()), Parachain(1));
|
||||
pub BridgeTable: Vec<(NetworkId, MultiLocation, Option<MultiAsset>)>
|
||||
= vec![(Remote::get(), Parachain(1).into(), None)];
|
||||
}
|
||||
type TheBridge =
|
||||
TestBridge<BridgeBlobDispatcher<TestRemoteIncomingRouter, RemoteParaBridgeUniversalLocation>>;
|
||||
type RelayExporter = HaulBlobExporter<TheBridge, Remote, ()>;
|
||||
type LocalInnerRouter =
|
||||
UnpaidExecutingRouter<UniversalLocation, ParaBridgeUniversalLocation, RelayExporter>;
|
||||
type LocalBridgingRouter =
|
||||
UnpaidRemoteExporter<NetworkExportTable<BridgeTable>, LocalInnerRouter, UniversalLocation>;
|
||||
type LocalRouter = (LocalInnerRouter, LocalBridgingRouter);
|
||||
|
||||
/// ```nocompile
|
||||
/// local | remote
|
||||
/// |
|
||||
/// GlobalConsensus(Local::get()) | GlobalConsensus(Remote::get())
|
||||
/// || |
|
||||
/// || |
|
||||
/// || |
|
||||
/// \/ |
|
||||
/// Parachain(1) ===> Parachain(1)
|
||||
/// ```
|
||||
#[test]
|
||||
fn sending_to_bridged_chain_works() {
|
||||
let msg = Xcm(vec![Trap(1)]);
|
||||
assert_eq!(
|
||||
send_xcm::<LocalRouter>((Parent, Remote::get(), Parachain(1)).into(), msg)
|
||||
.unwrap()
|
||||
.1,
|
||||
MultiAssets::new()
|
||||
);
|
||||
assert_eq!(TheBridge::service(), 1);
|
||||
assert_eq!(
|
||||
take_received_remote_messages(),
|
||||
vec![(Here.into(), Xcm(vec![UniversalOrigin(Local::get().into()), Trap(1)]))]
|
||||
);
|
||||
}
|
||||
|
||||
/// ```nocompile
|
||||
/// local | remote
|
||||
/// |
|
||||
/// GlobalConsensus(Local::get()) | GlobalConsensus(Remote::get())
|
||||
/// || |
|
||||
/// || |
|
||||
/// || |
|
||||
/// \/ |
|
||||
/// Parachain(1) ===> Parachain(1) ===> Parachain(1000)
|
||||
/// ```
|
||||
#[test]
|
||||
fn sending_to_sibling_of_bridged_chain_works() {
|
||||
let msg = Xcm(vec![Trap(1)]);
|
||||
let dest = (Parent, Remote::get(), Parachain(1000)).into();
|
||||
assert_eq!(send_xcm::<LocalRouter>(dest, msg).unwrap().1, MultiAssets::new());
|
||||
assert_eq!(TheBridge::service(), 1);
|
||||
let expected = vec![(
|
||||
(Parent, Parachain(1000)).into(),
|
||||
Xcm(vec![UniversalOrigin(Local::get().into()), Trap(1)]),
|
||||
)];
|
||||
assert_eq!(take_received_remote_messages(), expected);
|
||||
}
|
||||
|
||||
/// ```nocompile
|
||||
/// local | remote
|
||||
/// |
|
||||
/// GlobalConsensus(Local::get()) | GlobalConsensus(Remote::get())
|
||||
/// || | /\
|
||||
/// || | ||
|
||||
/// || | ||
|
||||
/// \/ | ||
|
||||
/// Parachain(1) ===> Parachain(1)
|
||||
/// ```
|
||||
#[test]
|
||||
fn sending_to_relay_of_bridged_chain_works() {
|
||||
let msg = Xcm(vec![Trap(1)]);
|
||||
let dest = (Parent, Remote::get()).into();
|
||||
assert_eq!(send_xcm::<LocalRouter>(dest, msg).unwrap().1, MultiAssets::new());
|
||||
assert_eq!(TheBridge::service(), 1);
|
||||
let expected = vec![(Parent.into(), Xcm(vec![UniversalOrigin(Local::get().into()), Trap(1)]))];
|
||||
assert_eq!(take_received_remote_messages(), expected);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright 2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! This test is when we're sending an XCM from a parachain whose relay-chain hosts a bridge to
|
||||
//! another relay-chain. The destination of the XCM is within the global consensus of the
|
||||
//! remote side of the bridge.
|
||||
|
||||
use super::*;
|
||||
|
||||
parameter_types! {
|
||||
pub UniversalLocation: Junctions = X2(GlobalConsensus(Local::get()), Parachain(1000));
|
||||
pub RelayUniversalLocation: Junctions = X1(GlobalConsensus(Local::get()));
|
||||
pub RemoteUniversalLocation: Junctions = X1(GlobalConsensus(Remote::get()));
|
||||
pub BridgeTable: Vec<(NetworkId, MultiLocation, Option<MultiAsset>)>
|
||||
= vec![(Remote::get(), MultiLocation::parent(), None)];
|
||||
}
|
||||
type TheBridge =
|
||||
TestBridge<BridgeBlobDispatcher<TestRemoteIncomingRouter, RemoteUniversalLocation>>;
|
||||
type RelayExporter = HaulBlobExporter<TheBridge, Remote, ()>;
|
||||
type LocalInnerRouter =
|
||||
UnpaidExecutingRouter<UniversalLocation, RelayUniversalLocation, RelayExporter>;
|
||||
type LocalBridgeRouter =
|
||||
UnpaidRemoteExporter<NetworkExportTable<BridgeTable>, LocalInnerRouter, UniversalLocation>;
|
||||
type LocalRouter = (LocalInnerRouter, LocalBridgeRouter);
|
||||
|
||||
/// ```nocompile
|
||||
/// local | remote
|
||||
/// |
|
||||
/// GlobalConsensus(Local::get()) ========> GlobalConsensus(Remote::get())
|
||||
/// /\ |
|
||||
/// || |
|
||||
/// || |
|
||||
/// || |
|
||||
/// Parachain(1000) |
|
||||
/// ```
|
||||
#[test]
|
||||
fn sending_to_bridged_chain_works() {
|
||||
let msg = Xcm(vec![Trap(1)]);
|
||||
assert_eq!(
|
||||
send_xcm::<LocalRouter>((Parent, Parent, Remote::get()).into(), msg).unwrap().1,
|
||||
MultiAssets::new()
|
||||
);
|
||||
assert_eq!(TheBridge::service(), 1);
|
||||
assert_eq!(
|
||||
take_received_remote_messages(),
|
||||
vec![(
|
||||
Here.into(),
|
||||
Xcm(vec![
|
||||
UniversalOrigin(Local::get().into()),
|
||||
DescendOrigin(Parachain(1000).into()),
|
||||
Trap(1)
|
||||
])
|
||||
)]
|
||||
);
|
||||
}
|
||||
|
||||
/// ```nocompile
|
||||
/// local | remote
|
||||
/// |
|
||||
/// GlobalConsensus(Local::get()) ========> GlobalConsensus(Remote::get())
|
||||
/// /\ | ||
|
||||
/// || | ||
|
||||
/// || | ||
|
||||
/// || | \/
|
||||
/// Parachain(1000) | Parachain(1000)
|
||||
/// ```
|
||||
#[test]
|
||||
fn sending_to_parachain_of_bridged_chain_works() {
|
||||
let msg = Xcm(vec![Trap(1)]);
|
||||
let dest = (Parent, Parent, Remote::get(), Parachain(1000)).into();
|
||||
assert_eq!(send_xcm::<LocalRouter>(dest, msg).unwrap().1, MultiAssets::new());
|
||||
assert_eq!(TheBridge::service(), 1);
|
||||
let expected = vec![(
|
||||
Parachain(1000).into(),
|
||||
Xcm(vec![
|
||||
UniversalOrigin(Local::get().into()),
|
||||
DescendOrigin(Parachain(1000).into()),
|
||||
Trap(1),
|
||||
]),
|
||||
)];
|
||||
assert_eq!(take_received_remote_messages(), expected);
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
// Copyright 2022 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn expect_pallet_should_work() {
|
||||
AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]);
|
||||
// They want to transfer 100 of our native asset from sovereign account of parachain #1 into #2
|
||||
// and let them know to hand it to account #3.
|
||||
let message = Xcm(vec![ExpectPallet {
|
||||
index: 1,
|
||||
name: b"Balances".as_ref().into(),
|
||||
module_name: b"pallet_balances".as_ref().into(),
|
||||
crate_major: 1,
|
||||
min_crate_minor: 42,
|
||||
}]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(50, 50),
|
||||
);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10)));
|
||||
|
||||
let message = Xcm(vec![ExpectPallet {
|
||||
index: 1,
|
||||
name: b"Balances".as_ref().into(),
|
||||
module_name: b"pallet_balances".as_ref().into(),
|
||||
crate_major: 1,
|
||||
min_crate_minor: 41,
|
||||
}]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(50, 50),
|
||||
);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expect_pallet_should_fail_correctly() {
|
||||
AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]);
|
||||
let message = Xcm(vec![ExpectPallet {
|
||||
index: 1,
|
||||
name: b"Balances".as_ref().into(),
|
||||
module_name: b"pallet_balances".as_ref().into(),
|
||||
crate_major: 1,
|
||||
min_crate_minor: 60,
|
||||
}]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(50, 50),
|
||||
);
|
||||
assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::VersionIncompatible));
|
||||
|
||||
let message = Xcm(vec![ExpectPallet {
|
||||
index: 1,
|
||||
name: b"System".as_ref().into(),
|
||||
module_name: b"pallet_balances".as_ref().into(),
|
||||
crate_major: 1,
|
||||
min_crate_minor: 42,
|
||||
}]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(50, 50),
|
||||
);
|
||||
assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::NameMismatch));
|
||||
|
||||
let message = Xcm(vec![ExpectPallet {
|
||||
index: 1,
|
||||
name: b"Balances".as_ref().into(),
|
||||
module_name: b"pallet_system".as_ref().into(),
|
||||
crate_major: 1,
|
||||
min_crate_minor: 42,
|
||||
}]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(50, 50),
|
||||
);
|
||||
assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::NameMismatch));
|
||||
|
||||
let message = Xcm(vec![ExpectPallet {
|
||||
index: 0,
|
||||
name: b"Balances".as_ref().into(),
|
||||
module_name: b"pallet_balances".as_ref().into(),
|
||||
crate_major: 1,
|
||||
min_crate_minor: 42,
|
||||
}]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(50, 50),
|
||||
);
|
||||
assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::NameMismatch));
|
||||
|
||||
let message = Xcm(vec![ExpectPallet {
|
||||
index: 2,
|
||||
name: b"Balances".as_ref().into(),
|
||||
module_name: b"pallet_balances".as_ref().into(),
|
||||
crate_major: 1,
|
||||
min_crate_minor: 42,
|
||||
}]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(50, 50),
|
||||
);
|
||||
assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::PalletNotFound));
|
||||
|
||||
let message = Xcm(vec![ExpectPallet {
|
||||
index: 1,
|
||||
name: b"Balances".as_ref().into(),
|
||||
module_name: b"pallet_balances".as_ref().into(),
|
||||
crate_major: 2,
|
||||
min_crate_minor: 42,
|
||||
}]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(50, 50),
|
||||
);
|
||||
assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::VersionIncompatible));
|
||||
|
||||
let message = Xcm(vec![ExpectPallet {
|
||||
index: 1,
|
||||
name: b"Balances".as_ref().into(),
|
||||
module_name: b"pallet_balances".as_ref().into(),
|
||||
crate_major: 0,
|
||||
min_crate_minor: 42,
|
||||
}]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(50, 50),
|
||||
);
|
||||
assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::VersionIncompatible));
|
||||
|
||||
let message = Xcm(vec![ExpectPallet {
|
||||
index: 1,
|
||||
name: b"Balances".as_ref().into(),
|
||||
module_name: b"pallet_balances".as_ref().into(),
|
||||
crate_major: 1,
|
||||
min_crate_minor: 43,
|
||||
}]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(50, 50),
|
||||
);
|
||||
assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::VersionIncompatible));
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
// Copyright 2022 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
use LockTraceItem::*;
|
||||
|
||||
#[test]
|
||||
fn lock_roundtrip_should_work() {
|
||||
// Account #3 and Parachain #1 can execute for free
|
||||
AllowUnpaidFrom::set(vec![(3u64,).into(), (Parent, Parachain(1)).into()]);
|
||||
// Account #3 owns 1000 native parent tokens.
|
||||
add_asset((3u64,), (Parent, 1000u128));
|
||||
// Sending a message costs 10 parent-tokens.
|
||||
set_send_price((Parent, 10u128));
|
||||
|
||||
// They want to lock 100 of the native parent tokens to be unlocked only by Parachain #1.
|
||||
let message = Xcm(vec![
|
||||
WithdrawAsset((Parent, 100u128).into()),
|
||||
SetAppendix(
|
||||
vec![DepositAsset { assets: AllCounted(2).into(), beneficiary: (3u64,).into() }].into(),
|
||||
),
|
||||
LockAsset { asset: (Parent, 100u128).into(), unlocker: (Parent, Parachain(1)).into() },
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r =
|
||||
XcmExecutor::<TestConfig>::execute_xcm((3u64,), message, hash, Weight::from_parts(50, 50));
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(40, 40)));
|
||||
assert_eq!(asset_list((3u64,)), vec![(Parent, 990u128).into()]);
|
||||
|
||||
let expected_msg = Xcm::<()>(vec![NoteUnlockable {
|
||||
owner: (Parent, Parachain(42), 3u64).into(),
|
||||
asset: (Parent, 100u128).into(),
|
||||
}]);
|
||||
let expected_hash = fake_message_hash(&expected_msg);
|
||||
assert_eq!(sent_xcm(), vec![((Parent, Parachain(1)).into(), expected_msg, expected_hash)]);
|
||||
assert_eq!(
|
||||
take_lock_trace(),
|
||||
vec![Lock {
|
||||
asset: (Parent, 100u128).into(),
|
||||
owner: (3u64,).into(),
|
||||
unlocker: (Parent, Parachain(1)).into(),
|
||||
}]
|
||||
);
|
||||
|
||||
// Now we'll unlock it.
|
||||
let message =
|
||||
Xcm(vec![UnlockAsset { asset: (Parent, 100u128).into(), target: (3u64,).into() }]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
(Parent, Parachain(1)),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(50, 50),
|
||||
);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_fee_paying_should_work() {
|
||||
// Account #3 and Parachain #1 can execute for free
|
||||
AllowUnpaidFrom::set(vec![(3u64,).into()]);
|
||||
// Account #3 owns 1000 native parent tokens.
|
||||
add_asset((3u64,), (Parent, 1000u128));
|
||||
// Sending a message costs 10 parent-tokens.
|
||||
set_send_price((Parent, 10u128));
|
||||
|
||||
// They want to lock 100 of the native parent tokens to be unlocked only by Parachain #1.
|
||||
let message = Xcm(vec![
|
||||
SetFeesMode { jit_withdraw: true },
|
||||
LockAsset { asset: (Parent, 100u128).into(), unlocker: (Parent, Parachain(1)).into() },
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r =
|
||||
XcmExecutor::<TestConfig>::execute_xcm((3u64,), message, hash, Weight::from_parts(50, 50));
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(20, 20)));
|
||||
assert_eq!(asset_list((3u64,)), vec![(Parent, 990u128).into()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lock_should_fail_correctly() {
|
||||
// Account #3 can execute for free
|
||||
AllowUnpaidFrom::set(vec![(3u64,).into(), (Parent, Parachain(1)).into()]);
|
||||
|
||||
// #3 wants to lock 100 of the native parent tokens to be unlocked only by parachain ../#1,
|
||||
// but they don't have any.
|
||||
let message = Xcm(vec![LockAsset {
|
||||
asset: (Parent, 100u128).into(),
|
||||
unlocker: (Parent, Parachain(1)).into(),
|
||||
}]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r =
|
||||
XcmExecutor::<TestConfig>::execute_xcm((3u64,), message, hash, Weight::from_parts(50, 50));
|
||||
assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::LockError));
|
||||
assert_eq!(sent_xcm(), vec![]);
|
||||
assert_eq!(take_lock_trace(), vec![]);
|
||||
|
||||
// Account #3 owns 1000 native parent tokens.
|
||||
add_asset((3u64,), (Parent, 1000u128));
|
||||
// But we require a price to be paid for the sending
|
||||
set_send_price((Parent, 10u128));
|
||||
|
||||
// #3 wants to lock 100 of the native parent tokens to be unlocked only by parachain ../#1,
|
||||
// but there's nothing to pay the fees for sending the notification message.
|
||||
let message = Xcm(vec![LockAsset {
|
||||
asset: (Parent, 100u128).into(),
|
||||
unlocker: (Parent, Parachain(1)).into(),
|
||||
}]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r =
|
||||
XcmExecutor::<TestConfig>::execute_xcm((3u64,), message, hash, Weight::from_parts(50, 50));
|
||||
assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::NotHoldingFees));
|
||||
assert_eq!(sent_xcm(), vec![]);
|
||||
assert_eq!(take_lock_trace(), vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remote_unlock_roundtrip_should_work() {
|
||||
// Account #3 can execute for free
|
||||
AllowUnpaidFrom::set(vec![(3u64,).into(), (Parent, Parachain(1)).into()]);
|
||||
// Account #3 owns 1000 native parent tokens.
|
||||
add_asset((3u64,), (Parent, 1000u128));
|
||||
// Sending a message costs 10 parent-tokens.
|
||||
set_send_price((Parent, 10u128));
|
||||
|
||||
// We have been told by Parachain #1 that Account #3 has locked funds which we can unlock.
|
||||
let message =
|
||||
Xcm(vec![NoteUnlockable { asset: (Parent, 100u128).into(), owner: (3u64,).into() }]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
(Parent, Parachain(1)),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(50, 50),
|
||||
);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10)));
|
||||
assert_eq!(
|
||||
take_lock_trace(),
|
||||
vec![Note {
|
||||
asset: (Parent, 100u128).into(),
|
||||
owner: (3u64,).into(),
|
||||
locker: (Parent, Parachain(1)).into(),
|
||||
}]
|
||||
);
|
||||
|
||||
// Let's request those funds be unlocked.
|
||||
let message = Xcm(vec![
|
||||
WithdrawAsset((Parent, 100u128).into()),
|
||||
SetAppendix(
|
||||
vec![DepositAsset { assets: AllCounted(2).into(), beneficiary: (3u64,).into() }].into(),
|
||||
),
|
||||
RequestUnlock { asset: (Parent, 100u128).into(), locker: (Parent, Parachain(1)).into() },
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r =
|
||||
XcmExecutor::<TestConfig>::execute_xcm((3u64,), message, hash, Weight::from_parts(50, 50));
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(40, 40)));
|
||||
assert_eq!(asset_list((3u64,)), vec![(Parent, 990u128).into()]);
|
||||
|
||||
let expected_msg =
|
||||
Xcm::<()>(vec![UnlockAsset { target: (3u64,).into(), asset: (Parent, 100u128).into() }]);
|
||||
let expected_hash = fake_message_hash(&expected_msg);
|
||||
assert_eq!(sent_xcm(), vec![((Parent, Parachain(1)).into(), expected_msg, expected_hash)]);
|
||||
assert_eq!(
|
||||
take_lock_trace(),
|
||||
vec![Reduce {
|
||||
asset: (Parent, 100u128).into(),
|
||||
owner: (3u64,).into(),
|
||||
locker: (Parent, Parachain(1)).into(),
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remote_unlock_should_fail_correctly() {
|
||||
// Account #3 can execute for free
|
||||
AllowUnpaidFrom::set(vec![(3u64,).into(), (Parent, Parachain(1)).into()]);
|
||||
// But we require a price to be paid for the sending
|
||||
set_send_price((Parent, 10u128));
|
||||
|
||||
// We want to unlock 100 of the native parent tokens which were locked for us on parachain.
|
||||
// This won't work as we don't have any record of them being locked for us.
|
||||
// No message will be sent and no lock records changed.
|
||||
let message = Xcm(vec![RequestUnlock {
|
||||
asset: (Parent, 100u128).into(),
|
||||
locker: (Parent, Parachain(1)).into(),
|
||||
}]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r =
|
||||
XcmExecutor::<TestConfig>::execute_xcm((3u64,), message, hash, Weight::from_parts(50, 50));
|
||||
assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::LockError));
|
||||
assert_eq!(sent_xcm(), vec![]);
|
||||
assert_eq!(take_lock_trace(), vec![]);
|
||||
|
||||
// We have been told by Parachain #1 that Account #3 has locked funds which we can unlock.
|
||||
let message =
|
||||
Xcm(vec![NoteUnlockable { asset: (Parent, 100u128).into(), owner: (3u64,).into() }]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
(Parent, Parachain(1)),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(50, 50),
|
||||
);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10)));
|
||||
let _discard = take_lock_trace();
|
||||
|
||||
// We want to unlock 100 of the native parent tokens which were locked for us on parachain.
|
||||
// This won't work now as we don't have the funds to send the onward message.
|
||||
// No message will be sent and no lock records changed.
|
||||
let message = Xcm(vec![RequestUnlock {
|
||||
asset: (Parent, 100u128).into(),
|
||||
locker: (Parent, Parachain(1)).into(),
|
||||
}]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r =
|
||||
XcmExecutor::<TestConfig>::execute_xcm((3u64,), message, hash, Weight::from_parts(50, 50));
|
||||
assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::NotHoldingFees));
|
||||
|
||||
assert_eq!(sent_xcm(), vec![]);
|
||||
assert_eq!(take_lock_trace(), vec![]);
|
||||
}
|
||||
@@ -0,0 +1,656 @@
|
||||
// Copyright 2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{barriers::AllowSubscriptionsFrom, test_utils::*};
|
||||
pub use crate::{
|
||||
AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowTopLevelPaidExecutionFrom,
|
||||
AllowUnpaidExecutionFrom, FixedRateOfFungible, FixedWeightBounds, TakeWeightCredit,
|
||||
};
|
||||
use frame_support::traits::{ContainsPair, Everything};
|
||||
pub use frame_support::{
|
||||
dispatch::{
|
||||
DispatchError, DispatchInfo, DispatchResultWithPostInfo, Dispatchable, GetDispatchInfo,
|
||||
Parameter, PostDispatchInfo,
|
||||
},
|
||||
ensure, parameter_types,
|
||||
sp_runtime::DispatchErrorWithPostInfo,
|
||||
traits::{Contains, Get, IsInVec},
|
||||
};
|
||||
pub use parity_scale_codec::{Decode, Encode};
|
||||
pub use sp_io::hashing::blake2_256;
|
||||
pub use sp_std::{
|
||||
cell::RefCell,
|
||||
collections::{btree_map::BTreeMap, btree_set::BTreeSet},
|
||||
fmt::Debug,
|
||||
marker::PhantomData,
|
||||
};
|
||||
pub use xcm::latest::{prelude::*, Weight};
|
||||
pub use xcm_executor::{
|
||||
traits::{
|
||||
AssetExchange, AssetLock, ConvertOrigin, Enact, ExportXcm, FeeManager, FeeReason,
|
||||
LockError, OnResponse, TransactAsset,
|
||||
},
|
||||
Assets, Config,
|
||||
};
|
||||
|
||||
pub enum TestOrigin {
|
||||
Root,
|
||||
Relay,
|
||||
Signed(u64),
|
||||
Parachain(u32),
|
||||
}
|
||||
|
||||
/// A dummy call.
|
||||
///
|
||||
/// Each item contains the amount of weight that it *wants* to consume as the first item, and the actual amount (if
|
||||
/// different from the former) in the second option.
|
||||
#[derive(Debug, Encode, Decode, Eq, PartialEq, Clone, Copy, scale_info::TypeInfo)]
|
||||
pub enum TestCall {
|
||||
OnlyRoot(Weight, Option<Weight>),
|
||||
OnlyParachain(Weight, Option<Weight>, Option<u32>),
|
||||
OnlySigned(Weight, Option<Weight>, Option<u64>),
|
||||
Any(Weight, Option<Weight>),
|
||||
}
|
||||
impl Dispatchable for TestCall {
|
||||
type RuntimeOrigin = TestOrigin;
|
||||
type Config = ();
|
||||
type Info = ();
|
||||
type PostInfo = PostDispatchInfo;
|
||||
fn dispatch(self, origin: Self::RuntimeOrigin) -> DispatchResultWithPostInfo {
|
||||
let mut post_info = PostDispatchInfo::default();
|
||||
let maybe_actual = match self {
|
||||
TestCall::OnlyRoot(_, maybe_actual) |
|
||||
TestCall::OnlySigned(_, maybe_actual, _) |
|
||||
TestCall::OnlyParachain(_, maybe_actual, _) |
|
||||
TestCall::Any(_, maybe_actual) => maybe_actual,
|
||||
};
|
||||
post_info.actual_weight = maybe_actual;
|
||||
if match (&origin, &self) {
|
||||
(TestOrigin::Parachain(i), TestCall::OnlyParachain(_, _, Some(j))) => i == j,
|
||||
(TestOrigin::Signed(i), TestCall::OnlySigned(_, _, Some(j))) => i == j,
|
||||
(TestOrigin::Root, TestCall::OnlyRoot(..)) |
|
||||
(TestOrigin::Parachain(_), TestCall::OnlyParachain(_, _, None)) |
|
||||
(TestOrigin::Signed(_), TestCall::OnlySigned(_, _, None)) |
|
||||
(_, TestCall::Any(..)) => true,
|
||||
_ => false,
|
||||
} {
|
||||
Ok(post_info)
|
||||
} else {
|
||||
Err(DispatchErrorWithPostInfo { error: DispatchError::BadOrigin, post_info })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GetDispatchInfo for TestCall {
|
||||
fn get_dispatch_info(&self) -> DispatchInfo {
|
||||
let weight = *match self {
|
||||
TestCall::OnlyRoot(estimate, ..) |
|
||||
TestCall::OnlyParachain(estimate, ..) |
|
||||
TestCall::OnlySigned(estimate, ..) |
|
||||
TestCall::Any(estimate, ..) => estimate,
|
||||
};
|
||||
DispatchInfo { weight, ..Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
pub static SENT_XCM: RefCell<Vec<(MultiLocation, Xcm<()>, XcmHash)>> = RefCell::new(Vec::new());
|
||||
pub static EXPORTED_XCM: RefCell<
|
||||
Vec<(NetworkId, u32, InteriorMultiLocation, InteriorMultiLocation, Xcm<()>, XcmHash)>
|
||||
> = RefCell::new(Vec::new());
|
||||
pub static EXPORTER_OVERRIDE: RefCell<Option<(
|
||||
fn(
|
||||
NetworkId,
|
||||
u32,
|
||||
&InteriorMultiLocation,
|
||||
&InteriorMultiLocation,
|
||||
&Xcm<()>,
|
||||
) -> Result<MultiAssets, SendError>,
|
||||
fn(
|
||||
NetworkId,
|
||||
u32,
|
||||
InteriorMultiLocation,
|
||||
InteriorMultiLocation,
|
||||
Xcm<()>,
|
||||
) -> Result<XcmHash, SendError>,
|
||||
)>> = RefCell::new(None);
|
||||
pub static SEND_PRICE: RefCell<MultiAssets> = RefCell::new(MultiAssets::new());
|
||||
}
|
||||
pub fn sent_xcm() -> Vec<(MultiLocation, opaque::Xcm, XcmHash)> {
|
||||
SENT_XCM.with(|q| (*q.borrow()).clone())
|
||||
}
|
||||
pub fn set_send_price(p: impl Into<MultiAsset>) {
|
||||
SEND_PRICE.with(|l| l.replace(p.into().into()));
|
||||
}
|
||||
pub fn exported_xcm(
|
||||
) -> Vec<(NetworkId, u32, InteriorMultiLocation, InteriorMultiLocation, opaque::Xcm, XcmHash)> {
|
||||
EXPORTED_XCM.with(|q| (*q.borrow()).clone())
|
||||
}
|
||||
pub fn set_exporter_override(
|
||||
price: fn(
|
||||
NetworkId,
|
||||
u32,
|
||||
&InteriorMultiLocation,
|
||||
&InteriorMultiLocation,
|
||||
&Xcm<()>,
|
||||
) -> Result<MultiAssets, SendError>,
|
||||
deliver: fn(
|
||||
NetworkId,
|
||||
u32,
|
||||
InteriorMultiLocation,
|
||||
InteriorMultiLocation,
|
||||
Xcm<()>,
|
||||
) -> Result<XcmHash, SendError>,
|
||||
) {
|
||||
EXPORTER_OVERRIDE.with(|x| x.replace(Some((price, deliver))));
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn clear_exporter_override() {
|
||||
EXPORTER_OVERRIDE.with(|x| x.replace(None));
|
||||
}
|
||||
pub struct TestMessageSender;
|
||||
impl SendXcm for TestMessageSender {
|
||||
type Ticket = (MultiLocation, Xcm<()>, XcmHash);
|
||||
fn validate(
|
||||
dest: &mut Option<MultiLocation>,
|
||||
msg: &mut Option<Xcm<()>>,
|
||||
) -> SendResult<(MultiLocation, Xcm<()>, XcmHash)> {
|
||||
let msg = msg.take().unwrap();
|
||||
let hash = fake_message_hash(&msg);
|
||||
let triplet = (dest.take().unwrap(), msg, hash);
|
||||
Ok((triplet, SEND_PRICE.with(|l| l.borrow().clone())))
|
||||
}
|
||||
fn deliver(triplet: (MultiLocation, Xcm<()>, XcmHash)) -> Result<XcmHash, SendError> {
|
||||
let hash = triplet.2;
|
||||
SENT_XCM.with(|q| q.borrow_mut().push(triplet));
|
||||
Ok(hash)
|
||||
}
|
||||
}
|
||||
pub struct TestMessageExporter;
|
||||
impl ExportXcm for TestMessageExporter {
|
||||
type Ticket = (NetworkId, u32, InteriorMultiLocation, InteriorMultiLocation, Xcm<()>, XcmHash);
|
||||
fn validate(
|
||||
network: NetworkId,
|
||||
channel: u32,
|
||||
uni_src: &mut Option<InteriorMultiLocation>,
|
||||
dest: &mut Option<InteriorMultiLocation>,
|
||||
msg: &mut Option<Xcm<()>>,
|
||||
) -> SendResult<(NetworkId, u32, InteriorMultiLocation, InteriorMultiLocation, Xcm<()>, XcmHash)>
|
||||
{
|
||||
let (s, d, m) = (uni_src.take().unwrap(), dest.take().unwrap(), msg.take().unwrap());
|
||||
let r: Result<MultiAssets, SendError> = EXPORTER_OVERRIDE.with(|e| {
|
||||
if let Some((ref f, _)) = &*e.borrow() {
|
||||
f(network, channel, &s, &d, &m)
|
||||
} else {
|
||||
Ok(MultiAssets::new())
|
||||
}
|
||||
});
|
||||
let h = fake_message_hash(&m);
|
||||
match r {
|
||||
Ok(price) => Ok(((network, channel, s, d, m, h), price)),
|
||||
Err(e) => {
|
||||
*uni_src = Some(s);
|
||||
*dest = Some(d);
|
||||
*msg = Some(m);
|
||||
Err(e)
|
||||
},
|
||||
}
|
||||
}
|
||||
fn deliver(
|
||||
tuple: (NetworkId, u32, InteriorMultiLocation, InteriorMultiLocation, Xcm<()>, XcmHash),
|
||||
) -> Result<XcmHash, SendError> {
|
||||
EXPORTER_OVERRIDE.with(|e| {
|
||||
if let Some((_, ref f)) = &*e.borrow() {
|
||||
let (network, channel, uni_src, dest, msg, _hash) = tuple;
|
||||
f(network, channel, uni_src, dest, msg)
|
||||
} else {
|
||||
let hash = tuple.5;
|
||||
EXPORTED_XCM.with(|q| q.borrow_mut().push(tuple));
|
||||
Ok(hash)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
pub static ASSETS: RefCell<BTreeMap<MultiLocation, Assets>> = RefCell::new(BTreeMap::new());
|
||||
}
|
||||
pub fn assets(who: impl Into<MultiLocation>) -> Assets {
|
||||
ASSETS.with(|a| a.borrow().get(&who.into()).cloned()).unwrap_or_default()
|
||||
}
|
||||
pub fn asset_list(who: impl Into<MultiLocation>) -> Vec<MultiAsset> {
|
||||
MultiAssets::from(assets(who)).into_inner()
|
||||
}
|
||||
pub fn add_asset(who: impl Into<MultiLocation>, what: impl Into<MultiAsset>) {
|
||||
ASSETS.with(|a| a.borrow_mut().entry(who.into()).or_insert(Assets::new()).subsume(what.into()));
|
||||
}
|
||||
|
||||
pub struct TestAssetTransactor;
|
||||
impl TransactAsset for TestAssetTransactor {
|
||||
fn deposit_asset(
|
||||
what: &MultiAsset,
|
||||
who: &MultiLocation,
|
||||
_context: &XcmContext,
|
||||
) -> Result<(), XcmError> {
|
||||
add_asset(who.clone(), what.clone());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn withdraw_asset(
|
||||
what: &MultiAsset,
|
||||
who: &MultiLocation,
|
||||
_maybe_context: Option<&XcmContext>,
|
||||
) -> Result<Assets, XcmError> {
|
||||
ASSETS.with(|a| {
|
||||
a.borrow_mut()
|
||||
.get_mut(who)
|
||||
.ok_or(XcmError::NotWithdrawable)?
|
||||
.try_take(what.clone().into())
|
||||
.map_err(|_| XcmError::NotWithdrawable)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_account(l: impl Into<MultiLocation>) -> Result<u64, MultiLocation> {
|
||||
Ok(match l.into() {
|
||||
// Siblings at 2000+id
|
||||
MultiLocation { parents: 1, interior: X1(Parachain(id)) } => 2000 + id as u64,
|
||||
// Accounts are their number
|
||||
MultiLocation { parents: 0, interior: X1(AccountIndex64 { index, .. }) } => index,
|
||||
// Children at 1000+id
|
||||
MultiLocation { parents: 0, interior: X1(Parachain(id)) } => 1000 + id as u64,
|
||||
// Self at 3000
|
||||
MultiLocation { parents: 0, interior: Here } => 3000,
|
||||
// Parent at 3001
|
||||
MultiLocation { parents: 1, interior: Here } => 3001,
|
||||
l => {
|
||||
// Is it a foreign-consensus?
|
||||
let uni = ExecutorUniversalLocation::get();
|
||||
if l.parents as usize != uni.len() {
|
||||
return Err(l)
|
||||
}
|
||||
match l.first_interior() {
|
||||
Some(GlobalConsensus(Kusama)) => 4000,
|
||||
Some(GlobalConsensus(Polkadot)) => 4001,
|
||||
_ => return Err(l),
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub struct TestOriginConverter;
|
||||
impl ConvertOrigin<TestOrigin> for TestOriginConverter {
|
||||
fn convert_origin(
|
||||
origin: impl Into<MultiLocation>,
|
||||
kind: OriginKind,
|
||||
) -> Result<TestOrigin, MultiLocation> {
|
||||
use OriginKind::*;
|
||||
match (kind, origin.into()) {
|
||||
(Superuser, _) => Ok(TestOrigin::Root),
|
||||
(SovereignAccount, l) => Ok(TestOrigin::Signed(to_account(l)?)),
|
||||
(Native, MultiLocation { parents: 0, interior: X1(Parachain(id)) }) =>
|
||||
Ok(TestOrigin::Parachain(id)),
|
||||
(Native, MultiLocation { parents: 1, interior: Here }) => Ok(TestOrigin::Relay),
|
||||
(Native, MultiLocation { parents: 0, interior: X1(AccountIndex64 { index, .. }) }) =>
|
||||
Ok(TestOrigin::Signed(index)),
|
||||
(_, origin) => Err(origin),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
pub static IS_RESERVE: RefCell<BTreeMap<MultiLocation, Vec<MultiAssetFilter>>> = RefCell::new(BTreeMap::new());
|
||||
pub static IS_TELEPORTER: RefCell<BTreeMap<MultiLocation, Vec<MultiAssetFilter>>> = RefCell::new(BTreeMap::new());
|
||||
pub static UNIVERSAL_ALIASES: RefCell<BTreeSet<(MultiLocation, Junction)>> = RefCell::new(BTreeSet::new());
|
||||
}
|
||||
pub fn add_reserve(from: MultiLocation, asset: MultiAssetFilter) {
|
||||
IS_RESERVE.with(|r| r.borrow_mut().entry(from).or_default().push(asset));
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn add_teleporter(from: MultiLocation, asset: MultiAssetFilter) {
|
||||
IS_TELEPORTER.with(|r| r.borrow_mut().entry(from).or_default().push(asset));
|
||||
}
|
||||
pub fn add_universal_alias(bridge: impl Into<MultiLocation>, consensus: impl Into<Junction>) {
|
||||
UNIVERSAL_ALIASES.with(|r| r.borrow_mut().insert((bridge.into(), consensus.into())));
|
||||
}
|
||||
pub fn clear_universal_aliases() {
|
||||
UNIVERSAL_ALIASES.with(|r| r.replace(Default::default()));
|
||||
}
|
||||
|
||||
pub struct TestIsReserve;
|
||||
impl ContainsPair<MultiAsset, MultiLocation> for TestIsReserve {
|
||||
fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool {
|
||||
IS_RESERVE
|
||||
.with(|r| r.borrow().get(origin).map_or(false, |v| v.iter().any(|a| a.matches(asset))))
|
||||
}
|
||||
}
|
||||
pub struct TestIsTeleporter;
|
||||
impl ContainsPair<MultiAsset, MultiLocation> for TestIsTeleporter {
|
||||
fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool {
|
||||
IS_TELEPORTER
|
||||
.with(|r| r.borrow().get(origin).map_or(false, |v| v.iter().any(|a| a.matches(asset))))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestUniversalAliases;
|
||||
impl Contains<(MultiLocation, Junction)> for TestUniversalAliases {
|
||||
fn contains(t: &(MultiLocation, Junction)) -> bool {
|
||||
UNIVERSAL_ALIASES.with(|r| r.borrow().contains(t))
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ResponseSlot {
|
||||
Expecting(MultiLocation),
|
||||
Received(Response),
|
||||
}
|
||||
thread_local! {
|
||||
pub static QUERIES: RefCell<BTreeMap<u64, ResponseSlot>> = RefCell::new(BTreeMap::new());
|
||||
}
|
||||
pub struct TestResponseHandler;
|
||||
impl OnResponse for TestResponseHandler {
|
||||
fn expecting_response(
|
||||
origin: &MultiLocation,
|
||||
query_id: u64,
|
||||
_querier: Option<&MultiLocation>,
|
||||
) -> bool {
|
||||
QUERIES.with(|q| match q.borrow().get(&query_id) {
|
||||
Some(ResponseSlot::Expecting(ref l)) => l == origin,
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
fn on_response(
|
||||
_origin: &MultiLocation,
|
||||
query_id: u64,
|
||||
_querier: Option<&MultiLocation>,
|
||||
response: xcm::latest::Response,
|
||||
_max_weight: Weight,
|
||||
_context: &XcmContext,
|
||||
) -> Weight {
|
||||
QUERIES.with(|q| {
|
||||
q.borrow_mut().entry(query_id).and_modify(|v| {
|
||||
if matches!(*v, ResponseSlot::Expecting(..)) {
|
||||
*v = ResponseSlot::Received(response);
|
||||
}
|
||||
});
|
||||
});
|
||||
Weight::from_parts(10, 10)
|
||||
}
|
||||
}
|
||||
pub fn expect_response(query_id: u64, from: MultiLocation) {
|
||||
QUERIES.with(|q| q.borrow_mut().insert(query_id, ResponseSlot::Expecting(from)));
|
||||
}
|
||||
pub fn response(query_id: u64) -> Option<Response> {
|
||||
QUERIES.with(|q| {
|
||||
q.borrow().get(&query_id).and_then(|v| match v {
|
||||
ResponseSlot::Received(r) => Some(r.clone()),
|
||||
_ => None,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static ExecutorUniversalLocation: InteriorMultiLocation
|
||||
= (ByGenesis([0; 32]), Parachain(42)).into();
|
||||
pub UnitWeightCost: Weight = Weight::from_parts(10, 10);
|
||||
}
|
||||
parameter_types! {
|
||||
// Nothing is allowed to be paid/unpaid by default.
|
||||
pub static AllowExplicitUnpaidFrom: Vec<MultiLocation> = vec![];
|
||||
pub static AllowUnpaidFrom: Vec<MultiLocation> = vec![];
|
||||
pub static AllowPaidFrom: Vec<MultiLocation> = vec![];
|
||||
pub static AllowSubsFrom: Vec<MultiLocation> = vec![];
|
||||
// 1_000_000_000_000 => 1 unit of asset for 1 unit of ref time weight.
|
||||
// 1024 * 1024 => 1 unit of asset for 1 unit of proof size weight.
|
||||
pub static WeightPrice: (AssetId, u128, u128) =
|
||||
(From::from(Here), 1_000_000_000_000, 1024 * 1024);
|
||||
pub static MaxInstructions: u32 = 100;
|
||||
}
|
||||
|
||||
pub type TestBarrier = (
|
||||
TakeWeightCredit,
|
||||
AllowKnownQueryResponses<TestResponseHandler>,
|
||||
AllowTopLevelPaidExecutionFrom<IsInVec<AllowPaidFrom>>,
|
||||
AllowExplicitUnpaidExecutionFrom<IsInVec<AllowExplicitUnpaidFrom>>,
|
||||
AllowUnpaidExecutionFrom<IsInVec<AllowUnpaidFrom>>,
|
||||
AllowSubscriptionsFrom<IsInVec<AllowSubsFrom>>,
|
||||
);
|
||||
|
||||
thread_local! {
|
||||
pub static IS_WAIVED: RefCell<Vec<FeeReason>> = RefCell::new(vec![]);
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn set_fee_waiver(waived: Vec<FeeReason>) {
|
||||
IS_WAIVED.with(|l| l.replace(waived));
|
||||
}
|
||||
|
||||
pub struct TestFeeManager;
|
||||
impl FeeManager for TestFeeManager {
|
||||
fn is_waived(_: Option<&MultiLocation>, r: FeeReason) -> bool {
|
||||
IS_WAIVED.with(|l| l.borrow().contains(&r))
|
||||
}
|
||||
fn handle_fee(_: MultiAssets) {}
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||
pub enum LockTraceItem {
|
||||
Lock { unlocker: MultiLocation, asset: MultiAsset, owner: MultiLocation },
|
||||
Unlock { unlocker: MultiLocation, asset: MultiAsset, owner: MultiLocation },
|
||||
Note { locker: MultiLocation, asset: MultiAsset, owner: MultiLocation },
|
||||
Reduce { locker: MultiLocation, asset: MultiAsset, owner: MultiLocation },
|
||||
}
|
||||
thread_local! {
|
||||
pub static NEXT_INDEX: RefCell<u32> = RefCell::new(0);
|
||||
pub static LOCK_TRACE: RefCell<Vec<LockTraceItem>> = RefCell::new(Vec::new());
|
||||
pub static ALLOWED_UNLOCKS: RefCell<BTreeMap<(MultiLocation, MultiLocation), Assets>> = RefCell::new(BTreeMap::new());
|
||||
pub static ALLOWED_REQUEST_UNLOCKS: RefCell<BTreeMap<(MultiLocation, MultiLocation), Assets>> = RefCell::new(BTreeMap::new());
|
||||
}
|
||||
|
||||
pub fn take_lock_trace() -> Vec<LockTraceItem> {
|
||||
LOCK_TRACE.with(|l| l.replace(Vec::new()))
|
||||
}
|
||||
pub fn allow_unlock(
|
||||
unlocker: impl Into<MultiLocation>,
|
||||
asset: impl Into<MultiAsset>,
|
||||
owner: impl Into<MultiLocation>,
|
||||
) {
|
||||
ALLOWED_UNLOCKS.with(|l| {
|
||||
l.borrow_mut()
|
||||
.entry((owner.into(), unlocker.into()))
|
||||
.or_default()
|
||||
.subsume(asset.into())
|
||||
});
|
||||
}
|
||||
pub fn disallow_unlock(
|
||||
unlocker: impl Into<MultiLocation>,
|
||||
asset: impl Into<MultiAsset>,
|
||||
owner: impl Into<MultiLocation>,
|
||||
) {
|
||||
ALLOWED_UNLOCKS.with(|l| {
|
||||
l.borrow_mut()
|
||||
.entry((owner.into(), unlocker.into()))
|
||||
.or_default()
|
||||
.saturating_take(asset.into().into())
|
||||
});
|
||||
}
|
||||
pub fn unlock_allowed(unlocker: &MultiLocation, asset: &MultiAsset, owner: &MultiLocation) -> bool {
|
||||
ALLOWED_UNLOCKS.with(|l| {
|
||||
l.borrow_mut()
|
||||
.get(&(owner.clone(), unlocker.clone()))
|
||||
.map_or(false, |x| x.contains_asset(asset))
|
||||
})
|
||||
}
|
||||
pub fn allow_request_unlock(
|
||||
locker: impl Into<MultiLocation>,
|
||||
asset: impl Into<MultiAsset>,
|
||||
owner: impl Into<MultiLocation>,
|
||||
) {
|
||||
ALLOWED_REQUEST_UNLOCKS.with(|l| {
|
||||
l.borrow_mut()
|
||||
.entry((owner.into(), locker.into()))
|
||||
.or_default()
|
||||
.subsume(asset.into())
|
||||
});
|
||||
}
|
||||
pub fn disallow_request_unlock(
|
||||
locker: impl Into<MultiLocation>,
|
||||
asset: impl Into<MultiAsset>,
|
||||
owner: impl Into<MultiLocation>,
|
||||
) {
|
||||
ALLOWED_REQUEST_UNLOCKS.with(|l| {
|
||||
l.borrow_mut()
|
||||
.entry((owner.into(), locker.into()))
|
||||
.or_default()
|
||||
.saturating_take(asset.into().into())
|
||||
});
|
||||
}
|
||||
pub fn request_unlock_allowed(
|
||||
locker: &MultiLocation,
|
||||
asset: &MultiAsset,
|
||||
owner: &MultiLocation,
|
||||
) -> bool {
|
||||
ALLOWED_REQUEST_UNLOCKS.with(|l| {
|
||||
l.borrow_mut()
|
||||
.get(&(owner.clone(), locker.clone()))
|
||||
.map_or(false, |x| x.contains_asset(asset))
|
||||
})
|
||||
}
|
||||
|
||||
pub struct TestTicket(LockTraceItem);
|
||||
impl Enact for TestTicket {
|
||||
fn enact(self) -> Result<(), LockError> {
|
||||
match &self.0 {
|
||||
LockTraceItem::Lock { unlocker, asset, owner } =>
|
||||
allow_unlock(unlocker.clone(), asset.clone(), owner.clone()),
|
||||
LockTraceItem::Unlock { unlocker, asset, owner } =>
|
||||
disallow_unlock(unlocker.clone(), asset.clone(), owner.clone()),
|
||||
LockTraceItem::Reduce { locker, asset, owner } =>
|
||||
disallow_request_unlock(locker.clone(), asset.clone(), owner.clone()),
|
||||
_ => {},
|
||||
}
|
||||
LOCK_TRACE.with(move |l| l.borrow_mut().push(self.0));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestAssetLock;
|
||||
impl AssetLock for TestAssetLock {
|
||||
type LockTicket = TestTicket;
|
||||
type UnlockTicket = TestTicket;
|
||||
type ReduceTicket = TestTicket;
|
||||
|
||||
fn prepare_lock(
|
||||
unlocker: MultiLocation,
|
||||
asset: MultiAsset,
|
||||
owner: MultiLocation,
|
||||
) -> Result<Self::LockTicket, LockError> {
|
||||
ensure!(assets(owner.clone()).contains_asset(&asset), LockError::AssetNotOwned);
|
||||
Ok(TestTicket(LockTraceItem::Lock { unlocker, asset, owner }))
|
||||
}
|
||||
|
||||
fn prepare_unlock(
|
||||
unlocker: MultiLocation,
|
||||
asset: MultiAsset,
|
||||
owner: MultiLocation,
|
||||
) -> Result<Self::UnlockTicket, LockError> {
|
||||
ensure!(unlock_allowed(&unlocker, &asset, &owner), LockError::NotLocked);
|
||||
Ok(TestTicket(LockTraceItem::Unlock { unlocker, asset, owner }))
|
||||
}
|
||||
|
||||
fn note_unlockable(
|
||||
locker: MultiLocation,
|
||||
asset: MultiAsset,
|
||||
owner: MultiLocation,
|
||||
) -> Result<(), LockError> {
|
||||
allow_request_unlock(locker.clone(), asset.clone(), owner.clone());
|
||||
let item = LockTraceItem::Note { locker, asset, owner };
|
||||
LOCK_TRACE.with(move |l| l.borrow_mut().push(item));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn prepare_reduce_unlockable(
|
||||
locker: MultiLocation,
|
||||
asset: MultiAsset,
|
||||
owner: MultiLocation,
|
||||
) -> Result<Self::ReduceTicket, xcm_executor::traits::LockError> {
|
||||
ensure!(request_unlock_allowed(&locker, &asset, &owner), LockError::NotLocked);
|
||||
Ok(TestTicket(LockTraceItem::Reduce { locker, asset, owner }))
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
pub static EXCHANGE_ASSETS: RefCell<Assets> = RefCell::new(Assets::new());
|
||||
}
|
||||
pub fn set_exchange_assets(assets: impl Into<MultiAssets>) {
|
||||
EXCHANGE_ASSETS.with(|a| a.replace(assets.into().into()));
|
||||
}
|
||||
pub fn exchange_assets() -> MultiAssets {
|
||||
EXCHANGE_ASSETS.with(|a| a.borrow().clone().into())
|
||||
}
|
||||
pub struct TestAssetExchange;
|
||||
impl AssetExchange for TestAssetExchange {
|
||||
fn exchange_asset(
|
||||
_origin: Option<&MultiLocation>,
|
||||
give: Assets,
|
||||
want: &MultiAssets,
|
||||
maximal: bool,
|
||||
) -> Result<Assets, Assets> {
|
||||
let mut have = EXCHANGE_ASSETS.with(|l| l.borrow().clone());
|
||||
ensure!(have.contains_assets(want), give);
|
||||
let get = if maximal {
|
||||
std::mem::replace(&mut have, Assets::new())
|
||||
} else {
|
||||
have.saturating_take(want.clone().into())
|
||||
};
|
||||
have.subsume_assets(give);
|
||||
EXCHANGE_ASSETS.with(|l| l.replace(have));
|
||||
Ok(get)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestConfig;
|
||||
impl Config for TestConfig {
|
||||
type RuntimeCall = TestCall;
|
||||
type XcmSender = TestMessageSender;
|
||||
type AssetTransactor = TestAssetTransactor;
|
||||
type OriginConverter = TestOriginConverter;
|
||||
type IsReserve = TestIsReserve;
|
||||
type IsTeleporter = TestIsTeleporter;
|
||||
type UniversalLocation = ExecutorUniversalLocation;
|
||||
type Barrier = TestBarrier;
|
||||
type Weigher = FixedWeightBounds<UnitWeightCost, TestCall, MaxInstructions>;
|
||||
type Trader = FixedRateOfFungible<WeightPrice, ()>;
|
||||
type ResponseHandler = TestResponseHandler;
|
||||
type AssetTrap = TestAssetTrap;
|
||||
type AssetLocker = TestAssetLock;
|
||||
type AssetExchanger = TestAssetExchange;
|
||||
type AssetClaims = TestAssetTrap;
|
||||
type SubscriptionService = TestSubscriptionService;
|
||||
type PalletInstancesInfo = TestPalletsInfo;
|
||||
type MaxAssetsIntoHolding = MaxAssetsIntoHolding;
|
||||
type FeeManager = TestFeeManager;
|
||||
type UniversalAliases = TestUniversalAliases;
|
||||
type MessageExporter = TestMessageExporter;
|
||||
type CallDispatcher = TestCall;
|
||||
type SafeCallFilter = Everything;
|
||||
}
|
||||
|
||||
pub fn fungible_multi_asset(location: MultiLocation, amount: u128) -> MultiAsset {
|
||||
(AssetId::from(location), Fungibility::Fungible(amount)).into()
|
||||
}
|
||||
|
||||
pub fn fake_message_hash<T>(message: &Xcm<T>) -> XcmHash {
|
||||
message.using_encoded(sp_io::hashing::blake2_256)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright 2022 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::{test_utils::*, *};
|
||||
use core::convert::TryInto;
|
||||
use frame_support::{
|
||||
assert_err,
|
||||
traits::{ConstU32, ContainsPair},
|
||||
weights::constants::{WEIGHT_PROOF_SIZE_PER_MB, WEIGHT_REF_TIME_PER_SECOND},
|
||||
};
|
||||
use xcm_executor::{traits::prelude::*, Config, XcmExecutor};
|
||||
|
||||
mod mock;
|
||||
use mock::*;
|
||||
|
||||
mod assets;
|
||||
mod barriers;
|
||||
mod basic;
|
||||
mod bridging;
|
||||
mod expecting;
|
||||
mod locking;
|
||||
mod origins;
|
||||
mod querying;
|
||||
mod transacting;
|
||||
mod version_subscriptions;
|
||||
mod weight;
|
||||
@@ -0,0 +1,140 @@
|
||||
// Copyright 2022 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn universal_origin_should_work() {
|
||||
AllowUnpaidFrom::set(vec![X1(Parachain(1)).into(), X1(Parachain(2)).into()]);
|
||||
clear_universal_aliases();
|
||||
// Parachain 1 may represent Kusama to us
|
||||
add_universal_alias(Parachain(1), Kusama);
|
||||
// Parachain 2 may represent Polkadot to us
|
||||
add_universal_alias(Parachain(2), Polkadot);
|
||||
|
||||
let message = Xcm(vec![
|
||||
UniversalOrigin(GlobalConsensus(Kusama)),
|
||||
TransferAsset { assets: (Parent, 100u128).into(), beneficiary: Here.into() },
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(2),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(50, 50),
|
||||
);
|
||||
assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::InvalidLocation));
|
||||
|
||||
let message = Xcm(vec![
|
||||
UniversalOrigin(GlobalConsensus(Kusama)),
|
||||
TransferAsset { assets: (Parent, 100u128).into(), beneficiary: Here.into() },
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(50, 50),
|
||||
);
|
||||
assert_eq!(r, Outcome::Incomplete(Weight::from_parts(20, 20), XcmError::NotWithdrawable));
|
||||
|
||||
add_asset((Ancestor(2), GlobalConsensus(Kusama)), (Parent, 100));
|
||||
let message = Xcm(vec![
|
||||
UniversalOrigin(GlobalConsensus(Kusama)),
|
||||
TransferAsset { assets: (Parent, 100u128).into(), beneficiary: Here.into() },
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(50, 50),
|
||||
);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(20, 20)));
|
||||
assert_eq!(asset_list((Ancestor(2), GlobalConsensus(Kusama))), vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn export_message_should_work() {
|
||||
// Bridge chain (assumed to be Relay) lets Parachain #1 have message execution for free.
|
||||
AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]);
|
||||
// Local parachain #1 issues a transfer asset on Polkadot Relay-chain, transfering 100 Planck to
|
||||
// Polkadot parachain #2.
|
||||
let expected_message = Xcm(vec![TransferAsset {
|
||||
assets: (Here, 100u128).into(),
|
||||
beneficiary: Parachain(2).into(),
|
||||
}]);
|
||||
let expected_hash = fake_message_hash(&expected_message);
|
||||
let message = Xcm(vec![ExportMessage {
|
||||
network: Polkadot,
|
||||
destination: Here,
|
||||
xcm: expected_message.clone(),
|
||||
}]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(50, 50),
|
||||
);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10)));
|
||||
let uni_src = (ByGenesis([0; 32]), Parachain(42), Parachain(1)).into();
|
||||
assert_eq!(
|
||||
exported_xcm(),
|
||||
vec![(Polkadot, 403611790, uni_src, Here, expected_message, expected_hash)]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unpaid_execution_should_work() {
|
||||
// Bridge chain (assumed to be Relay) lets Parachain #1 have message execution for free.
|
||||
AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]);
|
||||
// Bridge chain (assumed to be Relay) lets Parachain #2 have message execution for free if it
|
||||
// asks.
|
||||
AllowExplicitUnpaidFrom::set(vec![X1(Parachain(2)).into()]);
|
||||
// Asking for unpaid execution of up to 9 weight on the assumption it is origin of #2.
|
||||
let message = Xcm(vec![UnpaidExecution {
|
||||
weight_limit: Limited(Weight::from_parts(9, 9)),
|
||||
check_origin: Some(Parachain(2).into()),
|
||||
}]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
message.clone(),
|
||||
hash,
|
||||
Weight::from_parts(50, 50),
|
||||
);
|
||||
assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::BadOrigin));
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(2),
|
||||
message.clone(),
|
||||
hash,
|
||||
Weight::from_parts(50, 50),
|
||||
);
|
||||
assert_eq!(r, Outcome::Error(XcmError::Barrier));
|
||||
|
||||
let message = Xcm(vec![UnpaidExecution {
|
||||
weight_limit: Limited(Weight::from_parts(10, 10)),
|
||||
check_origin: Some(Parachain(2).into()),
|
||||
}]);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(2),
|
||||
message.clone(),
|
||||
hash,
|
||||
Weight::from_parts(50, 50),
|
||||
);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10)));
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
// Copyright 2022 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn pallet_query_should_work() {
|
||||
AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]);
|
||||
// They want to transfer 100 of our native asset from sovereign account of parachain #1 into #2
|
||||
// and let them know to hand it to account #3.
|
||||
let message = Xcm(vec![QueryPallet {
|
||||
module_name: "Error".into(),
|
||||
response_info: QueryResponseInfo {
|
||||
destination: Parachain(1).into(),
|
||||
query_id: 1,
|
||||
max_weight: Weight::from_parts(50, 50),
|
||||
},
|
||||
}]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(50, 50),
|
||||
);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10)));
|
||||
|
||||
let expected_msg = Xcm::<()>(vec![QueryResponse {
|
||||
query_id: 1,
|
||||
max_weight: Weight::from_parts(50, 50),
|
||||
response: Response::PalletsInfo(vec![].try_into().unwrap()),
|
||||
querier: Some(Here.into()),
|
||||
}]);
|
||||
let expected_hash = fake_message_hash(&expected_msg);
|
||||
assert_eq!(sent_xcm(), vec![(Parachain(1).into(), expected_msg, expected_hash)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pallet_query_with_results_should_work() {
|
||||
AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]);
|
||||
// They want to transfer 100 of our native asset from sovereign account of parachain #1 into #2
|
||||
// and let them know to hand it to account #3.
|
||||
let message = Xcm(vec![QueryPallet {
|
||||
module_name: "pallet_balances".into(),
|
||||
response_info: QueryResponseInfo {
|
||||
destination: Parachain(1).into(),
|
||||
query_id: 1,
|
||||
max_weight: Weight::from_parts(50, 50),
|
||||
},
|
||||
}]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(
|
||||
Parachain(1),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(50, 50),
|
||||
);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10)));
|
||||
|
||||
let expected_msg = Xcm::<()>(vec![QueryResponse {
|
||||
query_id: 1,
|
||||
max_weight: Weight::from_parts(50, 50),
|
||||
response: Response::PalletsInfo(
|
||||
vec![PalletInfo::new(
|
||||
1,
|
||||
b"Balances".as_ref().into(),
|
||||
b"pallet_balances".as_ref().into(),
|
||||
1,
|
||||
42,
|
||||
69,
|
||||
)
|
||||
.unwrap()]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
),
|
||||
querier: Some(Here.into()),
|
||||
}]);
|
||||
let expected_hash = fake_message_hash(&expected_msg);
|
||||
assert_eq!(sent_xcm(), vec![(Parachain(1).into(), expected_msg, expected_hash)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prepaid_result_of_query_should_get_free_execution() {
|
||||
let query_id = 33;
|
||||
// We put this in manually here, but normally this would be done at the point of crafting the message.
|
||||
expect_response(query_id, Parent.into());
|
||||
|
||||
let the_response = Response::Assets((Parent, 100u128).into());
|
||||
let message = Xcm::<TestCall>(vec![QueryResponse {
|
||||
query_id,
|
||||
response: the_response.clone(),
|
||||
max_weight: Weight::from_parts(10, 10),
|
||||
querier: Some(Here.into()),
|
||||
}]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let weight_limit = Weight::from_parts(10, 10);
|
||||
|
||||
// First time the response gets through since we're expecting it...
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Parent, message.clone(), hash, weight_limit);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10)));
|
||||
assert_eq!(response(query_id).unwrap(), the_response);
|
||||
|
||||
// Second time it doesn't, since we're not.
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Parent, message.clone(), hash, weight_limit);
|
||||
assert_eq!(r, Outcome::Error(XcmError::Barrier));
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
// Copyright 2022 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn transacting_should_work() {
|
||||
AllowUnpaidFrom::set(vec![Parent.into()]);
|
||||
|
||||
let message = Xcm::<TestCall>(vec![Transact {
|
||||
origin_kind: OriginKind::Native,
|
||||
require_weight_at_most: Weight::from_parts(50, 50),
|
||||
call: TestCall::Any(Weight::from_parts(50, 50), None).encode().into(),
|
||||
}]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let weight_limit = Weight::from_parts(60, 60);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Parent, message, hash, weight_limit);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(60, 60)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transacting_should_respect_max_weight_requirement() {
|
||||
AllowUnpaidFrom::set(vec![Parent.into()]);
|
||||
|
||||
let message = Xcm::<TestCall>(vec![Transact {
|
||||
origin_kind: OriginKind::Native,
|
||||
require_weight_at_most: Weight::from_parts(40, 40),
|
||||
call: TestCall::Any(Weight::from_parts(50, 50), None).encode().into(),
|
||||
}]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let weight_limit = Weight::from_parts(60, 60);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Parent, message, hash, weight_limit);
|
||||
assert_eq!(r, Outcome::Incomplete(Weight::from_parts(50, 50), XcmError::MaxWeightInvalid));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transacting_should_refund_weight() {
|
||||
AllowUnpaidFrom::set(vec![Parent.into()]);
|
||||
|
||||
let message = Xcm::<TestCall>(vec![Transact {
|
||||
origin_kind: OriginKind::Native,
|
||||
require_weight_at_most: Weight::from_parts(50, 50),
|
||||
call: TestCall::Any(Weight::from_parts(50, 50), Some(Weight::from_parts(30, 30)))
|
||||
.encode()
|
||||
.into(),
|
||||
}]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let weight_limit = Weight::from_parts(60, 60);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Parent, message, hash, weight_limit);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(40, 40)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn paid_transacting_should_refund_payment_for_unused_weight() {
|
||||
let one: MultiLocation = AccountIndex64 { index: 1, network: None }.into();
|
||||
AllowPaidFrom::set(vec![one.clone()]);
|
||||
add_asset(AccountIndex64 { index: 1, network: None }, (Parent, 200u128));
|
||||
WeightPrice::set((Parent.into(), 1_000_000_000_000, 1024 * 1024));
|
||||
|
||||
let origin = one.clone();
|
||||
let fees = (Parent, 200u128).into();
|
||||
let message = Xcm::<TestCall>(vec![
|
||||
WithdrawAsset((Parent, 200u128).into()), // enough for 200 units of weight.
|
||||
BuyExecution { fees, weight_limit: Limited(Weight::from_parts(100, 100)) },
|
||||
Transact {
|
||||
origin_kind: OriginKind::Native,
|
||||
require_weight_at_most: Weight::from_parts(50, 50),
|
||||
// call estimated at 50 but only takes 10.
|
||||
call: TestCall::Any(Weight::from_parts(50, 50), Some(Weight::from_parts(10, 10)))
|
||||
.encode()
|
||||
.into(),
|
||||
},
|
||||
RefundSurplus,
|
||||
DepositAsset { assets: AllCounted(1).into(), beneficiary: one.clone() },
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let weight_limit = Weight::from_parts(100, 100);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(origin, message, hash, weight_limit);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(60, 60)));
|
||||
assert_eq!(
|
||||
asset_list(AccountIndex64 { index: 1, network: None }),
|
||||
vec![(Parent, 80u128).into()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn report_successful_transact_status_should_work() {
|
||||
AllowUnpaidFrom::set(vec![Parent.into()]);
|
||||
|
||||
let message = Xcm::<TestCall>(vec![
|
||||
Transact {
|
||||
origin_kind: OriginKind::Native,
|
||||
require_weight_at_most: Weight::from_parts(50, 50),
|
||||
call: TestCall::Any(Weight::from_parts(50, 50), None).encode().into(),
|
||||
},
|
||||
ReportTransactStatus(QueryResponseInfo {
|
||||
destination: Parent.into(),
|
||||
query_id: 42,
|
||||
max_weight: Weight::from_parts(5000, 5000),
|
||||
}),
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let weight_limit = Weight::from_parts(70, 70);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Parent, message, hash, weight_limit);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(70, 70)));
|
||||
let expected_msg = Xcm(vec![QueryResponse {
|
||||
response: Response::DispatchResult(MaybeErrorCode::Success),
|
||||
query_id: 42,
|
||||
max_weight: Weight::from_parts(5000, 5000),
|
||||
querier: Some(Here.into()),
|
||||
}]);
|
||||
let expected_hash = fake_message_hash(&expected_msg);
|
||||
assert_eq!(sent_xcm(), vec![(Parent.into(), expected_msg, expected_hash)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn report_failed_transact_status_should_work() {
|
||||
AllowUnpaidFrom::set(vec![Parent.into()]);
|
||||
|
||||
let message = Xcm::<TestCall>(vec![
|
||||
Transact {
|
||||
origin_kind: OriginKind::Native,
|
||||
require_weight_at_most: Weight::from_parts(50, 50),
|
||||
call: TestCall::OnlyRoot(Weight::from_parts(50, 50), None).encode().into(),
|
||||
},
|
||||
ReportTransactStatus(QueryResponseInfo {
|
||||
destination: Parent.into(),
|
||||
query_id: 42,
|
||||
max_weight: Weight::from_parts(5000, 5000),
|
||||
}),
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let weight_limit = Weight::from_parts(70, 70);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Parent, message, hash, weight_limit);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(70, 70)));
|
||||
let expected_msg = Xcm(vec![QueryResponse {
|
||||
response: Response::DispatchResult(MaybeErrorCode::Error(vec![2])),
|
||||
query_id: 42,
|
||||
max_weight: Weight::from_parts(5000, 5000),
|
||||
querier: Some(Here.into()),
|
||||
}]);
|
||||
let expected_hash = fake_message_hash(&expected_msg);
|
||||
assert_eq!(sent_xcm(), vec![(Parent.into(), expected_msg, expected_hash)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear_transact_status_should_work() {
|
||||
AllowUnpaidFrom::set(vec![Parent.into()]);
|
||||
|
||||
let message = Xcm::<TestCall>(vec![
|
||||
Transact {
|
||||
origin_kind: OriginKind::Native,
|
||||
require_weight_at_most: Weight::from_parts(50, 50),
|
||||
call: TestCall::OnlyRoot(Weight::from_parts(50, 50), None).encode().into(),
|
||||
},
|
||||
ClearTransactStatus,
|
||||
ReportTransactStatus(QueryResponseInfo {
|
||||
destination: Parent.into(),
|
||||
query_id: 42,
|
||||
max_weight: Weight::from_parts(5000, 5000),
|
||||
}),
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let weight_limit = Weight::from_parts(80, 80);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Parent, message, hash, weight_limit);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(80, 80)));
|
||||
let expected_msg = Xcm(vec![QueryResponse {
|
||||
response: Response::DispatchResult(MaybeErrorCode::Success),
|
||||
query_id: 42,
|
||||
max_weight: Weight::from_parts(5000, 5000),
|
||||
querier: Some(Here.into()),
|
||||
}]);
|
||||
let expected_hash = fake_message_hash(&expected_msg);
|
||||
assert_eq!(sent_xcm(), vec![(Parent.into(), expected_msg, expected_hash)]);
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
// Copyright 2022 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn simple_version_subscriptions_should_work() {
|
||||
AllowSubsFrom::set(vec![Parent.into()]);
|
||||
|
||||
let origin = Parachain(1000);
|
||||
let message = Xcm::<TestCall>(vec![
|
||||
SetAppendix(Xcm(vec![])),
|
||||
SubscribeVersion { query_id: 42, max_response_weight: Weight::from_parts(5000, 5000) },
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let weight_limit = Weight::from_parts(20, 20);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(origin, message, hash, weight_limit);
|
||||
assert_eq!(r, Outcome::Error(XcmError::Barrier));
|
||||
|
||||
let origin = Parachain(1000);
|
||||
let message = Xcm::<TestCall>(vec![SubscribeVersion {
|
||||
query_id: 42,
|
||||
max_response_weight: Weight::from_parts(5000, 5000),
|
||||
}]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let weight_limit = Weight::from_parts(10, 10);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(origin, message.clone(), hash, weight_limit);
|
||||
assert_eq!(r, Outcome::Error(XcmError::Barrier));
|
||||
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Parent, message, hash, weight_limit);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10)));
|
||||
|
||||
assert_eq!(
|
||||
SubscriptionRequests::get(),
|
||||
vec![(Parent.into(), Some((42, Weight::from_parts(5000, 5000))))]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_subscription_instruction_should_work() {
|
||||
let origin = Parachain(1000);
|
||||
let message = Xcm::<TestCall>(vec![
|
||||
DescendOrigin(X1(AccountIndex64 { index: 1, network: None })),
|
||||
SubscribeVersion { query_id: 42, max_response_weight: Weight::from_parts(5000, 5000) },
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let weight_limit = Weight::from_parts(20, 20);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm_in_credit(
|
||||
origin.clone(),
|
||||
message,
|
||||
hash,
|
||||
weight_limit,
|
||||
weight_limit,
|
||||
);
|
||||
assert_eq!(r, Outcome::Incomplete(Weight::from_parts(20, 20), XcmError::BadOrigin));
|
||||
|
||||
let message = Xcm::<TestCall>(vec![
|
||||
SetAppendix(Xcm(vec![])),
|
||||
SubscribeVersion { query_id: 42, max_response_weight: Weight::from_parts(5000, 5000) },
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm_in_credit(
|
||||
origin,
|
||||
message,
|
||||
hash,
|
||||
weight_limit,
|
||||
weight_limit,
|
||||
);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(20, 20)));
|
||||
|
||||
assert_eq!(
|
||||
SubscriptionRequests::get(),
|
||||
vec![(Parachain(1000).into(), Some((42, Weight::from_parts(5000, 5000))))]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_version_unsubscriptions_should_work() {
|
||||
AllowSubsFrom::set(vec![Parent.into()]);
|
||||
|
||||
let origin = Parachain(1000);
|
||||
let message = Xcm::<TestCall>(vec![SetAppendix(Xcm(vec![])), UnsubscribeVersion]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let weight_limit = Weight::from_parts(20, 20);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(origin, message, hash, weight_limit);
|
||||
assert_eq!(r, Outcome::Error(XcmError::Barrier));
|
||||
|
||||
let origin = Parachain(1000);
|
||||
let message = Xcm::<TestCall>(vec![UnsubscribeVersion]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let weight_limit = Weight::from_parts(10, 10);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(origin, message.clone(), hash, weight_limit);
|
||||
assert_eq!(r, Outcome::Error(XcmError::Barrier));
|
||||
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Parent, message, hash, weight_limit);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10)));
|
||||
|
||||
assert_eq!(SubscriptionRequests::get(), vec![(Parent.into(), None)]);
|
||||
assert_eq!(sent_xcm(), vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_unsubscription_instruction_should_work() {
|
||||
let origin = Parachain(1000);
|
||||
|
||||
// Not allowed to do it when origin has been changed.
|
||||
let message = Xcm::<TestCall>(vec![
|
||||
DescendOrigin(X1(AccountIndex64 { index: 1, network: None })),
|
||||
UnsubscribeVersion,
|
||||
]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let weight_limit = Weight::from_parts(20, 20);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm_in_credit(
|
||||
origin.clone(),
|
||||
message,
|
||||
hash,
|
||||
weight_limit,
|
||||
weight_limit,
|
||||
);
|
||||
assert_eq!(r, Outcome::Incomplete(Weight::from_parts(20, 20), XcmError::BadOrigin));
|
||||
|
||||
// Fine to do it when origin is untouched.
|
||||
let message = Xcm::<TestCall>(vec![SetAppendix(Xcm(vec![])), UnsubscribeVersion]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm_in_credit(
|
||||
origin,
|
||||
message,
|
||||
hash,
|
||||
weight_limit,
|
||||
weight_limit,
|
||||
);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(20, 20)));
|
||||
|
||||
assert_eq!(SubscriptionRequests::get(), vec![(Parachain(1000).into(), None)]);
|
||||
assert_eq!(sent_xcm(), vec![]);
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
// Copyright 2022 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn fixed_rate_of_fungible_should_work() {
|
||||
parameter_types! {
|
||||
pub static WeightPrice: (AssetId, u128, u128) =
|
||||
(Here.into(), WEIGHT_REF_TIME_PER_SECOND.into(), WEIGHT_PROOF_SIZE_PER_MB.into());
|
||||
}
|
||||
|
||||
let mut trader = FixedRateOfFungible::<WeightPrice, ()>::new();
|
||||
// supplies 100 unit of asset, 80 still remains after purchasing weight
|
||||
assert_eq!(
|
||||
trader
|
||||
.buy_weight(Weight::from_parts(10, 10), fungible_multi_asset(Here.into(), 100).into()),
|
||||
Ok(fungible_multi_asset(Here.into(), 80).into()),
|
||||
);
|
||||
// should have nothing left, as 5 + 5 = 10, and we supplied 10 units of asset.
|
||||
assert_eq!(
|
||||
trader.buy_weight(Weight::from_parts(5, 5), fungible_multi_asset(Here.into(), 10).into()),
|
||||
Ok(vec![].into()),
|
||||
);
|
||||
// should have 5 left, as there are no proof size components
|
||||
assert_eq!(
|
||||
trader.buy_weight(Weight::from_parts(5, 0), fungible_multi_asset(Here.into(), 10).into()),
|
||||
Ok(fungible_multi_asset(Here.into(), 5).into()),
|
||||
);
|
||||
// not enough to purchase the combined weights
|
||||
assert_err!(
|
||||
trader.buy_weight(Weight::from_parts(5, 5), fungible_multi_asset(Here.into(), 5).into()),
|
||||
XcmError::TooExpensive,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors_should_return_unused_weight() {
|
||||
// we'll let them have message execution for free.
|
||||
AllowUnpaidFrom::set(vec![Here.into()]);
|
||||
// We own 1000 of our tokens.
|
||||
add_asset(Here, (Here, 11u128));
|
||||
let mut message = Xcm(vec![
|
||||
// First xfer results in an error on the last message only
|
||||
TransferAsset {
|
||||
assets: (Here, 1u128).into(),
|
||||
beneficiary: X1(AccountIndex64 { index: 3, network: None }).into(),
|
||||
},
|
||||
// Second xfer results in error third message and after
|
||||
TransferAsset {
|
||||
assets: (Here, 2u128).into(),
|
||||
beneficiary: X1(AccountIndex64 { index: 3, network: None }).into(),
|
||||
},
|
||||
// Third xfer results in error second message and after
|
||||
TransferAsset {
|
||||
assets: (Here, 4u128).into(),
|
||||
beneficiary: X1(AccountIndex64 { index: 3, network: None }).into(),
|
||||
},
|
||||
]);
|
||||
// Weight limit of 70 is needed.
|
||||
let limit = <TestConfig as Config>::Weigher::weight(&mut message).unwrap();
|
||||
assert_eq!(limit, Weight::from_parts(30, 30));
|
||||
|
||||
let hash = fake_message_hash(&message);
|
||||
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Here, message.clone(), hash, limit);
|
||||
assert_eq!(r, Outcome::Complete(Weight::from_parts(30, 30)));
|
||||
assert_eq!(asset_list(AccountIndex64 { index: 3, network: None }), vec![(Here, 7u128).into()]);
|
||||
assert_eq!(asset_list(Here), vec![(Here, 4u128).into()]);
|
||||
assert_eq!(sent_xcm(), vec![]);
|
||||
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Here, message.clone(), hash, limit);
|
||||
assert_eq!(r, Outcome::Incomplete(Weight::from_parts(30, 30), XcmError::NotWithdrawable));
|
||||
assert_eq!(asset_list(AccountIndex64 { index: 3, network: None }), vec![(Here, 10u128).into()]);
|
||||
assert_eq!(asset_list(Here), vec![(Here, 1u128).into()]);
|
||||
assert_eq!(sent_xcm(), vec![]);
|
||||
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Here, message.clone(), hash, limit);
|
||||
assert_eq!(r, Outcome::Incomplete(Weight::from_parts(20, 20), XcmError::NotWithdrawable));
|
||||
assert_eq!(asset_list(AccountIndex64 { index: 3, network: None }), vec![(Here, 11u128).into()]);
|
||||
assert_eq!(asset_list(Here), vec![]);
|
||||
assert_eq!(sent_xcm(), vec![]);
|
||||
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(Here, message, hash, limit);
|
||||
assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::NotWithdrawable));
|
||||
assert_eq!(asset_list(AccountIndex64 { index: 3, network: None }), vec![(Here, 11u128).into()]);
|
||||
assert_eq!(asset_list(Here), vec![]);
|
||||
assert_eq!(sent_xcm(), vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn weight_bounds_should_respect_instructions_limit() {
|
||||
MaxInstructions::set(3);
|
||||
let mut message = Xcm(vec![ClearOrigin; 4]);
|
||||
// 4 instructions are too many.
|
||||
assert_eq!(<TestConfig as Config>::Weigher::weight(&mut message), Err(()));
|
||||
|
||||
let mut message =
|
||||
Xcm(vec![SetErrorHandler(Xcm(vec![ClearOrigin])), SetAppendix(Xcm(vec![ClearOrigin]))]);
|
||||
// 4 instructions are too many, even when hidden within 2.
|
||||
assert_eq!(<TestConfig as Config>::Weigher::weight(&mut message), Err(()));
|
||||
|
||||
let mut message =
|
||||
Xcm(vec![SetErrorHandler(Xcm(vec![SetErrorHandler(Xcm(vec![SetErrorHandler(Xcm(
|
||||
vec![ClearOrigin],
|
||||
))]))]))]);
|
||||
// 4 instructions are too many, even when it's just one that's 3 levels deep.
|
||||
assert_eq!(<TestConfig as Config>::Weigher::weight(&mut message), Err(()));
|
||||
|
||||
let mut message =
|
||||
Xcm(vec![SetErrorHandler(Xcm(vec![SetErrorHandler(Xcm(vec![ClearOrigin]))]))]);
|
||||
// 3 instructions are OK.
|
||||
assert_eq!(
|
||||
<TestConfig as Config>::Weigher::weight(&mut message),
|
||||
Ok(Weight::from_parts(30, 30))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn weight_trader_tuple_should_work() {
|
||||
let para_1: MultiLocation = Parachain(1).into();
|
||||
let para_2: MultiLocation = Parachain(2).into();
|
||||
|
||||
parameter_types! {
|
||||
pub static HereWeightPrice: (AssetId, u128, u128) =
|
||||
(Here.into(), WEIGHT_REF_TIME_PER_SECOND.into(), WEIGHT_PROOF_SIZE_PER_MB.into());
|
||||
pub static Para1WeightPrice: (AssetId, u128, u128) =
|
||||
(Parachain(1).into(), WEIGHT_REF_TIME_PER_SECOND.into(), WEIGHT_PROOF_SIZE_PER_MB.into());
|
||||
}
|
||||
|
||||
type Traders = (
|
||||
// trader one
|
||||
FixedRateOfFungible<HereWeightPrice, ()>,
|
||||
// trader two
|
||||
FixedRateOfFungible<Para1WeightPrice, ()>,
|
||||
);
|
||||
|
||||
let mut traders = Traders::new();
|
||||
// trader one buys weight
|
||||
assert_eq!(
|
||||
traders.buy_weight(Weight::from_parts(5, 5), fungible_multi_asset(Here.into(), 10).into()),
|
||||
Ok(vec![].into()),
|
||||
);
|
||||
// trader one refunds
|
||||
assert_eq!(
|
||||
traders.refund_weight(Weight::from_parts(2, 2)),
|
||||
Some(fungible_multi_asset(Here.into(), 4))
|
||||
);
|
||||
|
||||
let mut traders = Traders::new();
|
||||
// trader one failed; trader two buys weight
|
||||
assert_eq!(
|
||||
traders.buy_weight(Weight::from_parts(5, 5), fungible_multi_asset(para_1, 10).into()),
|
||||
Ok(vec![].into()),
|
||||
);
|
||||
// trader two refunds
|
||||
assert_eq!(
|
||||
traders.refund_weight(Weight::from_parts(2, 2)),
|
||||
Some(fungible_multi_asset(para_1, 4))
|
||||
);
|
||||
|
||||
let mut traders = Traders::new();
|
||||
// all traders fails
|
||||
assert_err!(
|
||||
traders.buy_weight(Weight::from_parts(5, 5), fungible_multi_asset(para_2, 10).into()),
|
||||
XcmError::TooExpensive,
|
||||
);
|
||||
// and no refund
|
||||
assert_eq!(traders.refund_weight(Weight::from_parts(2, 2)), None);
|
||||
}
|
||||
@@ -0,0 +1,382 @@
|
||||
// Copyright 2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Traits and utilities to help with origin mutation and bridging.
|
||||
|
||||
use frame_support::{ensure, traits::Get};
|
||||
use parity_scale_codec::{Decode, Encode};
|
||||
use sp_std::{convert::TryInto, marker::PhantomData, prelude::*};
|
||||
use xcm::prelude::*;
|
||||
use xcm_executor::traits::{validate_export, ExportXcm};
|
||||
use SendError::*;
|
||||
|
||||
fn ensure_is_remote(
|
||||
universal_local: impl Into<InteriorMultiLocation>,
|
||||
dest: impl Into<MultiLocation>,
|
||||
) -> Result<(NetworkId, InteriorMultiLocation), MultiLocation> {
|
||||
let dest = dest.into();
|
||||
let universal_local = universal_local.into();
|
||||
let local_net = match universal_local.global_consensus() {
|
||||
Ok(x) => x,
|
||||
Err(_) => return Err(dest),
|
||||
};
|
||||
let universal_destination: InteriorMultiLocation = universal_local
|
||||
.into_location()
|
||||
.appended_with(dest)
|
||||
.map_err(|x| x.1)?
|
||||
.try_into()?;
|
||||
let (remote_dest, remote_net) = match universal_destination.split_first() {
|
||||
(d, Some(GlobalConsensus(n))) if n != local_net => (d, n),
|
||||
_ => return Err(dest),
|
||||
};
|
||||
Ok((remote_net, remote_dest))
|
||||
}
|
||||
|
||||
/// Implementation of `SendXcm` which uses the given `ExportXcm` implementation in order to forward
|
||||
/// the message over a bridge.
|
||||
///
|
||||
/// No effort is made to charge for any bridge fees, so this can only be used when it is known
|
||||
/// that the message sending cannot be abused in any way.
|
||||
///
|
||||
/// This is only useful when the local chain has bridging capabilities.
|
||||
pub struct UnpaidLocalExporter<Exporter, UniversalLocation>(
|
||||
PhantomData<(Exporter, UniversalLocation)>,
|
||||
);
|
||||
impl<Exporter: ExportXcm, UniversalLocation: Get<InteriorMultiLocation>> SendXcm
|
||||
for UnpaidLocalExporter<Exporter, UniversalLocation>
|
||||
{
|
||||
type Ticket = Exporter::Ticket;
|
||||
|
||||
fn validate(
|
||||
dest: &mut Option<MultiLocation>,
|
||||
xcm: &mut Option<Xcm<()>>,
|
||||
) -> SendResult<Exporter::Ticket> {
|
||||
let d = dest.take().ok_or(MissingArgument)?;
|
||||
let universal_source = UniversalLocation::get();
|
||||
let devolved = match ensure_is_remote(universal_source, d) {
|
||||
Ok(x) => x,
|
||||
Err(d) => {
|
||||
*dest = Some(d);
|
||||
return Err(NotApplicable)
|
||||
},
|
||||
};
|
||||
let (network, destination) = devolved;
|
||||
let xcm = xcm.take().ok_or(SendError::MissingArgument)?;
|
||||
validate_export::<Exporter>(network, 0, universal_source, destination, xcm)
|
||||
}
|
||||
|
||||
fn deliver(ticket: Exporter::Ticket) -> Result<XcmHash, SendError> {
|
||||
Exporter::deliver(ticket)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ExporterFor {
|
||||
/// Return the locally-routable bridge (if any) capable of forwarding `message` to the
|
||||
/// `remote_location` on the remote `network`, together with the payment which is required.
|
||||
///
|
||||
/// The payment is specified from the local context, not the bridge chain. This is the
|
||||
/// total amount to withdraw in to Holding and should cover both payment for the execution on
|
||||
/// the bridge chain as well as payment for the use of the `ExportMessage` instruction.
|
||||
fn exporter_for(
|
||||
network: &NetworkId,
|
||||
remote_location: &InteriorMultiLocation,
|
||||
message: &Xcm<()>,
|
||||
) -> Option<(MultiLocation, Option<MultiAsset>)>;
|
||||
}
|
||||
|
||||
#[impl_trait_for_tuples::impl_for_tuples(30)]
|
||||
impl ExporterFor for Tuple {
|
||||
fn exporter_for(
|
||||
network: &NetworkId,
|
||||
remote_location: &InteriorMultiLocation,
|
||||
message: &Xcm<()>,
|
||||
) -> Option<(MultiLocation, Option<MultiAsset>)> {
|
||||
for_tuples!( #(
|
||||
if let Some(r) = Tuple::exporter_for(network, remote_location, message) {
|
||||
return Some(r);
|
||||
}
|
||||
)* );
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NetworkExportTable<T>(sp_std::marker::PhantomData<T>);
|
||||
impl<T: Get<Vec<(NetworkId, MultiLocation, Option<MultiAsset>)>>> ExporterFor
|
||||
for NetworkExportTable<T>
|
||||
{
|
||||
fn exporter_for(
|
||||
network: &NetworkId,
|
||||
_: &InteriorMultiLocation,
|
||||
_: &Xcm<()>,
|
||||
) -> Option<(MultiLocation, Option<MultiAsset>)> {
|
||||
T::get().into_iter().find(|(ref j, ..)| j == network).map(|(_, l, p)| (l, p))
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of `SendXcm` which wraps the message inside an `ExportMessage` instruction
|
||||
/// and sends it to a destination known to be able to handle it.
|
||||
///
|
||||
/// No effort is made to make payment to the bridge for its services, so the bridge location
|
||||
/// must have been configured with a barrier rule allowing unpaid execution for this message
|
||||
/// coming from our origin.
|
||||
///
|
||||
/// This is only useful if we have special dispensation by the remote bridges to have the
|
||||
/// `ExportMessage` instruction executed without payment.
|
||||
pub struct UnpaidRemoteExporter<Bridges, Router, UniversalLocation>(
|
||||
PhantomData<(Bridges, Router, UniversalLocation)>,
|
||||
);
|
||||
impl<Bridges: ExporterFor, Router: SendXcm, UniversalLocation: Get<InteriorMultiLocation>> SendXcm
|
||||
for UnpaidRemoteExporter<Bridges, Router, UniversalLocation>
|
||||
{
|
||||
type Ticket = Router::Ticket;
|
||||
|
||||
fn validate(
|
||||
dest: &mut Option<MultiLocation>,
|
||||
xcm: &mut Option<Xcm<()>>,
|
||||
) -> SendResult<Router::Ticket> {
|
||||
let d = dest.ok_or(MissingArgument)?;
|
||||
let devolved = ensure_is_remote(UniversalLocation::get(), d).map_err(|_| NotApplicable)?;
|
||||
let (remote_network, remote_location) = devolved;
|
||||
let xcm = xcm.take().ok_or(MissingArgument)?;
|
||||
let (bridge, maybe_payment) =
|
||||
Bridges::exporter_for(&remote_network, &remote_location, &xcm).ok_or(NotApplicable)?;
|
||||
ensure!(maybe_payment.is_none(), Unroutable);
|
||||
|
||||
// We then send a normal message to the bridge asking it to export the prepended
|
||||
// message to the remote chain. This will only work if the bridge will do the message
|
||||
// export for free. Common-good chains will typically be afforded this.
|
||||
let message =
|
||||
Xcm(vec![ExportMessage { network: remote_network, destination: remote_location, xcm }]);
|
||||
let (v, mut cost) = validate_send::<Router>(bridge, message)?;
|
||||
if let Some(payment) = maybe_payment {
|
||||
cost.push(payment);
|
||||
}
|
||||
Ok((v, cost))
|
||||
}
|
||||
|
||||
fn deliver(validation: Router::Ticket) -> Result<XcmHash, SendError> {
|
||||
Router::deliver(validation)
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of `SendXcm` which wraps the message inside an `ExportMessage` instruction
|
||||
/// and sends it to a destination known to be able to handle it.
|
||||
///
|
||||
/// The `ExportMessage` instruction on the bridge is paid for from the local chain's sovereign
|
||||
/// account on the bridge. The amount paid is determined through the `ExporterFor` trait.
|
||||
pub struct SovereignPaidRemoteExporter<Bridges, Router, UniversalLocation>(
|
||||
PhantomData<(Bridges, Router, UniversalLocation)>,
|
||||
);
|
||||
impl<Bridges: ExporterFor, Router: SendXcm, UniversalLocation: Get<InteriorMultiLocation>> SendXcm
|
||||
for SovereignPaidRemoteExporter<Bridges, Router, UniversalLocation>
|
||||
{
|
||||
type Ticket = Router::Ticket;
|
||||
|
||||
fn validate(
|
||||
dest: &mut Option<MultiLocation>,
|
||||
xcm: &mut Option<Xcm<()>>,
|
||||
) -> SendResult<Router::Ticket> {
|
||||
let d = *dest.as_ref().ok_or(MissingArgument)?;
|
||||
let devolved = ensure_is_remote(UniversalLocation::get(), d).map_err(|_| NotApplicable)?;
|
||||
let (remote_network, remote_location) = devolved;
|
||||
|
||||
let xcm = xcm.take().ok_or(MissingArgument)?;
|
||||
let (bridge, maybe_payment) =
|
||||
Bridges::exporter_for(&remote_network, &remote_location, &xcm).ok_or(NotApplicable)?;
|
||||
|
||||
let local_from_bridge =
|
||||
UniversalLocation::get().invert_target(&bridge).map_err(|_| Unroutable)?;
|
||||
let export_instruction =
|
||||
ExportMessage { network: remote_network, destination: remote_location, xcm };
|
||||
|
||||
let message = Xcm(if let Some(ref payment) = maybe_payment {
|
||||
let fees = payment
|
||||
.clone()
|
||||
.reanchored(&bridge, UniversalLocation::get())
|
||||
.map_err(|_| Unroutable)?;
|
||||
vec![
|
||||
WithdrawAsset(fees.clone().into()),
|
||||
BuyExecution { fees, weight_limit: Unlimited },
|
||||
export_instruction,
|
||||
RefundSurplus,
|
||||
DepositAsset { assets: All.into(), beneficiary: local_from_bridge },
|
||||
]
|
||||
} else {
|
||||
vec![export_instruction]
|
||||
});
|
||||
|
||||
// We then send a normal message to the bridge asking it to export the prepended
|
||||
// message to the remote chain.
|
||||
let (v, mut cost) = validate_send::<Router>(bridge, message)?;
|
||||
if let Some(bridge_payment) = maybe_payment {
|
||||
cost.push(bridge_payment);
|
||||
}
|
||||
Ok((v, cost))
|
||||
}
|
||||
|
||||
fn deliver(ticket: Router::Ticket) -> Result<XcmHash, SendError> {
|
||||
Router::deliver(ticket)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DispatchBlob {
|
||||
/// Dispatches an incoming blob and returns the unexpectable weight consumed by the dispatch.
|
||||
fn dispatch_blob(blob: Vec<u8>) -> Result<(), DispatchBlobError>;
|
||||
}
|
||||
|
||||
pub trait HaulBlob {
|
||||
/// Sends a blob over some point-to-point link. This will generally be implemented by a bridge.
|
||||
fn haul_blob(blob: Vec<u8>) -> Result<(), HaulBlobError>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum HaulBlobError {
|
||||
/// Represents point-to-point link failure with a human-readable explanation of the specific issue is provided.
|
||||
Transport(&'static str),
|
||||
}
|
||||
|
||||
impl From<HaulBlobError> for SendError {
|
||||
fn from(err: HaulBlobError) -> Self {
|
||||
match err {
|
||||
HaulBlobError::Transport(reason) => SendError::Transport(reason),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Encode, Decode)]
|
||||
pub struct BridgeMessage {
|
||||
/// The message destination as a *Universal Location*. This means it begins with a
|
||||
/// `GlobalConsensus` junction describing the network under which global consensus happens.
|
||||
/// If this does not match our global consensus then it's a fatal error.
|
||||
universal_dest: VersionedInteriorMultiLocation,
|
||||
message: VersionedXcm<()>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum DispatchBlobError {
|
||||
Unbridgable,
|
||||
InvalidEncoding,
|
||||
UnsupportedLocationVersion,
|
||||
UnsupportedXcmVersion,
|
||||
RoutingError,
|
||||
NonUniversalDestination,
|
||||
WrongGlobal,
|
||||
}
|
||||
|
||||
pub struct BridgeBlobDispatcher<Router, OurPlace>(PhantomData<(Router, OurPlace)>);
|
||||
impl<Router: SendXcm, OurPlace: Get<InteriorMultiLocation>> DispatchBlob
|
||||
for BridgeBlobDispatcher<Router, OurPlace>
|
||||
{
|
||||
fn dispatch_blob(blob: Vec<u8>) -> Result<(), DispatchBlobError> {
|
||||
let our_universal = OurPlace::get();
|
||||
let our_global =
|
||||
our_universal.global_consensus().map_err(|()| DispatchBlobError::Unbridgable)?;
|
||||
let BridgeMessage { universal_dest, message } =
|
||||
Decode::decode(&mut &blob[..]).map_err(|_| DispatchBlobError::InvalidEncoding)?;
|
||||
let universal_dest: InteriorMultiLocation = universal_dest
|
||||
.try_into()
|
||||
.map_err(|_| DispatchBlobError::UnsupportedLocationVersion)?;
|
||||
// `universal_dest` is the desired destination within the universe: first we need to check
|
||||
// we're in the right global consensus.
|
||||
let intended_global = universal_dest
|
||||
.global_consensus()
|
||||
.map_err(|()| DispatchBlobError::NonUniversalDestination)?;
|
||||
ensure!(intended_global == our_global, DispatchBlobError::WrongGlobal);
|
||||
let dest = universal_dest.relative_to(&our_universal);
|
||||
let message: Xcm<()> =
|
||||
message.try_into().map_err(|_| DispatchBlobError::UnsupportedXcmVersion)?;
|
||||
let _ = send_xcm::<Router>(dest, message).map_err(|_| DispatchBlobError::RoutingError)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HaulBlobExporter<Bridge, BridgedNetwork, Price>(
|
||||
PhantomData<(Bridge, BridgedNetwork, Price)>,
|
||||
);
|
||||
impl<Bridge: HaulBlob, BridgedNetwork: Get<NetworkId>, Price: Get<MultiAssets>> ExportXcm
|
||||
for HaulBlobExporter<Bridge, BridgedNetwork, Price>
|
||||
{
|
||||
type Ticket = (Vec<u8>, XcmHash);
|
||||
|
||||
fn validate(
|
||||
network: NetworkId,
|
||||
_channel: u32,
|
||||
universal_source: &mut Option<InteriorMultiLocation>,
|
||||
destination: &mut Option<InteriorMultiLocation>,
|
||||
message: &mut Option<Xcm<()>>,
|
||||
) -> Result<((Vec<u8>, XcmHash), MultiAssets), SendError> {
|
||||
let bridged_network = BridgedNetwork::get();
|
||||
ensure!(&network == &bridged_network, SendError::NotApplicable);
|
||||
// We don't/can't use the `channel` for this adapter.
|
||||
let dest = destination.take().ok_or(SendError::MissingArgument)?;
|
||||
let universal_dest = match dest.pushed_front_with(GlobalConsensus(bridged_network)) {
|
||||
Ok(d) => d.into(),
|
||||
Err((dest, _)) => {
|
||||
*destination = Some(dest);
|
||||
return Err(SendError::NotApplicable)
|
||||
},
|
||||
};
|
||||
let (local_net, local_sub) = universal_source
|
||||
.take()
|
||||
.ok_or(SendError::MissingArgument)?
|
||||
.split_global()
|
||||
.map_err(|()| SendError::Unroutable)?;
|
||||
let mut inner: Xcm<()> = vec![UniversalOrigin(GlobalConsensus(local_net))].into();
|
||||
if local_sub != Here {
|
||||
inner.inner_mut().push(DescendOrigin(local_sub));
|
||||
}
|
||||
inner
|
||||
.inner_mut()
|
||||
.extend(message.take().ok_or(SendError::MissingArgument)?.into_iter());
|
||||
let message = VersionedXcm::from(inner);
|
||||
let hash = message.using_encoded(sp_io::hashing::blake2_256);
|
||||
let blob = BridgeMessage { universal_dest, message }.encode();
|
||||
Ok(((blob, hash), Price::get()))
|
||||
}
|
||||
|
||||
fn deliver((blob, hash): (Vec<u8>, XcmHash)) -> Result<XcmHash, SendError> {
|
||||
Bridge::haul_blob(blob)?;
|
||||
Ok(hash)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn ensure_is_remote_works() {
|
||||
// A Kusama parachain is remote from the Polkadot Relay.
|
||||
let x = ensure_is_remote(Polkadot, (Parent, Kusama, Parachain(1000)));
|
||||
assert_eq!(x, Ok((Kusama, Parachain(1000).into())));
|
||||
|
||||
// Polkadot Relay is remote from a Kusama parachain.
|
||||
let x = ensure_is_remote((Kusama, Parachain(1000)), (Parent, Parent, Polkadot));
|
||||
assert_eq!(x, Ok((Polkadot, Here)));
|
||||
|
||||
// Our own parachain is local.
|
||||
let x = ensure_is_remote(Polkadot, Parachain(1000));
|
||||
assert_eq!(x, Err(Parachain(1000).into()));
|
||||
|
||||
// Polkadot's parachain is not remote if we are Polkadot.
|
||||
let x = ensure_is_remote(Polkadot, (Parent, Polkadot, Parachain(1000)));
|
||||
assert_eq!(x, Err((Parent, Polkadot, Parachain(1000)).into()));
|
||||
|
||||
// If we don't have a consensus ancestor, then we cannot determine remoteness.
|
||||
let x = ensure_is_remote((), (Parent, Polkadot, Parachain(1000)));
|
||||
assert_eq!(x, Err((Parent, Polkadot, Parachain(1000)).into()));
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,10 @@
|
||||
use frame_support::{
|
||||
dispatch::GetDispatchInfo,
|
||||
traits::{tokens::currency::Currency as CurrencyT, Get, OnUnbalanced as OnUnbalancedT},
|
||||
weights::{constants::WEIGHT_REF_TIME_PER_SECOND, WeightToFee as WeightToFeeT},
|
||||
weights::{
|
||||
constants::{WEIGHT_PROOF_SIZE_PER_MB, WEIGHT_REF_TIME_PER_SECOND},
|
||||
WeightToFee as WeightToFeeT,
|
||||
},
|
||||
};
|
||||
use parity_scale_codec::Decode;
|
||||
use sp_runtime::traits::{SaturatedConversion, Saturating, Zero};
|
||||
@@ -44,10 +47,10 @@ impl<T: Get<Weight>, C: Decode + GetDispatchInfo, M: Get<u32>> WeightBounds<C>
|
||||
|
||||
impl<T: Get<Weight>, C: Decode + GetDispatchInfo, M> FixedWeightBounds<T, C, M> {
|
||||
fn weight_with_limit(message: &Xcm<C>, instrs_limit: &mut u32) -> Result<Weight, ()> {
|
||||
let mut r: Weight = 0;
|
||||
let mut r: Weight = Weight::zero();
|
||||
*instrs_limit = instrs_limit.checked_sub(message.0.len() as u32).ok_or(())?;
|
||||
for m in message.0.iter() {
|
||||
r = r.checked_add(Self::instr_weight_with_limit(m, instrs_limit)?).ok_or(())?;
|
||||
r = r.checked_add(&Self::instr_weight_with_limit(m, instrs_limit)?).ok_or(())?;
|
||||
}
|
||||
Ok(r)
|
||||
}
|
||||
@@ -55,14 +58,12 @@ impl<T: Get<Weight>, C: Decode + GetDispatchInfo, M> FixedWeightBounds<T, C, M>
|
||||
instruction: &Instruction<C>,
|
||||
instrs_limit: &mut u32,
|
||||
) -> Result<Weight, ()> {
|
||||
T::get()
|
||||
.checked_add(match instruction {
|
||||
Transact { require_weight_at_most, .. } => *require_weight_at_most,
|
||||
SetErrorHandler(xcm) | SetAppendix(xcm) =>
|
||||
Self::weight_with_limit(xcm, instrs_limit)?,
|
||||
_ => 0,
|
||||
})
|
||||
.ok_or(())
|
||||
let instr_weight = match instruction {
|
||||
Transact { require_weight_at_most, .. } => *require_weight_at_most,
|
||||
SetErrorHandler(xcm) | SetAppendix(xcm) => Self::weight_with_limit(xcm, instrs_limit)?,
|
||||
_ => Weight::zero(),
|
||||
};
|
||||
T::get().checked_add(&instr_weight).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,10 +93,10 @@ where
|
||||
Instruction<C>: xcm::GetWeight<W>,
|
||||
{
|
||||
fn weight_with_limit(message: &Xcm<C>, instrs_limit: &mut u32) -> Result<Weight, ()> {
|
||||
let mut r: Weight = 0;
|
||||
let mut r: Weight = Weight::zero();
|
||||
*instrs_limit = instrs_limit.checked_sub(message.0.len() as u32).ok_or(())?;
|
||||
for m in message.0.iter() {
|
||||
r = r.checked_add(Self::instr_weight_with_limit(m, instrs_limit)?).ok_or(())?;
|
||||
r = r.checked_add(&Self::instr_weight_with_limit(m, instrs_limit)?).ok_or(())?;
|
||||
}
|
||||
Ok(r)
|
||||
}
|
||||
@@ -104,15 +105,12 @@ where
|
||||
instrs_limit: &mut u32,
|
||||
) -> Result<Weight, ()> {
|
||||
use xcm::GetWeight;
|
||||
instruction
|
||||
.weight()
|
||||
.checked_add(match instruction {
|
||||
Transact { require_weight_at_most, .. } => *require_weight_at_most,
|
||||
SetErrorHandler(xcm) | SetAppendix(xcm) =>
|
||||
Self::weight_with_limit(xcm, instrs_limit)?,
|
||||
_ => 0,
|
||||
})
|
||||
.ok_or(())
|
||||
let instr_weight = match instruction {
|
||||
Transact { require_weight_at_most, .. } => *require_weight_at_most,
|
||||
SetErrorHandler(xcm) | SetAppendix(xcm) => Self::weight_with_limit(xcm, instrs_limit)?,
|
||||
_ => Weight::zero(),
|
||||
};
|
||||
instruction.weight().checked_add(&instr_weight).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,74 +126,18 @@ impl TakeRevenue for () {
|
||||
fn take_revenue(_revenue: MultiAsset) {}
|
||||
}
|
||||
|
||||
/// Simple fee calculator that requires payment in a single concrete fungible at a fixed rate.
|
||||
///
|
||||
/// The constant `Get` type parameter should be the concrete fungible ID and the amount of it required for
|
||||
/// one second of weight.
|
||||
#[deprecated = "Use `FixedRateOfFungible` instead"]
|
||||
pub struct FixedRateOfConcreteFungible<T: Get<(MultiLocation, u128)>, R: TakeRevenue>(
|
||||
Weight,
|
||||
u128,
|
||||
PhantomData<(T, R)>,
|
||||
);
|
||||
#[allow(deprecated)]
|
||||
impl<T: Get<(MultiLocation, u128)>, R: TakeRevenue> WeightTrader
|
||||
for FixedRateOfConcreteFungible<T, R>
|
||||
{
|
||||
fn new() -> Self {
|
||||
Self(0, 0, PhantomData)
|
||||
}
|
||||
|
||||
fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result<Assets, XcmError> {
|
||||
log::trace!(
|
||||
target: "xcm::weight",
|
||||
"FixedRateOfConcreteFungible::buy_weight weight: {:?}, payment: {:?}",
|
||||
weight, payment,
|
||||
);
|
||||
let (id, units_per_second) = T::get();
|
||||
let amount = units_per_second * (weight as u128) / (WEIGHT_REF_TIME_PER_SECOND as u128);
|
||||
let unused =
|
||||
payment.checked_sub((id, amount).into()).map_err(|_| XcmError::TooExpensive)?;
|
||||
self.0 = self.0.saturating_add(weight);
|
||||
self.1 = self.1.saturating_add(amount);
|
||||
Ok(unused)
|
||||
}
|
||||
|
||||
fn refund_weight(&mut self, weight: Weight) -> Option<MultiAsset> {
|
||||
log::trace!(target: "xcm::weight", "FixedRateOfConcreteFungible::refund_weight weight: {:?}", weight);
|
||||
let (id, units_per_second) = T::get();
|
||||
let weight = weight.min(self.0);
|
||||
let amount = units_per_second * (weight as u128) / (WEIGHT_REF_TIME_PER_SECOND as u128);
|
||||
self.0 -= weight;
|
||||
self.1 = self.1.saturating_sub(amount);
|
||||
if amount > 0 {
|
||||
Some((Concrete(id), amount).into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
impl<T: Get<(MultiLocation, u128)>, R: TakeRevenue> Drop for FixedRateOfConcreteFungible<T, R> {
|
||||
fn drop(&mut self) {
|
||||
if self.1 > 0 {
|
||||
R::take_revenue((Concrete(T::get().0), self.1).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple fee calculator that requires payment in a single fungible at a fixed rate.
|
||||
///
|
||||
/// The constant `Get` type parameter should be the fungible ID and the amount of it required for
|
||||
/// one second of weight.
|
||||
pub struct FixedRateOfFungible<T: Get<(AssetId, u128)>, R: TakeRevenue>(
|
||||
/// The constant `Get` type parameter should be the fungible ID, the amount of it required for one
|
||||
/// second of weight and the amount required for 1 MB of proof.
|
||||
pub struct FixedRateOfFungible<T: Get<(AssetId, u128, u128)>, R: TakeRevenue>(
|
||||
Weight,
|
||||
u128,
|
||||
PhantomData<(T, R)>,
|
||||
);
|
||||
impl<T: Get<(AssetId, u128)>, R: TakeRevenue> WeightTrader for FixedRateOfFungible<T, R> {
|
||||
impl<T: Get<(AssetId, u128, u128)>, R: TakeRevenue> WeightTrader for FixedRateOfFungible<T, R> {
|
||||
fn new() -> Self {
|
||||
Self(0, 0, PhantomData)
|
||||
Self(Weight::zero(), 0, PhantomData)
|
||||
}
|
||||
|
||||
fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result<Assets, XcmError> {
|
||||
@@ -204,8 +146,10 @@ impl<T: Get<(AssetId, u128)>, R: TakeRevenue> WeightTrader for FixedRateOfFungib
|
||||
"FixedRateOfFungible::buy_weight weight: {:?}, payment: {:?}",
|
||||
weight, payment,
|
||||
);
|
||||
let (id, units_per_second) = T::get();
|
||||
let amount = units_per_second * (weight as u128) / (WEIGHT_REF_TIME_PER_SECOND as u128);
|
||||
let (id, units_per_second, units_per_mb) = T::get();
|
||||
let amount = (units_per_second * (weight.ref_time() as u128) /
|
||||
(WEIGHT_REF_TIME_PER_SECOND as u128)) +
|
||||
(units_per_mb * (weight.proof_size() as u128) / (WEIGHT_PROOF_SIZE_PER_MB as u128));
|
||||
if amount == 0 {
|
||||
return Ok(payment)
|
||||
}
|
||||
@@ -218,9 +162,11 @@ impl<T: Get<(AssetId, u128)>, R: TakeRevenue> WeightTrader for FixedRateOfFungib
|
||||
|
||||
fn refund_weight(&mut self, weight: Weight) -> Option<MultiAsset> {
|
||||
log::trace!(target: "xcm::weight", "FixedRateOfFungible::refund_weight weight: {:?}", weight);
|
||||
let (id, units_per_second) = T::get();
|
||||
let (id, units_per_second, units_per_mb) = T::get();
|
||||
let weight = weight.min(self.0);
|
||||
let amount = units_per_second * (weight as u128) / (WEIGHT_REF_TIME_PER_SECOND as u128);
|
||||
let amount = (units_per_second * (weight.ref_time() as u128) /
|
||||
(WEIGHT_REF_TIME_PER_SECOND as u128)) +
|
||||
(units_per_mb * (weight.proof_size() as u128) / (WEIGHT_PROOF_SIZE_PER_MB as u128));
|
||||
self.0 -= weight;
|
||||
self.1 = self.1.saturating_sub(amount);
|
||||
if amount > 0 {
|
||||
@@ -231,7 +177,7 @@ impl<T: Get<(AssetId, u128)>, R: TakeRevenue> WeightTrader for FixedRateOfFungib
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Get<(AssetId, u128)>, R: TakeRevenue> Drop for FixedRateOfFungible<T, R> {
|
||||
impl<T: Get<(AssetId, u128, u128)>, R: TakeRevenue> Drop for FixedRateOfFungible<T, R> {
|
||||
fn drop(&mut self) {
|
||||
if self.1 > 0 {
|
||||
R::take_revenue((T::get().0, self.1).into());
|
||||
@@ -261,13 +207,12 @@ impl<
|
||||
> WeightTrader for UsingComponents<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced>
|
||||
{
|
||||
fn new() -> Self {
|
||||
Self(0, Zero::zero(), PhantomData)
|
||||
Self(Weight::zero(), Zero::zero(), PhantomData)
|
||||
}
|
||||
|
||||
fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result<Assets, XcmError> {
|
||||
log::trace!(target: "xcm::weight", "UsingComponents::buy_weight weight: {:?}, payment: {:?}", weight, payment);
|
||||
let amount =
|
||||
WeightToFee::weight_to_fee(&frame_support::weights::Weight::from_ref_time(weight));
|
||||
let amount = WeightToFee::weight_to_fee(&weight);
|
||||
let u128_amount: u128 = amount.try_into().map_err(|_| XcmError::Overflow)?;
|
||||
let required = (Concrete(AssetId::get()), u128_amount).into();
|
||||
let unused = payment.checked_sub(required).map_err(|_| XcmError::TooExpensive)?;
|
||||
@@ -279,8 +224,7 @@ impl<
|
||||
fn refund_weight(&mut self, weight: Weight) -> Option<MultiAsset> {
|
||||
log::trace!(target: "xcm::weight", "UsingComponents::refund_weight weight: {:?}", weight);
|
||||
let weight = weight.min(self.0);
|
||||
let amount =
|
||||
WeightToFee::weight_to_fee(&frame_support::weights::Weight::from_ref_time(weight));
|
||||
let amount = WeightToFee::weight_to_fee(&weight);
|
||||
self.0 -= weight;
|
||||
self.1 = self.1.saturating_sub(amount);
|
||||
let amount: u128 = amount.saturated_into();
|
||||
|
||||
Reference in New Issue
Block a user