From 5045eedb2682b506130a5a3dc021a4a338191d93 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rios Date: Wed, 4 Dec 2024 12:52:22 +0100 Subject: [PATCH 01/11] WETH integration test --- Cargo.lock | 2 - Cargo.toml | 4 + debug-executor/.cargo-ok | 1 + debug-executor/Cargo.toml | 204 +++ debug-executor/Cargo.toml.orig | 49 + debug-executor/src/assets.rs | 695 ++++++++++ debug-executor/src/config.rs | 126 ++ debug-executor/src/lib.rs | 1188 +++++++++++++++++ debug-executor/src/traits/asset_exchange.rs | 58 + debug-executor/src/traits/asset_lock.rs | 131 ++ debug-executor/src/traits/asset_transfer.rs | 93 ++ debug-executor/src/traits/conversion.rs | 144 ++ debug-executor/src/traits/drop_assets.rs | 79 ++ debug-executor/src/traits/export.rs | 140 ++ debug-executor/src/traits/fee_manager.rs | 60 + .../src/traits/filter_asset_location.rs | 35 + debug-executor/src/traits/hrmp.rs | 56 + debug-executor/src/traits/mod.rs | 64 + debug-executor/src/traits/on_response.rs | 180 +++ .../src/traits/process_transaction.rs | 58 + debug-executor/src/traits/record_xcm.rs | 46 + debug-executor/src/traits/should_execute.rs | 113 ++ debug-executor/src/traits/token_matching.rs | 106 ++ debug-executor/src/traits/transact_asset.rs | 411 ++++++ debug-executor/src/traits/weight.rs | 114 ++ integration-tests/chopsticks/README.md | 4 + integration-tests/chopsticks/bun.lockb | Bin 199341 -> 199373 bytes .../chopsticks/overrides/polimec.ts | 85 +- .../chopsticks/overrides/polkadot-hub.ts | 42 +- integration-tests/chopsticks/package.json | 1 + integration-tests/chopsticks/src/constants.ts | 2 +- .../chopsticks/src/managers/BaseManager.ts | 35 +- .../chopsticks/src/managers/PolimecManager.ts | 46 +- .../src/managers/PolkadotHubManager.ts | 58 +- .../src/managers/PolkadotManager.ts | 28 +- integration-tests/chopsticks/src/setup.ts | 5 + .../chopsticks/src/tests/hub.test.ts | 114 +- .../chopsticks/src/tests/polimec.test.ts | 58 +- .../chopsticks/src/tests/polkadot.test.ts | 18 +- .../chopsticks/src/transfers/BaseTransfer.ts | 36 +- .../chopsticks/src/transfers/HubToPolimec.ts | 219 ++- .../chopsticks/src/transfers/PolimecToHub.ts | 29 +- .../src/transfers/PolkadotToPolimec.ts | 27 +- integration-tests/chopsticks/src/types.ts | 162 ++- integration-tests/chopsticks/src/utils.ts | 77 +- integration-tests/src/tests/runtime_apis.rs | 12 + pallets/funding/src/storage_migrations.rs | 6 +- .../custom_migrations/asset_id_migration.rs | 13 +- runtimes/polimec/src/lib.rs | 18 +- runtimes/polimec/src/xcm_config.rs | 118 +- 50 files changed, 5073 insertions(+), 297 deletions(-) create mode 100644 debug-executor/.cargo-ok create mode 100644 debug-executor/Cargo.toml create mode 100644 debug-executor/Cargo.toml.orig create mode 100644 debug-executor/src/assets.rs create mode 100644 debug-executor/src/config.rs create mode 100644 debug-executor/src/lib.rs create mode 100644 debug-executor/src/traits/asset_exchange.rs create mode 100644 debug-executor/src/traits/asset_lock.rs create mode 100644 debug-executor/src/traits/asset_transfer.rs create mode 100644 debug-executor/src/traits/conversion.rs create mode 100644 debug-executor/src/traits/drop_assets.rs create mode 100644 debug-executor/src/traits/export.rs create mode 100644 debug-executor/src/traits/fee_manager.rs create mode 100644 debug-executor/src/traits/filter_asset_location.rs create mode 100644 debug-executor/src/traits/hrmp.rs create mode 100644 debug-executor/src/traits/mod.rs create mode 100644 debug-executor/src/traits/on_response.rs create mode 100644 debug-executor/src/traits/process_transaction.rs create mode 100644 debug-executor/src/traits/record_xcm.rs create mode 100644 debug-executor/src/traits/should_execute.rs create mode 100644 debug-executor/src/traits/token_matching.rs create mode 100644 debug-executor/src/traits/transact_asset.rs create mode 100644 debug-executor/src/traits/weight.rs mode change 100644 => 100755 integration-tests/chopsticks/bun.lockb diff --git a/Cargo.lock b/Cargo.lock index 40b4ce282..aa4994e9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13745,8 +13745,6 @@ dependencies = [ [[package]] name = "staging-xcm-executor" version = "14.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5b83ea34a2ba2083c6f5bfec468fb00535d0e0788a78237d06da32dba76be9" dependencies = [ "environmental", "frame-benchmarking", diff --git a/Cargo.toml b/Cargo.toml index 2759b888b..dd9ae38ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "macros", "macros/tests", "polimec-common/*", + "debug-executor", ] default-members = ["nodes/*", "pallets/*"] resolver = "2" @@ -248,3 +249,6 @@ cumulus-pallet-session-benchmarking = { version = "16.0.0", default-features = f polimec-runtime = { path = "runtimes/polimec" } rococo-runtime-constants = { version = "14.0.0" } rococo-runtime = { version = "14.0.0" } + +[patch.crates-io] +xcm-executor = { path = "./debug-executor", package = "staging-xcm-executor", default-features = false } \ No newline at end of file diff --git a/debug-executor/.cargo-ok b/debug-executor/.cargo-ok new file mode 100644 index 000000000..5f8b79583 --- /dev/null +++ b/debug-executor/.cargo-ok @@ -0,0 +1 @@ +{"v":1} \ No newline at end of file diff --git a/debug-executor/Cargo.toml b/debug-executor/Cargo.toml new file mode 100644 index 000000000..2a7f1502b --- /dev/null +++ b/debug-executor/Cargo.toml @@ -0,0 +1,204 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +name = "staging-xcm-executor" +version = "14.0.0" +authors = ["Parity Technologies "] +description = "An abstract and configurable XCM message executor. (polkadot v1.13.0)" +license = "GPL-3.0-only" + +[dependencies.codec] +version = "3.6.12" +features = ["derive"] +default-features = false +package = "parity-scale-codec" + +[dependencies.environmental] +version = "1.1.4" +default-features = false + +[dependencies.frame-benchmarking] +version = "35.0.0" +optional = true +default-features = false + +[dependencies.frame-support] +version = "35.0.0" +default-features = false + +[dependencies.impl-trait-for-tuples] +version = "0.2.2" + +[dependencies.log] +version = "0.4.21" +default-features = false + +[dependencies.scale-info] +version = "2.11.1" +features = [ + "derive", + "serde", +] +default-features = false + +[dependencies.sp-arithmetic] +version = "26.0.0" +default-features = false + +[dependencies.sp-core] +version = "34.0.0" +default-features = false + +[dependencies.sp-io] +version = "37.0.0" +default-features = false + +[dependencies.sp-runtime] +version = "38.0.0" +default-features = false + +[dependencies.sp-std] +version = "14.0.0" +default-features = false + +[dependencies.sp-weights] +version = "31.0.0" +default-features = false + +[dependencies.xcm] +version = "14.0.0" +default-features = false +package = "staging-xcm" + +[features] +default = ["std"] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +std = [ + "codec/std", + "environmental/std", + "frame-benchmarking/std", + "frame-support/std", + "log/std", + "scale-info/std", + "sp-arithmetic/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "sp-weights/std", + "xcm/std", +] + +[lints.clippy.all] +level = "allow" +priority = 0 + +[lints.clippy.bind_instead_of_map] +level = "allow" +priority = 2 + +[lints.clippy.borrowed-box] +level = "allow" +priority = 2 + +[lints.clippy.complexity] +level = "warn" +priority = 1 + +[lints.clippy.correctness] +level = "warn" +priority = 1 + +[lints.clippy.default_constructed_unit_structs] +level = "allow" +priority = 2 + +[lints.clippy.derivable_impls] +level = "allow" +priority = 2 + +[lints.clippy.eq_op] +level = "allow" +priority = 2 + +[lints.clippy.erasing_op] +level = "allow" +priority = 2 + +[lints.clippy.extra-unused-type-parameters] +level = "allow" +priority = 2 + +[lints.clippy.identity-op] +level = "allow" +priority = 2 + +[lints.clippy.if-same-then-else] +level = "allow" +priority = 2 + +[lints.clippy.needless-lifetimes] +level = "allow" +priority = 2 + +[lints.clippy.needless_option_as_deref] +level = "allow" +priority = 2 + +[lints.clippy.nonminimal-bool] +level = "allow" +priority = 2 + +[lints.clippy.option-map-unit-fn] +level = "allow" +priority = 2 + +[lints.clippy.stable_sort_primitive] +level = "allow" +priority = 2 + +[lints.clippy.too-many-arguments] +level = "allow" +priority = 2 + +[lints.clippy.type_complexity] +level = "allow" +priority = 2 + +[lints.clippy.unit_arg] +level = "allow" +priority = 2 + +[lints.clippy.unnecessary_cast] +level = "allow" +priority = 2 + +[lints.clippy.useless_conversion] +level = "allow" +priority = 2 + +[lints.clippy.while_immutable_condition] +level = "allow" +priority = 2 + +[lints.clippy.zero-prefixed-literal] +level = "allow" +priority = 2 + +[lints.rust.suspicious_double_ref_op] +level = "allow" +priority = 2 diff --git a/debug-executor/Cargo.toml.orig b/debug-executor/Cargo.toml.orig new file mode 100644 index 000000000..29917f026 --- /dev/null +++ b/debug-executor/Cargo.toml.orig @@ -0,0 +1,49 @@ +[package] +name = "staging-xcm-executor" +description = "An abstract and configurable XCM message executor. (polkadot v1.13.0)" +authors.workspace = true +edition.workspace = true +license.workspace = true +version = "14.0.0" + +[lints] +workspace = true + +[dependencies] +impl-trait-for-tuples = "0.2.2" +environmental = { version = "1.1.4", default-features = false } +codec = { package = "parity-scale-codec", version = "3.6.12", default-features = false, features = ["derive"] } +scale-info = { version = "2.11.1", default-features = false, features = ["derive", "serde"] } +xcm = { package = "staging-xcm", path = "..", default-features = false, version = "14.0.0" } +sp-std = { path = "../../../substrate/primitives/std", default-features = false, version = "14.0.0" } +sp-io = { path = "../../../substrate/primitives/io", default-features = false, version = "37.0.0" } +sp-arithmetic = { path = "../../../substrate/primitives/arithmetic", default-features = false, version = "26.0.0" } +sp-core = { path = "../../../substrate/primitives/core", default-features = false, version = "34.0.0" } +sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false, version = "38.0.0" } +sp-weights = { path = "../../../substrate/primitives/weights", default-features = false, version = "31.0.0" } +frame-support = { path = "../../../substrate/frame/support", default-features = false, version = "35.0.0" } +log = { workspace = true } +frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true, version = "35.0.0" } + +[features] +default = ["std"] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +std = [ + "codec/std", + "environmental/std", + "frame-benchmarking/std", + "frame-support/std", + "log/std", + "scale-info/std", + "sp-arithmetic/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "sp-weights/std", + "xcm/std", +] diff --git a/debug-executor/src/assets.rs b/debug-executor/src/assets.rs new file mode 100644 index 000000000..96906b712 --- /dev/null +++ b/debug-executor/src/assets.rs @@ -0,0 +1,695 @@ +// Copyright (C) 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 . + +use sp_runtime::{traits::Saturating, RuntimeDebug}; +use sp_std::{ + collections::{btree_map::BTreeMap, btree_set::BTreeSet}, + mem, + prelude::*, +}; +use xcm::latest::{ + Asset, AssetFilter, AssetId, AssetInstance, Assets, + Fungibility::{Fungible, NonFungible}, + InteriorLocation, Location, Reanchorable, + WildAsset::{All, AllCounted, AllOf, AllOfCounted}, + WildFungibility::{Fungible as WildFungible, NonFungible as WildNonFungible}, +}; + +/// Map of non-wildcard fungible and non-fungible assets held in the holding register. +#[derive(Default, Clone, RuntimeDebug, Eq, PartialEq)] +pub struct AssetsInHolding { + /// The fungible assets. + pub fungible: BTreeMap, + + /// The non-fungible assets. + // TODO: Consider BTreeMap> + // or even BTreeMap> + pub non_fungible: BTreeSet<(AssetId, AssetInstance)>, +} + +impl From for AssetsInHolding { + fn from(asset: Asset) -> AssetsInHolding { + let mut result = Self::default(); + result.subsume(asset); + result + } +} + +impl From> for AssetsInHolding { + fn from(assets: Vec) -> AssetsInHolding { + let mut result = Self::default(); + for asset in assets.into_iter() { + result.subsume(asset) + } + result + } +} + +impl From for AssetsInHolding { + fn from(assets: Assets) -> AssetsInHolding { + assets.into_inner().into() + } +} + +impl From for Vec { + fn from(a: AssetsInHolding) -> Self { + a.into_assets_iter().collect() + } +} + +impl From for Assets { + fn from(a: AssetsInHolding) -> Self { + a.into_assets_iter().collect::>().into() + } +} + +/// An error emitted by `take` operations. +#[derive(Debug)] +pub enum TakeError { + /// There was an attempt to take an asset without saturating (enough of) which did not exist. + AssetUnderflow(Asset), +} + +impl AssetsInHolding { + /// New value, containing no assets. + pub fn new() -> Self { + Self::default() + } + + /// Total number of distinct assets. + pub fn len(&self) -> usize { + self.fungible.len() + self.non_fungible.len() + } + + /// Returns `true` if `self` contains no assets. + pub fn is_empty(&self) -> bool { + self.fungible.is_empty() && self.non_fungible.is_empty() + } + + /// A borrowing iterator over the fungible assets. + pub fn fungible_assets_iter(&self) -> impl Iterator + '_ { + self.fungible.iter().map(|(id, &amount)| Asset { fun: Fungible(amount), id: id.clone() }) + } + + /// A borrowing iterator over the non-fungible assets. + pub fn non_fungible_assets_iter(&self) -> impl Iterator + '_ { + self.non_fungible.iter().map(|(id, instance)| Asset { fun: NonFungible(*instance), id: id.clone() }) + } + + /// A consuming iterator over all assets. + pub fn into_assets_iter(self) -> impl Iterator { + self.fungible + .into_iter() + .map(|(id, amount)| Asset { fun: Fungible(amount), id }) + .chain(self.non_fungible.into_iter().map(|(id, instance)| Asset { fun: NonFungible(instance), id })) + } + + /// A borrowing iterator over all assets. + pub fn assets_iter(&self) -> impl Iterator + '_ { + self.fungible_assets_iter().chain(self.non_fungible_assets_iter()) + } + + /// Mutate `self` to contain all given `assets`, saturating if necessary. + /// + /// NOTE: [`AssetsInHolding`] are always sorted, allowing us to optimize this function from + /// `O(n^2)` to `O(n)`. + pub fn subsume_assets(&mut self, mut assets: AssetsInHolding) { + let mut f_iter = assets.fungible.iter_mut(); + let mut g_iter = self.fungible.iter_mut(); + if let (Some(mut f), Some(mut g)) = (f_iter.next(), g_iter.next()) { + loop { + if f.0 == g.0 { + // keys are equal. in this case, we add `self`'s balance for the asset onto + // `assets`, balance, knowing that the `append` operation which follows will + // clobber `self`'s value and only use `assets`'s. + (*f.1).saturating_accrue(*g.1); + } + if f.0 <= g.0 { + f = match f_iter.next() { + Some(x) => x, + None => break, + }; + } + if f.0 >= g.0 { + g = match g_iter.next() { + Some(x) => x, + None => break, + }; + } + } + } + self.fungible.append(&mut assets.fungible); + self.non_fungible.append(&mut assets.non_fungible); + } + + /// Mutate `self` to contain the given `asset`, saturating if necessary. + /// + /// Wildcard values of `asset` do nothing. + pub fn subsume(&mut self, asset: Asset) { + match asset.fun { + Fungible(amount) => { + self.fungible.entry(asset.id).and_modify(|e| *e = e.saturating_add(amount)).or_insert(amount); + }, + NonFungible(instance) => { + self.non_fungible.insert((asset.id, instance)); + }, + } + } + + /// Swaps two mutable AssetsInHolding, without deinitializing either one. + pub fn swapped(&mut self, mut with: AssetsInHolding) -> Self { + mem::swap(&mut *self, &mut with); + with + } + + /// Alter any concretely identified assets by prepending the given `Location`. + /// + /// WARNING: For now we consider this infallible and swallow any errors. It is thus the caller's + /// responsibility to ensure that any internal asset IDs are able to be prepended without + /// overflow. + pub fn prepend_location(&mut self, prepend: &Location) { + let mut fungible = Default::default(); + mem::swap(&mut self.fungible, &mut fungible); + self.fungible = fungible + .into_iter() + .map(|(mut id, amount)| { + let _ = id.prepend_with(prepend); + (id, amount) + }) + .collect(); + let mut non_fungible = Default::default(); + mem::swap(&mut self.non_fungible, &mut non_fungible); + self.non_fungible = non_fungible + .into_iter() + .map(|(mut class, inst)| { + let _ = class.prepend_with(prepend); + (class, inst) + }) + .collect(); + } + + /// Mutate the assets to be interpreted as the same assets from the perspective of a `target` + /// chain. The local chain's `context` is provided. + /// + /// Any assets which were unable to be reanchored are introduced into `failed_bin`. + pub fn reanchor(&mut self, target: &Location, context: &InteriorLocation, mut maybe_failed_bin: Option<&mut Self>) { + let mut fungible = Default::default(); + mem::swap(&mut self.fungible, &mut fungible); + self.fungible = fungible + .into_iter() + .filter_map(|(mut id, amount)| match id.reanchor(target, context) { + Ok(()) => Some((id, amount)), + Err(()) => { + maybe_failed_bin.as_mut().map(|f| f.fungible.insert(id, amount)); + None + }, + }) + .collect(); + let mut non_fungible = Default::default(); + mem::swap(&mut self.non_fungible, &mut non_fungible); + self.non_fungible = non_fungible + .into_iter() + .filter_map(|(mut class, inst)| match class.reanchor(target, context) { + Ok(()) => Some((class, inst)), + Err(()) => { + maybe_failed_bin.as_mut().map(|f| f.non_fungible.insert((class, inst))); + None + }, + }) + .collect(); + } + + /// Returns `true` if `asset` is contained within `self`. + pub fn contains_asset(&self, asset: &Asset) -> bool { + match asset { + Asset { fun: Fungible(amount), id } => self.fungible.get(id).map_or(false, |a| a >= amount), + Asset { fun: NonFungible(instance), id } => self.non_fungible.contains(&(id.clone(), *instance)), + } + } + + /// Returns `true` if all `assets` are contained within `self`. + pub fn contains_assets(&self, assets: &Assets) -> bool { + assets.inner().iter().all(|a| self.contains_asset(a)) + } + + /// Returns `true` if all `assets` are contained within `self`. + pub fn contains(&self, assets: &AssetsInHolding) -> bool { + assets.fungible.iter().all(|(k, v)| self.fungible.get(k).map_or(false, |a| a >= v)) && + self.non_fungible.is_superset(&assets.non_fungible) + } + + /// Returns an error unless all `assets` are contained in `self`. In the case of an error, the + /// first asset in `assets` which is not wholly in `self` is returned. + pub fn ensure_contains(&self, assets: &Assets) -> Result<(), TakeError> { + for asset in assets.inner().iter() { + match asset { + Asset { fun: Fungible(amount), id } => + if self.fungible.get(id).map_or(true, |a| a < amount) { + return Err(TakeError::AssetUnderflow((id.clone(), *amount).into())) + }, + Asset { fun: NonFungible(instance), id } => { + let id_instance = (id.clone(), *instance); + if !self.non_fungible.contains(&id_instance) { + return Err(TakeError::AssetUnderflow(id_instance.into())) + } + }, + } + } + return Ok(()) + } + + /// Mutates `self` to its original value less `mask` and returns assets that were removed. + /// + /// If `saturate` is `true`, then `self` is considered to be masked by `mask`, thereby avoiding + /// any attempt at reducing it by assets it does not contain. In this case, the function is + /// infallible. If `saturate` is `false` and `mask` references a definite asset which `self` + /// does not contain then an error is returned. + /// + /// The number of unique assets which are removed will respect the `count` parameter in the + /// counted wildcard variants. + /// + /// Returns `Ok` with the definite assets token from `self` and mutates `self` to its value + /// minus `mask`. Returns `Err` in the non-saturating case where `self` did not contain (enough + /// of) a definite asset to be removed. + fn general_take(&mut self, mask: AssetFilter, saturate: bool) -> Result { + let mut taken = AssetsInHolding::new(); + let maybe_limit = mask.limit().map(|x| x as usize); + match mask { + // TODO: Counted variants where we define `limit`. + AssetFilter::Wild(All) | AssetFilter::Wild(AllCounted(_)) => { + if maybe_limit.map_or(true, |l| self.len() <= l) { + return Ok(self.swapped(AssetsInHolding::new())) + } else { + let fungible = mem::replace(&mut self.fungible, Default::default()); + fungible.into_iter().for_each(|(c, amount)| { + if maybe_limit.map_or(true, |l| taken.len() < l) { + taken.fungible.insert(c, amount); + } else { + self.fungible.insert(c, amount); + } + }); + let non_fungible = mem::replace(&mut self.non_fungible, Default::default()); + non_fungible.into_iter().for_each(|(c, instance)| { + if maybe_limit.map_or(true, |l| taken.len() < l) { + taken.non_fungible.insert((c, instance)); + } else { + self.non_fungible.insert((c, instance)); + } + }); + } + }, + AssetFilter::Wild(AllOfCounted { fun: WildFungible, id, .. }) | + AssetFilter::Wild(AllOf { fun: WildFungible, id }) => + if maybe_limit.map_or(true, |l| l >= 1) { + if let Some((id, amount)) = self.fungible.remove_entry(&id) { + taken.fungible.insert(id, amount); + } + }, + AssetFilter::Wild(AllOfCounted { fun: WildNonFungible, id, .. }) | + AssetFilter::Wild(AllOf { fun: WildNonFungible, id }) => { + let non_fungible = mem::replace(&mut self.non_fungible, Default::default()); + non_fungible.into_iter().for_each(|(c, instance)| { + if c == id && maybe_limit.map_or(true, |l| taken.len() < l) { + taken.non_fungible.insert((c, instance)); + } else { + self.non_fungible.insert((c, instance)); + } + }); + }, + AssetFilter::Definite(assets) => { + if !saturate { + self.ensure_contains(&assets)?; + } + for asset in assets.into_inner().into_iter() { + match asset { + Asset { fun: Fungible(amount), id } => { + let (remove, amount) = match self.fungible.get_mut(&id) { + Some(self_amount) => { + let amount = amount.min(*self_amount); + *self_amount -= amount; + (*self_amount == 0, amount) + }, + None => (false, 0), + }; + if remove { + self.fungible.remove(&id); + } + if amount > 0 { + taken.subsume(Asset::from((id, amount)).into()); + } + }, + Asset { fun: NonFungible(instance), id } => { + let id_instance = (id, instance); + if self.non_fungible.remove(&id_instance) { + taken.subsume(id_instance.into()) + } + }, + } + } + }, + } + Ok(taken) + } + + /// Mutates `self` to its original value less `mask` and returns `true` iff it contains at least + /// `mask`. + /// + /// Returns `Ok` with the non-wildcard equivalence of `mask` taken and mutates `self` to its + /// value minus `mask` if `self` contains `asset`, and return `Err` otherwise. + pub fn saturating_take(&mut self, asset: AssetFilter) -> AssetsInHolding { + self.general_take(asset, true).expect("general_take never results in error when saturating") + } + + /// Mutates `self` to its original value less `mask` and returns `true` iff it contains at least + /// `mask`. + /// + /// Returns `Ok` with the non-wildcard equivalence of `asset` taken and mutates `self` to its + /// value minus `asset` if `self` contains `asset`, and return `Err` otherwise. + pub fn try_take(&mut self, mask: AssetFilter) -> Result { + self.general_take(mask, false) + } + + /// Consumes `self` and returns its original value excluding `asset` iff it contains at least + /// `asset`. + pub fn checked_sub(mut self, asset: Asset) -> Result { + match asset.fun { + Fungible(amount) => { + let remove = if let Some(balance) = self.fungible.get_mut(&asset.id) { + if *balance >= amount { + *balance -= amount; + *balance == 0 + } else { + return Err(self) + } + } else { + return Err(self) + }; + if remove { + self.fungible.remove(&asset.id); + } + Ok(self) + }, + NonFungible(instance) => + if self.non_fungible.remove(&(asset.id, instance)) { + Ok(self) + } else { + Err(self) + }, + } + } + + /// Return the assets in `self`, but (asset-wise) of no greater value than `mask`. + /// + /// The number of unique assets which are returned will respect the `count` parameter in the + /// counted wildcard variants of `mask`. + /// + /// Example: + /// + /// ``` + /// use staging_xcm_executor::AssetsInHolding; + /// use xcm::latest::prelude::*; + /// let assets_i_have: AssetsInHolding = vec![ (Here, 100).into(), (Junctions::from([GeneralIndex(0)]), 100).into() ].into(); + /// let assets_they_want: AssetFilter = vec![ (Here, 200).into(), (Junctions::from([GeneralIndex(0)]), 50).into() ].into(); + /// + /// let assets_we_can_trade: AssetsInHolding = assets_i_have.min(&assets_they_want); + /// assert_eq!(assets_we_can_trade.into_assets_iter().collect::>(), vec![ + /// (Here, 100).into(), (Junctions::from([GeneralIndex(0)]), 50).into(), + /// ]); + /// ``` + pub fn min(&self, mask: &AssetFilter) -> AssetsInHolding { + let mut masked = AssetsInHolding::new(); + let maybe_limit = mask.limit().map(|x| x as usize); + if maybe_limit.map_or(false, |l| l == 0) { + return masked + } + match mask { + AssetFilter::Wild(All) | AssetFilter::Wild(AllCounted(_)) => { + if maybe_limit.map_or(true, |l| self.len() <= l) { + return self.clone() + } else { + for (c, &amount) in self.fungible.iter() { + masked.fungible.insert(c.clone(), amount); + if maybe_limit.map_or(false, |l| masked.len() >= l) { + return masked + } + } + for (c, instance) in self.non_fungible.iter() { + masked.non_fungible.insert((c.clone(), *instance)); + if maybe_limit.map_or(false, |l| masked.len() >= l) { + return masked + } + } + } + }, + AssetFilter::Wild(AllOfCounted { fun: WildFungible, id, .. }) | + AssetFilter::Wild(AllOf { fun: WildFungible, id }) => + if let Some(&amount) = self.fungible.get(&id) { + masked.fungible.insert(id.clone(), amount); + }, + AssetFilter::Wild(AllOfCounted { fun: WildNonFungible, id, .. }) | + AssetFilter::Wild(AllOf { fun: WildNonFungible, id }) => + for (c, instance) in self.non_fungible.iter() { + if c == id { + masked.non_fungible.insert((c.clone(), *instance)); + if maybe_limit.map_or(false, |l| masked.len() >= l) { + return masked + } + } + }, + AssetFilter::Definite(assets) => + for asset in assets.inner().iter() { + match asset { + Asset { fun: Fungible(amount), id } => + if let Some(m) = self.fungible.get(id) { + masked.subsume((id.clone(), Fungible(*amount.min(m))).into()); + }, + Asset { fun: NonFungible(instance), id } => { + let id_instance = (id.clone(), *instance); + if self.non_fungible.contains(&id_instance) { + masked.subsume(id_instance.into()); + } + }, + } + }, + } + masked + } +} + +#[cfg(test)] +mod tests { + use super::*; + use xcm::latest::prelude::*; + #[allow(non_snake_case)] + /// Concrete fungible constructor + fn CF(amount: u128) -> Asset { + (Here, amount).into() + } + #[allow(non_snake_case)] + /// Concrete non-fungible constructor + fn CNF(instance_id: u8) -> Asset { + (Here, [instance_id; 4]).into() + } + + fn test_assets() -> AssetsInHolding { + let mut assets = AssetsInHolding::new(); + assets.subsume(CF(300)); + assets.subsume(CNF(40)); + assets + } + + #[test] + fn subsume_assets_works() { + let t1 = test_assets(); + let mut t2 = AssetsInHolding::new(); + t2.subsume(CF(300)); + t2.subsume(CNF(50)); + let mut r1 = t1.clone(); + r1.subsume_assets(t2.clone()); + let mut r2 = t1.clone(); + for a in t2.assets_iter() { + r2.subsume(a) + } + assert_eq!(r1, r2); + } + + #[test] + fn checked_sub_works() { + let t = test_assets(); + let t = t.checked_sub(CF(150)).unwrap(); + let t = t.checked_sub(CF(151)).unwrap_err(); + let t = t.checked_sub(CF(150)).unwrap(); + let t = t.checked_sub(CF(1)).unwrap_err(); + let t = t.checked_sub(CNF(41)).unwrap_err(); + let t = t.checked_sub(CNF(40)).unwrap(); + let t = t.checked_sub(CNF(40)).unwrap_err(); + assert_eq!(t, AssetsInHolding::new()); + } + + #[test] + fn into_assets_iter_works() { + let assets = test_assets(); + let mut iter = assets.into_assets_iter(); + // Order defined by implementation: CF, CNF + assert_eq!(Some(CF(300)), iter.next()); + assert_eq!(Some(CNF(40)), iter.next()); + assert_eq!(None, iter.next()); + } + + #[test] + fn assets_into_works() { + let mut assets_vec: Vec = Vec::new(); + assets_vec.push(CF(300)); + assets_vec.push(CNF(40)); + // Push same group of tokens again + assets_vec.push(CF(300)); + assets_vec.push(CNF(40)); + + let assets: AssetsInHolding = assets_vec.into(); + let mut iter = assets.into_assets_iter(); + // Fungibles add + assert_eq!(Some(CF(600)), iter.next()); + // Non-fungibles collapse + assert_eq!(Some(CNF(40)), iter.next()); + assert_eq!(None, iter.next()); + } + + #[test] + fn min_all_and_none_works() { + let assets = test_assets(); + let none = Assets::new().into(); + let all = All.into(); + + let none_min = assets.min(&none); + assert_eq!(None, none_min.assets_iter().next()); + let all_min = assets.min(&all); + assert!(all_min.assets_iter().eq(assets.assets_iter())); + } + + #[test] + fn min_counted_works() { + let mut assets = AssetsInHolding::new(); + assets.subsume(CNF(40)); + assets.subsume(CF(3000)); + assets.subsume(CNF(80)); + let all = WildAsset::AllCounted(6).into(); + + let all = assets.min(&all); + let all = all.assets_iter().collect::>(); + assert_eq!(all, vec![CF(3000), CNF(40), CNF(80)]); + } + + #[test] + fn min_all_concrete_works() { + let assets = test_assets(); + let fungible = Wild((Here, WildFungible).into()); + let non_fungible = Wild((Here, WildNonFungible).into()); + + let fungible = assets.min(&fungible); + let fungible = fungible.assets_iter().collect::>(); + assert_eq!(fungible, vec![CF(300)]); + let non_fungible = assets.min(&non_fungible); + let non_fungible = non_fungible.assets_iter().collect::>(); + assert_eq!(non_fungible, vec![CNF(40)]); + } + + #[test] + fn min_basic_works() { + let assets1 = test_assets(); + + let mut assets2 = AssetsInHolding::new(); + // This is more then 300, so it should stay at 300 + assets2.subsume(CF(600)); + // This asset should be included + assets2.subsume(CNF(40)); + let assets2: Assets = assets2.into(); + + let assets_min = assets1.min(&assets2.into()); + let assets_min = assets_min.into_assets_iter().collect::>(); + assert_eq!(assets_min, vec![CF(300), CNF(40)]); + } + + #[test] + fn saturating_take_all_and_none_works() { + let mut assets = test_assets(); + + let taken_none = assets.saturating_take(vec![].into()); + assert_eq!(None, taken_none.assets_iter().next()); + let taken_all = assets.saturating_take(All.into()); + // Everything taken + assert_eq!(None, assets.assets_iter().next()); + let all_iter = taken_all.assets_iter(); + assert!(all_iter.eq(test_assets().assets_iter())); + } + + #[test] + fn saturating_take_all_concrete_works() { + let mut assets = test_assets(); + let fungible = Wild((Here, WildFungible).into()); + let non_fungible = Wild((Here, WildNonFungible).into()); + + let fungible = assets.saturating_take(fungible); + let fungible = fungible.assets_iter().collect::>(); + assert_eq!(fungible, vec![CF(300)]); + let non_fungible = assets.saturating_take(non_fungible); + let non_fungible = non_fungible.assets_iter().collect::>(); + assert_eq!(non_fungible, vec![CNF(40)]); + } + + #[test] + fn saturating_take_basic_works() { + let mut assets1 = test_assets(); + + let mut assets2 = AssetsInHolding::new(); + // This is more then 300, so it takes everything + assets2.subsume(CF(600)); + // This asset should be taken + assets2.subsume(CNF(40)); + let assets2: Assets = assets2.into(); + + let taken = assets1.saturating_take(assets2.into()); + let taken = taken.into_assets_iter().collect::>(); + assert_eq!(taken, vec![CF(300), CNF(40)]); + } + + #[test] + fn try_take_all_counted_works() { + let mut assets = AssetsInHolding::new(); + assets.subsume(CNF(40)); + assets.subsume(CF(3000)); + assets.subsume(CNF(80)); + let all = assets.try_take(WildAsset::AllCounted(6).into()).unwrap(); + assert_eq!(Assets::from(all).inner(), &vec![CF(3000), CNF(40), CNF(80)]); + } + + #[test] + fn try_take_fungibles_counted_works() { + let mut assets = AssetsInHolding::new(); + assets.subsume(CNF(40)); + assets.subsume(CF(3000)); + assets.subsume(CNF(80)); + assert_eq!(Assets::from(assets).inner(), &vec![CF(3000), CNF(40), CNF(80),]); + } + + #[test] + fn try_take_non_fungibles_counted_works() { + let mut assets = AssetsInHolding::new(); + assets.subsume(CNF(40)); + assets.subsume(CF(3000)); + assets.subsume(CNF(80)); + assert_eq!(Assets::from(assets).inner(), &vec![CF(3000), CNF(40), CNF(80)]); + } +} diff --git a/debug-executor/src/config.rs b/debug-executor/src/config.rs new file mode 100644 index 000000000..30d30cc7f --- /dev/null +++ b/debug-executor/src/config.rs @@ -0,0 +1,126 @@ +// Copyright (C) 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 . + +use crate::traits::{ + AssetExchange, AssetLock, CallDispatcher, ClaimAssets, ConvertOrigin, DropAssets, ExportXcm, FeeManager, + HandleHrmpChannelAccepted, HandleHrmpChannelClosing, HandleHrmpNewChannelOpenRequest, OnResponse, + ProcessTransaction, RecordXcm, ShouldExecute, TransactAsset, VersionChangeNotifier, WeightBounds, WeightTrader, +}; +use frame_support::{ + dispatch::{GetDispatchInfo, Parameter, PostDispatchInfo}, + traits::{Contains, ContainsPair, Get, PalletsInfoAccess}, +}; +use sp_runtime::traits::Dispatchable; +use xcm::prelude::*; + +/// The trait to parameterize the `XcmExecutor`. +pub trait Config { + /// The outer call dispatch type. + type RuntimeCall: Parameter + Dispatchable + GetDispatchInfo; + + /// How to send an onward XCM message. + type XcmSender: SendXcm; + + /// How to withdraw and deposit an asset. + type AssetTransactor: TransactAsset; + + /// How to get a call origin from a `OriginKind` value. + type OriginConverter: ConvertOrigin<::RuntimeOrigin>; + + /// Combinations of (Asset, Location) pairs which we trust as reserves. + type IsReserve: ContainsPair; + + /// Combinations of (Asset, Location) pairs which we trust as teleporters. + type IsTeleporter: ContainsPair; + + /// A list of (Origin, Target) pairs allowing a given Origin to be substituted with its + /// corresponding Target pair. + type Aliasers: ContainsPair; + + /// This chain's Universal Location. + type UniversalLocation: Get; + + /// Whether we should execute the given XCM at all. + type Barrier: ShouldExecute; + + /// The means of determining an XCM message's weight. + type Weigher: WeightBounds; + + /// The means of purchasing weight credit for XCM execution. + type Trader: WeightTrader; + + /// What to do when a response of a query is found. + type ResponseHandler: OnResponse; + + /// The general asset trap - handler for when assets are left in the Holding Register at the + /// end of execution. + type AssetTrap: DropAssets; + + /// Handler for asset locking. + type AssetLocker: AssetLock; + + /// Handler for exchanging assets. + type AssetExchanger: AssetExchange; + + /// The handler for when there is an instruction to claim assets. + type AssetClaims: ClaimAssets; + + /// How we handle version subscription requests. + type SubscriptionService: VersionChangeNotifier; + + /// Information on all pallets. + type PalletInstancesInfo: PalletsInfoAccess; + + /// The maximum number of assets we target to have in the Holding Register at any one time. + /// + /// NOTE: In the worse case, the Holding Register may contain up to twice as many assets as this + /// and any benchmarks should take that into account. + type MaxAssetsIntoHolding: Get; + + /// Configure the fees. + type FeeManager: FeeManager; + + /// The method of exporting a message. + type MessageExporter: ExportXcm; + + /// The origin locations and specific universal junctions to which they are allowed to elevate + /// themselves. + type UniversalAliases: Contains<(Location, Junction)>; + + /// The call dispatcher used by XCM. + /// + /// XCM will use this to dispatch any calls. When no special call dispatcher is required, + /// this can be set to the same type as `Self::Call`. + type CallDispatcher: CallDispatcher; + + /// The safe call filter for `Transact`. + /// + /// Use this type to explicitly whitelist calls that cannot undergo recursion. This is a + /// temporary measure until we properly account for proof size weights for XCM instructions. + type SafeCallFilter: Contains; + + /// Transactional processor for XCM instructions. + type TransactionalProcessor: ProcessTransaction; + + /// Allows optional logic execution for the `HrmpNewChannelOpenRequest` XCM notification. + type HrmpNewChannelOpenRequestHandler: HandleHrmpNewChannelOpenRequest; + /// Allows optional logic execution for the `HrmpChannelAccepted` XCM notification. + type HrmpChannelAcceptedHandler: HandleHrmpChannelAccepted; + /// Allows optional logic execution for the `HrmpChannelClosing` XCM notification. + type HrmpChannelClosingHandler: HandleHrmpChannelClosing; + /// Allows recording the last executed XCM (used by dry-run runtime APIs). + type XcmRecorder: RecordXcm; +} diff --git a/debug-executor/src/lib.rs b/debug-executor/src/lib.rs new file mode 100644 index 000000000..0330f843d --- /dev/null +++ b/debug-executor/src/lib.rs @@ -0,0 +1,1188 @@ +// Copyright (C) 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 . + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Decode, Encode}; +use frame_support::{ + dispatch::GetDispatchInfo, + ensure, + traits::{Contains, ContainsPair, Defensive, Get, PalletsInfoAccess}, +}; +use sp_core::defer; +use sp_io::hashing::blake2_128; +use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; +use sp_weights::Weight; +use xcm::latest::prelude::*; + +pub mod traits; +use traits::{ + validate_export, AssetExchange, AssetLock, CallDispatcher, ClaimAssets, ConvertOrigin, DropAssets, Enact, + ExportXcm, FeeManager, FeeReason, HandleHrmpChannelAccepted, HandleHrmpChannelClosing, + HandleHrmpNewChannelOpenRequest, OnResponse, ProcessTransaction, Properties, ShouldExecute, TransactAsset, + VersionChangeNotifier, WeightBounds, WeightTrader, XcmAssetTransfers, +}; + +pub use traits::RecordXcm; + +mod assets; +pub use assets::AssetsInHolding; +mod config; +pub use config::Config; + +/// A struct to specify how fees are being paid. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct FeesMode { + /// If true, then the fee assets are taken directly from the origin's on-chain account, + /// otherwise the fee assets are taken from the holding register. + /// + /// Defaults to false. + pub jit_withdraw: bool, +} + +const RECURSION_LIMIT: u8 = 10; + +environmental::environmental!(recursion_count: u8); + +/// The XCM executor. +pub struct XcmExecutor { + holding: AssetsInHolding, + holding_limit: usize, + context: XcmContext, + original_origin: Location, + trader: Config::Trader, + /// The most recent error result and instruction index into the fragment in which it occurred, + /// if any. + error: Option<(u32, XcmError)>, + /// The surplus weight, defined as the amount by which `max_weight` is + /// an over-estimate of the actual weight consumed. We do it this way to avoid needing the + /// execution engine to keep track of all instructions' weights (it only needs to care about + /// the weight of dynamically determined instructions such as `Transact`). + total_surplus: Weight, + total_refunded: Weight, + error_handler: Xcm, + error_handler_weight: Weight, + appendix: Xcm, + appendix_weight: Weight, + transact_status: MaybeErrorCode, + fees_mode: FeesMode, + _config: PhantomData, +} + +#[cfg(feature = "runtime-benchmarks")] +impl XcmExecutor { + pub fn holding(&self) -> &AssetsInHolding { + &self.holding + } + + pub fn set_holding(&mut self, v: AssetsInHolding) { + self.holding = v + } + + pub fn holding_limit(&self) -> &usize { + &self.holding_limit + } + + pub fn set_holding_limit(&mut self, v: usize) { + self.holding_limit = v + } + + pub fn origin(&self) -> &Option { + &self.context.origin + } + + pub fn set_origin(&mut self, v: Option) { + self.context.origin = v + } + + pub fn original_origin(&self) -> &Location { + &self.original_origin + } + + pub fn set_original_origin(&mut self, v: Location) { + self.original_origin = v + } + + pub fn trader(&self) -> &Config::Trader { + &self.trader + } + + pub fn set_trader(&mut self, v: Config::Trader) { + self.trader = v + } + + pub fn error(&self) -> &Option<(u32, XcmError)> { + &self.error + } + + pub fn set_error(&mut self, v: Option<(u32, XcmError)>) { + self.error = v + } + + pub fn total_surplus(&self) -> &Weight { + &self.total_surplus + } + + pub fn set_total_surplus(&mut self, v: Weight) { + self.total_surplus = v + } + + pub fn total_refunded(&self) -> &Weight { + &self.total_refunded + } + + pub fn set_total_refunded(&mut self, v: Weight) { + self.total_refunded = v + } + + pub fn error_handler(&self) -> &Xcm { + &self.error_handler + } + + pub fn set_error_handler(&mut self, v: Xcm) { + self.error_handler = v + } + + pub fn error_handler_weight(&self) -> &Weight { + &self.error_handler_weight + } + + pub fn set_error_handler_weight(&mut self, v: Weight) { + self.error_handler_weight = v + } + + pub fn appendix(&self) -> &Xcm { + &self.appendix + } + + pub fn set_appendix(&mut self, v: Xcm) { + self.appendix = v + } + + pub fn appendix_weight(&self) -> &Weight { + &self.appendix_weight + } + + pub fn set_appendix_weight(&mut self, v: Weight) { + self.appendix_weight = v + } + + pub fn transact_status(&self) -> &MaybeErrorCode { + &self.transact_status + } + + pub fn set_transact_status(&mut self, v: MaybeErrorCode) { + self.transact_status = v + } + + pub fn fees_mode(&self) -> &FeesMode { + &self.fees_mode + } + + pub fn set_fees_mode(&mut self, v: FeesMode) { + self.fees_mode = v + } + + pub fn topic(&self) -> &Option<[u8; 32]> { + &self.context.topic + } + + pub fn set_topic(&mut self, v: Option<[u8; 32]>) { + self.context.topic = v; + } +} + +pub struct WeighedMessage(Weight, Xcm); +impl PreparedMessage for WeighedMessage { + fn weight_of(&self) -> Weight { + self.0 + } +} + +#[cfg(any(test, feature = "std"))] +impl WeighedMessage { + pub fn new(weight: Weight, message: Xcm) -> Self { + Self(weight, message) + } +} + +impl ExecuteXcm for XcmExecutor { + type Prepared = WeighedMessage; + + fn prepare(mut message: Xcm) -> Result> { + match Config::Weigher::weight(&mut message) { + Ok(weight) => Ok(WeighedMessage(weight, message)), + Err(_) => Err(message), + } + } + + fn execute( + origin: impl Into, + WeighedMessage(xcm_weight, mut message): WeighedMessage, + id: &mut XcmHash, + weight_credit: Weight, + ) -> Outcome { + let origin = origin.into(); + log::trace!( + target: "xcm::execute", + "origin: {origin:?}, message: {message:?}, weight_credit: {weight_credit:?}", + ); + let mut properties = Properties { weight_credit, message_id: None }; + + // We only want to record under certain conditions (mainly only during dry-running), + // so as to not degrade regular performance. + if Config::XcmRecorder::should_record() { + Config::XcmRecorder::record(message.clone().into()); + } + + if let Err(e) = Config::Barrier::should_execute(&origin, message.inner_mut(), xcm_weight, &mut properties) { + log::trace!( + target: "xcm::execute", + "Barrier blocked execution! Error: {e:?}. \ + (origin: {origin:?}, message: {message:?}, properties: {properties:?})", + ); + return Outcome::Error { error: XcmError::Barrier } + } + + *id = properties.message_id.unwrap_or(*id); + + let mut vm = Self::new(origin, *id); + + while !message.0.is_empty() { + let result = vm.process(message); + log::trace!(target: "xcm::execute", "result: {result:?}"); + message = if let Err(error) = result { + vm.total_surplus.saturating_accrue(error.weight); + vm.error = Some((error.index, error.xcm_error)); + vm.take_error_handler().or_else(|| vm.take_appendix()) + } else { + vm.drop_error_handler(); + vm.take_appendix() + } + } + + vm.post_process(xcm_weight) + } + + fn charge_fees(origin: impl Into, fees: Assets) -> XcmResult { + let origin = origin.into(); + if !Config::FeeManager::is_waived(Some(&origin), FeeReason::ChargeFees) { + for asset in fees.inner() { + Config::AssetTransactor::withdraw_asset(&asset, &origin, None)?; + } + Config::FeeManager::handle_fee(fees, None, FeeReason::ChargeFees); + } + Ok(()) + } +} + +impl XcmAssetTransfers for XcmExecutor { + type AssetTransactor = Config::AssetTransactor; + type IsReserve = Config::IsReserve; + type IsTeleporter = Config::IsTeleporter; +} + +#[derive(Debug)] +pub struct ExecutorError { + pub index: u32, + pub xcm_error: XcmError, + pub weight: Weight, +} + +#[cfg(feature = "runtime-benchmarks")] +impl From for frame_benchmarking::BenchmarkError { + fn from(error: ExecutorError) -> Self { + log::error!("XCM ERROR >> Index: {:?}, Error: {:?}, Weight: {:?}", error.index, error.xcm_error, error.weight); + Self::Stop("xcm executor error: see error logs") + } +} + +impl XcmExecutor { + pub fn new(origin: impl Into, message_id: XcmHash) -> Self { + let origin = origin.into(); + Self { + holding: AssetsInHolding::new(), + holding_limit: Config::MaxAssetsIntoHolding::get() as usize, + context: XcmContext { origin: Some(origin.clone()), message_id, topic: None }, + original_origin: origin, + trader: Config::Trader::new(), + error: None, + total_surplus: Weight::zero(), + total_refunded: Weight::zero(), + error_handler: Xcm(vec![]), + error_handler_weight: Weight::zero(), + appendix: Xcm(vec![]), + appendix_weight: Weight::zero(), + transact_status: Default::default(), + fees_mode: FeesMode { jit_withdraw: false }, + _config: PhantomData, + } + } + + /// Execute any final operations after having executed the XCM message. + /// This includes refunding surplus weight, trapping extra holding funds, and returning any + /// errors during execution. + pub fn post_process(mut self, xcm_weight: Weight) -> Outcome { + // We silently drop any error from our attempt to refund the surplus as it's a charitable + // thing so best-effort is all we will do. + let _ = self.refund_surplus(); + drop(self.trader); + + let mut weight_used = xcm_weight.saturating_sub(self.total_surplus); + + if !self.holding.is_empty() { + log::trace!( + target: "xcm::post_process", + "Trapping assets in holding register: {:?}, context: {:?} (original_origin: {:?})", + self.holding, self.context, self.original_origin, + ); + let effective_origin = self.context.origin.as_ref().unwrap_or(&self.original_origin); + let trap_weight = Config::AssetTrap::drop_assets(effective_origin, self.holding, &self.context); + weight_used.saturating_accrue(trap_weight); + }; + + match self.error { + None => Outcome::Complete { used: weight_used }, + // TODO: #2841 #REALWEIGHT We should deduct the cost of any instructions following + // the error which didn't end up being executed. + Some((_i, e)) => { + log::trace!(target: "xcm::post_process", "Execution errored at {:?}: {:?} (original_origin: {:?})", _i, e, self.original_origin); + Outcome::Incomplete { used: weight_used, error: e } + }, + } + } + + fn origin_ref(&self) -> Option<&Location> { + self.context.origin.as_ref() + } + + fn cloned_origin(&self) -> Option { + self.context.origin.clone() + } + + /// Send an XCM, charging fees from Holding as needed. + fn send(&mut self, dest: Location, msg: Xcm<()>, reason: FeeReason) -> Result { + log::trace!( + target: "xcm::send", "Sending msg: {msg:?}, to destination: {dest:?}, (reason: {reason:?})" + ); + let (ticket, fee) = validate_send::(dest, msg)?; + self.take_fee(fee, reason)?; + Config::XcmSender::deliver(ticket).map_err(Into::into) + } + + /// Remove the registered error handler and return it. Do not refund its weight. + fn take_error_handler(&mut self) -> Xcm { + let mut r = Xcm::(vec![]); + sp_std::mem::swap(&mut self.error_handler, &mut r); + self.error_handler_weight = Weight::zero(); + r + } + + /// Drop the registered error handler and refund its weight. + fn drop_error_handler(&mut self) { + self.error_handler = Xcm::(vec![]); + self.total_surplus.saturating_accrue(self.error_handler_weight); + self.error_handler_weight = Weight::zero(); + } + + /// Remove the registered appendix and return it. + fn take_appendix(&mut self) -> Xcm { + let mut r = Xcm::(vec![]); + sp_std::mem::swap(&mut self.appendix, &mut r); + self.appendix_weight = Weight::zero(); + r + } + + fn ensure_can_subsume_assets(&self, assets_length: usize) -> Result<(), XcmError> { + // worst-case, holding.len becomes 2 * holding_limit. + // this guarantees that if holding.len() == holding_limit and you have more than + // `holding_limit` items (which has a best case outcome of holding.len() == holding_limit), + // then the operation is guaranteed to succeed. + let worst_case_holding_len = self.holding.len() + assets_length; + log::trace!(target: "xcm::ensure_can_subsume_assets", "worst_case_holding_len: {:?}, holding_limit: {:?}", worst_case_holding_len, self.holding_limit); + ensure!(worst_case_holding_len <= self.holding_limit * 2, XcmError::HoldingWouldOverflow); + Ok(()) + } + + /// Refund any unused weight. + fn refund_surplus(&mut self) -> Result<(), XcmError> { + let current_surplus = self.total_surplus.saturating_sub(self.total_refunded); + log::trace!( + target: "xcm::refund_surplus", + "total_surplus: {:?}, total_refunded: {:?}, current_surplus: {:?}", + self.total_surplus, + self.total_refunded, + current_surplus, + ); + if current_surplus.any_gt(Weight::zero()) { + if let Some(w) = self.trader.refund_weight(current_surplus, &self.context) { + if !self.holding.contains_asset(&(w.id.clone(), 1).into()) && self.ensure_can_subsume_assets(1).is_err() + { + let _ = self + .trader + .buy_weight(current_surplus, w.into(), &self.context) + .defensive_proof("refund_weight returned an asset capable of buying weight; qed"); + log::error!( + target: "xcm::refund_surplus", + "error: HoldingWouldOverflow", + ); + return Err(XcmError::HoldingWouldOverflow) + } + self.total_refunded.saturating_accrue(current_surplus); + self.holding.subsume_assets(w.into()); + } + } + log::trace!( + target: "xcm::refund_surplus", + "total_refunded: {:?}", + self.total_refunded, + ); + Ok(()) + } + + fn take_fee(&mut self, fee: Assets, reason: FeeReason) -> XcmResult { + if Config::FeeManager::is_waived(self.origin_ref(), reason.clone()) { + return Ok(()) + } + log::trace!( + target: "xcm::fees", + "taking fee: {:?} from origin_ref: {:?} in fees_mode: {:?} for a reason: {:?}", + fee, + self.origin_ref(), + self.fees_mode, + reason, + ); + let paid = if self.fees_mode.jit_withdraw { + let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; + for asset in fee.inner() { + Config::AssetTransactor::withdraw_asset(&asset, origin, Some(&self.context))?; + } + fee + } else { + self.holding.try_take(fee.into()).map_err(|_| XcmError::NotHoldingFees)?.into() + }; + Config::FeeManager::handle_fee(paid, Some(&self.context), reason); + Ok(()) + } + + /// Calculates what `local_querier` would be from the perspective of `destination`. + fn to_querier(local_querier: Option, destination: &Location) -> Result, XcmError> { + Ok(match local_querier { + None => None, + Some(q) => Some( + q.reanchored(&destination, &Config::UniversalLocation::get()).map_err(|_| XcmError::ReanchorFailed)?, + ), + }) + } + + /// Send a bare `QueryResponse` message containing `response` informed by the given `info`. + /// + /// The `local_querier` argument is the querier (if any) specified from the *local* perspective. + fn respond( + &mut self, + local_querier: Option, + response: Response, + info: QueryResponseInfo, + fee_reason: FeeReason, + ) -> Result { + let querier = Self::to_querier(local_querier, &info.destination)?; + let QueryResponseInfo { destination, query_id, max_weight } = info; + let instruction = QueryResponse { query_id, response, max_weight, querier }; + let message = Xcm(vec![instruction]); + self.send(destination, message, fee_reason) + } + + fn try_reanchor( + reanchorable: T, + destination: &Location, + ) -> Result<(T, InteriorLocation), XcmError> { + let reanchor_context = Config::UniversalLocation::get(); + let reanchored = reanchorable.reanchored(&destination, &reanchor_context).map_err(|error| { + log::error!(target: "xcm::reanchor", "Failed reanchoring with error {error:?}"); + XcmError::ReanchorFailed + })?; + Ok((reanchored, reanchor_context)) + } + + /// NOTE: Any assets which were unable to be reanchored are introduced into `failed_bin`. + fn reanchored( + mut assets: AssetsInHolding, + dest: &Location, + maybe_failed_bin: Option<&mut AssetsInHolding>, + ) -> Assets { + let reanchor_context = Config::UniversalLocation::get(); + assets.reanchor(dest, &reanchor_context, maybe_failed_bin); + assets.into_assets_iter().collect::>().into() + } + + #[cfg(feature = "runtime-benchmarks")] + pub fn bench_process(&mut self, xcm: Xcm) -> Result<(), ExecutorError> { + self.process(xcm) + } + + fn process(&mut self, xcm: Xcm) -> Result<(), ExecutorError> { + log::trace!( + target: "xcm::process", + "origin: {:?}, total_surplus/refunded: {:?}/{:?}, error_handler_weight: {:?}", + self.origin_ref(), + self.total_surplus, + self.total_refunded, + self.error_handler_weight, + ); + let mut result = Ok(()); + for (i, instr) in xcm.0.into_iter().enumerate() { + match &mut result { + r @ Ok(()) => { + // Initialize the recursion count only the first time we hit this code in our + // potential recursive execution. + let inst_res = recursion_count::using_once(&mut 1, || { + recursion_count::with(|count| { + if *count > RECURSION_LIMIT { + return Err(XcmError::ExceedsStackLimit) + } + *count = count.saturating_add(1); + Ok(()) + }) + // This should always return `Some`, but let's play it safe. + .unwrap_or(Ok(()))?; + + // Ensure that we always decrement the counter whenever we finish processing + // the instruction. + defer! { + recursion_count::with(|count| { + *count = count.saturating_sub(1); + }); + } + + self.process_instruction(instr) + }); + if let Err(e) = inst_res { + log::trace!(target: "xcm::execute", "!!! ERROR: {:?}", e); + *r = Err(ExecutorError { index: i as u32, xcm_error: e, weight: Weight::zero() }); + } + }, + Err(ref mut error) => + if let Ok(x) = Config::Weigher::instr_weight(&instr) { + error.weight.saturating_accrue(x) + }, + } + } + result + } + + /// Process a single XCM instruction, mutating the state of the XCM virtual machine. + fn process_instruction(&mut self, instr: Instruction) -> Result<(), XcmError> { + log::trace!( + target: "xcm::process_instruction", + "=== {:?}", + instr + ); + match instr { + WithdrawAsset(assets) => { + let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; + self.ensure_can_subsume_assets(assets.len())?; + Config::TransactionalProcessor::process(|| { + // Take `assets` from the origin account (on-chain)... + for asset in assets.inner() { + Config::AssetTransactor::withdraw_asset(asset, origin, Some(&self.context))?; + } + Ok(()) + }) + .and_then(|_| { + // ...and place into holding. + self.holding.subsume_assets(assets.into()); + Ok(()) + }) + }, + ReserveAssetDeposited(assets) => { + // check whether we trust origin to be our reserve location for this asset. + let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; + self.ensure_can_subsume_assets(assets.len())?; + for asset in assets.inner() { + // Must ensure that we recognise the asset as being managed by the origin. + ensure!(Config::IsReserve::contains(asset, origin), XcmError::UntrustedReserveLocation); + } + self.holding.subsume_assets(assets.into()); + Ok(()) + }, + TransferAsset { assets, beneficiary } => { + Config::TransactionalProcessor::process(|| { + // Take `assets` from the origin account (on-chain) and place into dest account. + let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; + for asset in assets.inner() { + Config::AssetTransactor::transfer_asset(&asset, origin, &beneficiary, &self.context)?; + } + Ok(()) + }) + }, + TransferReserveAsset { mut assets, dest, xcm } => { + Config::TransactionalProcessor::process(|| { + let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; + // Take `assets` from the origin account (on-chain) and place into dest account. + for asset in assets.inner() { + Config::AssetTransactor::transfer_asset(asset, origin, &dest, &self.context)?; + } + let reanchor_context = Config::UniversalLocation::get(); + assets.reanchor(&dest, &reanchor_context).map_err(|()| XcmError::LocationFull)?; + let mut message = vec![ReserveAssetDeposited(assets), ClearOrigin]; + message.extend(xcm.0.into_iter()); + self.send(dest, Xcm(message), FeeReason::TransferReserveAsset)?; + Ok(()) + }) + }, + ReceiveTeleportedAsset(assets) => { + let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; + self.ensure_can_subsume_assets(assets.len())?; + Config::TransactionalProcessor::process(|| { + // check whether we trust origin to teleport this asset to us via config trait. + for asset in assets.inner() { + // We only trust the origin to send us assets that they identify as their + // sovereign assets. + ensure!(Config::IsTeleporter::contains(asset, origin), XcmError::UntrustedTeleportLocation); + // We should check that the asset can actually be teleported in (for this to + // be in error, there would need to be an accounting violation by one of the + // trusted chains, so it's unlikely, but we don't want to punish a possibly + // innocent chain/user). + Config::AssetTransactor::can_check_in(origin, asset, &self.context)?; + Config::AssetTransactor::check_in(origin, asset, &self.context); + } + Ok(()) + }) + .and_then(|_| { + self.holding.subsume_assets(assets.into()); + Ok(()) + }) + }, + Transact { origin_kind, require_weight_at_most, mut call } => { + // We assume that the Relay-chain is allowed to use transact on this parachain. + let origin = self.cloned_origin().ok_or_else(|| { + log::trace!( + target: "xcm::process_instruction::transact", + "No origin provided", + ); + + XcmError::BadOrigin + })?; + + // TODO: #2841 #TRANSACTFILTER allow the trait to issue filters for the relay-chain + let message_call = call.take_decoded().map_err(|_| { + log::trace!( + target: "xcm::process_instruction::transact", + "Failed to decode call", + ); + + XcmError::FailedToDecode + })?; + + log::trace!( + target: "xcm::process_instruction::transact", + "Processing call: {message_call:?}", + ); + + if !Config::SafeCallFilter::contains(&message_call) { + log::trace!( + target: "xcm::process_instruction::transact", + "Call filtered by `SafeCallFilter`", + ); + + return Err(XcmError::NoPermission) + } + + let dispatch_origin = + Config::OriginConverter::convert_origin(origin.clone(), origin_kind).map_err(|_| { + log::trace!( + target: "xcm::process_instruction::transact", + "Failed to convert origin {origin:?} and origin kind {origin_kind:?} to a local origin." + ); + + XcmError::BadOrigin + })?; + + log::trace!( + target: "xcm::process_instruction::transact", + "Dispatching with origin: {dispatch_origin:?}", + ); + + let weight = message_call.get_dispatch_info().weight; + + if !weight.all_lte(require_weight_at_most) { + log::trace!( + target: "xcm::process_instruction::transact", + "Max {weight} bigger than require at most {require_weight_at_most}", + ); + + return Err(XcmError::MaxWeightInvalid) + } + + let maybe_actual_weight = match Config::CallDispatcher::dispatch(message_call, dispatch_origin) { + Ok(post_info) => { + log::trace!( + target: "xcm::process_instruction::transact", + "Dispatch successful: {post_info:?}" + ); + self.transact_status = MaybeErrorCode::Success; + post_info.actual_weight + }, + Err(error_and_info) => { + log::trace!( + target: "xcm::process_instruction::transact", + "Dispatch failed {error_and_info:?}" + ); + + self.transact_status = error_and_info.error.encode().into(); + error_and_info.post_info.actual_weight + }, + }; + let actual_weight = maybe_actual_weight.unwrap_or(weight); + let surplus = weight.saturating_sub(actual_weight); + // We assume that the `Config::Weigher` will counts the `require_weight_at_most` + // for the estimate of how much weight this instruction will take. Now that we know + // that it's less, we credit it. + // + // We make the adjustment for the total surplus, which is used eventually + // reported back to the caller and this ensures that they account for the total + // weight consumed correctly (potentially allowing them to do more operations in a + // block than they otherwise would). + self.total_surplus.saturating_accrue(surplus); + Ok(()) + }, + QueryResponse { query_id, response, max_weight, querier } => { + let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; + Config::ResponseHandler::on_response( + origin, + query_id, + querier.as_ref(), + response, + max_weight, + &self.context, + ); + Ok(()) + }, + DescendOrigin(who) => self + .context + .origin + .as_mut() + .ok_or(XcmError::BadOrigin)? + .append_with(who) + .map_err(|_| XcmError::LocationFull), + ClearOrigin => { + self.context.origin = None; + Ok(()) + }, + ReportError(response_info) => { + // Report the given result by sending a QueryResponse XCM to a previously given + // outcome destination if one was registered. + self.respond( + self.cloned_origin(), + Response::ExecutionResult(self.error), + response_info, + FeeReason::Report, + )?; + Ok(()) + }, + DepositAsset { assets, beneficiary } => { + let old_holding = self.holding.clone(); + let result = Config::TransactionalProcessor::process(|| { + let deposited = self.holding.saturating_take(assets); + for asset in deposited.into_assets_iter() { + Config::AssetTransactor::deposit_asset(&asset, &beneficiary, Some(&self.context))?; + } + Ok(()) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + self.holding = old_holding; + } + result + }, + DepositReserveAsset { assets, dest, xcm } => { + let old_holding = self.holding.clone(); + let result = Config::TransactionalProcessor::process(|| { + // we need to do this take/put cycle to solve wildcards and get exact assets to + // be weighed + let to_weigh = self.holding.saturating_take(assets.clone()); + self.holding.subsume_assets(to_weigh.clone()); + let to_weigh_reanchored = Self::reanchored(to_weigh, &dest, None); + let mut message_to_weigh = vec![ReserveAssetDeposited(to_weigh_reanchored), ClearOrigin]; + message_to_weigh.extend(xcm.0.clone().into_iter()); + let (_, fee) = validate_send::(dest.clone(), Xcm(message_to_weigh))?; + // set aside fee to be charged by XcmSender + let transport_fee = self.holding.saturating_take(fee.into()); + + // now take assets to deposit (excluding transport_fee) + let deposited = self.holding.saturating_take(assets); + for asset in deposited.assets_iter() { + Config::AssetTransactor::deposit_asset(&asset, &dest, Some(&self.context))?; + } + // Note that we pass `None` as `maybe_failed_bin` and drop any assets which + // cannot be reanchored because we have already called `deposit_asset` on all + // assets. + let assets = Self::reanchored(deposited, &dest, None); + let mut message = vec![ReserveAssetDeposited(assets), ClearOrigin]; + message.extend(xcm.0.into_iter()); + // put back transport_fee in holding register to be charged by XcmSender + self.holding.subsume_assets(transport_fee); + self.send(dest, Xcm(message), FeeReason::DepositReserveAsset)?; + Ok(()) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + self.holding = old_holding; + } + result + }, + InitiateReserveWithdraw { assets, reserve, xcm } => { + let old_holding = self.holding.clone(); + let result = Config::TransactionalProcessor::process(|| { + // Note that here we are able to place any assets which could not be reanchored + // back into Holding. + let assets = + Self::reanchored(self.holding.saturating_take(assets), &reserve, Some(&mut self.holding)); + let mut message = vec![WithdrawAsset(assets), ClearOrigin]; + message.extend(xcm.0.into_iter()); + self.send(reserve, Xcm(message), FeeReason::InitiateReserveWithdraw)?; + Ok(()) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + self.holding = old_holding; + } + result + }, + InitiateTeleport { assets, dest, xcm } => { + let old_holding = self.holding.clone(); + let result = (|| -> Result<(), XcmError> { + // We must do this first in order to resolve wildcards. + let assets = self.holding.saturating_take(assets); + for asset in assets.assets_iter() { + // We should check that the asset can actually be teleported out (for this + // to be in error, there would need to be an accounting violation by + // ourselves, so it's unlikely, but we don't want to allow that kind of bug + // to leak into a trusted chain. + Config::AssetTransactor::can_check_out(&dest, &asset, &self.context)?; + } + // Note that we pass `None` as `maybe_failed_bin` and drop any assets which + // cannot be reanchored because we have already checked all assets out. + let reanchored_assets = Self::reanchored(assets.clone(), &dest, None); + let mut message = vec![ReceiveTeleportedAsset(reanchored_assets), ClearOrigin]; + message.extend(xcm.0.into_iter()); + self.send(dest.clone(), Xcm(message), FeeReason::InitiateTeleport)?; + + for asset in assets.assets_iter() { + Config::AssetTransactor::check_out(&dest, &asset, &self.context); + } + Ok(()) + })(); + if result.is_err() { + self.holding = old_holding; + } + result + }, + ReportHolding { response_info, assets } => { + // Note that we pass `None` as `maybe_failed_bin` since no assets were ever removed + // from Holding. + let assets = Self::reanchored(self.holding.min(&assets), &response_info.destination, None); + self.respond(self.cloned_origin(), Response::Assets(assets), response_info, FeeReason::Report)?; + Ok(()) + }, + BuyExecution { fees, weight_limit } => { + // There is no need to buy any weight if `weight_limit` is `Unlimited` since it + // would indicate that `AllowTopLevelPaidExecutionFrom` was unused for execution + // and thus there is some other reason why it has been determined that this XCM + // should be executed. + let Some(weight) = Option::::from(weight_limit) else { return Ok(()) }; + let old_holding = self.holding.clone(); + // pay for `weight` using up to `fees` of the holding register. + let max_fee = self.holding.try_take(fees.into()).map_err(|_| XcmError::NotHoldingFees)?; + log::trace!( + target: "xcm::execute", + "max_fee: {max_fee:?}", + ); + let result = || -> Result<(), XcmError> { + let unspent = self.trader.buy_weight(weight, max_fee, &self.context)?; + log::trace!( + target: "xcm::execute", + "unspent: {unspent:?}", + ); + self.holding.subsume_assets(unspent); + Ok(()) + }(); + if result.is_err() { + self.holding = old_holding; + } + result + }, + RefundSurplus => self.refund_surplus(), + SetErrorHandler(mut handler) => { + let handler_weight = + Config::Weigher::weight(&mut handler).map_err(|()| XcmError::WeightNotComputable)?; + self.total_surplus.saturating_accrue(self.error_handler_weight); + self.error_handler = handler; + self.error_handler_weight = handler_weight; + Ok(()) + }, + SetAppendix(mut appendix) => { + let appendix_weight = + Config::Weigher::weight(&mut appendix).map_err(|()| XcmError::WeightNotComputable)?; + self.total_surplus.saturating_accrue(self.appendix_weight); + self.appendix = appendix; + self.appendix_weight = appendix_weight; + Ok(()) + }, + ClearError => { + self.error = None; + Ok(()) + }, + ClaimAsset { assets, ticket } => { + let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; + self.ensure_can_subsume_assets(assets.len())?; + let ok = Config::AssetClaims::claim_assets(origin, &ticket, &assets, &self.context); + ensure!(ok, XcmError::UnknownClaim); + self.holding.subsume_assets(assets.into()); + Ok(()) + }, + Trap(code) => Err(XcmError::Trap(code)), + SubscribeVersion { query_id, max_response_weight } => { + let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; + // We don't allow derivative origins to subscribe since it would otherwise pose a + // DoS risk. + ensure!(&self.original_origin == origin, XcmError::BadOrigin); + Config::SubscriptionService::start(origin, query_id, max_response_weight, &self.context) + }, + UnsubscribeVersion => { + let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; + ensure!(&self.original_origin == origin, XcmError::BadOrigin); + Config::SubscriptionService::stop(origin, &self.context) + }, + BurnAsset(assets) => { + self.holding.saturating_take(assets.into()); + Ok(()) + }, + ExpectAsset(assets) => self.holding.ensure_contains(&assets).map_err(|_| XcmError::ExpectationFalse), + ExpectOrigin(origin) => { + ensure!(self.context.origin == origin, XcmError::ExpectationFalse); + Ok(()) + }, + ExpectError(error) => { + ensure!(self.error == error, XcmError::ExpectationFalse); + Ok(()) + }, + ExpectTransactStatus(transact_status) => { + ensure!(self.transact_status == transact_status, XcmError::ExpectationFalse); + Ok(()) + }, + QueryPallet { module_name, response_info } => { + let pallets = Config::PalletInstancesInfo::infos() + .into_iter() + .filter(|x| x.module_name.as_bytes() == &module_name[..]) + .map(|x| { + PalletInfo::new( + x.index as u32, + x.name.as_bytes().into(), + x.module_name.as_bytes().into(), + x.crate_version.major as u32, + x.crate_version.minor as u32, + x.crate_version.patch as u32, + ) + }) + .collect::, XcmError>>()?; + let QueryResponseInfo { destination, query_id, max_weight } = response_info; + let response = Response::PalletsInfo(pallets.try_into().map_err(|_| XcmError::Overflow)?); + let querier = Self::to_querier(self.cloned_origin(), &destination)?; + let instruction = QueryResponse { query_id, response, max_weight, querier }; + let message = Xcm(vec![instruction]); + self.send(destination, message, FeeReason::QueryPallet)?; + Ok(()) + }, + ExpectPallet { index, name, module_name, crate_major, min_crate_minor } => { + let pallet = Config::PalletInstancesInfo::infos() + .into_iter() + .find(|x| x.index == index as usize) + .ok_or(XcmError::PalletNotFound)?; + ensure!(pallet.name.as_bytes() == &name[..], XcmError::NameMismatch); + ensure!(pallet.module_name.as_bytes() == &module_name[..], XcmError::NameMismatch); + let major = pallet.crate_version.major as u32; + ensure!(major == crate_major, XcmError::VersionIncompatible); + let minor = pallet.crate_version.minor as u32; + ensure!(minor >= min_crate_minor, XcmError::VersionIncompatible); + Ok(()) + }, + ReportTransactStatus(response_info) => { + self.respond( + self.cloned_origin(), + Response::DispatchResult(self.transact_status.clone()), + response_info, + FeeReason::Report, + )?; + Ok(()) + }, + ClearTransactStatus => { + self.transact_status = Default::default(); + Ok(()) + }, + UniversalOrigin(new_global) => { + let universal_location = Config::UniversalLocation::get(); + ensure!(universal_location.first() != Some(&new_global), XcmError::InvalidLocation); + let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?; + let origin_xform = (origin, new_global); + let ok = Config::UniversalAliases::contains(&origin_xform); + ensure!(ok, XcmError::InvalidLocation); + let (_, new_global) = origin_xform; + let new_origin = Junctions::from([new_global]).relative_to(&universal_location); + self.context.origin = Some(new_origin); + Ok(()) + }, + ExportMessage { network, destination, xcm } => { + // The actual message sent to the bridge for forwarding is prepended with + // `UniversalOrigin` and `DescendOrigin` in order to ensure that the message is + // executed with this Origin. + // + // Prepend the desired message with instructions which effectively rewrite the + // origin. + // + // This only works because the remote chain empowers the bridge + // to speak for the local network. + let origin = self.context.origin.as_ref().ok_or(XcmError::BadOrigin)?.clone(); + let universal_source = + Config::UniversalLocation::get().within_global(origin).map_err(|()| XcmError::Unanchored)?; + let hash = (self.origin_ref(), &destination).using_encoded(blake2_128); + let channel = u32::decode(&mut hash.as_ref()).unwrap_or(0); + // Hash identifies the lane on the exporter which we use. We use the pairwise + // combination of the origin and destination to ensure origin/destination pairs + // will generally have their own lanes. + let (ticket, fee) = validate_export::( + network, + channel, + universal_source, + destination.clone(), + xcm, + )?; + let old_holding = self.holding.clone(); + let result = Config::TransactionalProcessor::process(|| { + self.take_fee(fee, FeeReason::Export { network, destination })?; + let _ = Config::MessageExporter::deliver(ticket).defensive_proof( + "`deliver` called immediately after `validate_export`; \ + `take_fee` does not affect the validity of the ticket; qed", + ); + Ok(()) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + self.holding = old_holding; + } + result + }, + LockAsset { asset, unlocker } => { + let old_holding = self.holding.clone(); + let result = Config::TransactionalProcessor::process(|| { + let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?; + let (remote_asset, context) = Self::try_reanchor(asset.clone(), &unlocker)?; + let lock_ticket = Config::AssetLocker::prepare_lock(unlocker.clone(), asset, origin.clone())?; + let owner = origin.reanchored(&unlocker, &context).map_err(|_| XcmError::ReanchorFailed)?; + let msg = Xcm::<()>(vec![NoteUnlockable { asset: remote_asset, owner }]); + let (ticket, price) = validate_send::(unlocker, msg)?; + self.take_fee(price, FeeReason::LockAsset)?; + lock_ticket.enact()?; + Config::XcmSender::deliver(ticket)?; + Ok(()) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + self.holding = old_holding; + } + result + }, + UnlockAsset { asset, target } => { + let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?; + Config::AssetLocker::prepare_unlock(origin, asset, target)?.enact()?; + Ok(()) + }, + NoteUnlockable { asset, owner } => { + let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?; + Config::AssetLocker::note_unlockable(origin, asset, owner)?; + Ok(()) + }, + RequestUnlock { asset, locker } => { + let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?; + let remote_asset = Self::try_reanchor(asset.clone(), &locker)?.0; + let remote_target = Self::try_reanchor(origin.clone(), &locker)?.0; + let reduce_ticket = + Config::AssetLocker::prepare_reduce_unlockable(locker.clone(), asset, origin.clone())?; + let msg = Xcm::<()>(vec![UnlockAsset { asset: remote_asset, target: remote_target }]); + let (ticket, price) = validate_send::(locker, msg)?; + let old_holding = self.holding.clone(); + let result = Config::TransactionalProcessor::process(|| { + self.take_fee(price, FeeReason::RequestUnlock)?; + reduce_ticket.enact()?; + Config::XcmSender::deliver(ticket)?; + Ok(()) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + self.holding = old_holding; + } + result + }, + ExchangeAsset { give, want, maximal } => { + let old_holding = self.holding.clone(); + let give = self.holding.saturating_take(give); + let result = (|| -> Result<(), XcmError> { + self.ensure_can_subsume_assets(want.len())?; + let exchange_result = + Config::AssetExchanger::exchange_asset(self.origin_ref(), give, &want, maximal); + if let Ok(received) = exchange_result { + self.holding.subsume_assets(received.into()); + Ok(()) + } else { + Err(XcmError::NoDeal) + } + })(); + if result.is_err() { + self.holding = old_holding; + } + result + }, + SetFeesMode { jit_withdraw } => { + self.fees_mode = FeesMode { jit_withdraw }; + Ok(()) + }, + SetTopic(topic) => { + self.context.topic = Some(topic); + Ok(()) + }, + ClearTopic => { + self.context.topic = None; + Ok(()) + }, + AliasOrigin(target) => { + let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; + if Config::Aliasers::contains(origin, &target) { + self.context.origin = Some(target); + Ok(()) + } else { + Err(XcmError::NoPermission) + } + }, + UnpaidExecution { check_origin, .. } => { + ensure!(check_origin.is_none() || self.context.origin == check_origin, XcmError::BadOrigin); + Ok(()) + }, + HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => + Config::TransactionalProcessor::process(|| { + Config::HrmpNewChannelOpenRequestHandler::handle(sender, max_message_size, max_capacity) + }), + HrmpChannelAccepted { recipient } => + Config::TransactionalProcessor::process(|| Config::HrmpChannelAcceptedHandler::handle(recipient)), + HrmpChannelClosing { initiator, sender, recipient } => Config::TransactionalProcessor::process(|| { + Config::HrmpChannelClosingHandler::handle(initiator, sender, recipient) + }), + } + } +} diff --git a/debug-executor/src/traits/asset_exchange.rs b/debug-executor/src/traits/asset_exchange.rs new file mode 100644 index 000000000..432a7498e --- /dev/null +++ b/debug-executor/src/traits/asset_exchange.rs @@ -0,0 +1,58 @@ +// Copyright (C) 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 . + +use crate::AssetsInHolding; +use xcm::prelude::*; + +/// A service for exchanging assets. +pub trait AssetExchange { + /// Handler for exchanging an asset. + /// + /// - `origin`: The location attempting the exchange; this should generally not matter. + /// - `give`: The assets which have been removed from the caller. + /// - `want`: The minimum amount of assets which should be given to the caller in case any + /// exchange happens. If more assets are provided, then they should generally be of the same + /// asset class if at all possible. + /// - `maximal`: If `true`, then as much as possible should be exchanged. + /// + /// `Ok` is returned along with the new set of assets which have been exchanged for `give`. At + /// least want must be in the set. Some assets originally in `give` may also be in this set. In + /// the case of returning an `Err`, then `give` is returned. + fn exchange_asset( + origin: Option<&Location>, + give: AssetsInHolding, + want: &Assets, + maximal: bool, + ) -> Result; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl AssetExchange for Tuple { + fn exchange_asset( + origin: Option<&Location>, + give: AssetsInHolding, + want: &Assets, + maximal: bool, + ) -> Result { + for_tuples!( #( + let give = match Tuple::exchange_asset(origin, give, want, maximal) { + Ok(r) => return Ok(r), + Err(a) => a, + }; + )* ); + Err(give) + } +} diff --git a/debug-executor/src/traits/asset_lock.rs b/debug-executor/src/traits/asset_lock.rs new file mode 100644 index 000000000..f344c2b7d --- /dev/null +++ b/debug-executor/src/traits/asset_lock.rs @@ -0,0 +1,131 @@ +// Copyright (C) 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 . + +use sp_std::convert::Infallible; +use xcm::prelude::*; + +#[derive(Debug)] +pub enum LockError { + NotApplicable, + WouldClobber, + BadOrigin, + NotLocked, + NotEnoughLocked, + Unimplemented, + NotTrusted, + BadOwner, + UnknownAsset, + AssetNotOwned, + NoResources, + UnexpectedState, + InUse, +} + +impl From for XcmError { + fn from(e: LockError) -> XcmError { + use LockError::*; + match e { + NotApplicable => XcmError::AssetNotFound, + BadOrigin => XcmError::BadOrigin, + WouldClobber | NotLocked | NotEnoughLocked | Unimplemented | NotTrusted | BadOwner | UnknownAsset | + AssetNotOwned | NoResources | UnexpectedState | InUse => XcmError::LockError, + } + } +} + +pub trait Enact { + /// Enact a lock. This should generally be infallible if called immediately after being + /// received. + fn enact(self) -> Result<(), LockError>; +} + +impl Enact for Infallible { + fn enact(self) -> Result<(), LockError> { + unreachable!() + } +} + +/// Define a handler for notification of an asset being locked and for the unlock instruction. +pub trait AssetLock { + /// `Enact` implementer for `prepare_lock`. This type may be dropped safely to avoid doing the + /// lock. + type LockTicket: Enact; + + /// `Enact` implementer for `prepare_unlock`. This type may be dropped safely to avoid doing the + /// unlock. + type UnlockTicket: Enact; + + /// `Enact` implementer for `prepare_reduce_unlockable`. This type may be dropped safely to + /// avoid doing the unlock. + type ReduceTicket: Enact; + + /// Prepare to lock an asset. On success, a `Self::LockTicket` it returned, which can be used + /// to actually enact the lock. + /// + /// WARNING: Don't call this with an undropped instance of `Self::LockTicket` or + /// `Self::UnlockTicket`. + fn prepare_lock(unlocker: Location, asset: Asset, owner: Location) -> Result; + + /// Prepare to unlock an asset. On success, a `Self::UnlockTicket` it returned, which can be + /// used to actually enact the lock. + /// + /// WARNING: Don't call this with an undropped instance of `Self::LockTicket` or + /// `Self::UnlockTicket`. + fn prepare_unlock(locker: Location, asset: Asset, owner: Location) -> Result; + + /// Handler for when a location reports to us that an asset has been locked for us to unlock + /// at a later stage. + /// + /// If there is no way to handle the lock report, then this should return an error so that the + /// sending chain can ensure the lock does not remain. + /// + /// We should only act upon this message if we believe that the `origin` is honest. + fn note_unlockable(locker: Location, asset: Asset, owner: Location) -> Result<(), LockError>; + + /// Handler for when an owner wishes to unlock an asset on a remote chain. + /// + /// Returns a ticket which can be used to actually note the reduction in unlockable assets that + /// `owner` commands on `locker`. + /// + /// WARNING: Don't call this with an undropped instance of `Self::ReduceTicket`. + fn prepare_reduce_unlockable( + locker: Location, + asset: Asset, + owner: Location, + ) -> Result; +} + +impl AssetLock for () { + type LockTicket = Infallible; + type ReduceTicket = Infallible; + type UnlockTicket = Infallible; + + fn prepare_lock(_: Location, _: Asset, _: Location) -> Result { + Err(LockError::NotApplicable) + } + + fn prepare_unlock(_: Location, _: Asset, _: Location) -> Result { + Err(LockError::NotApplicable) + } + + fn note_unlockable(_: Location, _: Asset, _: Location) -> Result<(), LockError> { + Err(LockError::NotApplicable) + } + + fn prepare_reduce_unlockable(_: Location, _: Asset, _: Location) -> Result { + Err(LockError::NotApplicable) + } +} diff --git a/debug-executor/src/traits/asset_transfer.rs b/debug-executor/src/traits/asset_transfer.rs new file mode 100644 index 000000000..ccb0a1b3e --- /dev/null +++ b/debug-executor/src/traits/asset_transfer.rs @@ -0,0 +1,93 @@ +// Copyright (C) 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 . + +use crate::traits::TransactAsset; +use frame_support::traits::ContainsPair; +use scale_info::TypeInfo; +use sp_runtime::codec::{Decode, Encode}; +use xcm::prelude::*; + +/// Errors related to determining asset transfer support. +#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo)] +pub enum Error { + /// Reserve chain could not be determined for assets. + UnknownReserve, +} + +/// Specify which type of asset transfer is required for a particular `(asset, dest)` combination. +#[derive(Clone, Encode, Decode, PartialEq, Debug, TypeInfo)] +pub enum TransferType { + /// should teleport `asset` to `dest` + Teleport, + /// should reserve-transfer `asset` to `dest`, using local chain as reserve + LocalReserve, + /// should reserve-transfer `asset` to `dest`, using `dest` as reserve + DestinationReserve, + /// should reserve-transfer `asset` to `dest`, using remote chain `Location` as reserve + RemoteReserve(VersionedLocation), +} + +/// A trait for identifying asset transfer type based on `IsTeleporter` and `IsReserve` +/// configurations. +pub trait XcmAssetTransfers { + /// Combinations of (Asset, Location) pairs which we trust as reserves. Meaning + /// reserve-based-transfers are to be used for assets matching this filter. + type IsReserve: ContainsPair; + + /// Combinations of (Asset, Location) pairs which we trust as teleporters. Meaning teleports are + /// to be used for assets matching this filter. + type IsTeleporter: ContainsPair; + + /// How to withdraw and deposit an asset. + type AssetTransactor: TransactAsset; + + /// Determine transfer type to be used for transferring `asset` from local chain to `dest`. + fn determine_for(asset: &Asset, dest: &Location) -> Result { + if Self::IsTeleporter::contains(asset, dest) { + // we trust destination for teleporting asset + return Ok(TransferType::Teleport) + } else if Self::IsReserve::contains(asset, dest) { + // we trust destination as asset reserve location + return Ok(TransferType::DestinationReserve) + } + + // try to determine reserve location based on asset id/location + let asset_location = asset.id.0.chain_location(); + if asset_location == Location::here() || Self::IsTeleporter::contains(asset, &asset_location) { + // if the asset is local, then it's a local reserve + // it's also a local reserve if the asset's location is not `here` but it's a location + // where it can be teleported to `here` => local reserve + Ok(TransferType::LocalReserve) + } else if Self::IsReserve::contains(asset, &asset_location) { + // remote location that is recognized as reserve location for asset + Ok(TransferType::RemoteReserve(asset_location.into())) + } else { + // remote location that is not configured either as teleporter or reserve => cannot + // determine asset reserve + Err(Error::UnknownReserve) + } + } +} + +impl XcmAssetTransfers for () { + type AssetTransactor = (); + type IsReserve = (); + type IsTeleporter = (); + + fn determine_for(_: &Asset, _: &Location) -> Result { + return Err(Error::UnknownReserve); + } +} diff --git a/debug-executor/src/traits/conversion.rs b/debug-executor/src/traits/conversion.rs new file mode 100644 index 000000000..dcfdbec32 --- /dev/null +++ b/debug-executor/src/traits/conversion.rs @@ -0,0 +1,144 @@ +// Copyright (C) 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 . + +use frame_support::traits::{Contains, OriginTrait}; +use sp_runtime::{traits::Dispatchable, DispatchErrorWithPostInfo}; +use sp_std::{marker::PhantomData, result::Result}; +use xcm::latest::prelude::*; + +/// Means of converting a location into an account identifier. +pub trait ConvertLocation { + /// Convert the `location` into `Some` account ID, or `None` if not possible. + fn convert_location(location: &Location) -> Option; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl ConvertLocation for Tuple { + fn convert_location(l: &Location) -> Option { + for_tuples!( #( + match Tuple::convert_location(l) { + Some(result) => return Some(result), + None => {}, + } + )* ); + None + } +} + +/// A converter `trait` for origin types. +/// +/// Can be amalgamated into tuples. If any of the tuple elements returns `Ok(_)`, it short circuits. +/// Else, the `Err(_)` of the last tuple item is returned. Each intermediate `Err(_)` might return a +/// different `origin` of type `Origin` which is passed to the next convert item. +/// +/// ```rust +/// # use xcm::latest::{Location, Junctions, Junction, OriginKind}; +/// # use staging_xcm_executor::traits::ConvertOrigin; +/// // A convertor that will bump the para id and pass it to the next one. +/// struct BumpParaId; +/// impl ConvertOrigin for BumpParaId { +/// fn convert_origin(origin: impl Into, _: OriginKind) -> Result { +/// match origin.into().unpack() { +/// (0, [Junction::Parachain(id)]) => { +/// Err([Junction::Parachain(id + 1)].into()) +/// } +/// _ => unreachable!() +/// } +/// } +/// } +/// +/// struct AcceptPara7; +/// impl ConvertOrigin for AcceptPara7 { +/// fn convert_origin(origin: impl Into, _: OriginKind) -> Result { +/// let origin = origin.into(); +/// match origin.unpack() { +/// (0, [Junction::Parachain(id)]) if *id == 7 => { +/// Ok(7) +/// } +/// _ => Err(origin) +/// } +/// } +/// } +/// # fn main() { +/// let origin: Location = [Junction::Parachain(6)].into(); +/// assert!( +/// <(BumpParaId, AcceptPara7) as ConvertOrigin>::convert_origin(origin, OriginKind::Native) +/// .is_ok() +/// ); +/// # } +/// ``` +pub trait ConvertOrigin { + /// Attempt to convert `origin` to the generic `Origin` whilst consuming it. + fn convert_origin(origin: impl Into, kind: OriginKind) -> Result; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl ConvertOrigin for Tuple { + fn convert_origin(origin: impl Into, kind: OriginKind) -> Result { + for_tuples!( #( + let origin = match Tuple::convert_origin(origin, kind) { + Err(o) => o, + r => return r + }; + )* ); + let origin = origin.into(); + log::trace!( + target: "xcm::convert_origin", + "could not convert: origin: {:?}, kind: {:?}", + origin, + kind, + ); + Err(origin) + } +} + +/// Defines how a call is dispatched with given origin. +/// Allows to customize call dispatch, such as adapting the origin based on the call +/// or modifying the call. +pub trait CallDispatcher { + fn dispatch( + call: Call, + origin: Call::RuntimeOrigin, + ) -> Result>; +} + +pub struct WithOriginFilter(PhantomData); +impl CallDispatcher for WithOriginFilter +where + Call: Dispatchable, + Call::RuntimeOrigin: OriginTrait, + <::RuntimeOrigin as OriginTrait>::Call: 'static, + Filter: Contains<<::RuntimeOrigin as OriginTrait>::Call> + 'static, +{ + fn dispatch( + call: Call, + mut origin: ::RuntimeOrigin, + ) -> Result<::PostInfo, DispatchErrorWithPostInfo<::PostInfo>> { + origin.add_filter(Filter::contains); + call.dispatch(origin) + } +} + +// We implement it for every calls so they can dispatch themselves +// (without any change). +impl CallDispatcher for Call { + fn dispatch( + call: Call, + origin: Call::RuntimeOrigin, + ) -> Result> { + call.dispatch(origin) + } +} diff --git a/debug-executor/src/traits/drop_assets.rs b/debug-executor/src/traits/drop_assets.rs new file mode 100644 index 000000000..9cb13e0dd --- /dev/null +++ b/debug-executor/src/traits/drop_assets.rs @@ -0,0 +1,79 @@ +// Copyright (C) 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 . + +use crate::AssetsInHolding; +use core::marker::PhantomData; +use frame_support::traits::Contains; +use xcm::latest::{Assets, Location, Weight, XcmContext}; + +/// Define a handler for when some non-empty `AssetsInHolding` value should be dropped. +pub trait DropAssets { + /// Handler for receiving dropped assets. Returns the weight consumed by this operation. + fn drop_assets(origin: &Location, assets: AssetsInHolding, context: &XcmContext) -> Weight; +} +impl DropAssets for () { + fn drop_assets(_origin: &Location, _assets: AssetsInHolding, _context: &XcmContext) -> Weight { + Weight::zero() + } +} + +/// Morph a given `DropAssets` implementation into one which can filter based on assets. This can +/// be used to ensure that `AssetsInHolding` values which hold no value are ignored. +pub struct FilterAssets(PhantomData<(D, A)>); + +impl> DropAssets for FilterAssets { + fn drop_assets(origin: &Location, assets: AssetsInHolding, context: &XcmContext) -> Weight { + if A::contains(&assets) { + D::drop_assets(origin, assets, context) + } else { + Weight::zero() + } + } +} + +/// Morph a given `DropAssets` implementation into one which can filter based on origin. This can +/// be used to ban origins which don't have proper protections/policies against misuse of the +/// asset trap facility don't get to use it. +pub struct FilterOrigin(PhantomData<(D, O)>); + +impl> DropAssets for FilterOrigin { + fn drop_assets(origin: &Location, assets: AssetsInHolding, context: &XcmContext) -> Weight { + if O::contains(origin) { + D::drop_assets(origin, assets, context) + } else { + Weight::zero() + } + } +} + +/// Define any handlers for the `AssetClaim` instruction. +pub trait ClaimAssets { + /// Claim any assets available to `origin` and return them in a single `Assets` value, together + /// with the weight used by this operation. + fn claim_assets(origin: &Location, ticket: &Location, what: &Assets, context: &XcmContext) -> bool; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl ClaimAssets for Tuple { + fn claim_assets(origin: &Location, ticket: &Location, what: &Assets, context: &XcmContext) -> bool { + for_tuples!( #( + if Tuple::claim_assets(origin, ticket, what, context) { + return true; + } + )* ); + false + } +} diff --git a/debug-executor/src/traits/export.rs b/debug-executor/src/traits/export.rs new file mode 100644 index 000000000..2ddfcff73 --- /dev/null +++ b/debug-executor/src/traits/export.rs @@ -0,0 +1,140 @@ +// Copyright (C) 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 . + +use xcm::latest::prelude::*; + +/// Utility for delivering a message to a system under a different (non-local) consensus with a +/// spoofed origin. This essentially defines the behaviour of the `ExportMessage` XCM instruction. +/// +/// This is quite different to `SendXcm`; `SendXcm` assumes that the local side's location will be +/// preserved to be represented as the value of the Origin register in the messages execution. +/// +/// This trait on the other hand assumes that we do not necessarily want the Origin register to +/// contain the local (i.e. the caller chain's) location, since it will generally be exporting a +/// message on behalf of another consensus system. Therefore in addition to the message, the +/// destination must be given in two parts: the network and the interior location within it. +/// +/// We also require the caller to state exactly what location they purport to be representing. The +/// destination must accept the local location to represent that location or the operation will +/// fail. +pub trait ExportXcm { + /// Intermediate value which connects the two phases of the export operation. + type Ticket; + + /// Check whether the given `message` is deliverable to the given `destination` on `network`, + /// spoofing its source as `universal_source` and if so determine the cost which will be paid by + /// this chain to do so, returning a `Ticket` token which can be used to enact delivery. + /// + /// The `channel` to be used on the `network`'s export mechanism (bridge, probably) must also + /// be provided. + /// + /// The `destination` and `message` must be `Some` (or else an error will be returned) and they + /// may only be consumed if the `Err` is not `NotApplicable`. + /// + /// If it is not a destination which can be reached with this type but possibly could by others, + /// then this *MUST* return `NotApplicable`. Any other error will cause the tuple + /// implementation (used to compose routing systems from different delivery agents) to exit + /// early without trying alternative means of delivery. + fn validate( + network: NetworkId, + channel: u32, + universal_source: &mut Option, + destination: &mut Option, + message: &mut Option>, + ) -> SendResult; + + /// Actually carry out the delivery operation for a previously validated message sending. + /// + /// The implementation should do everything possible to ensure that this function is infallible + /// if called immediately after `validate`. Returning an error here would result in a price + /// paid without the service being delivered. + fn deliver(ticket: Self::Ticket) -> Result; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl ExportXcm for Tuple { + for_tuples! { type Ticket = (#( Option ),* ); } + + fn validate( + network: NetworkId, + channel: u32, + universal_source: &mut Option, + destination: &mut Option, + message: &mut Option>, + ) -> SendResult { + let mut maybe_cost: Option = None; + let one_ticket: Self::Ticket = (for_tuples! { #( + if maybe_cost.is_some() { + None + } else { + match Tuple::validate(network, channel, universal_source, destination, message) { + Err(SendError::NotApplicable) => None, + Err(e) => { return Err(e) }, + Ok((v, c)) => { + maybe_cost = Some(c); + Some(v) + }, + } + } + ),* }); + if let Some(cost) = maybe_cost { + Ok((one_ticket, cost)) + } else { + Err(SendError::NotApplicable) + } + } + + fn deliver(mut one_ticket: Self::Ticket) -> Result { + for_tuples!( #( + if let Some(validated) = one_ticket.Tuple.take() { + return Tuple::deliver(validated); + } + )* ); + Err(SendError::Unroutable) + } +} + +/// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps +/// both in `Some` before passing them as as mutable references into `T::send_xcm`. +pub fn validate_export( + network: NetworkId, + channel: u32, + universal_source: InteriorLocation, + dest: InteriorLocation, + msg: Xcm<()>, +) -> SendResult { + T::validate(network, channel, &mut Some(universal_source), &mut Some(dest), &mut Some(msg)) +} + +/// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps +/// both in `Some` before passing them as as mutable references into `T::send_xcm`. +/// +/// Returns either `Ok` with the price of the delivery, or `Err` with the reason why the message +/// could not be sent. +/// +/// Generally you'll want to validate and get the price first to ensure that the sender can pay it +/// before actually doing the delivery. +pub fn export_xcm( + network: NetworkId, + channel: u32, + universal_source: InteriorLocation, + dest: InteriorLocation, + msg: Xcm<()>, +) -> Result<(XcmHash, Assets), SendError> { + let (ticket, price) = T::validate(network, channel, &mut Some(universal_source), &mut Some(dest), &mut Some(msg))?; + let hash = T::deliver(ticket)?; + Ok((hash, price)) +} diff --git a/debug-executor/src/traits/fee_manager.rs b/debug-executor/src/traits/fee_manager.rs new file mode 100644 index 000000000..b6e303daa --- /dev/null +++ b/debug-executor/src/traits/fee_manager.rs @@ -0,0 +1,60 @@ +// Copyright (C) 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 . + +use xcm::prelude::*; + +/// Handle stuff to do with taking fees in certain XCM instructions. +pub trait FeeManager { + /// Determine if a fee should be waived. + fn is_waived(origin: Option<&Location>, r: FeeReason) -> bool; + + /// Do something with the fee which has been paid. Doing nothing here silently burns the + /// fees. + fn handle_fee(fee: Assets, context: Option<&XcmContext>, r: FeeReason); +} + +/// Context under which a fee is paid. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum FeeReason { + /// When a reporting instruction is called. + Report, + /// When the `TransferReserveAsset` instruction is called. + TransferReserveAsset, + /// When the `DepositReserveAsset` instruction is called. + DepositReserveAsset, + /// When the `InitiateReserveWithdraw` instruction is called. + InitiateReserveWithdraw, + /// When the `InitiateTeleport` instruction is called. + InitiateTeleport, + /// When the `QueryPallet` instruction is called. + QueryPallet, + /// When the `ExportMessage` instruction is called (and includes the network ID). + Export { network: NetworkId, destination: InteriorLocation }, + /// The `charge_fees` API. + ChargeFees, + /// When the `LockAsset` instruction is called. + LockAsset, + /// When the `RequestUnlock` instruction is called. + RequestUnlock, +} + +impl FeeManager for () { + fn is_waived(_: Option<&Location>, _: FeeReason) -> bool { + false + } + + fn handle_fee(_: Assets, _: Option<&XcmContext>, _: FeeReason) {} +} diff --git a/debug-executor/src/traits/filter_asset_location.rs b/debug-executor/src/traits/filter_asset_location.rs new file mode 100644 index 000000000..5d0c32890 --- /dev/null +++ b/debug-executor/src/traits/filter_asset_location.rs @@ -0,0 +1,35 @@ +// Copyright (C) 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 . + +use frame_support::traits::ContainsPair; +use xcm::latest::{Asset, Location}; + +/// Filters assets/location pairs. +/// +/// Can be amalgamated into tuples. If any item returns `true`, it short-circuits, else `false` is +/// returned. +#[deprecated = "Use `frame_support::traits::ContainsPair` instead"] +pub trait FilterAssetLocation { + /// A filter to distinguish between asset/location pairs. + fn contains(asset: &Asset, origin: &Location) -> bool; +} + +#[allow(deprecated)] +impl> FilterAssetLocation for T { + fn contains(asset: &Asset, origin: &Location) -> bool { + T::contains(asset, origin) + } +} diff --git a/debug-executor/src/traits/hrmp.rs b/debug-executor/src/traits/hrmp.rs new file mode 100644 index 000000000..b6bbb9316 --- /dev/null +++ b/debug-executor/src/traits/hrmp.rs @@ -0,0 +1,56 @@ +// Copyright (C) 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 . + +use xcm::latest::Result as XcmResult; + +/// Executes logic when a `HrmpNewChannelOpenRequest` XCM notification is received. +pub trait HandleHrmpNewChannelOpenRequest { + fn handle(sender: u32, max_message_size: u32, max_capacity: u32) -> XcmResult; +} + +/// Executes optional logic when a `HrmpChannelAccepted` XCM notification is received. +pub trait HandleHrmpChannelAccepted { + fn handle(recipient: u32) -> XcmResult; +} + +/// Executes optional logic when a `HrmpChannelClosing` XCM notification is received. +pub trait HandleHrmpChannelClosing { + fn handle(initiator: u32, sender: u32, recipient: u32) -> XcmResult; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl HandleHrmpNewChannelOpenRequest for Tuple { + fn handle(sender: u32, max_message_size: u32, max_capacity: u32) -> XcmResult { + for_tuples!( #( Tuple::handle(sender, max_message_size, max_capacity)?; )* ); + Ok(()) + } +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl HandleHrmpChannelAccepted for Tuple { + fn handle(recipient: u32) -> XcmResult { + for_tuples!( #( Tuple::handle(recipient)?; )* ); + Ok(()) + } +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl HandleHrmpChannelClosing for Tuple { + fn handle(initiator: u32, sender: u32, recipient: u32) -> XcmResult { + for_tuples!( #( Tuple::handle(initiator, sender, recipient)?; )* ); + Ok(()) + } +} diff --git a/debug-executor/src/traits/mod.rs b/debug-executor/src/traits/mod.rs new file mode 100644 index 000000000..3095d2af8 --- /dev/null +++ b/debug-executor/src/traits/mod.rs @@ -0,0 +1,64 @@ +// Copyright (C) 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 . + +//! Various traits used in configuring the executor. + +mod conversion; +pub use conversion::{CallDispatcher, ConvertLocation, ConvertOrigin, WithOriginFilter}; +mod drop_assets; +pub use drop_assets::{ClaimAssets, DropAssets}; +mod asset_exchange; +pub use asset_exchange::AssetExchange; +mod asset_lock; +pub use asset_lock::{AssetLock, Enact, LockError}; +mod asset_transfer; +pub use asset_transfer::{Error as AssetTransferError, TransferType, XcmAssetTransfers}; +mod export; +pub use export::{export_xcm, validate_export, ExportXcm}; +mod fee_manager; +pub use fee_manager::{FeeManager, FeeReason}; +mod filter_asset_location; +#[allow(deprecated)] +pub use filter_asset_location::FilterAssetLocation; +mod token_matching; +pub use token_matching::{Error, MatchesFungible, MatchesFungibles, MatchesNonFungible, MatchesNonFungibles}; +mod on_response; +pub use on_response::{OnResponse, QueryHandler, QueryResponseStatus, VersionChangeNotifier}; +mod process_transaction; +pub use process_transaction::ProcessTransaction; +mod should_execute; +pub use should_execute::{CheckSuspension, Properties, ShouldExecute}; +mod transact_asset; +pub use transact_asset::TransactAsset; +mod hrmp; +pub use hrmp::{HandleHrmpChannelAccepted, HandleHrmpChannelClosing, HandleHrmpNewChannelOpenRequest}; +mod record_xcm; +mod weight; +pub use record_xcm::RecordXcm; +#[deprecated = "Use `sp_runtime::traits::` instead"] +pub use sp_runtime::traits::{Identity, TryConvertInto as JustTry}; +pub use weight::{WeightBounds, WeightTrader}; + +pub mod prelude { + pub use super::{ + export_xcm, validate_export, AssetExchange, AssetLock, ClaimAssets, ConvertOrigin, DropAssets, Enact, Error, + ExportXcm, FeeManager, FeeReason, LockError, MatchesFungible, MatchesFungibles, MatchesNonFungible, + MatchesNonFungibles, OnResponse, ProcessTransaction, ShouldExecute, TransactAsset, VersionChangeNotifier, + WeightBounds, WeightTrader, WithOriginFilter, + }; + #[allow(deprecated)] + pub use super::{Identity, JustTry}; +} diff --git a/debug-executor/src/traits/on_response.rs b/debug-executor/src/traits/on_response.rs new file mode 100644 index 000000000..fce5868ea --- /dev/null +++ b/debug-executor/src/traits/on_response.rs @@ -0,0 +1,180 @@ +// Copyright (C) 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 . + +use crate::{Junctions::Here, Xcm}; +use codec::{Decode, Encode}; +use core::result; +use frame_support::{pallet_prelude::Get, parameter_types}; +use sp_arithmetic::traits::Zero; +use sp_std::fmt::Debug; +use xcm::latest::{ + Error as XcmError, InteriorLocation, Location, QueryId, Response, Result as XcmResult, Weight, XcmContext, +}; + +/// Define what needs to be done upon receiving a query response. +pub trait OnResponse { + /// Returns `true` if we are expecting a response from `origin` for query `query_id` that was + /// queried by `querier`. + fn expecting_response(origin: &Location, query_id: u64, querier: Option<&Location>) -> bool; + /// Handler for receiving a `response` from `origin` relating to `query_id` initiated by + /// `querier`. + fn on_response( + origin: &Location, + query_id: u64, + querier: Option<&Location>, + response: Response, + max_weight: Weight, + context: &XcmContext, + ) -> Weight; +} +impl OnResponse for () { + fn expecting_response(_origin: &Location, _query_id: u64, _querier: Option<&Location>) -> bool { + false + } + + fn on_response( + _origin: &Location, + _query_id: u64, + _querier: Option<&Location>, + _response: Response, + _max_weight: Weight, + _context: &XcmContext, + ) -> Weight { + Weight::zero() + } +} + +/// Trait for a type which handles notifying a destination of XCM version changes. +pub trait VersionChangeNotifier { + /// Start notifying `location` should the XCM version of this chain change. + /// + /// When it does, this type should ensure a `QueryResponse` message is sent with the given + /// `query_id` & `max_weight` and with a `response` of `Response::Version`. This should happen + /// until/unless `stop` is called with the correct `query_id`. + /// + /// If the `location` has an ongoing notification and when this function is called, then an + /// error should be returned. + fn start(location: &Location, query_id: QueryId, max_weight: Weight, context: &XcmContext) -> XcmResult; + + /// Stop notifying `location` should the XCM change. Returns an error if there is no existing + /// notification set up. + fn stop(location: &Location, context: &XcmContext) -> XcmResult; + + /// Return true if a location is subscribed to XCM version changes. + fn is_subscribed(location: &Location) -> bool; +} + +impl VersionChangeNotifier for () { + fn start(_: &Location, _: QueryId, _: Weight, _: &XcmContext) -> XcmResult { + Err(XcmError::Unimplemented) + } + + fn stop(_: &Location, _: &XcmContext) -> XcmResult { + Err(XcmError::Unimplemented) + } + + fn is_subscribed(_: &Location) -> bool { + false + } +} + +/// The possible state of an XCM query response. +#[derive(Debug, PartialEq, Eq, Encode, Decode)] +pub enum QueryResponseStatus { + /// The response has arrived, and includes the inner Response and the block number it arrived + /// at. + Ready { response: Response, at: BlockNumber }, + /// The response has not yet arrived, the XCM might still be executing or the response might be + /// in transit. + Pending { timeout: BlockNumber }, + /// No response with the given `QueryId` was found, or the response was already queried and + /// removed from local storage. + NotFound, + /// Got an unexpected XCM version. + UnexpectedVersion, +} + +/// Provides methods to expect responses from XCMs and query their status. +pub trait QueryHandler { + type BlockNumber: Zero + Encode; + type Error; + type UniversalLocation: Get; + + /// Attempt to create a new query ID and register it as a query that is yet to respond. + fn new_query( + responder: impl Into, + timeout: Self::BlockNumber, + match_querier: impl Into, + ) -> QueryId; + + /// Consume `message` and return another which is equivalent to it except that it reports + /// back the outcome. + /// + /// - `message`: The message whose outcome should be reported. + /// - `responder`: The origin from which a response should be expected. + /// - `timeout`: The block number after which it is permissible to return `NotFound` from + /// `take_response`. + /// + /// `report_outcome` may return an error if the `responder` is not invertible. + /// + /// It is assumed that the querier of the response will be `Here`. + /// The response can be queried with `take_response`. + fn report_outcome( + message: &mut Xcm<()>, + responder: impl Into, + timeout: Self::BlockNumber, + ) -> result::Result; + + /// Attempt to remove and return the response of query with ID `query_id`. + fn take_response(id: QueryId) -> QueryResponseStatus; + + /// Makes sure to expect a response with the given id. + #[cfg(feature = "runtime-benchmarks")] + fn expect_response(id: QueryId, response: Response); +} + +parameter_types! { + pub UniversalLocation: InteriorLocation = Here; +} + +impl QueryHandler for () { + type BlockNumber = u64; + type Error = (); + type UniversalLocation = UniversalLocation; + + fn take_response(_query_id: QueryId) -> QueryResponseStatus { + QueryResponseStatus::NotFound + } + + fn new_query( + _responder: impl Into, + _timeout: Self::BlockNumber, + _match_querier: impl Into, + ) -> QueryId { + 0u64 + } + + fn report_outcome( + _message: &mut Xcm<()>, + _responder: impl Into, + _timeout: Self::BlockNumber, + ) -> Result { + Err(()) + } + + #[cfg(feature = "runtime-benchmarks")] + fn expect_response(_id: QueryId, _response: crate::Response) {} +} diff --git a/debug-executor/src/traits/process_transaction.rs b/debug-executor/src/traits/process_transaction.rs new file mode 100644 index 000000000..246ae5def --- /dev/null +++ b/debug-executor/src/traits/process_transaction.rs @@ -0,0 +1,58 @@ +// Copyright 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 . + +use xcm::latest::prelude::*; + +/// Provides mechanisms for transactional processing of XCM instructions. +/// +/// This trait defines the behavior required to process XCM instructions in a transactional +/// manner. Implementers of this trait can ensure that XCM instructions are executed +/// atomically, meaning they either fully succeed or fully fail without any partial effects. +/// +/// Implementers of this trait can also choose to not process XCM instructions transactionally. +/// This is useful for cases where the implementer is not able to provide transactional guarantees. +/// In this case the `IS_TRANSACTIONAL` constant should be set to `false`. +/// The `()` type implements this trait in a non-transactional manner. +pub trait ProcessTransaction { + /// Whether or not the implementor of the this trait is actually transactional. + const IS_TRANSACTIONAL: bool; + + /// Processes an XCM instruction encapsulated within the provided closure. Responsible for + /// processing an XCM instruction transactionally. If the closure returns an error, any + /// changes made during its execution should be rolled back. In the case where the + /// implementer is not able to provide transactional guarantees, the closure should be + /// executed as is. + /// # Parameters + /// - `f`: A closure that encapsulates the XCM instruction being processed. It will return a + /// `Result` indicating the success or failure of the instruction. + /// + /// # Returns + /// - A `Result` indicating the overall success or failure of the transactional process. + fn process(f: F) -> Result<(), XcmError> + where + F: FnOnce() -> Result<(), XcmError>; +} + +impl ProcessTransaction for () { + const IS_TRANSACTIONAL: bool = false; + + fn process(f: F) -> Result<(), XcmError> + where + F: FnOnce() -> Result<(), XcmError>, + { + f() + } +} diff --git a/debug-executor/src/traits/record_xcm.rs b/debug-executor/src/traits/record_xcm.rs new file mode 100644 index 000000000..d0b5bf92d --- /dev/null +++ b/debug-executor/src/traits/record_xcm.rs @@ -0,0 +1,46 @@ +// Copyright (C) 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 . + +//! Trait for recording XCMs and a dummy implementation. + +use xcm::latest::Xcm; + +/// Trait for recording XCMs. +pub trait RecordXcm { + /// Whether or not we should record incoming XCMs. + fn should_record() -> bool; + /// Enable or disable recording. + fn set_record_xcm(enabled: bool); + /// Get recorded XCM. + /// Returns `None` if no message was sent, or if recording was off. + fn recorded_xcm() -> Option>; + /// Record `xcm`. + fn record(xcm: Xcm<()>); +} + +impl RecordXcm for () { + fn should_record() -> bool { + false + } + + fn set_record_xcm(_: bool) {} + + fn recorded_xcm() -> Option> { + None + } + + fn record(_: Xcm<()>) {} +} diff --git a/debug-executor/src/traits/should_execute.rs b/debug-executor/src/traits/should_execute.rs new file mode 100644 index 000000000..e76d56bfe --- /dev/null +++ b/debug-executor/src/traits/should_execute.rs @@ -0,0 +1,113 @@ +// Copyright (C) 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 . + +use frame_support::traits::ProcessMessageError; +use sp_std::result::Result; +use xcm::latest::{Instruction, Location, Weight, XcmHash}; + +/// Properties of an XCM message and its imminent execution. +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct Properties { + /// The amount of weight that the system has determined this + /// message may utilize in its execution. Typically non-zero only because of prior fee + /// payment, but could in principle be due to other factors. + pub weight_credit: Weight, + /// The identity of the message, if one is known. If left as `None`, then it will generally + /// default to the hash of the message which may be non-unique. + pub message_id: Option, +} + +/// Trait to determine whether the execution engine should actually execute a given XCM. +/// +/// Can be amalgamated into a tuple to have multiple trials. If any of the tuple elements returns +/// `Ok(())`, the execution stops. Else, `Err(_)` is returned if all elements reject the message. +pub trait ShouldExecute { + /// Returns `Ok(())` if the given `message` may be executed. + /// + /// - `origin`: The origin (sender) of the message. + /// - `instructions`: The message itself. + /// - `max_weight`: The (possibly over-) estimation of the weight of execution of the message. + /// - `properties`: Various pre-established properties of the message which may be mutated by + /// this API. + fn should_execute( + origin: &Location, + instructions: &mut [Instruction], + max_weight: Weight, + properties: &mut Properties, + ) -> Result<(), ProcessMessageError>; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl ShouldExecute for Tuple { + fn should_execute( + origin: &Location, + instructions: &mut [Instruction], + max_weight: Weight, + properties: &mut Properties, + ) -> Result<(), ProcessMessageError> { + for_tuples!( #( + match Tuple::should_execute(origin, instructions, max_weight, properties) { + Ok(()) => return Ok(()), + _ => (), + } + )* ); + log::trace!( + target: "xcm::should_execute", + "did not pass barrier: origin: {:?}, instructions: {:?}, max_weight: {:?}, properties: {:?}", + origin, + instructions, + max_weight, + properties, + ); + Err(ProcessMessageError::Unsupported) + } +} + +/// Trait to determine whether the execution engine is suspended from executing a given XCM. +/// +/// The trait method is given the same parameters as `ShouldExecute::should_execute`, so that the +/// implementer will have all the context necessary to determine whether or not to suspend the +/// XCM executor. +/// +/// Can be chained together in tuples to have multiple rounds of checks. If all of the tuple +/// elements returns false, then execution is not suspended. Otherwise, execution is suspended +/// if any of the tuple elements returns true. +pub trait CheckSuspension { + fn is_suspended( + origin: &Location, + instructions: &mut [Instruction], + max_weight: Weight, + properties: &mut Properties, + ) -> bool; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl CheckSuspension for Tuple { + fn is_suspended( + origin: &Location, + instruction: &mut [Instruction], + max_weight: Weight, + properties: &mut Properties, + ) -> bool { + for_tuples!( #( + if Tuple::is_suspended(origin, instruction, max_weight, properties) { + return true + } + )* ); + + false + } +} diff --git a/debug-executor/src/traits/token_matching.rs b/debug-executor/src/traits/token_matching.rs new file mode 100644 index 000000000..6300b00aa --- /dev/null +++ b/debug-executor/src/traits/token_matching.rs @@ -0,0 +1,106 @@ +// Copyright (C) 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 . + +use sp_std::result; +use xcm::latest::prelude::*; + +pub trait MatchesFungible { + fn matches_fungible(a: &Asset) -> Option; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl MatchesFungible for Tuple { + fn matches_fungible(a: &Asset) -> Option { + for_tuples!( #( + match Tuple::matches_fungible(a) { o @ Some(_) => return o, _ => () } + )* ); + log::trace!(target: "xcm::matches_fungible", "did not match fungible asset: {:?}", &a); + None + } +} + +pub trait MatchesNonFungible { + fn matches_nonfungible(a: &Asset) -> Option; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl MatchesNonFungible for Tuple { + fn matches_nonfungible(a: &Asset) -> Option { + for_tuples!( #( + match Tuple::matches_nonfungible(a) { o @ Some(_) => return o, _ => () } + )* ); + log::trace!(target: "xcm::matches_non_fungible", "did not match non-fungible asset: {:?}", &a); + None + } +} + +/// Errors associated with [`MatchesFungibles`] operation. +#[derive(Debug, PartialEq, Eq)] +pub enum Error { + /// The given asset is not handled. (According to [`XcmError::AssetNotFound`]) + AssetNotHandled, + /// `Location` to `AccountId` conversion failed. + AccountIdConversionFailed, + /// `u128` amount to currency `Balance` conversion failed. + AmountToBalanceConversionFailed, + /// `Location` to `AssetId`/`ClassId` conversion failed. + AssetIdConversionFailed, + /// `AssetInstance` to non-fungibles instance ID conversion failed. + InstanceConversionFailed, +} + +impl From for XcmError { + fn from(e: Error) -> Self { + use XcmError::FailedToTransactAsset; + match e { + Error::AssetNotHandled => XcmError::AssetNotFound, + Error::AccountIdConversionFailed => FailedToTransactAsset("AccountIdConversionFailed"), + Error::AmountToBalanceConversionFailed => FailedToTransactAsset("AmountToBalanceConversionFailed"), + Error::AssetIdConversionFailed => FailedToTransactAsset("AssetIdConversionFailed"), + Error::InstanceConversionFailed => FailedToTransactAsset("InstanceConversionFailed"), + } + } +} + +pub trait MatchesFungibles { + fn matches_fungibles(a: &Asset) -> result::Result<(AssetId, Balance), Error>; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl MatchesFungibles for Tuple { + fn matches_fungibles(a: &Asset) -> result::Result<(AssetId, Balance), Error> { + for_tuples!( #( + match Tuple::matches_fungibles(a) { o @ Ok(_) => return o, _ => () } + )* ); + log::trace!(target: "xcm::matches_fungibles", "did not match fungibles asset: {:?}", &a); + Err(Error::AssetNotHandled) + } +} + +pub trait MatchesNonFungibles { + fn matches_nonfungibles(a: &Asset) -> result::Result<(AssetId, Instance), Error>; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl MatchesNonFungibles for Tuple { + fn matches_nonfungibles(a: &Asset) -> result::Result<(AssetId, Instance), Error> { + for_tuples!( #( + match Tuple::matches_nonfungibles(a) { o @ Ok(_) => return o, _ => () } + )* ); + log::trace!(target: "xcm::matches_non_fungibles", "did not match fungibles asset: {:?}", &a); + Err(Error::AssetNotHandled) + } +} diff --git a/debug-executor/src/traits/transact_asset.rs b/debug-executor/src/traits/transact_asset.rs new file mode 100644 index 000000000..b2c14c694 --- /dev/null +++ b/debug-executor/src/traits/transact_asset.rs @@ -0,0 +1,411 @@ +// Copyright (C) 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 . + +use crate::AssetsInHolding; +use sp_std::result::Result; +use xcm::latest::{Asset, Error as XcmError, Location, Result as XcmResult, XcmContext}; + +/// Facility for asset transacting. +/// +/// This should work with as many asset/location combinations as possible. Locations to support may +/// include non-account locations such as a `[Junction::Parachain]`. Different +/// chains may handle them in different ways. +/// +/// Can be amalgamated as a tuple of items that implement this trait. In such executions, if any of +/// the transactors returns `Ok(())`, then it will short circuit. Else, execution is passed to the +/// next transactor. +pub trait TransactAsset { + /// Ensure that `check_in` will do as expected. + /// + /// When composed as a tuple, all type-items are called and at least one must result in `Ok`. + fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { + Err(XcmError::Unimplemented) + } + + /// An asset has been teleported in from the given origin. This should do whatever housekeeping + /// is needed. + /// + /// NOTE: This will make only a best-effort at bookkeeping. The caller should ensure that + /// `can_check_in` has returned with `Ok` in order to guarantee that this operation proceeds + /// properly. + /// + /// Implementation note: In general this will do one of two things: On chains where the asset is + /// native, it will reduce the assets from a special "teleported" account so that a) + /// total-issuance is preserved; and b) to ensure that no more assets can be teleported in than + /// were teleported out overall (this should not be needed if the teleporting chains are to be + /// trusted, but better to be safe than sorry). On chains where the asset is not native then it + /// will generally just be a no-op. + /// + /// When composed as a tuple, all type-items are called. It is up to the implementer that there + /// exists no value for `_what` which can cause side-effects for more than one of the + /// type-items. + fn check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) {} + + /// Ensure that `check_out` will do as expected. + /// + /// When composed as a tuple, all type-items are called and at least one must result in `Ok`. + fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { + Err(XcmError::Unimplemented) + } + + /// An asset has been teleported out to the given destination. This should do whatever + /// housekeeping is needed. + /// + /// Implementation note: In general this will do one of two things: On chains where the asset is + /// native, it will increase the assets in a special "teleported" account so that a) + /// total-issuance is preserved; and b) to ensure that no more assets can be teleported in than + /// were teleported out overall (this should not be needed if the teleporting chains are to be + /// trusted, but better to be safe than sorry). On chains where the asset is not native then it + /// will generally just be a no-op. + /// + /// When composed as a tuple, all type-items are called. It is up to the implementer that there + /// exists no value for `_what` which can cause side-effects for more than one of the + /// type-items. + fn check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) {} + + /// Deposit the `what` asset into the account of `who`. + /// + /// Implementations should return `XcmError::FailedToTransactAsset` if deposit failed. + fn deposit_asset(_what: &Asset, _who: &Location, _context: Option<&XcmContext>) -> XcmResult { + Err(XcmError::Unimplemented) + } + + /// Withdraw the given asset from the consensus system. Return the actual asset(s) withdrawn, + /// which should always be equal to `_what`. + /// + /// The XCM `_maybe_context` parameter may be `None` when the caller of `withdraw_asset` is + /// outside of the context of a currently-executing XCM. An example will be the `charge_fees` + /// method in the XCM executor. + /// + /// Implementations should return `XcmError::FailedToTransactAsset` if withdraw failed. + fn withdraw_asset( + _what: &Asset, + _who: &Location, + _maybe_context: Option<&XcmContext>, + ) -> Result { + Err(XcmError::Unimplemented) + } + + /// Move an `asset` `from` one location in `to` another location. + /// + /// Returns `XcmError::FailedToTransactAsset` if transfer failed. + /// + /// ## Notes + /// This function is meant to only be implemented by the type implementing `TransactAsset`, and + /// not be called directly. Most common API usages will instead call `transfer_asset`, which in + /// turn has a default implementation that calls `internal_transfer_asset`. As such, **please + /// do not call this method directly unless you know what you're doing**. + fn internal_transfer_asset( + _asset: &Asset, + _from: &Location, + _to: &Location, + _context: &XcmContext, + ) -> Result { + Err(XcmError::Unimplemented) + } + + /// Move an `asset` `from` one location in `to` another location. + /// + /// Attempts to use `internal_transfer_asset` and if not available then falls back to using a + /// two-part withdraw/deposit. + fn transfer_asset( + asset: &Asset, + from: &Location, + to: &Location, + context: &XcmContext, + ) -> Result { + match Self::internal_transfer_asset(asset, from, to, context) { + Err(XcmError::AssetNotFound | XcmError::Unimplemented) => { + let assets = Self::withdraw_asset(asset, from, Some(context))?; + // Not a very forgiving attitude; once we implement roll-backs then it'll be nicer. + Self::deposit_asset(asset, to, Some(context))?; + Ok(assets) + }, + result => result, + } + } +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl TransactAsset for Tuple { + fn can_check_in(origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult { + for_tuples!( #( + match Tuple::can_check_in(origin, what, context) { + Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (), + r => return r, + } + )* ); + log::trace!( + target: "xcm::TransactAsset::can_check_in", + "asset not found: what: {:?}, origin: {:?}, context: {:?}", + what, + origin, + context, + ); + Err(XcmError::AssetNotFound) + } + + fn check_in(origin: &Location, what: &Asset, context: &XcmContext) { + for_tuples!( #( + Tuple::check_in(origin, what, context); + )* ); + } + + fn can_check_out(dest: &Location, what: &Asset, context: &XcmContext) -> XcmResult { + for_tuples!( #( + match Tuple::can_check_out(dest, what, context) { + Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (), + r => return r, + } + )* ); + log::trace!( + target: "xcm::TransactAsset::can_check_out", + "asset not found: what: {:?}, dest: {:?}, context: {:?}", + what, + dest, + context, + ); + Err(XcmError::AssetNotFound) + } + + fn check_out(dest: &Location, what: &Asset, context: &XcmContext) { + for_tuples!( #( + Tuple::check_out(dest, what, context); + )* ); + } + + fn deposit_asset(what: &Asset, who: &Location, context: Option<&XcmContext>) -> XcmResult { + for_tuples!( #( + match Tuple::deposit_asset(what, who, context) { + Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (), + r => return r, + } + )* ); + log::trace!( + target: "xcm::TransactAsset::deposit_asset", + "did not deposit asset: what: {:?}, who: {:?}, context: {:?}", + what, + who, + context, + ); + Err(XcmError::AssetNotFound) + } + + fn withdraw_asset( + what: &Asset, + who: &Location, + maybe_context: Option<&XcmContext>, + ) -> Result { + for_tuples!( #( + match Tuple::withdraw_asset(what, who, maybe_context) { + Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (), + r => return r, + } + )* ); + log::trace!( + target: "xcm::TransactAsset::withdraw_asset", + "did not withdraw asset: what: {:?}, who: {:?}, maybe_context: {:?}", + what, + who, + maybe_context, + ); + Err(XcmError::AssetNotFound) + } + + fn internal_transfer_asset( + what: &Asset, + from: &Location, + to: &Location, + context: &XcmContext, + ) -> Result { + for_tuples!( #( + match Tuple::internal_transfer_asset(what, from, to, context) { + Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (), + r => return r, + } + )* ); + log::trace!( + target: "xcm::TransactAsset::internal_transfer_asset", + "did not transfer asset: what: {:?}, from: {:?}, to: {:?}, context: {:?}", + what, + from, + to, + context, + ); + Err(XcmError::AssetNotFound) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use xcm::latest::Junctions::Here; + + pub struct UnimplementedTransactor; + impl TransactAsset for UnimplementedTransactor {} + + pub struct NotFoundTransactor; + impl TransactAsset for NotFoundTransactor { + fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { + Err(XcmError::AssetNotFound) + } + + fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { + Err(XcmError::AssetNotFound) + } + + fn deposit_asset(_what: &Asset, _who: &Location, _context: Option<&XcmContext>) -> XcmResult { + Err(XcmError::AssetNotFound) + } + + fn withdraw_asset( + _what: &Asset, + _who: &Location, + _context: Option<&XcmContext>, + ) -> Result { + Err(XcmError::AssetNotFound) + } + + fn internal_transfer_asset( + _what: &Asset, + _from: &Location, + _to: &Location, + _context: &XcmContext, + ) -> Result { + Err(XcmError::AssetNotFound) + } + } + + pub struct OverflowTransactor; + impl TransactAsset for OverflowTransactor { + fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { + Err(XcmError::Overflow) + } + + fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { + Err(XcmError::Overflow) + } + + fn deposit_asset(_what: &Asset, _who: &Location, _context: Option<&XcmContext>) -> XcmResult { + Err(XcmError::Overflow) + } + + fn withdraw_asset( + _what: &Asset, + _who: &Location, + _context: Option<&XcmContext>, + ) -> Result { + Err(XcmError::Overflow) + } + + fn internal_transfer_asset( + _what: &Asset, + _from: &Location, + _to: &Location, + _context: &XcmContext, + ) -> Result { + Err(XcmError::Overflow) + } + } + + pub struct SuccessfulTransactor; + impl TransactAsset for SuccessfulTransactor { + fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { + Ok(()) + } + + fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { + Ok(()) + } + + fn deposit_asset(_what: &Asset, _who: &Location, _context: Option<&XcmContext>) -> XcmResult { + Ok(()) + } + + fn withdraw_asset( + _what: &Asset, + _who: &Location, + _context: Option<&XcmContext>, + ) -> Result { + Ok(AssetsInHolding::default()) + } + + fn internal_transfer_asset( + _what: &Asset, + _from: &Location, + _to: &Location, + _context: &XcmContext, + ) -> Result { + Ok(AssetsInHolding::default()) + } + } + + #[test] + fn defaults_to_asset_not_found() { + type MultiTransactor = (UnimplementedTransactor, NotFoundTransactor, UnimplementedTransactor); + + assert_eq!( + MultiTransactor::deposit_asset( + &(Here, 1u128).into(), + &Here.into(), + Some(&XcmContext::with_message_id([0; 32])), + ), + Err(XcmError::AssetNotFound) + ); + } + + #[test] + fn unimplemented_and_not_found_continue_iteration() { + type MultiTransactor = (UnimplementedTransactor, NotFoundTransactor, SuccessfulTransactor); + + assert_eq!( + MultiTransactor::deposit_asset( + &(Here, 1u128).into(), + &Here.into(), + Some(&XcmContext::with_message_id([0; 32])), + ), + Ok(()) + ); + } + + #[test] + fn unexpected_error_stops_iteration() { + type MultiTransactor = (OverflowTransactor, SuccessfulTransactor); + + assert_eq!( + MultiTransactor::deposit_asset( + &(Here, 1u128).into(), + &Here.into(), + Some(&XcmContext::with_message_id([0; 32])), + ), + Err(XcmError::Overflow) + ); + } + + #[test] + fn success_stops_iteration() { + type MultiTransactor = (SuccessfulTransactor, OverflowTransactor); + + assert_eq!( + MultiTransactor::deposit_asset( + &(Here, 1u128).into(), + &Here.into(), + Some(&XcmContext::with_message_id([0; 32])), + ), + Ok(()), + ); + } +} diff --git a/debug-executor/src/traits/weight.rs b/debug-executor/src/traits/weight.rs new file mode 100644 index 000000000..df7be6668 --- /dev/null +++ b/debug-executor/src/traits/weight.rs @@ -0,0 +1,114 @@ +// Copyright (C) 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 . + +use crate::AssetsInHolding; +use sp_std::result::Result; +use xcm::latest::{prelude::*, Weight}; + +/// Determine the weight of an XCM message. +pub trait WeightBounds { + /// Return the maximum amount of weight that an attempted execution of this message could + /// consume. + fn weight(message: &mut Xcm) -> Result; + + /// Return the maximum amount of weight that an attempted execution of this instruction could + /// consume. + fn instr_weight(instruction: &Instruction) -> Result; +} + +#[allow(unused)] +/// A means of getting approximate weight consumption for a given destination message executor and a +/// message. +pub trait UniversalWeigher { + /// Get the upper limit of weight required for `dest` to execute `message`. + fn weigh(dest: impl Into, message: Xcm<()>) -> Result; +} + +/// Charge for weight in order to execute XCM. +/// +/// A `WeightTrader` may also be put into a tuple, in which case the default behavior of +/// `buy_weight` and `refund_weight` would be to attempt to call each tuple element's own +/// implementation of these two functions, in the order of which they appear in the tuple, +/// returning early when a successful result is returned. +pub trait WeightTrader: Sized { + /// Create a new trader instance. + fn new() -> Self; + + /// Purchase execution weight credit in return for up to a given `payment`. If less of the + /// payment is required then the surplus is returned. If the `payment` cannot be used to pay + /// for the `weight`, then an error is returned. + fn buy_weight( + &mut self, + weight: Weight, + payment: AssetsInHolding, + context: &XcmContext, + ) -> Result; + + /// Attempt a refund of `weight` into some asset. The caller does not guarantee that the weight + /// was purchased using `buy_weight`. + /// + /// Default implementation refunds nothing. + fn refund_weight(&mut self, _weight: Weight, _context: &XcmContext) -> Option { + None + } +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl WeightTrader for Tuple { + fn new() -> Self { + for_tuples!( ( #( Tuple::new() ),* ) ) + } + + fn buy_weight( + &mut self, + weight: Weight, + payment: AssetsInHolding, + context: &XcmContext, + ) -> Result { + let mut too_expensive_error_found = false; + let mut last_error = None; + for_tuples!( #( + match Tuple.buy_weight(weight, payment.clone(), context) { + Ok(assets) => return Ok(assets), + Err(e) => { + if let XcmError::TooExpensive = e { + too_expensive_error_found = true; + } + last_error = Some(e) + } + } + )* ); + + log::trace!(target: "xcm::buy_weight", "last_error: {:?}, too_expensive_error_found: {}", last_error, too_expensive_error_found); + + // if we have multiple traders, and first one returns `TooExpensive` and others fail e.g. + // `AssetNotFound` then it is more accurate to return `TooExpensive` then `AssetNotFound` + Err(if too_expensive_error_found { + XcmError::TooExpensive + } else { + last_error.unwrap_or(XcmError::TooExpensive) + }) + } + + fn refund_weight(&mut self, weight: Weight, context: &XcmContext) -> Option { + for_tuples!( #( + if let Some(asset) = Tuple.refund_weight(weight, context) { + return Some(asset); + } + )* ); + None + } +} diff --git a/integration-tests/chopsticks/README.md b/integration-tests/chopsticks/README.md index 0ec45cfb1..8f0d3d2ab 100644 --- a/integration-tests/chopsticks/README.md +++ b/integration-tests/chopsticks/README.md @@ -6,6 +6,10 @@ To install dependencies: bun install ``` +```bash +bun papi +``` + To start the chains: ```bash diff --git a/integration-tests/chopsticks/bun.lockb b/integration-tests/chopsticks/bun.lockb old mode 100644 new mode 100755 index 56471c64812e618aa1325ea23e75d986391b171b..70d9cc07c2d5478b29505635135fdb75ea22dbcc GIT binary patch delta 13571 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 wRfSSobA&g;4aMs=iqf`pi_@% delta 13822 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 AG5`Po diff --git a/integration-tests/chopsticks/overrides/polimec.ts b/integration-tests/chopsticks/overrides/polimec.ts index 686ea3260..88e513518 100644 --- a/integration-tests/chopsticks/overrides/polimec.ts +++ b/integration-tests/chopsticks/overrides/polimec.ts @@ -1,9 +1,28 @@ import { INITIAL_BALANCES } from '@/constants'; -import { Accounts, Assets } from '@/types'; +import { Accounts, Asset, AssetLocation, AssetSourceRelation } from '@/types'; export const POLIMEC_WASM = '../../target/release/wbuild/polimec-runtime/polimec_runtime.compact.compressed.wasm'; +const usdc_location = { + parents: 1, + interior: { + x3: [{ parachain: 1000 }, { palletInstance: 50 }, { generalIndex: 1337 }], + }, +}; +const usdt_location = { + parents: 1, + interior: { + x3: [{ parachain: 1000 }, { palletInstance: 50 }, { generalIndex: 1984 }], + }, +}; +const dot_location = { + parents: 1, + interior: { + here: undefined, + }, +}; + export const polimec_storage = { System: { Account: [ @@ -21,23 +40,81 @@ export const polimec_storage = { ForeignAssets: { Account: [ [ - [Assets.USDC, Accounts.BOB], + [usdc_location, Accounts.BOB], { balance: INITIAL_BALANCES.USDC, }, ], [ - [Assets.USDT, Accounts.BOB], + [usdt_location, Accounts.BOB], { balance: INITIAL_BALANCES.USDT, }, ], [ - [Assets.DOT, Accounts.BOB], + [dot_location, Accounts.BOB], { balance: INITIAL_BALANCES.DOT, }, ], ], + Asset: [ + [ + [usdc_location], + { + owner: Accounts.ALICE, + issuer: Accounts.ALICE, + admin: Accounts.ALICE, + freezer: Accounts.ALICE, + supply: INITIAL_BALANCES.USDC, + deposit: 0n, + min_balance: 70000n, + is_sufficient: true, + accounts: 1, + sufficients: 1, + approvals: 0, + status: 'Live', + }, + ], + [ + [usdt_location], + { + owner: Accounts.ALICE, + issuer: Accounts.ALICE, + admin: Accounts.ALICE, + freezer: Accounts.ALICE, + supply: INITIAL_BALANCES.USDT, + deposit: 0n, + min_balance: 70000n, + is_sufficient: true, + accounts: 1, + sufficients: 1, + approvals: 0, + status: 'Live', + }, + ], + [ + [dot_location], + { + owner: Accounts.ALICE, + issuer: Accounts.ALICE, + admin: Accounts.ALICE, + freezer: Accounts.ALICE, + supply: INITIAL_BALANCES.DOT, + deposit: 0n, + min_balance: 100000000n, + is_sufficient: true, + accounts: 1, + sufficients: 1, + approvals: 0, + status: 'Live', + }, + ], + ], + Metadata: [ + [[usdc_location], { symbol: 'USDC', name: 'USDC', decimals: 6, isFrozen: false }], + [[usdt_location], { symbol: 'USDT', name: 'USDC', decimals: 6, isFrozen: false }], + [[dot_location], { symbol: 'DOT', name: 'DOT', decimals: 10, isFrozen: false }], + ], }, } as const; diff --git a/integration-tests/chopsticks/overrides/polkadot-hub.ts b/integration-tests/chopsticks/overrides/polkadot-hub.ts index 9fb9a8a41..12f529940 100644 --- a/integration-tests/chopsticks/overrides/polkadot-hub.ts +++ b/integration-tests/chopsticks/overrides/polkadot-hub.ts @@ -1,5 +1,26 @@ import { INITIAL_BALANCES } from '@/constants'; -import { Accounts, Assets } from '@/types'; +import { Accounts, Asset } from '@/types'; + + +const weth_location = { + parents: 2, + interior: { + x2: [ + { + globalConsensus: { + ethereum: { + chainId: 1n + } + } + }, + { + accountKey20: { + key: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + } + } + ] + } +} export const polkadot_hub_storage = { System: { @@ -18,24 +39,25 @@ export const polkadot_hub_storage = { Assets: { Account: [ [ - [Assets.USDT, Accounts.ALICE], + [Asset.USDT, Accounts.ALICE], { balance: INITIAL_BALANCES.USDT, }, ], [ - [Assets.USDC, Accounts.ALICE], + [Asset.USDC, Accounts.ALICE], { balance: INITIAL_BALANCES.USDC, }, ], - [ - [Assets.UNKNOWN, Accounts.ALICE], - { - balance: INITIAL_BALANCES.USDT, - }, - ], ], }, - // TODO: Add the foreignAssets storage to give to ALICE WETH = INITIAL_BALANCES.WETH + ForeignAssets: { + Account: [ + [[weth_location, Accounts.ALICE], { balance: 10000000000000 }] + ], + Asset: [ + [[weth_location], { supply: 10000000000000 }] + ] + } } as const; diff --git a/integration-tests/chopsticks/package.json b/integration-tests/chopsticks/package.json index ccff3ea5c..553d626be 100644 --- a/integration-tests/chopsticks/package.json +++ b/integration-tests/chopsticks/package.json @@ -18,6 +18,7 @@ "dependencies": { "@polkadot-api/descriptors": "file:.papi/descriptors", "@polkadot/keyring": "13.2.3", + "lodash": "^4.17.21", "polkadot-api": "^1.7.7" } } diff --git a/integration-tests/chopsticks/src/constants.ts b/integration-tests/chopsticks/src/constants.ts index 4c8b2f611..9c73d0b37 100644 --- a/integration-tests/chopsticks/src/constants.ts +++ b/integration-tests/chopsticks/src/constants.ts @@ -11,7 +11,7 @@ export const INITIAL_BALANCES = { export const TRANSFER_AMOUNTS = { TOKENS: 2n * 10n ** 6n, // e.g. 2 USDC NATIVE: 2n * 10n ** 10n, // e.g. 2 DOT - BRIDGED: 1n * 10n ** 17n, // e.g. 0.1 WETH + BRIDGED: 1n * 10n ** 10n, // e.g. 0.1 WETH } as const; export const DERIVE_PATHS = { diff --git a/integration-tests/chopsticks/src/managers/BaseManager.ts b/integration-tests/chopsticks/src/managers/BaseManager.ts index 81a4a7669..a01552486 100644 --- a/integration-tests/chopsticks/src/managers/BaseManager.ts +++ b/integration-tests/chopsticks/src/managers/BaseManager.ts @@ -1,5 +1,13 @@ import { DERIVE_PATHS } from '@/constants'; -import type { Accounts, ChainClient, ChainToDefinition, Chains } from '@/types'; +import { + type Accounts, + type Asset, + AssetLocation, + type AssetSourceRelation, + type ChainClient, + type ChainToDefinition, + type Chains, +} from '@/types'; import { sr25519CreateDerive } from '@polkadot-labs/hdkd'; import { DEV_PHRASE, entropyToMiniSecret, mnemonicToEntropy } from '@polkadot-labs/hdkd-helpers'; import type { PolkadotSigner, TypedApi } from 'polkadot-api'; @@ -67,10 +75,25 @@ export abstract class BaseChainManager { return events[0]?.payload.actual_fee || 0n; } - async getNativeBalanceOf(account: Accounts) { - const api = this.getApi(this.getChainType()); - const balance = await api.query.System.Account.getValue(account); - return balance.data.free; + // Make sure to override this in the other managers + abstract getAssetSourceRelation(asset: Asset): AssetSourceRelation; + + async getAssetBalanceOf(account: Accounts, asset: Asset): Promise { + const chain = this.getChainType(); + const api = this.getApi(chain); + const asset_source_relation = this.getAssetSourceRelation(asset); + const asset_location = AssetLocation(asset, asset_source_relation); + const account_balances_result = await api.apis.FungiblesApi.query_account_balances(account); + + if (account_balances_result.success === true && account_balances_result.value.type === 'V4') { + const assets = account_balances_result.value.value; + for (const asset of assets) { + if (asset.id === asset_location && asset.fun.type === 'Fungible') { + return asset.fun.value; + } + } + } + return 0n; } // @ts-expect-error - TODO: Not sure which is the correct type for this @@ -78,8 +101,6 @@ export abstract class BaseChainManager { abstract getChainType(): Chains; - abstract getAssetBalanceOf(account: Accounts, asset: number): Promise; - abstract connect(): void; abstract disconnect(): void; diff --git a/integration-tests/chopsticks/src/managers/PolimecManager.ts b/integration-tests/chopsticks/src/managers/PolimecManager.ts index 303c86939..68a6b1d46 100644 --- a/integration-tests/chopsticks/src/managers/PolimecManager.ts +++ b/integration-tests/chopsticks/src/managers/PolimecManager.ts @@ -1,5 +1,6 @@ -import { type Accounts, type Assets, Chains } from '@/types'; +import { type Accounts, Asset, AssetLocation, AssetSourceRelation, Chains } from '@/types'; import { polimec } from '@polkadot-api/descriptors'; +import { isEqual } from 'lodash'; import { createClient } from 'polkadot-api'; import { getWsProvider } from 'polkadot-api/ws-provider/web'; import { BaseChainManager } from './BaseManager'; @@ -34,13 +35,48 @@ export class PolimecManager extends BaseChainManager { return '58kXueYKLr5b8yCeY3Gd1nLQX2zSJLXjfMzTAuksNq25CFEL' as Accounts; } - async getAssetBalanceOf(account: Accounts, asset: Assets) { + getAssetSourceRelation(asset: Asset): AssetSourceRelation { + switch (asset) { + case Asset.DOT: + return AssetSourceRelation.Parent; + case Asset.USDT: + return AssetSourceRelation.Sibling; + case Asset.USDC: + return AssetSourceRelation.Sibling; + case Asset.WETH: + // Placeholder + return AssetSourceRelation.Self; + } + } + + async getAssetBalanceOf(account: Accounts, asset: Asset): Promise { const api = this.getApi(Chains.Polimec); - const balance = await api.query.ForeignAssets.Account.getValue(asset, account); - return balance?.balance || 0n; + const asset_source_relation = this.getAssetSourceRelation(asset); + const asset_location = AssetLocation(asset, asset_source_relation).value; + const account_balances_result = await api.apis.FungiblesApi.query_account_balances(account); + console.log('Requested asset location in PolimecManager'); + console.dir(asset_location, { depth: null, colors: true }); + console.log('\n\n'); + if (account_balances_result.success === true && account_balances_result.value.type === 'V4') { + const assets = account_balances_result.value.value; + for (const asset of assets) { + if (Bun.deepEquals(asset.id, asset_location)) { + console.log('Found asset. Balance is: ', asset.fun.value); + console.dir(asset, { depth: null, colors: true }); + console.log('\n\n'); + return asset.fun.value; + } + console.log('Not it chief: \n'); + console.dir(asset, { depth: null, colors: true }); + console.log('\n\n'); + } + } + console.log('Asset not found'); + console.log('\n\n'); + return 0n; } - async getXcmFee() { + async getLocalXcmFee() { const api = this.getApi(Chains.Polimec); const events = await api.event.PolkadotXcm.FeesPaid.pull(); if (!events.length) return 0n; diff --git a/integration-tests/chopsticks/src/managers/PolkadotHubManager.ts b/integration-tests/chopsticks/src/managers/PolkadotHubManager.ts index 1e889e2d8..0d7d9f13a 100644 --- a/integration-tests/chopsticks/src/managers/PolkadotHubManager.ts +++ b/integration-tests/chopsticks/src/managers/PolkadotHubManager.ts @@ -1,5 +1,6 @@ -import { type Accounts, type Assets, Chains } from '@/types'; +import { type Accounts, Asset, AssetLocation, AssetSourceRelation, Chains } from '@/types'; import { pah } from '@polkadot-api/descriptors'; +import { isEqual } from 'lodash'; import { createClient } from 'polkadot-api'; import { getWsProvider } from 'polkadot-api/ws-provider/web'; import { BaseChainManager } from './BaseManager'; @@ -30,12 +31,51 @@ export class PolkadotHubManager extends BaseChainManager { return api.tx.PolkadotXcm; } - async getAssetBalanceOf(account: Accounts, asset: Assets) { - const api = this.getApi(Chains.PolkadotHub); - const balance = await api.query.Assets.Account.getValue(asset, account); - return balance?.balance || 0n; + getAssetSourceRelation(asset: Asset): AssetSourceRelation { + switch (asset) { + case Asset.DOT: + return AssetSourceRelation.Parent; + case Asset.USDT: + return AssetSourceRelation.Self; + case Asset.USDC: + return AssetSourceRelation.Self; + case Asset.WETH: + // This is not actually used, so we use Self as a placeholder + return AssetSourceRelation.Self; + } } + async getAssetBalanceOf(account: Accounts, asset: Asset): Promise { + const api = this.getApi(Chains.PolkadotHub); + const asset_source_relation = this.getAssetSourceRelation(asset); + const asset_location = AssetLocation(asset, asset_source_relation).value; + const account_balances_result = await api.apis.FungiblesApi.query_account_balances(account); + console.log('Requested asset location in PolkadotHubManager'); + console.dir(asset_location, { depth: null, colors: true }); + let maybe_account_key_20 = asset_location.interior.value[1].value?.key?.asHex(); + console.log('Maybe Account key 20', maybe_account_key_20); + console.log('\n\n'); + if (account_balances_result.success === true && account_balances_result.value.type === 'V4') { + const assets = account_balances_result.value.value; + for (const asset of assets) { + if (Bun.deepEquals(asset.id, asset_location)) { + console.log('Found asset. Balance is: ', asset.fun.value); + console.dir(asset, { depth: null, colors: true }); + console.log('\n\n'); + return asset.fun.value; + } + console.log('Not it chief: \n'); + console.dir(asset, { depth: null, colors: true }); + console.log('\n\n'); + + let maybe_account_key_20 = asset.id.interior.value?.[1].value?.key?.asHex(); + console.log('Maybe Account key 20', maybe_account_key_20); + } + } + console.log('Asset not found'); + console.log('\n\n'); + return 0n; + } async getSwapCredit() { const api = this.getApi(Chains.PolkadotHub); const events = await api.event.AssetConversion.SwapCreditExecuted.pull(); @@ -45,6 +85,14 @@ export class PolkadotHubManager extends BaseChainManager { async getXcmFee() { const api = this.getApi(Chains.PolkadotHub); const events = await api.event.PolkadotXcm.FeesPaid.pull(); + console.log("Fees paid event") + console.dir(events, { depth: null, colors: true }); return (events[0]?.payload.fees[0].fun.value as bigint) || 0n; } + + async getTransactionFee() { + const api = this.getApi(Chains.PolkadotHub); + const events = await api.event.TransactionPayment.TransactionFeePaid.pull(); + return (events[0]?.payload.actual_fee as bigint) || 0n; + } } diff --git a/integration-tests/chopsticks/src/managers/PolkadotManager.ts b/integration-tests/chopsticks/src/managers/PolkadotManager.ts index 92beee323..e1b9bead6 100644 --- a/integration-tests/chopsticks/src/managers/PolkadotManager.ts +++ b/integration-tests/chopsticks/src/managers/PolkadotManager.ts @@ -1,4 +1,5 @@ -import { type Accounts, Chains } from '@/types'; +import { TRANSFER_AMOUNTS } from '@/constants.ts'; +import { type Accounts, Asset, AssetSourceRelation, Chains } from '@/types'; import { polkadot } from '@polkadot-api/descriptors'; import { createClient } from 'polkadot-api'; import { getWsProvider } from 'polkadot-api/ws-provider/web'; @@ -30,8 +31,29 @@ export class PolkadotManager extends BaseChainManager { return api.tx.XcmPallet; } - async getAssetBalanceOf(_account: Accounts, _asset: number): Promise { - throw new Error('Polkadot does not support assets'); + getAssetSourceRelation(asset: Asset): AssetSourceRelation { + switch (asset) { + case Asset.DOT: + return AssetSourceRelation.Self; + case Asset.USDT: + // Placeholder + return AssetSourceRelation.Self; + case Asset.USDC: + // Placeholder + return AssetSourceRelation.Self; + case Asset.WETH: + // Placeholder + return AssetSourceRelation.Self; + } + } + + async getAssetBalanceOf(account: Accounts, asset: Asset): Promise { + const api = this.getApi(this.getChainType()); + if (asset === Asset.DOT) { + const balance = await api.query.System.Account.getValue(account); + return balance.data.free; + } + return 0n; } async getXcmFee() { diff --git a/integration-tests/chopsticks/src/setup.ts b/integration-tests/chopsticks/src/setup.ts index 5f2119656..7a6e50f2f 100644 --- a/integration-tests/chopsticks/src/setup.ts +++ b/integration-tests/chopsticks/src/setup.ts @@ -45,6 +45,9 @@ export class ChainSetup { ]); console.log('✅ HRMP channels created'); + + // Needed to execute storage migrations within the new WASM before running tests. + await this.polimec?.newBlock(); } async cleanup() { @@ -80,6 +83,7 @@ export class ChainSetup { 'wasm-override': POLIMEC_WASM, 'import-storage': polimec_storage, 'build-block-mode': BuildBlockMode.Instant, + 'runtime-log-level': 5, }); } @@ -89,6 +93,7 @@ export class ChainSetup { port: 8001, 'import-storage': polkadot_hub_storage, 'build-block-mode': BuildBlockMode.Instant, + 'runtime-log-level': 5, }); } diff --git a/integration-tests/chopsticks/src/tests/hub.test.ts b/integration-tests/chopsticks/src/tests/hub.test.ts index 20b9de7b2..2f1852c8f 100644 --- a/integration-tests/chopsticks/src/tests/hub.test.ts +++ b/integration-tests/chopsticks/src/tests/hub.test.ts @@ -3,7 +3,7 @@ import { TRANSFER_AMOUNTS } from '@/constants'; import { createChainManager } from '@/managers/Factory'; import { ChainSetup } from '@/setup'; import { HubToPolimecTransfer } from '@/transfers/HubToPolimec'; -import { Accounts, Assets, Chains } from '@/types'; +import { Accounts, Asset, AssetSourceRelation, Chains } from '@/types'; describe('Polkadot Hub -> Polimec Transfer Tests', () => { const sourceManager = createChainManager(Chains.PolkadotHub); @@ -17,34 +17,94 @@ describe('Polkadot Hub -> Polimec Transfer Tests', () => { destManager.connect(); }); afterAll(async () => await chainSetup.cleanup()); + // + // test( + // 'Send DOT to Polimec', + // () => + // transferTest.testTransfer({ + // account: Accounts.ALICE, + // assets: [[Asset.DOT, TRANSFER_AMOUNTS.NATIVE, AssetSourceRelation.Parent]], + // }), + // { timeout: 25000 }, + // ); + // + // test( + // 'Send USDT to Polimec', + // () => + // transferTest.testTransfer({ + // account: Accounts.ALICE, + // assets: [[Asset.USDT, TRANSFER_AMOUNTS.TOKENS, AssetSourceRelation.Self]], + // }), + // { timeout: 25000 }, + // ); + // + // test( + // 'Send USDC to Polimec', + // () => + // transferTest.testTransfer({ + // account: Accounts.ALICE, + // assets: [[Asset.USDC, TRANSFER_AMOUNTS.TOKENS, AssetSourceRelation.Self]], + // }), + // { timeout: 25000 }, + // ); - test('Send DOT to Polimec', () => - transferTest.testTransfer({ - amount: TRANSFER_AMOUNTS.NATIVE, - account: Accounts.ALICE, - asset: Assets.DOT, - })); - - test('Send USDt to Polimec', () => - transferTest.testTransfer({ - amount: TRANSFER_AMOUNTS.TOKENS, - account: Accounts.ALICE, - asset: Assets.USDT, - })); - - test('Send USDC to Polimec', () => - transferTest.testTransfer({ - amount: TRANSFER_AMOUNTS.TOKENS, - account: Accounts.ALICE, - asset: Assets.USDC, - })); - - test('Send Unknown Asset to Polimec', () => - expect(() => + test( + 'Send WETH to Polimec', + () => transferTest.testTransfer({ - amount: TRANSFER_AMOUNTS.TOKENS, account: Accounts.ALICE, - asset: Assets.UNKNOWN, + assets: [ + [Asset.USDT, TRANSFER_AMOUNTS.TOKENS, AssetSourceRelation.Self], + [Asset.WETH, TRANSFER_AMOUNTS.BRIDGED, AssetSourceRelation.Self], + ], }), - ).toThrow()); + { timeout: 10000000 }, + ); + + // test('sandbox', async () => { + // console.log("hello"); + // const weth_1 = { + // parents: 2, + // interior: { + // x2: [ + // { + // globalConsensus: { + // ethereum: { + // chainId: 1n + // } + // } + // }, + // { + // accountKey20: { + // key: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + // } + // } + // ] + // } + // } + // + // const weth_2 = { + // parents: 2, + // interior: { + // x2: [ + // { + // globalConsensus: { + // ethereum: { + // chainId: 1n + // } + // } + // }, + // { + // accountKey20: { + // key: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + // } + // } + // ] + // } + // } + // + // const equals = Bun.deepEquals(weth_1, weth_2); + // expect(equals).toEqual(false); + // + // }, { timeout: 10000000 }); }); diff --git a/integration-tests/chopsticks/src/tests/polimec.test.ts b/integration-tests/chopsticks/src/tests/polimec.test.ts index 39e989d96..b053e10b7 100644 --- a/integration-tests/chopsticks/src/tests/polimec.test.ts +++ b/integration-tests/chopsticks/src/tests/polimec.test.ts @@ -4,7 +4,7 @@ import { createChainManager } from '@/managers/Factory'; import { polimec_storage } from '@/polimec'; import { ChainSetup } from '@/setup'; import { PolimecToHubTransfer } from '@/transfers/PolimecToHub'; -import { Accounts, Assets, Chains } from '@/types'; +import { Accounts, Asset, AssetSourceRelation, Chains } from '@/types'; describe('Polimec -> Hub Transfer Tests', () => { const sourceManager = createChainManager(Chains.Polimec); @@ -19,24 +19,44 @@ describe('Polimec -> Hub Transfer Tests', () => { }); afterAll(async () => await chainSetup.cleanup()); - test('Send USDC to Hub', () => - transferTest.testTransfer({ - amount: TRANSFER_AMOUNTS.TOKENS, - account: Accounts.BOB, - asset: Assets.USDC, - })); + async function getBalance(account: Accounts, asset: Asset) { + return await sourceManager.getAssetBalanceOf(account, asset); + } + test('Balance query', () => getBalance(Accounts.BOB, Asset.USDT), { timeout: 250000000 }); - test('Send USDt to Hub', () => - transferTest.testTransfer({ - amount: TRANSFER_AMOUNTS.TOKENS, - account: Accounts.BOB, - asset: Assets.USDT, - })); + test( + 'Send USDC to Hub', + () => + transferTest.testTransfer({ + amount: TRANSFER_AMOUNTS.TOKENS, + account: Accounts.BOB, + asset: Asset.USDC, + assetSourceRelation: AssetSourceRelation.Sibling, + }), + { timeout: 25000 }, + ); - test('Send DOT to Hub', () => - transferTest.testTransfer({ - amount: TRANSFER_AMOUNTS.NATIVE, - account: Accounts.BOB, - asset: Assets.DOT, - })); + test( + 'Send USDT to Hub', + () => + transferTest.testTransfer({ + amount: TRANSFER_AMOUNTS.TOKENS, + account: Accounts.BOB, + asset: Asset.USDT, + assetSourceRelation: AssetSourceRelation.Sibling, + }), + { timeout: 25000 }, + ); + + test( + 'Send DOT to Hub', + () => + transferTest.testTransfer({ + amount: TRANSFER_AMOUNTS.NATIVE, + account: Accounts.BOB, + asset: Asset.DOT, + assetSourceRelation: AssetSourceRelation.Parent, + }), + { timeout: 25000 }, + ); }); diff --git a/integration-tests/chopsticks/src/tests/polkadot.test.ts b/integration-tests/chopsticks/src/tests/polkadot.test.ts index 9a13531e3..699ec86ad 100644 --- a/integration-tests/chopsticks/src/tests/polkadot.test.ts +++ b/integration-tests/chopsticks/src/tests/polkadot.test.ts @@ -3,7 +3,7 @@ import { TRANSFER_AMOUNTS } from '@/constants'; import { createChainManager } from '@/managers/Factory'; import { ChainSetup } from '@/setup'; import { PolkadotToPolimecTransfer } from '@/transfers/PolkadotToPolimec'; -import { Accounts, Assets, Chains } from '@/types'; +import { Accounts, Asset, Chains } from '@/types'; describe('Polkadot -> Polimec Transfer Tests', () => { const chainSetup = new ChainSetup(); @@ -19,10 +19,14 @@ describe('Polkadot -> Polimec Transfer Tests', () => { }); afterAll(async () => await chainSetup.cleanup()); - test('Send DOT to Polimec', () => - transferTest.testTransfer({ - amount: TRANSFER_AMOUNTS.NATIVE, - account: Accounts.ALICE, - asset: Assets.DOT, - })); + test( + 'Send DOT to Polimec', + () => + transferTest.testTransfer({ + amount: TRANSFER_AMOUNTS.NATIVE, + account: Accounts.ALICE, + asset: Asset.DOT, + }), + { timeout: 25000 }, + ); }); diff --git a/integration-tests/chopsticks/src/transfers/BaseTransfer.ts b/integration-tests/chopsticks/src/transfers/BaseTransfer.ts index 90bfa4aab..6219e53a9 100644 --- a/integration-tests/chopsticks/src/transfers/BaseTransfer.ts +++ b/integration-tests/chopsticks/src/transfers/BaseTransfer.ts @@ -1,32 +1,39 @@ import { expect } from 'bun:test'; import type { BaseChainManager } from '@/managers/BaseManager'; -import type { Accounts, BalanceCheck, TransferResult } from '@/types'; +import { + type Accounts, + type Asset, + type AssetSourceRelation, + type BalanceCheck, + Chains, + type TransferResult, +} from '@/types'; -export interface BaseTransferOptions { - amount: bigint; +export interface TransferOptions { account: Accounts; + assets: [Asset, bigint, AssetSourceRelation][]; } -export abstract class BaseTransferTest { +export abstract class BaseTransferTest { constructor( protected sourceManager: BaseChainManager, protected destManager: BaseChainManager, ) {} - abstract executeTransfer(options: T): Promise; - abstract getBalances(options: Omit): Promise<{ balances: BalanceCheck }>; + abstract executeTransfer(options: TransferOptions): Promise; + abstract getBalances(options: TransferOptions): Promise<{ asset_balances: BalanceCheck[] }>; abstract verifyFinalBalances( - initialBalances: BalanceCheck, - finalBalances: BalanceCheck, - options: T, + initialBalances: BalanceCheck[], + finalBalances: BalanceCheck[], + options: TransferOptions, ): Promise; - async testTransfer(options: T) { - const { balances: initialBalances } = await this.getBalances(options); + async testTransfer(options: TransferOptions) { + const { asset_balances: initialBalances } = await this.getBalances(options); const blockNumbers = await this.executeTransfer(options); await this.waitForBlocks(blockNumbers); await this.verifyExecution(); - const { balances: finalBalances } = await this.getBalances(options); + const { asset_balances: finalBalances } = await this.getBalances(options); await this.verifyFinalBalances(initialBalances, finalBalances, options); } @@ -39,6 +46,11 @@ export abstract class BaseTransferTest { +import { BaseTransferTest, type TransferOptions } from './BaseTransfer'; + +export class HubToPolimecTransfer extends BaseTransferTest { constructor( protected override sourceManager: PolkadotHubManager, protected override destManager: PolimecManager, @@ -20,23 +34,26 @@ export class HubToPolimecTransfer extends BaseTransferTest { super(sourceManager, destManager); } - async executeTransfer({ amount, account, asset }: HubTransferOptions) { + async executeTransfer({ account, assets }: TransferOptions) { const [sourceBlock, destBlock] = await Promise.all([ this.sourceManager.getBlockNumber(), this.destManager.getBlockNumber(), ]); + const versioned_assets = getVersionedAssets(assets); + const data = createTransferData({ - amount, toChain: Chains.Polimec, + assets: versioned_assets, recv: account, - assetIndex: asset === Assets.DOT ? undefined : BigInt(asset), }); const api = this.sourceManager.getApi(Chains.PolkadotHub); - const res = await api.tx.PolkadotXcm.transfer_assets(data).signAndSubmit( - this.sourceManager.getSigner(account), - ); + const transfer = api.tx.PolkadotXcm.transfer_assets(data); + const res = await transfer.signAndSubmit(this.sourceManager.getSigner(account)); + + console.log("Transfer result"); + console.dir(res, { depth: null, colors: true }); expect(res.ok).toBeTrue(); return { sourceBlock, destBlock }; @@ -44,38 +61,150 @@ export class HubToPolimecTransfer extends BaseTransferTest { async getBalances({ account, - asset, - }: Omit): Promise<{ balances: PolimecBalanceCheck }> { - const isNativeTransfer = asset === Assets.DOT; - const treasuryAccount = this.destManager.getTreasuryAccount(); - return { - balances: { - source: isNativeTransfer - ? await this.sourceManager.getNativeBalanceOf(account) - : await this.sourceManager.getAssetBalanceOf(account, asset), + assets, + }: TransferOptions): Promise<{ asset_balances: PolimecBalanceCheck[] }> { + const asset_balances: PolimecBalanceCheck[] = []; + const treasury_account = this.destManager.getTreasuryAccount(); + for (const [asset] of assets) { + const balances: PolimecBalanceCheck = { + source: await this.sourceManager.getAssetBalanceOf(account, asset), destination: await this.destManager.getAssetBalanceOf(account, asset), - treasury: await this.destManager.getAssetBalanceOf(treasuryAccount, asset), - }, - }; + treasury: await this.destManager.getAssetBalanceOf(treasury_account, asset), + }; + asset_balances.push(balances); + } + return { asset_balances }; } async verifyFinalBalances( - initialBalances: PolimecBalanceCheck, - finalBalances: PolimecBalanceCheck, - { asset }: HubTransferOptions, + assetInitialBalances: PolimecBalanceCheck[], + assetFinalBalances: PolimecBalanceCheck[], + transferOptions: TransferOptions, ) { - // TODO: At the moment we exclude fees from the balance check since the PAPI team is wotking on some utilies to calculate fees. - const initialBalance = - asset === Assets.DOT - ? INITIAL_BALANCES.DOT - : asset === Assets.USDT - ? INITIAL_BALANCES.USDT - : INITIAL_BALANCES.USDC; - // Note: Initially every account on destination is empty. - expect(initialBalances.destination).toBe(0n); - expect(initialBalances.source).toBe(initialBalance); - expect(finalBalances.source).toBeLessThan(initialBalances.source); - expect(finalBalances.destination).toBeGreaterThan(initialBalances.destination); - expect(finalBalances.treasury).toBeGreaterThan(initialBalances.treasury); + const native_extrinsic_fee_amount = await this.sourceManager.getTransactionFee(); + const source_xcm_asset_fee_amount = await this.sourceManager.getXcmFee(); + const dest_xcm_asset_fee_amount = await this.calculatePolimecXcmFee(transferOptions); + + console.log('Native extrinsic fee amount: ', native_extrinsic_fee_amount); + console.log('Source xcm fee amount: ', source_xcm_asset_fee_amount); + console.log('Dest xcm fee amount: ', dest_xcm_asset_fee_amount); + + const fee_asset = transferOptions.assets[0][0]; + + for (let i = 0; i < transferOptions.assets.length; i++) { + const initialBalances = assetInitialBalances[i]; + const finalBalances = assetFinalBalances[i]; + const send_amount = transferOptions.assets[i][1]; + console.log('Send amount: ', send_amount); + + const asset = transferOptions.assets[i][0]; + console.log('Asset: ', asset); + + let expectedSourceBalanceSpent = send_amount; + let expectedDestBalanceSpent = 0n; + let expectedTreasuryBalanceGained = 0n; + + if (asset === Asset.DOT) { + expectedSourceBalanceSpent += native_extrinsic_fee_amount + source_xcm_asset_fee_amount; + } + if (asset === fee_asset) { + expectedDestBalanceSpent += dest_xcm_asset_fee_amount; + expectedTreasuryBalanceGained += dest_xcm_asset_fee_amount; + } + + console.log('Expected source balance spent: ', expectedSourceBalanceSpent); + console.log('Expected dest balance spent: ', expectedDestBalanceSpent); + console.log('Expected treasury balance gained: ', expectedTreasuryBalanceGained); + + expect(finalBalances.source).toBe( + initialBalances.source - expectedSourceBalanceSpent, + ); + expect(finalBalances.destination).toBe( + initialBalances.destination + send_amount - expectedDestBalanceSpent, + ); + expect(finalBalances.treasury).toBe(initialBalances.treasury + expectedTreasuryBalanceGained); + } + } + + async calculatePolimecXcmFee(transferOptions: TransferOptions): Promise { + let destinationExecutionFee: bigint; + + const sourceApi = this.sourceManager.getApi(Chains.PolkadotHub); + const destApi = this.destManager.getApi(Chains.Polimec); + + + + const versioned_assets = getVersionedAssets(transferOptions.assets); + const transferData = createTransferData({ + toChain: Chains.Polimec, + assets: versioned_assets, + recv: transferOptions.account, + }); + + let remoteFeeAssetId: XcmVersionedAssetId; + let lastAsset = unwrap(transferOptions.assets.at(0)); + if (lastAsset[2] === AssetSourceRelation.Self) { + lastAsset[2] = AssetSourceRelation.Sibling; + } + let versioned_asset = getVersionedAssets([lastAsset]); + if (versioned_asset.type === 'V4') { + remoteFeeAssetId = XcmVersionedAssetId.V4(unwrap(versioned_asset.value.at(0)).id); + } else { + throw new Error('Invalid versioned assets'); + } + + const localDryRunResult = await sourceApi.apis.DryRunApi.dry_run_call( + { type: 'system', value: DispatchRawOrigin.Signed(transferOptions.account) }, + { type: 'PolkadotXcm', value: { type: 'transfer_assets', value: transferData } }, + ); + + let forwardedXcms: I47tkk5e5nm6g7; + if (localDryRunResult.success && localDryRunResult.value.forwarded_xcms) { + forwardedXcms = localDryRunResult.value.forwarded_xcms; + } else { + throw new Error('Dry run failed'); + } + + const xcmsToPolimec = forwardedXcms.find( + ([location, _]) => + location.type === 'V4' && + location.value.parents === 1 && + location.value.interior.type === 'X1' && + location.value.interior.value.type === 'Parachain' && + location.value.interior.value.value === 3344, // Polimec's ParaID. + ); + if (!xcmsToPolimec) { + throw new Error('Could not find xcm to polimec'); + } + const messages = xcmsToPolimec[1]; + const remoteXcm = messages[0]; + + console.log('Remote XCM:'); + console.dir(remoteXcm, { depth: null, colors: true }); + const assets = await destApi.apis.XcmPaymentApi.query_acceptable_payment_assets(4); + console.log("Acceptable payment assets") + console.dir(assets, { depth: null, colors: true }); + const remoteXcmWeightResult = await destApi.apis.XcmPaymentApi.query_xcm_weight(remoteXcm); + console.log('XCM Weight:'); + console.dir(remoteXcmWeightResult, { depth: null, colors: true }); + if (remoteXcmWeightResult.success) { + console.log("fee asset id"); + console.dir(remoteFeeAssetId, { depth: null, colors: true }); + const remoteExecutionFeesResult = await destApi.apis.XcmPaymentApi.query_weight_to_asset_fee( + remoteXcmWeightResult.value, + remoteFeeAssetId, + ); + console.log('remoteExecutionFeesResult'); + console.dir(remoteExecutionFeesResult, { depth: null, colors: true }); + if (remoteExecutionFeesResult.success) { + destinationExecutionFee = remoteExecutionFeesResult.value; + } else { + throw new Error('Could not calculate destination xcm fee'); + } + } else { + throw new Error('Could not calculate xcm weight'); + } + + return destinationExecutionFee; } } diff --git a/integration-tests/chopsticks/src/transfers/PolimecToHub.ts b/integration-tests/chopsticks/src/transfers/PolimecToHub.ts index 7d65132f3..c4376cd23 100644 --- a/integration-tests/chopsticks/src/transfers/PolimecToHub.ts +++ b/integration-tests/chopsticks/src/transfers/PolimecToHub.ts @@ -2,15 +2,17 @@ import { expect } from 'bun:test'; import { INITIAL_BALANCES } from '@/constants'; import type { PolimecManager } from '@/managers/PolimecManager'; import type { PolkadotHubManager } from '@/managers/PolkadotHubManager'; -import { Assets, type BalanceCheck, Chains } from '@/types'; +import { + Asset, + type AssetSourceRelation, + type BalanceCheck, + Chains, + getVersionedAssets, +} from '@/types'; import { createTransferData } from '@/utils'; import { type BaseTransferOptions, BaseTransferTest } from './BaseTransfer'; -interface PolimecTransferOptions extends BaseTransferOptions { - asset: Assets; -} - -export class PolimecToHubTransfer extends BaseTransferTest { +export class PolimecToHubTransfer extends BaseTransferTest { constructor( protected override sourceManager: PolimecManager, protected override destManager: PolkadotHubManager, @@ -18,16 +20,16 @@ export class PolimecToHubTransfer extends BaseTransferTest): Promise<{ balances: BalanceCheck }> { - const isNativeTransfer = asset === Assets.DOT; return { balances: { source: await this.sourceManager.getAssetBalanceOf(account, asset), - destination: isNativeTransfer - ? await this.destManager.getNativeBalanceOf(account) - : await this.destManager.getAssetBalanceOf(account, asset), + destination: await this.destManager.getAssetBalanceOf(account, asset), }, }; } @@ -62,9 +61,9 @@ export class PolimecToHubTransfer extends BaseTransferTest { +export class PolkadotToPolimecTransfer extends BaseTransferTest { constructor( protected override sourceManager: PolkadotManager, protected override destManager: PolimecManager, @@ -17,17 +13,14 @@ export class PolkadotToPolimecTransfer extends BaseTransferTest): Promise<{ balances: PolimecBalanceCheck }> { + }: Omit): Promise<{ balances: PolimecBalanceCheck }> { const treasuryAccount = this.destManager.getTreasuryAccount(); return { balances: { - source: await this.sourceManager.getNativeBalanceOf(account), - destination: await this.destManager.getAssetBalanceOf(account, Assets.DOT), - treasury: await this.destManager.getAssetBalanceOf(treasuryAccount, Assets.DOT), + source: await this.sourceManager.getAssetBalanceOf(account, Asset.DOT), + destination: await this.destManager.getAssetBalanceOf(account, Asset.DOT), + treasury: await this.destManager.getAssetBalanceOf(treasuryAccount, Asset.DOT), }, }; } diff --git a/integration-tests/chopsticks/src/types.ts b/integration-tests/chopsticks/src/types.ts index a7beac378..3ecd18e74 100644 --- a/integration-tests/chopsticks/src/types.ts +++ b/integration-tests/chopsticks/src/types.ts @@ -1,6 +1,18 @@ -import type { pah, polimec, polkadot } from '@polkadot-api/descriptors'; -import type { PolkadotClient, TypedApi } from 'polkadot-api'; - +import { + XcmV3Junction, + XcmV3JunctionNetworkId, + XcmV3Junctions, + XcmV3MultiassetAssetId, + XcmV3MultiassetFungibility, + XcmVersionedAssets, + XcmVersionedLocation, + type pah, + type polimec, + type polkadot, +} from '@polkadot-api/descriptors'; +import type { Ia5l7mu5a6v49o } from '@polkadot-api/descriptors/dist/common-types'; +import { hexToU8a } from '@polkadot/util'; +import { FixedSizeBinary, type PolkadotClient, type TypedApi } from 'polkadot-api'; type Polimec = typeof polimec; type PolkadotHub = typeof pah; type Polkadot = typeof polkadot; @@ -10,35 +22,32 @@ export enum Chains { PolkadotHub = 'ws://localhost:8001', Polkadot = 'ws://localhost:8002', } - +export type ChainClient = { + api: TypedApi; + client: PolkadotClient; +}; export const ParaId = { [Chains.Polimec]: 3344, [Chains.PolkadotHub]: 1000, }; +export enum AssetSourceRelation { + Parent = 0, + Sibling = 1, + Self = 2, +} + export enum Accounts { BOB = '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty', ALICE = '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY', } -export enum Assets { - USDT = 1984, - DOT = 10, - USDC = 1337, - UNKNOWN = 42, -} - export type ChainToDefinition = { [Chains.Polimec]: Polimec; [Chains.PolkadotHub]: PolkadotHub; [Chains.Polkadot]: Polkadot; }; -export type ChainClient = { - api: TypedApi; - client: PolkadotClient; -}; - export interface TransferResult { sourceBlock: number; destBlock: number; @@ -54,17 +63,122 @@ export interface PolimecBalanceCheck extends BalanceCheck { } export interface TransferDataParams { - amount: bigint; toChain: Chains; - assetIndex?: bigint; + assets: XcmVersionedAssets; recv?: Accounts; isMultiHop?: boolean; - // TODO: Check if this flag is actually needed. - isFromBridge?: boolean; } -export interface CreateAssetsParams { - amount: bigint; - assetIndex?: bigint; - isFromBridge?: boolean; +export enum Asset { + USDT = 1984, + DOT = 10, + USDC = 1337, + WETH = 10000, +} + +export function AssetHubAssetLocation( + assetId: bigint, + source_relation: AssetSourceRelation, +): XcmVersionedLocation { + switch (source_relation) { + case AssetSourceRelation.Sibling: + return XcmVersionedLocation.V4({ + parents: 1, + interior: XcmV3Junctions.X3([ + XcmV3Junction.Parachain(ParaId[Chains.PolkadotHub]), + XcmV3Junction.PalletInstance(50), + XcmV3Junction.GeneralIndex(assetId), + ]), + }); + case AssetSourceRelation.Self: + return XcmVersionedLocation.V4({ + parents: 0, + interior: XcmV3Junctions.X2([ + XcmV3Junction.PalletInstance(50), + XcmV3Junction.GeneralIndex(assetId), + ]), + }); + case AssetSourceRelation.Parent: + return XcmVersionedLocation.V4({ + parents: 0, + interior: XcmV3Junctions.X3([ + XcmV3Junction.Parachain(ParaId[Chains.PolkadotHub]), + XcmV3Junction.PalletInstance(50), + XcmV3Junction.GeneralIndex(assetId), + ]), + }); + } +} + +export function NativeAssetLocation( + source_relation: AssetSourceRelation, + paraId?: number, +): XcmVersionedLocation { + switch (source_relation) { + case AssetSourceRelation.Sibling: + if (!paraId) { + throw new Error('You need to specify a paraId with SourceRelation.Sibling'); + } + return XcmVersionedLocation.V4({ + parents: 1, + interior: XcmV3Junctions.X1([XcmV3Junction.Parachain(paraId)]), + }); + case AssetSourceRelation.Self: + return XcmVersionedLocation.V4({ + parents: 0, + interior: XcmV3Junctions.Here(), + }); + case AssetSourceRelation.Parent: + return XcmVersionedLocation.V4({ + parents: 1, + interior: XcmV3Junctions.Here(), + }); + } +} + +export function EthereumAssetLocation(contract_address: FixedSizeBinary<20>): XcmVersionedLocation { + return XcmVersionedLocation.V4({ + parents: 2, + interior: XcmV3Junctions.X2([ + XcmV3Junction.GlobalConsensus(XcmV3JunctionNetworkId.Ethereum({ chain_id: 1n })), + XcmV3Junction.AccountKey20({ network: undefined, key: contract_address }), + ]), + }); +} + +export function AssetLocation( + asset: Asset, + asset_source_relation: AssetSourceRelation, +): XcmVersionedLocation { + switch (asset) { + case Asset.USDT: + return AssetHubAssetLocation(1984n, asset_source_relation); + + case Asset.USDC: + return AssetHubAssetLocation(1337n, asset_source_relation); + + case Asset.DOT: + return NativeAssetLocation(asset_source_relation); + + case Asset.WETH: { + const contract_hex = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; + const byteArray = hexToU8a(contract_hex); + return EthereumAssetLocation(new FixedSizeBinary(byteArray)); + } + } +} + +export function getVersionedAssets( + assets: [Asset, bigint, AssetSourceRelation][], +): XcmVersionedAssets { + const final_assets: Ia5l7mu5a6v49o[] = []; + for (const [asset, amount, asset_source_relation] of assets) { + const location = AssetLocation(asset, asset_source_relation); + final_assets.push({ + id: location.value, + fun: XcmV3MultiassetFungibility.Fungible(amount), + }); + } + + return XcmVersionedAssets.V4(final_assets); } diff --git a/integration-tests/chopsticks/src/utils.ts b/integration-tests/chopsticks/src/utils.ts index 670d9fb58..f442128e0 100644 --- a/integration-tests/chopsticks/src/utils.ts +++ b/integration-tests/chopsticks/src/utils.ts @@ -1,9 +1,11 @@ import { Accounts, + Asset, + AssetSourceRelation, Chains, - type CreateAssetsParams, ParaId, type TransferDataParams, + getVersionedAssets, } from '@/types'; import { XcmV3Instruction, @@ -15,7 +17,6 @@ import { XcmV3MultiassetWildMultiAsset, XcmV3WeightLimit, XcmVersionedAssetId, - XcmVersionedAssets, XcmVersionedLocation, XcmVersionedXcm, } from '@polkadot-api/descriptors'; @@ -57,61 +58,7 @@ const custom_xcm_on_dest = (): XcmVersionedXcm => { ]); }; -// TODO: Modify this function to allow the creation of an XcmVersionedAssets that supports also WETH/bridged assets. -const createHubAssets = ({ - amount, - assetIndex, - isFromBridge, -}: CreateAssetsParams): XcmVersionedAssets => - XcmVersionedAssets.V3([ - { - fun: XcmV3MultiassetFungibility.Fungible(amount), - id: XcmV3MultiassetAssetId.Concrete({ - parents: assetIndex ? 0 : 1, - interior: assetIndex - ? XcmV3Junctions.X2([ - XcmV3Junction.PalletInstance(50), - XcmV3Junction.GeneralIndex(assetIndex), - ]) - : XcmV3Junctions.Here(), - }), - }, - ]); - -const createDotAssets = ({ amount }: CreateAssetsParams): XcmVersionedAssets => - XcmVersionedAssets.V3([ - { - fun: XcmV3MultiassetFungibility.Fungible(amount), - id: XcmV3MultiassetAssetId.Concrete({ - parents: 0, - interior: XcmV3Junctions.Here(), - }), - }, - ]); - -const createPolimecAssets = ({ amount, assetIndex }: CreateAssetsParams): XcmVersionedAssets => { - if (!assetIndex) { - throw new Error('You need to specify an Asset ID while creating an asset for Polimec'); - } - return XcmVersionedAssets.V3([ - { - id: XcmV3MultiassetAssetId.Concrete({ - parents: 1, - interior: - assetIndex === 10n - ? XcmV3Junctions.Here() - : XcmV3Junctions.X3([ - XcmV3Junction.Parachain(ParaId[Chains.PolkadotHub]), - XcmV3Junction.PalletInstance(50), - XcmV3Junction.GeneralIndex(assetIndex), - ]), - }), - fun: XcmV3MultiassetFungibility.Fungible(amount), - }, - ]); -}; - -export const createTransferData = ({ amount, toChain, assetIndex, recv }: TransferDataParams) => { +export const createTransferData = ({ toChain, assets, recv }: TransferDataParams) => { if (toChain === Chains.Polkadot) { throw new Error('Invalid chain'); } @@ -133,16 +80,13 @@ export const createTransferData = ({ amount, toChain, assetIndex, recv }: Transf return { dest, beneficiary, - assets: - toChain === Chains.PolkadotHub - ? createPolimecAssets({ amount, assetIndex }) - : createHubAssets({ amount, assetIndex }), + assets, fee_asset_item: 0, weight_limit: XcmV3WeightLimit.Unlimited(), }; }; -export const createMultiHopTransferData = ({ amount }: TransferDataParams) => { +export const createDotMultiHopTransferData = (amount: bigint) => { const dest = XcmVersionedLocation.V3({ parents: 0, interior: XcmV3Junctions.X1(XcmV3Junction.Parachain(ParaId[Chains.PolkadotHub])), @@ -150,7 +94,7 @@ export const createMultiHopTransferData = ({ amount }: TransferDataParams) => { return { dest, - assets: createDotAssets({ amount }), + assets: getVersionedAssets([[Asset.DOT, amount]], AssetSourceRelation.Self), assets_transfer_type: Enum('Teleport'), remote_fees_id: XcmVersionedAssetId.V3( XcmV3MultiassetAssetId.Concrete({ @@ -163,3 +107,10 @@ export const createMultiHopTransferData = ({ amount }: TransferDataParams) => { weight_limit: XcmV3WeightLimit.Unlimited(), }; }; + +export function unwrap(value: T | undefined, errorMessage = 'Value is undefined'): T { + if (value === undefined) { + throw new Error(errorMessage); + } + return value; +} diff --git a/integration-tests/src/tests/runtime_apis.rs b/integration-tests/src/tests/runtime_apis.rs index 8a0f733c6..fe06ba00d 100644 --- a/integration-tests/src/tests/runtime_apis.rs +++ b/integration-tests/src/tests/runtime_apis.rs @@ -3,8 +3,10 @@ use assets_common::runtime_api::runtime_decl_for_fungibles_api::FungiblesApiV2; use frame_support::traits::{ fungible::{Inspect, Mutate as FMutate}, fungibles::Mutate, + tokens::ConversionToAssetBalance, }; use polimec_common::assets::AcceptedFundingAsset; +use polimec_runtime::PLMCToAssetBalance; use sp_arithmetic::FixedU128; use xcm::v4::Junctions::X3; use xcm_fee_payment_runtime_api::fees::runtime_decl_for_xcm_payment_api::XcmPaymentApiV1; @@ -106,3 +108,13 @@ mod fungibles_api { }); } } + +#[test] +fn sandbox() { + use super::*; + + PolimecNet::execute_with(|| { + let b = PLMCToAssetBalance::to_asset_balance(135_0_000_000_000, Location::here()); + dbg!(b); + }); +} diff --git a/pallets/funding/src/storage_migrations.rs b/pallets/funding/src/storage_migrations.rs index 7690415cc..736a1081f 100644 --- a/pallets/funding/src/storage_migrations.rs +++ b/pallets/funding/src/storage_migrations.rs @@ -95,10 +95,6 @@ pub mod v6 { log::info!("Starting migration to V5"); let translate_project_details = |_key, item: OldProjectMetadataOf| -> Option> { items += 1; - log::info!("project_details item {:?}", items); - - // let old_participation_currencies = item.participation_currencies.to_vec(); - // let new_participation_currencies: BoundedVec> = old_participation_currencies.try_into().ok()?; Some(ProjectMetadataOf:: { token_information: item.token_information, @@ -125,7 +121,6 @@ pub mod v6 { for mut old_migration in old_migrations { items += 1; - log::info!("migration items {:?}", items); let origin_junction = old_migration.origin.user.interior.take_first().unwrap(); let new_origin = MigrationOrigin { user: origin_junction, @@ -139,6 +134,7 @@ pub mod v6 { }; crate::UserMigrations::::translate(translate_migration); + log::info!("Migration to V5 completed. Migrated {} items", items); T::DbWeight::get().reads_writes(items, items) } } diff --git a/runtimes/polimec/src/custom_migrations/asset_id_migration.rs b/runtimes/polimec/src/custom_migrations/asset_id_migration.rs index 650c5f7d8..4622dc63f 100644 --- a/runtimes/polimec/src/custom_migrations/asset_id_migration.rs +++ b/runtimes/polimec/src/custom_migrations/asset_id_migration.rs @@ -115,12 +115,12 @@ impl OnRuntimeUpgrade for FromOldAssetIdMigration { fn on_runtime_upgrade() -> frame_support::weights::Weight { let version = Funding::on_chain_storage_version(); - log::info!("funding version: {:?}", version); + let runtime_version = ::Version::get(); if version != 5 { - log::info!("funding version is not 5"); - return frame_support::weights::Weight::zero(); + log::info!("AssetId Migration can be removed"); + return ::DbWeight::get().reads(1) } - let runtime_version = Runtime::version(); + log::info!("Running AssetId Migration..."); let mut items = 0; if runtime_version.spec_version == 1_000_000 { let id_map = BTreeMap::from([ @@ -133,7 +133,6 @@ impl OnRuntimeUpgrade for FromOldAssetIdMigration { let old_account_iterator = pallet_assets_storage_items::old_types::Account::iter().collect_vec(); for (old_asset_id, account, account_info) in old_account_iterator { items += 1; - log::info!("old_account item {:?}", items); pallet_assets_storage_items::new_types::Account::insert( id_map.get(&old_asset_id).unwrap(), account.clone(), @@ -145,7 +144,6 @@ impl OnRuntimeUpgrade for FromOldAssetIdMigration { let old_asset_iterator = pallet_assets_storage_items::old_types::Asset::iter().collect_vec(); for (old_asset_id, asset_info) in old_asset_iterator { items += 1; - log::info!("old_asset item {:?}", items); pallet_assets_storage_items::new_types::Asset::insert(id_map.get(&old_asset_id).unwrap(), asset_info); pallet_assets_storage_items::old_types::Asset::remove(old_asset_id); } @@ -153,7 +151,6 @@ impl OnRuntimeUpgrade for FromOldAssetIdMigration { let old_approvals_iterator = pallet_assets_storage_items::old_types::Approvals::iter().collect_vec(); for ((old_asset_id, owner, delegate), approval) in old_approvals_iterator { items += 1; - log::info!("old_approvals item {:?}", items); pallet_assets_storage_items::new_types::Approvals::insert( (id_map.get(&old_asset_id).unwrap(), owner.clone(), delegate.clone()), approval, @@ -190,6 +187,8 @@ impl OnRuntimeUpgrade for FromOldAssetIdMigration { } } + log::info!("Total items migrated: {:?}", items); + ::DbWeight::get().reads_writes(items, items) } diff --git a/runtimes/polimec/src/lib.rs b/runtimes/polimec/src/lib.rs index f0cbc0edb..178b4055c 100644 --- a/runtimes/polimec/src/lib.rs +++ b/runtimes/polimec/src/lib.rs @@ -1176,29 +1176,38 @@ impl pallet_dispenser::Config for Runtime { type WeightInfo = weights::pallet_dispenser::WeightInfo; type WhitelistedPolicy = DispenserWhitelistedPolicy; } -pub struct PLMCToFundingAssetBalance; -impl ConversionToAssetBalance for PLMCToFundingAssetBalance { +pub struct PLMCToAssetBalance; +impl ConversionToAssetBalance for PLMCToAssetBalance { type Error = InvalidTransaction; fn to_asset_balance(plmc_balance: Balance, asset_id: Location) -> Result { + if asset_id == Location::here() { + return Ok(plmc_balance); + } + let plmc_price = >::get_decimals_aware_price(Location::here(), USD_DECIMALS, PLMC_DECIMALS) .ok_or(InvalidTransaction::Payment)?; + let funding_asset_decimals = >::decimals(asset_id.clone()); + let funding_asset_price = >::get_decimals_aware_price(asset_id, USD_DECIMALS, funding_asset_decimals) .ok_or(InvalidTransaction::Payment)?; + let usd_balance = plmc_price.saturating_mul_int(plmc_balance); + let funding_asset_balance = funding_asset_price.reciprocal().ok_or(InvalidTransaction::Payment)?.saturating_mul_int(usd_balance); + Ok(funding_asset_balance) } } impl pallet_asset_tx_payment::Config for Runtime { type Fungibles = ForeignAssets; type OnChargeAssetTransaction = TxFeeFungiblesAdapter< - PLMCToFundingAssetBalance, + PLMCToAssetBalance, CreditFungiblesToAccount, AssetsToBlockAuthor, >; @@ -1716,9 +1725,10 @@ impl_runtime_apis! { let location: Location = xcm::v4::AssetId::try_from(asset).map_err(|_| XcmPaymentApiError::VersionedConversionFailed)?.0; let native_fee = TransactionPayment::weight_to_fee(weight); if location == Location::here() { + log::info!("Native fee in XcmPaymentApi: {:?}", native_fee); return Ok(native_fee) } - PLMCToFundingAssetBalance::to_asset_balance(native_fee, location).map_err(|_| XcmPaymentApiError::AssetNotFound) + PLMCToAssetBalance::to_asset_balance(native_fee, location).map_err(|_| XcmPaymentApiError::AssetNotFound) } diff --git a/runtimes/polimec/src/xcm_config.rs b/runtimes/polimec/src/xcm_config.rs index 811301b07..6302cfc92 100644 --- a/runtimes/polimec/src/xcm_config.rs +++ b/runtimes/polimec/src/xcm_config.rs @@ -17,8 +17,8 @@ extern crate alloc; use super::{ AccountId, AllPalletsWithSystem, Balance, Balances, ContributionTokens, EnsureRoot, ForeignAssets, Funding, - ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, ToTreasury, - TreasuryAccount, Vec, WeightToFee, + PLMCToAssetBalance, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, + ToTreasury, TreasuryAccount, Vec, WeightToFee, }; use core::marker::PhantomData; use cumulus_primitives_core::ParaId; @@ -26,8 +26,10 @@ use frame_support::{ ensure, pallet_prelude::*, parameter_types, - traits::{ConstU32, Contains, ContainsPair, Everything, Nothing, ProcessMessageError}, - weights::Weight, + traits::{ + ConstU32, Contains, ContainsPair, Everything, Nothing, OnUnbalanced as OnUnbalancedT, ProcessMessageError, + }, + weights::{Weight, WeightToFee as WeightToFeeT}, }; use pallet_xcm::XcmPassthrough; use polimec_common::assets::AcceptedFundingAsset; @@ -35,6 +37,7 @@ use polimec_common::assets::AcceptedFundingAsset; use polimec_common_test_utils::DummyXcmSender; use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::xcm_sender::NoPriceForMessageDelivery; +use sp_runtime::traits::Zero; use xcm::v4::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, @@ -42,13 +45,15 @@ use xcm_builder::{ FixedRateOfFungible, FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, IsConcrete, MatchXcm, MatchedConvertedConcreteId, MintLocation, NoChecking, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, TakeWeightCredit, TrailingSetTopicAsId, - UsingComponents, WithComputedOrigin, + SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, TakeRevenue, TakeWeightCredit, + TrailingSetTopicAsId, UsingComponents, WithComputedOrigin, }; use xcm_executor::{ - traits::{JustTry, Properties, ShouldExecute}, - XcmExecutor, + traits::{JustTry, Properties, ShouldExecute, WeightTrader}, + AssetsInHolding, XcmExecutor, }; +use frame_support::traits::tokens::ConversionToAssetBalance; + // DOT from Polkadot Asset Hub const DOT_PER_SECOND_EXECUTION: u128 = 0_2_000_000_000; // 0.2 DOT per second of execution time @@ -70,10 +75,9 @@ parameter_types! { GlobalConsensus(Polkadot), Parachain(ParachainInfo::parachain_id().into()), ).into(); - pub UniversalLocationNetworkId: NetworkId = UniversalLocation::get().global_consensus().unwrap(); pub const HereLocation: Location = Location::here(); pub AssetHubLocation: Location = (Parent, Parachain(1000)).into(); - + pub UniversalLocationNetworkId: NetworkId = UniversalLocation::get().global_consensus().unwrap(); pub CheckAccount: AccountId = PolkadotXcm::check_account(); /// The check account that is allowed to mint assets locally. Used for PLMC teleport /// checking once enabled. @@ -316,11 +320,7 @@ impl xcm_executor::Config for XcmConfig { type SafeCallFilter = Nothing; type SubscriptionService = PolkadotXcm; type Trader = ( - // TODO: `WeightToFee` has to be carefully considered. For now use default - UsingComponents, - FixedRateOfFungible, - FixedRateOfFungible, - FixedRateOfFungible, + AssetTrader, ); type TransactionalProcessor = FrameTransactionalProcessor; type UniversalAliases = Nothing; @@ -495,3 +495,91 @@ impl cumulus_pallet_xcmp_queue::migration::v5::V5Config for Runtime { // This must be the same as the `ChannelInfo` from the `Config`: type ChannelList = ParachainSystem; } + +/// Can be used to buy weight in exchange for an accepted asset. +/// Only one asset can be used to buy weight at a time. +pub struct AssetTrader { + weight_bought: Weight, + asset_spent: Option, + phantom: PhantomData, +} +impl WeightTrader for AssetTrader { + fn new() -> Self { + Self { weight_bought: Weight::zero(), asset_spent: None, phantom: PhantomData } + } + + fn buy_weight( + &mut self, + weight: Weight, + payment: AssetsInHolding, + context: &XcmContext, + ) -> Result { + log::trace!(target: "xcm::weight", "AssetsTrader::buy_weight weight: {:?}, payment: {:?}, context: {:?}", weight, payment, context); + let native_amount = WeightToFee::weight_to_fee(&weight); + let mut acceptable_assets = AcceptedFundingAsset::all_ids(); + acceptable_assets.push(Location::here()); + + // We know the executor always sends just one asset to pay for weight, even if the struct supports multiple. + let payment_fun = payment.fungible.clone(); + let (asset_id, asset_amount) = payment_fun.first_key_value().ok_or(XcmError::FeesNotMet)?; + ensure!(acceptable_assets.contains(&asset_id.0), XcmError::FeesNotMet); + + // If the trader was used already in this xcm execution, make sure we continue trading with the same asset + let old_amount = if let Some(asset) =&self.asset_spent { + ensure!(asset.id == *asset_id, XcmError::FeesNotMet); + if let Fungibility::Fungible(amount) = asset.fun { + amount + } else { + return Err(XcmError::FeesNotMet) + } + } else { + Zero::zero() + }; + + let required_asset_amount = + PLMCToAssetBalance::to_asset_balance(native_amount, asset_id.0.clone()).map_err(|_| XcmError::FeesNotMet)?; + ensure!(*asset_amount >= required_asset_amount, XcmError::FeesNotMet); + + let required = (AssetId(asset_id.0.clone()), required_asset_amount).into(); + let unused = payment.checked_sub(required).map_err(|_| XcmError::FeesNotMet)?; + + self.weight_bought = self.weight_bought.saturating_add(weight); + self.asset_spent = + Some(Asset { id: asset_id.clone(), fun: Fungibility::Fungible(old_amount + required_asset_amount) }); + + Ok(unused) + } + + fn refund_weight(&mut self, weight: Weight, context: &XcmContext) -> Option { + log::trace!(target: "xcm::weight", "AssetsTrader::refund_weight weight: {:?}, context: {:?}, available weight: {:?}, available amount: {:?}", weight, context, self.weight_bought, self.asset_spent); + let weight_refunded = weight.min(self.weight_bought); + self.weight_bought -= weight_refunded; + + let native_amount = WeightToFee::weight_to_fee(&weight_refunded); + let asset_id = self.asset_spent.clone()?.id; + let asset_amount = PLMCToAssetBalance::to_asset_balance(native_amount, asset_id.0.clone()).ok()?; + log::trace!(target: "xcm::weight", "AssetTrader::refund_weight amount to refund: {:?}", asset_amount); + + if let Fungibility::Fungible(amount) = self.asset_spent.clone()?.fun { + self.asset_spent = + Some(Asset { id: asset_id.clone(), fun: Fungibility::Fungible(amount.saturating_sub(asset_amount)) }); + } else { + log::trace!(target: "xcm::weight", "AssetTrader::refund_weight unexpected non-fungible asset found. Bug somewhere"); + return None; + } + + if asset_amount > 0 { + Some((asset_id.clone(), asset_amount).into()) + } else { + None + } + } +} +impl Drop for AssetTrader { + fn drop(&mut self) { + if let Some(asset) = &self.asset_spent { + log::trace!(target: "xcm::weight", "AssetTrader::drop asset: {:?}", asset); + Payee::take_revenue(asset.clone()); + } + } +} From 51d9482becff5391d8db44b5bef2c07bb226d67b Mon Sep 17 00:00:00 2001 From: Leonardo Razovic <4128940+lrazovic@users.noreply.github.com> Date: Wed, 15 Jan 2025 14:00:23 +0100 Subject: [PATCH 02/11] feat: convert key to hex --- integration-tests/chopsticks/bun.lockb | Bin 199373 -> 199316 bytes .../chopsticks/overrides/polimec.ts | 120 ++++++++++++------ .../chopsticks/overrides/polkadot-hub.ts | 4 +- integration-tests/chopsticks/package.json | 6 +- .../chopsticks/src/managers/PolimecManager.ts | 3 +- .../src/managers/PolkadotHubManager.ts | 21 +-- .../chopsticks/src/tests/hub.test.ts | 72 +++++------ integration-tests/chopsticks/src/types.ts | 3 +- integration-tests/chopsticks/src/utils.ts | 24 ++++ .../custom_migrations/asset_id_migration.rs | 4 + 10 files changed, 165 insertions(+), 92 deletions(-) diff --git a/integration-tests/chopsticks/bun.lockb b/integration-tests/chopsticks/bun.lockb index 70d9cc07c2d5478b29505635135fdb75ea22dbcc..a421c147ce68ff8cffb0ead1ddd80ca6185f431f 100755 GIT binary patch delta 47188 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%0DG&c<~fumG43$aAw% zV)0@?S}F#u2EGGU0Ly`Ift7$%Tpd^iYyc!q*5kSkSPS&Q{Tf_F2IXw4O(+}$4gkLZ zG762r&%l159Mu*eyH# zpHSEbNX2{Q3JZ_mYK^N5HcymK0>^=pfEQ4Zc0zq#a1Ik0;r|!%A3hiUv%nePwDiA3 z^Ep6_I1l^=L_x3zu4Z9xl+DI?pezQ7qKm*Wz-(N3l8ptXanel+8NK@}_ff zX_W}w0qz5Lf!_h~#ARHs07APBTm#AiSAqV(4d6O(3-|-L3H+(zFIL9LYT4FKg&zU)a}WKSIChT3Io?FvTqhsrCe`oLM6oxI4aR0lA)fx-NX=fV)+TN|2U zxm;7R%U^074Ytx?Gyj0P!X~CH

wF!pM7^$cu|RQy^b)rjpv>@U;&l%s<6067P9N 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 T2Op!B#`#BRw}yiXThID`FO+X@ delta 47383 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 diff --git a/integration-tests/chopsticks/overrides/polimec.ts b/integration-tests/chopsticks/overrides/polimec.ts index 88e513518..79eded3a0 100644 --- a/integration-tests/chopsticks/overrides/polimec.ts +++ b/integration-tests/chopsticks/overrides/polimec.ts @@ -23,6 +23,26 @@ const dot_location = { }, }; +const weth_location = { + parents: 2, + interior: { + x2: [ + { + globalConsensus: { + ethereum: { + chainId: 1n + } + } + }, + { + accountKey20: { + key: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + } + } + ] + } +} + export const polimec_storage = { System: { Account: [ @@ -57,52 +77,75 @@ export const polimec_storage = { balance: INITIAL_BALANCES.DOT, }, ], - ], - Asset: [ - [ - [usdc_location], - { - owner: Accounts.ALICE, - issuer: Accounts.ALICE, - admin: Accounts.ALICE, - freezer: Accounts.ALICE, - supply: INITIAL_BALANCES.USDC, - deposit: 0n, - min_balance: 70000n, - is_sufficient: true, - accounts: 1, - sufficients: 1, - approvals: 0, - status: 'Live', - }, - ], [ - [usdt_location], + [weth_location, Accounts.BOB], { - owner: Accounts.ALICE, - issuer: Accounts.ALICE, - admin: Accounts.ALICE, - freezer: Accounts.ALICE, - supply: INITIAL_BALANCES.USDT, - deposit: 0n, - min_balance: 70000n, - is_sufficient: true, - accounts: 1, - sufficients: 1, - approvals: 0, - status: 'Live', + balance: INITIAL_BALANCES.WETH, }, ], + ], + Asset: [ + // [ + // [usdc_location], + // { + // owner: Accounts.ALICE, + // issuer: Accounts.ALICE, + // admin: Accounts.ALICE, + // freezer: Accounts.ALICE, + // supply: INITIAL_BALANCES.USDC, + // deposit: 0n, + // min_balance: 70000n, + // is_sufficient: true, + // accounts: 1, + // sufficients: 1, + // approvals: 0, + // status: 'Live', + // }, + // ], + // [ + // [usdt_location], + // { + // owner: Accounts.ALICE, + // issuer: Accounts.ALICE, + // admin: Accounts.ALICE, + // freezer: Accounts.ALICE, + // supply: INITIAL_BALANCES.USDT, + // deposit: 0n, + // min_balance: 70000n, + // is_sufficient: true, + // accounts: 1, + // sufficients: 1, + // approvals: 0, + // status: 'Live', + // }, + // ], + // [ + // [dot_location], + // { + // owner: Accounts.ALICE, + // issuer: Accounts.ALICE, + // admin: Accounts.ALICE, + // freezer: Accounts.ALICE, + // supply: INITIAL_BALANCES.DOT, + // deposit: 0n, + // min_balance: 100000000n, + // is_sufficient: true, + // accounts: 1, + // sufficients: 1, + // approvals: 0, + // status: 'Live', + // }, + // ], [ - [dot_location], + [weth_location], { owner: Accounts.ALICE, issuer: Accounts.ALICE, admin: Accounts.ALICE, freezer: Accounts.ALICE, - supply: INITIAL_BALANCES.DOT, + supply: INITIAL_BALANCES.WETH, deposit: 0n, - min_balance: 100000000n, + min_balance: 1000000n, is_sufficient: true, accounts: 1, sufficients: 1, @@ -112,9 +155,10 @@ export const polimec_storage = { ], ], Metadata: [ - [[usdc_location], { symbol: 'USDC', name: 'USDC', decimals: 6, isFrozen: false }], - [[usdt_location], { symbol: 'USDT', name: 'USDC', decimals: 6, isFrozen: false }], - [[dot_location], { symbol: 'DOT', name: 'DOT', decimals: 10, isFrozen: false }], + // [[usdc_location], { symbol: 'USDC', name: 'USDC', decimals: 6, isFrozen: false }], + // [[usdt_location], { symbol: 'USDT', name: 'USDC', decimals: 6, isFrozen: false }], + // [[dot_location], { symbol: 'DOT', name: 'DOT', decimals: 10, isFrozen: false }], + [[weth_location], { symbol: 'WETH', name: 'WETH', decimals: 18, isFrozen: false }], ], }, } as const; diff --git a/integration-tests/chopsticks/overrides/polkadot-hub.ts b/integration-tests/chopsticks/overrides/polkadot-hub.ts index 12f529940..0bfbd4348 100644 --- a/integration-tests/chopsticks/overrides/polkadot-hub.ts +++ b/integration-tests/chopsticks/overrides/polkadot-hub.ts @@ -54,10 +54,10 @@ export const polkadot_hub_storage = { }, ForeignAssets: { Account: [ - [[weth_location, Accounts.ALICE], { balance: 10000000000000 }] + [[weth_location, Accounts.ALICE], { balance: INITIAL_BALANCES.WETH }] ], Asset: [ - [[weth_location], { supply: 10000000000000 }] + [[weth_location], { supply: INITIAL_BALANCES.WETH }] ] } } as const; diff --git a/integration-tests/chopsticks/package.json b/integration-tests/chopsticks/package.json index 553d626be..9a0c09ebd 100644 --- a/integration-tests/chopsticks/package.json +++ b/integration-tests/chopsticks/package.json @@ -13,12 +13,10 @@ "@types/bun": "latest" }, "peerDependencies": { - "typescript": "^5.0.0" + "typescript": "^5.7.3" }, "dependencies": { "@polkadot-api/descriptors": "file:.papi/descriptors", - "@polkadot/keyring": "13.2.3", - "lodash": "^4.17.21", - "polkadot-api": "^1.7.7" + "polkadot-api": "^1.8.2" } } diff --git a/integration-tests/chopsticks/src/managers/PolimecManager.ts b/integration-tests/chopsticks/src/managers/PolimecManager.ts index 68a6b1d46..fc75e9912 100644 --- a/integration-tests/chopsticks/src/managers/PolimecManager.ts +++ b/integration-tests/chopsticks/src/managers/PolimecManager.ts @@ -4,6 +4,7 @@ import { isEqual } from 'lodash'; import { createClient } from 'polkadot-api'; import { getWsProvider } from 'polkadot-api/ws-provider/web'; import { BaseChainManager } from './BaseManager'; +import { normalizeForComparison } from '@/utils.ts'; export class PolimecManager extends BaseChainManager { connect() { @@ -60,7 +61,7 @@ export class PolimecManager extends BaseChainManager { if (account_balances_result.success === true && account_balances_result.value.type === 'V4') { const assets = account_balances_result.value.value; for (const asset of assets) { - if (Bun.deepEquals(asset.id, asset_location)) { + if (Bun.deepEquals(normalizeForComparison(asset.id), normalizeForComparison(asset_location))) { console.log('Found asset. Balance is: ', asset.fun.value); console.dir(asset, { depth: null, colors: true }); console.log('\n\n'); diff --git a/integration-tests/chopsticks/src/managers/PolkadotHubManager.ts b/integration-tests/chopsticks/src/managers/PolkadotHubManager.ts index 0d7d9f13a..f74d522a8 100644 --- a/integration-tests/chopsticks/src/managers/PolkadotHubManager.ts +++ b/integration-tests/chopsticks/src/managers/PolkadotHubManager.ts @@ -1,9 +1,11 @@ import { type Accounts, Asset, AssetLocation, AssetSourceRelation, Chains } from '@/types'; import { pah } from '@polkadot-api/descriptors'; -import { isEqual } from 'lodash'; import { createClient } from 'polkadot-api'; import { getWsProvider } from 'polkadot-api/ws-provider/web'; import { BaseChainManager } from './BaseManager'; +import { normalizeForComparison } from '@/utils.ts'; + + export class PolkadotHubManager extends BaseChainManager { connect() { @@ -44,6 +46,7 @@ export class PolkadotHubManager extends BaseChainManager { return AssetSourceRelation.Self; } } + async getAssetBalanceOf(account: Accounts, asset: Asset): Promise { const api = this.getApi(Chains.PolkadotHub); const asset_source_relation = this.getAssetSourceRelation(asset); @@ -51,31 +54,31 @@ export class PolkadotHubManager extends BaseChainManager { const account_balances_result = await api.apis.FungiblesApi.query_account_balances(account); console.log('Requested asset location in PolkadotHubManager'); console.dir(asset_location, { depth: null, colors: true }); - let maybe_account_key_20 = asset_location.interior.value[1].value?.key?.asHex(); - console.log('Maybe Account key 20', maybe_account_key_20); console.log('\n\n'); if (account_balances_result.success === true && account_balances_result.value.type === 'V4') { const assets = account_balances_result.value.value; for (const asset of assets) { - if (Bun.deepEquals(asset.id, asset_location)) { + if ( + Bun.deepEquals(normalizeForComparison(asset.id), normalizeForComparison(asset_location)) + ) { console.log('Found asset. Balance is: ', asset.fun.value); - console.dir(asset, { depth: null, colors: true }); + console.dir(normalizeForComparison(asset), { depth: null, colors: true }); console.log('\n\n'); return asset.fun.value; } console.log('Not it chief: \n'); - console.dir(asset, { depth: null, colors: true }); + console.dir(normalizeForComparison(asset.id), { depth: null, colors: true }); + console.log('\n\n'); - let maybe_account_key_20 = asset.id.interior.value?.[1].value?.key?.asHex(); - console.log('Maybe Account key 20', maybe_account_key_20); } } console.log('Asset not found'); console.log('\n\n'); return 0n; } + async getSwapCredit() { const api = this.getApi(Chains.PolkadotHub); const events = await api.event.AssetConversion.SwapCreditExecuted.pull(); @@ -85,7 +88,7 @@ export class PolkadotHubManager extends BaseChainManager { async getXcmFee() { const api = this.getApi(Chains.PolkadotHub); const events = await api.event.PolkadotXcm.FeesPaid.pull(); - console.log("Fees paid event") + console.log('Fees paid event'); console.dir(events, { depth: null, colors: true }); return (events[0]?.payload.fees[0].fun.value as bigint) || 0n; } diff --git a/integration-tests/chopsticks/src/tests/hub.test.ts b/integration-tests/chopsticks/src/tests/hub.test.ts index 2f1852c8f..2d0ea2508 100644 --- a/integration-tests/chopsticks/src/tests/hub.test.ts +++ b/integration-tests/chopsticks/src/tests/hub.test.ts @@ -17,50 +17,50 @@ describe('Polkadot Hub -> Polimec Transfer Tests', () => { destManager.connect(); }); afterAll(async () => await chainSetup.cleanup()); - // - // test( - // 'Send DOT to Polimec', - // () => - // transferTest.testTransfer({ - // account: Accounts.ALICE, - // assets: [[Asset.DOT, TRANSFER_AMOUNTS.NATIVE, AssetSourceRelation.Parent]], - // }), - // { timeout: 25000 }, - // ); - // - // test( - // 'Send USDT to Polimec', - // () => - // transferTest.testTransfer({ - // account: Accounts.ALICE, - // assets: [[Asset.USDT, TRANSFER_AMOUNTS.TOKENS, AssetSourceRelation.Self]], - // }), - // { timeout: 25000 }, - // ); - // - // test( - // 'Send USDC to Polimec', - // () => - // transferTest.testTransfer({ - // account: Accounts.ALICE, - // assets: [[Asset.USDC, TRANSFER_AMOUNTS.TOKENS, AssetSourceRelation.Self]], - // }), - // { timeout: 25000 }, - // ); test( - 'Send WETH to Polimec', + 'Send DOT to Polimec', + () => + transferTest.testTransfer({ + account: Accounts.ALICE, + assets: [[Asset.DOT, TRANSFER_AMOUNTS.NATIVE, AssetSourceRelation.Parent]], + }), + { timeout: 25000 }, + ); + + test( + 'Send USDT to Polimec', () => transferTest.testTransfer({ account: Accounts.ALICE, - assets: [ - [Asset.USDT, TRANSFER_AMOUNTS.TOKENS, AssetSourceRelation.Self], - [Asset.WETH, TRANSFER_AMOUNTS.BRIDGED, AssetSourceRelation.Self], - ], + assets: [[Asset.USDT, TRANSFER_AMOUNTS.TOKENS, AssetSourceRelation.Self]], }), - { timeout: 10000000 }, + { timeout: 25000 }, ); + test( + 'Send USDC to Polimec', + () => + transferTest.testTransfer({ + account: Accounts.ALICE, + assets: [[Asset.USDC, TRANSFER_AMOUNTS.TOKENS, AssetSourceRelation.Self]], + }), + { timeout: 25000 }, + ); + + // test( + // 'Send WETH to Polimec', + // () => + // transferTest.testTransfer({ + // account: Accounts.ALICE, + // assets: [ + // // [Asset.USDT, TRANSFER_AMOUNTS.TOKENS, AssetSourceRelation.Self], + // [Asset.WETH, TRANSFER_AMOUNTS.BRIDGED, AssetSourceRelation.Self], + // ], + // }), + // { timeout: 25000 }, + // ); + // test('sandbox', async () => { // console.log("hello"); // const weth_1 = { diff --git a/integration-tests/chopsticks/src/types.ts b/integration-tests/chopsticks/src/types.ts index 3ecd18e74..6c75fed1e 100644 --- a/integration-tests/chopsticks/src/types.ts +++ b/integration-tests/chopsticks/src/types.ts @@ -162,8 +162,7 @@ export function AssetLocation( case Asset.WETH: { const contract_hex = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; - const byteArray = hexToU8a(contract_hex); - return EthereumAssetLocation(new FixedSizeBinary(byteArray)); + return EthereumAssetLocation(FixedSizeBinary.fromHex(contract_hex)); } } } diff --git a/integration-tests/chopsticks/src/utils.ts b/integration-tests/chopsticks/src/utils.ts index f442128e0..850dd08d8 100644 --- a/integration-tests/chopsticks/src/utils.ts +++ b/integration-tests/chopsticks/src/utils.ts @@ -114,3 +114,27 @@ export function unwrap(value: T | undefined, errorMessage = 'Value is undefin } return value; } + +export function normalizeForComparison(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (obj instanceof Object && 'asHex' in obj) { + return obj.asHex(); + } + + if (typeof obj === 'object') { + if (Array.isArray(obj)) { + return obj.map(normalizeForComparison); + } + + const normalized: any = {}; + for (const [key, value] of Object.entries(obj)) { + normalized[key] = normalizeForComparison(value); + } + return normalized; + } + + return obj; +} \ No newline at end of file diff --git a/runtimes/polimec/src/custom_migrations/asset_id_migration.rs b/runtimes/polimec/src/custom_migrations/asset_id_migration.rs index 4622dc63f..4f812ab4c 100644 --- a/runtimes/polimec/src/custom_migrations/asset_id_migration.rs +++ b/runtimes/polimec/src/custom_migrations/asset_id_migration.rs @@ -133,6 +133,8 @@ impl OnRuntimeUpgrade for FromOldAssetIdMigration { let old_account_iterator = pallet_assets_storage_items::old_types::Account::iter().collect_vec(); for (old_asset_id, account, account_info) in old_account_iterator { items += 1; + log::info!("old_account item {:?}", items); + pallet_assets_storage_items::new_types::Account::insert( id_map.get(&old_asset_id).unwrap(), account.clone(), @@ -144,6 +146,7 @@ impl OnRuntimeUpgrade for FromOldAssetIdMigration { let old_asset_iterator = pallet_assets_storage_items::old_types::Asset::iter().collect_vec(); for (old_asset_id, asset_info) in old_asset_iterator { items += 1; + log::info!("old_asset item {:?}", items); pallet_assets_storage_items::new_types::Asset::insert(id_map.get(&old_asset_id).unwrap(), asset_info); pallet_assets_storage_items::old_types::Asset::remove(old_asset_id); } @@ -151,6 +154,7 @@ impl OnRuntimeUpgrade for FromOldAssetIdMigration { let old_approvals_iterator = pallet_assets_storage_items::old_types::Approvals::iter().collect_vec(); for ((old_asset_id, owner, delegate), approval) in old_approvals_iterator { items += 1; + log::info!("old_approvals item {:?}", items); pallet_assets_storage_items::new_types::Approvals::insert( (id_map.get(&old_asset_id).unwrap(), owner.clone(), delegate.clone()), approval, From a7e7df8245ac62663033c7a87e5cd9e5298452a7 Mon Sep 17 00:00:00 2001 From: Leonardo Razovic <4128940+lrazovic@users.noreply.github.com> Date: Thu, 16 Jan 2025 14:55:32 +0100 Subject: [PATCH 03/11] feat: fix TS errors --- Cargo.lock | 2 + Cargo.toml | 4 - debug-executor/.cargo-ok | 1 - debug-executor/Cargo.toml | 204 --- debug-executor/Cargo.toml.orig | 49 - debug-executor/src/assets.rs | 695 ---------- debug-executor/src/config.rs | 126 -- debug-executor/src/lib.rs | 1188 ----------------- debug-executor/src/traits/asset_exchange.rs | 58 - debug-executor/src/traits/asset_lock.rs | 131 -- debug-executor/src/traits/asset_transfer.rs | 93 -- debug-executor/src/traits/conversion.rs | 144 -- debug-executor/src/traits/drop_assets.rs | 79 -- debug-executor/src/traits/export.rs | 140 -- debug-executor/src/traits/fee_manager.rs | 60 - .../src/traits/filter_asset_location.rs | 35 - debug-executor/src/traits/hrmp.rs | 56 - debug-executor/src/traits/mod.rs | 64 - debug-executor/src/traits/on_response.rs | 180 --- .../src/traits/process_transaction.rs | 58 - debug-executor/src/traits/record_xcm.rs | 46 - debug-executor/src/traits/should_execute.rs | 113 -- debug-executor/src/traits/token_matching.rs | 106 -- debug-executor/src/traits/transact_asset.rs | 411 ------ debug-executor/src/traits/weight.rs | 114 -- .../chopsticks/overrides/polimec.ts | 80 +- .../chopsticks/overrides/polkadot-hub.ts | 28 +- integration-tests/chopsticks/src/constants.ts | 2 +- .../chopsticks/src/managers/BaseManager.ts | 34 +- .../chopsticks/src/managers/PolimecManager.ts | 19 +- .../src/managers/PolkadotHubManager.ts | 21 +- .../src/managers/PolkadotManager.ts | 1 - integration-tests/chopsticks/src/setup.ts | 10 +- .../chopsticks/src/tests/polimec.test.ts | 112 +- .../chopsticks/src/tests/polkadot.test.ts | 56 +- .../chopsticks/src/transfers/BaseTransfer.ts | 38 +- .../chopsticks/src/transfers/HubToPolimec.ts | 55 +- .../chopsticks/src/transfers/PolimecToHub.ts | 59 +- .../src/transfers/PolkadotToPolimec.ts | 60 +- integration-tests/chopsticks/src/types.ts | 19 +- integration-tests/chopsticks/src/utils.ts | 20 +- 41 files changed, 245 insertions(+), 4526 deletions(-) delete mode 100644 debug-executor/.cargo-ok delete mode 100644 debug-executor/Cargo.toml delete mode 100644 debug-executor/Cargo.toml.orig delete mode 100644 debug-executor/src/assets.rs delete mode 100644 debug-executor/src/config.rs delete mode 100644 debug-executor/src/lib.rs delete mode 100644 debug-executor/src/traits/asset_exchange.rs delete mode 100644 debug-executor/src/traits/asset_lock.rs delete mode 100644 debug-executor/src/traits/asset_transfer.rs delete mode 100644 debug-executor/src/traits/conversion.rs delete mode 100644 debug-executor/src/traits/drop_assets.rs delete mode 100644 debug-executor/src/traits/export.rs delete mode 100644 debug-executor/src/traits/fee_manager.rs delete mode 100644 debug-executor/src/traits/filter_asset_location.rs delete mode 100644 debug-executor/src/traits/hrmp.rs delete mode 100644 debug-executor/src/traits/mod.rs delete mode 100644 debug-executor/src/traits/on_response.rs delete mode 100644 debug-executor/src/traits/process_transaction.rs delete mode 100644 debug-executor/src/traits/record_xcm.rs delete mode 100644 debug-executor/src/traits/should_execute.rs delete mode 100644 debug-executor/src/traits/token_matching.rs delete mode 100644 debug-executor/src/traits/transact_asset.rs delete mode 100644 debug-executor/src/traits/weight.rs diff --git a/Cargo.lock b/Cargo.lock index aa4994e9a..40b4ce282 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13745,6 +13745,8 @@ dependencies = [ [[package]] name = "staging-xcm-executor" version = "14.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5b83ea34a2ba2083c6f5bfec468fb00535d0e0788a78237d06da32dba76be9" dependencies = [ "environmental", "frame-benchmarking", diff --git a/Cargo.toml b/Cargo.toml index dd9ae38ef..2759b888b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,6 @@ members = [ "macros", "macros/tests", "polimec-common/*", - "debug-executor", ] default-members = ["nodes/*", "pallets/*"] resolver = "2" @@ -249,6 +248,3 @@ cumulus-pallet-session-benchmarking = { version = "16.0.0", default-features = f polimec-runtime = { path = "runtimes/polimec" } rococo-runtime-constants = { version = "14.0.0" } rococo-runtime = { version = "14.0.0" } - -[patch.crates-io] -xcm-executor = { path = "./debug-executor", package = "staging-xcm-executor", default-features = false } \ No newline at end of file diff --git a/debug-executor/.cargo-ok b/debug-executor/.cargo-ok deleted file mode 100644 index 5f8b79583..000000000 --- a/debug-executor/.cargo-ok +++ /dev/null @@ -1 +0,0 @@ -{"v":1} \ No newline at end of file diff --git a/debug-executor/Cargo.toml b/debug-executor/Cargo.toml deleted file mode 100644 index 2a7f1502b..000000000 --- a/debug-executor/Cargo.toml +++ /dev/null @@ -1,204 +0,0 @@ -# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO -# -# When uploading crates to the registry Cargo will automatically -# "normalize" Cargo.toml files for maximal compatibility -# with all versions of Cargo and also rewrite `path` dependencies -# to registry (e.g., crates.io) dependencies. -# -# If you are reading this file be aware that the original Cargo.toml -# will likely look very different (and much more reasonable). -# See Cargo.toml.orig for the original contents. - -[package] -edition = "2021" -name = "staging-xcm-executor" -version = "14.0.0" -authors = ["Parity Technologies "] -description = "An abstract and configurable XCM message executor. (polkadot v1.13.0)" -license = "GPL-3.0-only" - -[dependencies.codec] -version = "3.6.12" -features = ["derive"] -default-features = false -package = "parity-scale-codec" - -[dependencies.environmental] -version = "1.1.4" -default-features = false - -[dependencies.frame-benchmarking] -version = "35.0.0" -optional = true -default-features = false - -[dependencies.frame-support] -version = "35.0.0" -default-features = false - -[dependencies.impl-trait-for-tuples] -version = "0.2.2" - -[dependencies.log] -version = "0.4.21" -default-features = false - -[dependencies.scale-info] -version = "2.11.1" -features = [ - "derive", - "serde", -] -default-features = false - -[dependencies.sp-arithmetic] -version = "26.0.0" -default-features = false - -[dependencies.sp-core] -version = "34.0.0" -default-features = false - -[dependencies.sp-io] -version = "37.0.0" -default-features = false - -[dependencies.sp-runtime] -version = "38.0.0" -default-features = false - -[dependencies.sp-std] -version = "14.0.0" -default-features = false - -[dependencies.sp-weights] -version = "31.0.0" -default-features = false - -[dependencies.xcm] -version = "14.0.0" -default-features = false -package = "staging-xcm" - -[features] -default = ["std"] -runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", -] -std = [ - "codec/std", - "environmental/std", - "frame-benchmarking/std", - "frame-support/std", - "log/std", - "scale-info/std", - "sp-arithmetic/std", - "sp-core/std", - "sp-io/std", - "sp-runtime/std", - "sp-std/std", - "sp-weights/std", - "xcm/std", -] - -[lints.clippy.all] -level = "allow" -priority = 0 - -[lints.clippy.bind_instead_of_map] -level = "allow" -priority = 2 - -[lints.clippy.borrowed-box] -level = "allow" -priority = 2 - -[lints.clippy.complexity] -level = "warn" -priority = 1 - -[lints.clippy.correctness] -level = "warn" -priority = 1 - -[lints.clippy.default_constructed_unit_structs] -level = "allow" -priority = 2 - -[lints.clippy.derivable_impls] -level = "allow" -priority = 2 - -[lints.clippy.eq_op] -level = "allow" -priority = 2 - -[lints.clippy.erasing_op] -level = "allow" -priority = 2 - -[lints.clippy.extra-unused-type-parameters] -level = "allow" -priority = 2 - -[lints.clippy.identity-op] -level = "allow" -priority = 2 - -[lints.clippy.if-same-then-else] -level = "allow" -priority = 2 - -[lints.clippy.needless-lifetimes] -level = "allow" -priority = 2 - -[lints.clippy.needless_option_as_deref] -level = "allow" -priority = 2 - -[lints.clippy.nonminimal-bool] -level = "allow" -priority = 2 - -[lints.clippy.option-map-unit-fn] -level = "allow" -priority = 2 - -[lints.clippy.stable_sort_primitive] -level = "allow" -priority = 2 - -[lints.clippy.too-many-arguments] -level = "allow" -priority = 2 - -[lints.clippy.type_complexity] -level = "allow" -priority = 2 - -[lints.clippy.unit_arg] -level = "allow" -priority = 2 - -[lints.clippy.unnecessary_cast] -level = "allow" -priority = 2 - -[lints.clippy.useless_conversion] -level = "allow" -priority = 2 - -[lints.clippy.while_immutable_condition] -level = "allow" -priority = 2 - -[lints.clippy.zero-prefixed-literal] -level = "allow" -priority = 2 - -[lints.rust.suspicious_double_ref_op] -level = "allow" -priority = 2 diff --git a/debug-executor/Cargo.toml.orig b/debug-executor/Cargo.toml.orig deleted file mode 100644 index 29917f026..000000000 --- a/debug-executor/Cargo.toml.orig +++ /dev/null @@ -1,49 +0,0 @@ -[package] -name = "staging-xcm-executor" -description = "An abstract and configurable XCM message executor. (polkadot v1.13.0)" -authors.workspace = true -edition.workspace = true -license.workspace = true -version = "14.0.0" - -[lints] -workspace = true - -[dependencies] -impl-trait-for-tuples = "0.2.2" -environmental = { version = "1.1.4", default-features = false } -codec = { package = "parity-scale-codec", version = "3.6.12", default-features = false, features = ["derive"] } -scale-info = { version = "2.11.1", default-features = false, features = ["derive", "serde"] } -xcm = { package = "staging-xcm", path = "..", default-features = false, version = "14.0.0" } -sp-std = { path = "../../../substrate/primitives/std", default-features = false, version = "14.0.0" } -sp-io = { path = "../../../substrate/primitives/io", default-features = false, version = "37.0.0" } -sp-arithmetic = { path = "../../../substrate/primitives/arithmetic", default-features = false, version = "26.0.0" } -sp-core = { path = "../../../substrate/primitives/core", default-features = false, version = "34.0.0" } -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false, version = "38.0.0" } -sp-weights = { path = "../../../substrate/primitives/weights", default-features = false, version = "31.0.0" } -frame-support = { path = "../../../substrate/frame/support", default-features = false, version = "35.0.0" } -log = { workspace = true } -frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true, version = "35.0.0" } - -[features] -default = ["std"] -runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", -] -std = [ - "codec/std", - "environmental/std", - "frame-benchmarking/std", - "frame-support/std", - "log/std", - "scale-info/std", - "sp-arithmetic/std", - "sp-core/std", - "sp-io/std", - "sp-runtime/std", - "sp-std/std", - "sp-weights/std", - "xcm/std", -] diff --git a/debug-executor/src/assets.rs b/debug-executor/src/assets.rs deleted file mode 100644 index 96906b712..000000000 --- a/debug-executor/src/assets.rs +++ /dev/null @@ -1,695 +0,0 @@ -// Copyright (C) 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 . - -use sp_runtime::{traits::Saturating, RuntimeDebug}; -use sp_std::{ - collections::{btree_map::BTreeMap, btree_set::BTreeSet}, - mem, - prelude::*, -}; -use xcm::latest::{ - Asset, AssetFilter, AssetId, AssetInstance, Assets, - Fungibility::{Fungible, NonFungible}, - InteriorLocation, Location, Reanchorable, - WildAsset::{All, AllCounted, AllOf, AllOfCounted}, - WildFungibility::{Fungible as WildFungible, NonFungible as WildNonFungible}, -}; - -/// Map of non-wildcard fungible and non-fungible assets held in the holding register. -#[derive(Default, Clone, RuntimeDebug, Eq, PartialEq)] -pub struct AssetsInHolding { - /// The fungible assets. - pub fungible: BTreeMap, - - /// The non-fungible assets. - // TODO: Consider BTreeMap> - // or even BTreeMap> - pub non_fungible: BTreeSet<(AssetId, AssetInstance)>, -} - -impl From for AssetsInHolding { - fn from(asset: Asset) -> AssetsInHolding { - let mut result = Self::default(); - result.subsume(asset); - result - } -} - -impl From> for AssetsInHolding { - fn from(assets: Vec) -> AssetsInHolding { - let mut result = Self::default(); - for asset in assets.into_iter() { - result.subsume(asset) - } - result - } -} - -impl From for AssetsInHolding { - fn from(assets: Assets) -> AssetsInHolding { - assets.into_inner().into() - } -} - -impl From for Vec { - fn from(a: AssetsInHolding) -> Self { - a.into_assets_iter().collect() - } -} - -impl From for Assets { - fn from(a: AssetsInHolding) -> Self { - a.into_assets_iter().collect::>().into() - } -} - -/// An error emitted by `take` operations. -#[derive(Debug)] -pub enum TakeError { - /// There was an attempt to take an asset without saturating (enough of) which did not exist. - AssetUnderflow(Asset), -} - -impl AssetsInHolding { - /// New value, containing no assets. - pub fn new() -> Self { - Self::default() - } - - /// Total number of distinct assets. - pub fn len(&self) -> usize { - self.fungible.len() + self.non_fungible.len() - } - - /// Returns `true` if `self` contains no assets. - pub fn is_empty(&self) -> bool { - self.fungible.is_empty() && self.non_fungible.is_empty() - } - - /// A borrowing iterator over the fungible assets. - pub fn fungible_assets_iter(&self) -> impl Iterator + '_ { - self.fungible.iter().map(|(id, &amount)| Asset { fun: Fungible(amount), id: id.clone() }) - } - - /// A borrowing iterator over the non-fungible assets. - pub fn non_fungible_assets_iter(&self) -> impl Iterator + '_ { - self.non_fungible.iter().map(|(id, instance)| Asset { fun: NonFungible(*instance), id: id.clone() }) - } - - /// A consuming iterator over all assets. - pub fn into_assets_iter(self) -> impl Iterator { - self.fungible - .into_iter() - .map(|(id, amount)| Asset { fun: Fungible(amount), id }) - .chain(self.non_fungible.into_iter().map(|(id, instance)| Asset { fun: NonFungible(instance), id })) - } - - /// A borrowing iterator over all assets. - pub fn assets_iter(&self) -> impl Iterator + '_ { - self.fungible_assets_iter().chain(self.non_fungible_assets_iter()) - } - - /// Mutate `self` to contain all given `assets`, saturating if necessary. - /// - /// NOTE: [`AssetsInHolding`] are always sorted, allowing us to optimize this function from - /// `O(n^2)` to `O(n)`. - pub fn subsume_assets(&mut self, mut assets: AssetsInHolding) { - let mut f_iter = assets.fungible.iter_mut(); - let mut g_iter = self.fungible.iter_mut(); - if let (Some(mut f), Some(mut g)) = (f_iter.next(), g_iter.next()) { - loop { - if f.0 == g.0 { - // keys are equal. in this case, we add `self`'s balance for the asset onto - // `assets`, balance, knowing that the `append` operation which follows will - // clobber `self`'s value and only use `assets`'s. - (*f.1).saturating_accrue(*g.1); - } - if f.0 <= g.0 { - f = match f_iter.next() { - Some(x) => x, - None => break, - }; - } - if f.0 >= g.0 { - g = match g_iter.next() { - Some(x) => x, - None => break, - }; - } - } - } - self.fungible.append(&mut assets.fungible); - self.non_fungible.append(&mut assets.non_fungible); - } - - /// Mutate `self` to contain the given `asset`, saturating if necessary. - /// - /// Wildcard values of `asset` do nothing. - pub fn subsume(&mut self, asset: Asset) { - match asset.fun { - Fungible(amount) => { - self.fungible.entry(asset.id).and_modify(|e| *e = e.saturating_add(amount)).or_insert(amount); - }, - NonFungible(instance) => { - self.non_fungible.insert((asset.id, instance)); - }, - } - } - - /// Swaps two mutable AssetsInHolding, without deinitializing either one. - pub fn swapped(&mut self, mut with: AssetsInHolding) -> Self { - mem::swap(&mut *self, &mut with); - with - } - - /// Alter any concretely identified assets by prepending the given `Location`. - /// - /// WARNING: For now we consider this infallible and swallow any errors. It is thus the caller's - /// responsibility to ensure that any internal asset IDs are able to be prepended without - /// overflow. - pub fn prepend_location(&mut self, prepend: &Location) { - let mut fungible = Default::default(); - mem::swap(&mut self.fungible, &mut fungible); - self.fungible = fungible - .into_iter() - .map(|(mut id, amount)| { - let _ = id.prepend_with(prepend); - (id, amount) - }) - .collect(); - let mut non_fungible = Default::default(); - mem::swap(&mut self.non_fungible, &mut non_fungible); - self.non_fungible = non_fungible - .into_iter() - .map(|(mut class, inst)| { - let _ = class.prepend_with(prepend); - (class, inst) - }) - .collect(); - } - - /// Mutate the assets to be interpreted as the same assets from the perspective of a `target` - /// chain. The local chain's `context` is provided. - /// - /// Any assets which were unable to be reanchored are introduced into `failed_bin`. - pub fn reanchor(&mut self, target: &Location, context: &InteriorLocation, mut maybe_failed_bin: Option<&mut Self>) { - let mut fungible = Default::default(); - mem::swap(&mut self.fungible, &mut fungible); - self.fungible = fungible - .into_iter() - .filter_map(|(mut id, amount)| match id.reanchor(target, context) { - Ok(()) => Some((id, amount)), - Err(()) => { - maybe_failed_bin.as_mut().map(|f| f.fungible.insert(id, amount)); - None - }, - }) - .collect(); - let mut non_fungible = Default::default(); - mem::swap(&mut self.non_fungible, &mut non_fungible); - self.non_fungible = non_fungible - .into_iter() - .filter_map(|(mut class, inst)| match class.reanchor(target, context) { - Ok(()) => Some((class, inst)), - Err(()) => { - maybe_failed_bin.as_mut().map(|f| f.non_fungible.insert((class, inst))); - None - }, - }) - .collect(); - } - - /// Returns `true` if `asset` is contained within `self`. - pub fn contains_asset(&self, asset: &Asset) -> bool { - match asset { - Asset { fun: Fungible(amount), id } => self.fungible.get(id).map_or(false, |a| a >= amount), - Asset { fun: NonFungible(instance), id } => self.non_fungible.contains(&(id.clone(), *instance)), - } - } - - /// Returns `true` if all `assets` are contained within `self`. - pub fn contains_assets(&self, assets: &Assets) -> bool { - assets.inner().iter().all(|a| self.contains_asset(a)) - } - - /// Returns `true` if all `assets` are contained within `self`. - pub fn contains(&self, assets: &AssetsInHolding) -> bool { - assets.fungible.iter().all(|(k, v)| self.fungible.get(k).map_or(false, |a| a >= v)) && - self.non_fungible.is_superset(&assets.non_fungible) - } - - /// Returns an error unless all `assets` are contained in `self`. In the case of an error, the - /// first asset in `assets` which is not wholly in `self` is returned. - pub fn ensure_contains(&self, assets: &Assets) -> Result<(), TakeError> { - for asset in assets.inner().iter() { - match asset { - Asset { fun: Fungible(amount), id } => - if self.fungible.get(id).map_or(true, |a| a < amount) { - return Err(TakeError::AssetUnderflow((id.clone(), *amount).into())) - }, - Asset { fun: NonFungible(instance), id } => { - let id_instance = (id.clone(), *instance); - if !self.non_fungible.contains(&id_instance) { - return Err(TakeError::AssetUnderflow(id_instance.into())) - } - }, - } - } - return Ok(()) - } - - /// Mutates `self` to its original value less `mask` and returns assets that were removed. - /// - /// If `saturate` is `true`, then `self` is considered to be masked by `mask`, thereby avoiding - /// any attempt at reducing it by assets it does not contain. In this case, the function is - /// infallible. If `saturate` is `false` and `mask` references a definite asset which `self` - /// does not contain then an error is returned. - /// - /// The number of unique assets which are removed will respect the `count` parameter in the - /// counted wildcard variants. - /// - /// Returns `Ok` with the definite assets token from `self` and mutates `self` to its value - /// minus `mask`. Returns `Err` in the non-saturating case where `self` did not contain (enough - /// of) a definite asset to be removed. - fn general_take(&mut self, mask: AssetFilter, saturate: bool) -> Result { - let mut taken = AssetsInHolding::new(); - let maybe_limit = mask.limit().map(|x| x as usize); - match mask { - // TODO: Counted variants where we define `limit`. - AssetFilter::Wild(All) | AssetFilter::Wild(AllCounted(_)) => { - if maybe_limit.map_or(true, |l| self.len() <= l) { - return Ok(self.swapped(AssetsInHolding::new())) - } else { - let fungible = mem::replace(&mut self.fungible, Default::default()); - fungible.into_iter().for_each(|(c, amount)| { - if maybe_limit.map_or(true, |l| taken.len() < l) { - taken.fungible.insert(c, amount); - } else { - self.fungible.insert(c, amount); - } - }); - let non_fungible = mem::replace(&mut self.non_fungible, Default::default()); - non_fungible.into_iter().for_each(|(c, instance)| { - if maybe_limit.map_or(true, |l| taken.len() < l) { - taken.non_fungible.insert((c, instance)); - } else { - self.non_fungible.insert((c, instance)); - } - }); - } - }, - AssetFilter::Wild(AllOfCounted { fun: WildFungible, id, .. }) | - AssetFilter::Wild(AllOf { fun: WildFungible, id }) => - if maybe_limit.map_or(true, |l| l >= 1) { - if let Some((id, amount)) = self.fungible.remove_entry(&id) { - taken.fungible.insert(id, amount); - } - }, - AssetFilter::Wild(AllOfCounted { fun: WildNonFungible, id, .. }) | - AssetFilter::Wild(AllOf { fun: WildNonFungible, id }) => { - let non_fungible = mem::replace(&mut self.non_fungible, Default::default()); - non_fungible.into_iter().for_each(|(c, instance)| { - if c == id && maybe_limit.map_or(true, |l| taken.len() < l) { - taken.non_fungible.insert((c, instance)); - } else { - self.non_fungible.insert((c, instance)); - } - }); - }, - AssetFilter::Definite(assets) => { - if !saturate { - self.ensure_contains(&assets)?; - } - for asset in assets.into_inner().into_iter() { - match asset { - Asset { fun: Fungible(amount), id } => { - let (remove, amount) = match self.fungible.get_mut(&id) { - Some(self_amount) => { - let amount = amount.min(*self_amount); - *self_amount -= amount; - (*self_amount == 0, amount) - }, - None => (false, 0), - }; - if remove { - self.fungible.remove(&id); - } - if amount > 0 { - taken.subsume(Asset::from((id, amount)).into()); - } - }, - Asset { fun: NonFungible(instance), id } => { - let id_instance = (id, instance); - if self.non_fungible.remove(&id_instance) { - taken.subsume(id_instance.into()) - } - }, - } - } - }, - } - Ok(taken) - } - - /// Mutates `self` to its original value less `mask` and returns `true` iff it contains at least - /// `mask`. - /// - /// Returns `Ok` with the non-wildcard equivalence of `mask` taken and mutates `self` to its - /// value minus `mask` if `self` contains `asset`, and return `Err` otherwise. - pub fn saturating_take(&mut self, asset: AssetFilter) -> AssetsInHolding { - self.general_take(asset, true).expect("general_take never results in error when saturating") - } - - /// Mutates `self` to its original value less `mask` and returns `true` iff it contains at least - /// `mask`. - /// - /// Returns `Ok` with the non-wildcard equivalence of `asset` taken and mutates `self` to its - /// value minus `asset` if `self` contains `asset`, and return `Err` otherwise. - pub fn try_take(&mut self, mask: AssetFilter) -> Result { - self.general_take(mask, false) - } - - /// Consumes `self` and returns its original value excluding `asset` iff it contains at least - /// `asset`. - pub fn checked_sub(mut self, asset: Asset) -> Result { - match asset.fun { - Fungible(amount) => { - let remove = if let Some(balance) = self.fungible.get_mut(&asset.id) { - if *balance >= amount { - *balance -= amount; - *balance == 0 - } else { - return Err(self) - } - } else { - return Err(self) - }; - if remove { - self.fungible.remove(&asset.id); - } - Ok(self) - }, - NonFungible(instance) => - if self.non_fungible.remove(&(asset.id, instance)) { - Ok(self) - } else { - Err(self) - }, - } - } - - /// Return the assets in `self`, but (asset-wise) of no greater value than `mask`. - /// - /// The number of unique assets which are returned will respect the `count` parameter in the - /// counted wildcard variants of `mask`. - /// - /// Example: - /// - /// ``` - /// use staging_xcm_executor::AssetsInHolding; - /// use xcm::latest::prelude::*; - /// let assets_i_have: AssetsInHolding = vec![ (Here, 100).into(), (Junctions::from([GeneralIndex(0)]), 100).into() ].into(); - /// let assets_they_want: AssetFilter = vec![ (Here, 200).into(), (Junctions::from([GeneralIndex(0)]), 50).into() ].into(); - /// - /// let assets_we_can_trade: AssetsInHolding = assets_i_have.min(&assets_they_want); - /// assert_eq!(assets_we_can_trade.into_assets_iter().collect::>(), vec![ - /// (Here, 100).into(), (Junctions::from([GeneralIndex(0)]), 50).into(), - /// ]); - /// ``` - pub fn min(&self, mask: &AssetFilter) -> AssetsInHolding { - let mut masked = AssetsInHolding::new(); - let maybe_limit = mask.limit().map(|x| x as usize); - if maybe_limit.map_or(false, |l| l == 0) { - return masked - } - match mask { - AssetFilter::Wild(All) | AssetFilter::Wild(AllCounted(_)) => { - if maybe_limit.map_or(true, |l| self.len() <= l) { - return self.clone() - } else { - for (c, &amount) in self.fungible.iter() { - masked.fungible.insert(c.clone(), amount); - if maybe_limit.map_or(false, |l| masked.len() >= l) { - return masked - } - } - for (c, instance) in self.non_fungible.iter() { - masked.non_fungible.insert((c.clone(), *instance)); - if maybe_limit.map_or(false, |l| masked.len() >= l) { - return masked - } - } - } - }, - AssetFilter::Wild(AllOfCounted { fun: WildFungible, id, .. }) | - AssetFilter::Wild(AllOf { fun: WildFungible, id }) => - if let Some(&amount) = self.fungible.get(&id) { - masked.fungible.insert(id.clone(), amount); - }, - AssetFilter::Wild(AllOfCounted { fun: WildNonFungible, id, .. }) | - AssetFilter::Wild(AllOf { fun: WildNonFungible, id }) => - for (c, instance) in self.non_fungible.iter() { - if c == id { - masked.non_fungible.insert((c.clone(), *instance)); - if maybe_limit.map_or(false, |l| masked.len() >= l) { - return masked - } - } - }, - AssetFilter::Definite(assets) => - for asset in assets.inner().iter() { - match asset { - Asset { fun: Fungible(amount), id } => - if let Some(m) = self.fungible.get(id) { - masked.subsume((id.clone(), Fungible(*amount.min(m))).into()); - }, - Asset { fun: NonFungible(instance), id } => { - let id_instance = (id.clone(), *instance); - if self.non_fungible.contains(&id_instance) { - masked.subsume(id_instance.into()); - } - }, - } - }, - } - masked - } -} - -#[cfg(test)] -mod tests { - use super::*; - use xcm::latest::prelude::*; - #[allow(non_snake_case)] - /// Concrete fungible constructor - fn CF(amount: u128) -> Asset { - (Here, amount).into() - } - #[allow(non_snake_case)] - /// Concrete non-fungible constructor - fn CNF(instance_id: u8) -> Asset { - (Here, [instance_id; 4]).into() - } - - fn test_assets() -> AssetsInHolding { - let mut assets = AssetsInHolding::new(); - assets.subsume(CF(300)); - assets.subsume(CNF(40)); - assets - } - - #[test] - fn subsume_assets_works() { - let t1 = test_assets(); - let mut t2 = AssetsInHolding::new(); - t2.subsume(CF(300)); - t2.subsume(CNF(50)); - let mut r1 = t1.clone(); - r1.subsume_assets(t2.clone()); - let mut r2 = t1.clone(); - for a in t2.assets_iter() { - r2.subsume(a) - } - assert_eq!(r1, r2); - } - - #[test] - fn checked_sub_works() { - let t = test_assets(); - let t = t.checked_sub(CF(150)).unwrap(); - let t = t.checked_sub(CF(151)).unwrap_err(); - let t = t.checked_sub(CF(150)).unwrap(); - let t = t.checked_sub(CF(1)).unwrap_err(); - let t = t.checked_sub(CNF(41)).unwrap_err(); - let t = t.checked_sub(CNF(40)).unwrap(); - let t = t.checked_sub(CNF(40)).unwrap_err(); - assert_eq!(t, AssetsInHolding::new()); - } - - #[test] - fn into_assets_iter_works() { - let assets = test_assets(); - let mut iter = assets.into_assets_iter(); - // Order defined by implementation: CF, CNF - assert_eq!(Some(CF(300)), iter.next()); - assert_eq!(Some(CNF(40)), iter.next()); - assert_eq!(None, iter.next()); - } - - #[test] - fn assets_into_works() { - let mut assets_vec: Vec = Vec::new(); - assets_vec.push(CF(300)); - assets_vec.push(CNF(40)); - // Push same group of tokens again - assets_vec.push(CF(300)); - assets_vec.push(CNF(40)); - - let assets: AssetsInHolding = assets_vec.into(); - let mut iter = assets.into_assets_iter(); - // Fungibles add - assert_eq!(Some(CF(600)), iter.next()); - // Non-fungibles collapse - assert_eq!(Some(CNF(40)), iter.next()); - assert_eq!(None, iter.next()); - } - - #[test] - fn min_all_and_none_works() { - let assets = test_assets(); - let none = Assets::new().into(); - let all = All.into(); - - let none_min = assets.min(&none); - assert_eq!(None, none_min.assets_iter().next()); - let all_min = assets.min(&all); - assert!(all_min.assets_iter().eq(assets.assets_iter())); - } - - #[test] - fn min_counted_works() { - let mut assets = AssetsInHolding::new(); - assets.subsume(CNF(40)); - assets.subsume(CF(3000)); - assets.subsume(CNF(80)); - let all = WildAsset::AllCounted(6).into(); - - let all = assets.min(&all); - let all = all.assets_iter().collect::>(); - assert_eq!(all, vec![CF(3000), CNF(40), CNF(80)]); - } - - #[test] - fn min_all_concrete_works() { - let assets = test_assets(); - let fungible = Wild((Here, WildFungible).into()); - let non_fungible = Wild((Here, WildNonFungible).into()); - - let fungible = assets.min(&fungible); - let fungible = fungible.assets_iter().collect::>(); - assert_eq!(fungible, vec![CF(300)]); - let non_fungible = assets.min(&non_fungible); - let non_fungible = non_fungible.assets_iter().collect::>(); - assert_eq!(non_fungible, vec![CNF(40)]); - } - - #[test] - fn min_basic_works() { - let assets1 = test_assets(); - - let mut assets2 = AssetsInHolding::new(); - // This is more then 300, so it should stay at 300 - assets2.subsume(CF(600)); - // This asset should be included - assets2.subsume(CNF(40)); - let assets2: Assets = assets2.into(); - - let assets_min = assets1.min(&assets2.into()); - let assets_min = assets_min.into_assets_iter().collect::>(); - assert_eq!(assets_min, vec![CF(300), CNF(40)]); - } - - #[test] - fn saturating_take_all_and_none_works() { - let mut assets = test_assets(); - - let taken_none = assets.saturating_take(vec![].into()); - assert_eq!(None, taken_none.assets_iter().next()); - let taken_all = assets.saturating_take(All.into()); - // Everything taken - assert_eq!(None, assets.assets_iter().next()); - let all_iter = taken_all.assets_iter(); - assert!(all_iter.eq(test_assets().assets_iter())); - } - - #[test] - fn saturating_take_all_concrete_works() { - let mut assets = test_assets(); - let fungible = Wild((Here, WildFungible).into()); - let non_fungible = Wild((Here, WildNonFungible).into()); - - let fungible = assets.saturating_take(fungible); - let fungible = fungible.assets_iter().collect::>(); - assert_eq!(fungible, vec![CF(300)]); - let non_fungible = assets.saturating_take(non_fungible); - let non_fungible = non_fungible.assets_iter().collect::>(); - assert_eq!(non_fungible, vec![CNF(40)]); - } - - #[test] - fn saturating_take_basic_works() { - let mut assets1 = test_assets(); - - let mut assets2 = AssetsInHolding::new(); - // This is more then 300, so it takes everything - assets2.subsume(CF(600)); - // This asset should be taken - assets2.subsume(CNF(40)); - let assets2: Assets = assets2.into(); - - let taken = assets1.saturating_take(assets2.into()); - let taken = taken.into_assets_iter().collect::>(); - assert_eq!(taken, vec![CF(300), CNF(40)]); - } - - #[test] - fn try_take_all_counted_works() { - let mut assets = AssetsInHolding::new(); - assets.subsume(CNF(40)); - assets.subsume(CF(3000)); - assets.subsume(CNF(80)); - let all = assets.try_take(WildAsset::AllCounted(6).into()).unwrap(); - assert_eq!(Assets::from(all).inner(), &vec![CF(3000), CNF(40), CNF(80)]); - } - - #[test] - fn try_take_fungibles_counted_works() { - let mut assets = AssetsInHolding::new(); - assets.subsume(CNF(40)); - assets.subsume(CF(3000)); - assets.subsume(CNF(80)); - assert_eq!(Assets::from(assets).inner(), &vec![CF(3000), CNF(40), CNF(80),]); - } - - #[test] - fn try_take_non_fungibles_counted_works() { - let mut assets = AssetsInHolding::new(); - assets.subsume(CNF(40)); - assets.subsume(CF(3000)); - assets.subsume(CNF(80)); - assert_eq!(Assets::from(assets).inner(), &vec![CF(3000), CNF(40), CNF(80)]); - } -} diff --git a/debug-executor/src/config.rs b/debug-executor/src/config.rs deleted file mode 100644 index 30d30cc7f..000000000 --- a/debug-executor/src/config.rs +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (C) 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 . - -use crate::traits::{ - AssetExchange, AssetLock, CallDispatcher, ClaimAssets, ConvertOrigin, DropAssets, ExportXcm, FeeManager, - HandleHrmpChannelAccepted, HandleHrmpChannelClosing, HandleHrmpNewChannelOpenRequest, OnResponse, - ProcessTransaction, RecordXcm, ShouldExecute, TransactAsset, VersionChangeNotifier, WeightBounds, WeightTrader, -}; -use frame_support::{ - dispatch::{GetDispatchInfo, Parameter, PostDispatchInfo}, - traits::{Contains, ContainsPair, Get, PalletsInfoAccess}, -}; -use sp_runtime::traits::Dispatchable; -use xcm::prelude::*; - -/// The trait to parameterize the `XcmExecutor`. -pub trait Config { - /// The outer call dispatch type. - type RuntimeCall: Parameter + Dispatchable + GetDispatchInfo; - - /// How to send an onward XCM message. - type XcmSender: SendXcm; - - /// How to withdraw and deposit an asset. - type AssetTransactor: TransactAsset; - - /// How to get a call origin from a `OriginKind` value. - type OriginConverter: ConvertOrigin<::RuntimeOrigin>; - - /// Combinations of (Asset, Location) pairs which we trust as reserves. - type IsReserve: ContainsPair; - - /// Combinations of (Asset, Location) pairs which we trust as teleporters. - type IsTeleporter: ContainsPair; - - /// A list of (Origin, Target) pairs allowing a given Origin to be substituted with its - /// corresponding Target pair. - type Aliasers: ContainsPair; - - /// This chain's Universal Location. - type UniversalLocation: Get; - - /// Whether we should execute the given XCM at all. - type Barrier: ShouldExecute; - - /// The means of determining an XCM message's weight. - type Weigher: WeightBounds; - - /// The means of purchasing weight credit for XCM execution. - type Trader: WeightTrader; - - /// What to do when a response of a query is found. - type ResponseHandler: OnResponse; - - /// The general asset trap - handler for when assets are left in the Holding Register at the - /// end of execution. - type AssetTrap: DropAssets; - - /// Handler for asset locking. - type AssetLocker: AssetLock; - - /// Handler for exchanging assets. - type AssetExchanger: AssetExchange; - - /// The handler for when there is an instruction to claim assets. - type AssetClaims: ClaimAssets; - - /// How we handle version subscription requests. - type SubscriptionService: VersionChangeNotifier; - - /// Information on all pallets. - type PalletInstancesInfo: PalletsInfoAccess; - - /// The maximum number of assets we target to have in the Holding Register at any one time. - /// - /// NOTE: In the worse case, the Holding Register may contain up to twice as many assets as this - /// and any benchmarks should take that into account. - type MaxAssetsIntoHolding: Get; - - /// Configure the fees. - type FeeManager: FeeManager; - - /// The method of exporting a message. - type MessageExporter: ExportXcm; - - /// The origin locations and specific universal junctions to which they are allowed to elevate - /// themselves. - type UniversalAliases: Contains<(Location, Junction)>; - - /// The call dispatcher used by XCM. - /// - /// XCM will use this to dispatch any calls. When no special call dispatcher is required, - /// this can be set to the same type as `Self::Call`. - type CallDispatcher: CallDispatcher; - - /// The safe call filter for `Transact`. - /// - /// Use this type to explicitly whitelist calls that cannot undergo recursion. This is a - /// temporary measure until we properly account for proof size weights for XCM instructions. - type SafeCallFilter: Contains; - - /// Transactional processor for XCM instructions. - type TransactionalProcessor: ProcessTransaction; - - /// Allows optional logic execution for the `HrmpNewChannelOpenRequest` XCM notification. - type HrmpNewChannelOpenRequestHandler: HandleHrmpNewChannelOpenRequest; - /// Allows optional logic execution for the `HrmpChannelAccepted` XCM notification. - type HrmpChannelAcceptedHandler: HandleHrmpChannelAccepted; - /// Allows optional logic execution for the `HrmpChannelClosing` XCM notification. - type HrmpChannelClosingHandler: HandleHrmpChannelClosing; - /// Allows recording the last executed XCM (used by dry-run runtime APIs). - type XcmRecorder: RecordXcm; -} diff --git a/debug-executor/src/lib.rs b/debug-executor/src/lib.rs deleted file mode 100644 index 0330f843d..000000000 --- a/debug-executor/src/lib.rs +++ /dev/null @@ -1,1188 +0,0 @@ -// Copyright (C) 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 . - -#![cfg_attr(not(feature = "std"), no_std)] - -use codec::{Decode, Encode}; -use frame_support::{ - dispatch::GetDispatchInfo, - ensure, - traits::{Contains, ContainsPair, Defensive, Get, PalletsInfoAccess}, -}; -use sp_core::defer; -use sp_io::hashing::blake2_128; -use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; -use sp_weights::Weight; -use xcm::latest::prelude::*; - -pub mod traits; -use traits::{ - validate_export, AssetExchange, AssetLock, CallDispatcher, ClaimAssets, ConvertOrigin, DropAssets, Enact, - ExportXcm, FeeManager, FeeReason, HandleHrmpChannelAccepted, HandleHrmpChannelClosing, - HandleHrmpNewChannelOpenRequest, OnResponse, ProcessTransaction, Properties, ShouldExecute, TransactAsset, - VersionChangeNotifier, WeightBounds, WeightTrader, XcmAssetTransfers, -}; - -pub use traits::RecordXcm; - -mod assets; -pub use assets::AssetsInHolding; -mod config; -pub use config::Config; - -/// A struct to specify how fees are being paid. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct FeesMode { - /// If true, then the fee assets are taken directly from the origin's on-chain account, - /// otherwise the fee assets are taken from the holding register. - /// - /// Defaults to false. - pub jit_withdraw: bool, -} - -const RECURSION_LIMIT: u8 = 10; - -environmental::environmental!(recursion_count: u8); - -/// The XCM executor. -pub struct XcmExecutor { - holding: AssetsInHolding, - holding_limit: usize, - context: XcmContext, - original_origin: Location, - trader: Config::Trader, - /// The most recent error result and instruction index into the fragment in which it occurred, - /// if any. - error: Option<(u32, XcmError)>, - /// The surplus weight, defined as the amount by which `max_weight` is - /// an over-estimate of the actual weight consumed. We do it this way to avoid needing the - /// execution engine to keep track of all instructions' weights (it only needs to care about - /// the weight of dynamically determined instructions such as `Transact`). - total_surplus: Weight, - total_refunded: Weight, - error_handler: Xcm, - error_handler_weight: Weight, - appendix: Xcm, - appendix_weight: Weight, - transact_status: MaybeErrorCode, - fees_mode: FeesMode, - _config: PhantomData, -} - -#[cfg(feature = "runtime-benchmarks")] -impl XcmExecutor { - pub fn holding(&self) -> &AssetsInHolding { - &self.holding - } - - pub fn set_holding(&mut self, v: AssetsInHolding) { - self.holding = v - } - - pub fn holding_limit(&self) -> &usize { - &self.holding_limit - } - - pub fn set_holding_limit(&mut self, v: usize) { - self.holding_limit = v - } - - pub fn origin(&self) -> &Option { - &self.context.origin - } - - pub fn set_origin(&mut self, v: Option) { - self.context.origin = v - } - - pub fn original_origin(&self) -> &Location { - &self.original_origin - } - - pub fn set_original_origin(&mut self, v: Location) { - self.original_origin = v - } - - pub fn trader(&self) -> &Config::Trader { - &self.trader - } - - pub fn set_trader(&mut self, v: Config::Trader) { - self.trader = v - } - - pub fn error(&self) -> &Option<(u32, XcmError)> { - &self.error - } - - pub fn set_error(&mut self, v: Option<(u32, XcmError)>) { - self.error = v - } - - pub fn total_surplus(&self) -> &Weight { - &self.total_surplus - } - - pub fn set_total_surplus(&mut self, v: Weight) { - self.total_surplus = v - } - - pub fn total_refunded(&self) -> &Weight { - &self.total_refunded - } - - pub fn set_total_refunded(&mut self, v: Weight) { - self.total_refunded = v - } - - pub fn error_handler(&self) -> &Xcm { - &self.error_handler - } - - pub fn set_error_handler(&mut self, v: Xcm) { - self.error_handler = v - } - - pub fn error_handler_weight(&self) -> &Weight { - &self.error_handler_weight - } - - pub fn set_error_handler_weight(&mut self, v: Weight) { - self.error_handler_weight = v - } - - pub fn appendix(&self) -> &Xcm { - &self.appendix - } - - pub fn set_appendix(&mut self, v: Xcm) { - self.appendix = v - } - - pub fn appendix_weight(&self) -> &Weight { - &self.appendix_weight - } - - pub fn set_appendix_weight(&mut self, v: Weight) { - self.appendix_weight = v - } - - pub fn transact_status(&self) -> &MaybeErrorCode { - &self.transact_status - } - - pub fn set_transact_status(&mut self, v: MaybeErrorCode) { - self.transact_status = v - } - - pub fn fees_mode(&self) -> &FeesMode { - &self.fees_mode - } - - pub fn set_fees_mode(&mut self, v: FeesMode) { - self.fees_mode = v - } - - pub fn topic(&self) -> &Option<[u8; 32]> { - &self.context.topic - } - - pub fn set_topic(&mut self, v: Option<[u8; 32]>) { - self.context.topic = v; - } -} - -pub struct WeighedMessage(Weight, Xcm); -impl PreparedMessage for WeighedMessage { - fn weight_of(&self) -> Weight { - self.0 - } -} - -#[cfg(any(test, feature = "std"))] -impl WeighedMessage { - pub fn new(weight: Weight, message: Xcm) -> Self { - Self(weight, message) - } -} - -impl ExecuteXcm for XcmExecutor { - type Prepared = WeighedMessage; - - fn prepare(mut message: Xcm) -> Result> { - match Config::Weigher::weight(&mut message) { - Ok(weight) => Ok(WeighedMessage(weight, message)), - Err(_) => Err(message), - } - } - - fn execute( - origin: impl Into, - WeighedMessage(xcm_weight, mut message): WeighedMessage, - id: &mut XcmHash, - weight_credit: Weight, - ) -> Outcome { - let origin = origin.into(); - log::trace!( - target: "xcm::execute", - "origin: {origin:?}, message: {message:?}, weight_credit: {weight_credit:?}", - ); - let mut properties = Properties { weight_credit, message_id: None }; - - // We only want to record under certain conditions (mainly only during dry-running), - // so as to not degrade regular performance. - if Config::XcmRecorder::should_record() { - Config::XcmRecorder::record(message.clone().into()); - } - - if let Err(e) = Config::Barrier::should_execute(&origin, message.inner_mut(), xcm_weight, &mut properties) { - log::trace!( - target: "xcm::execute", - "Barrier blocked execution! Error: {e:?}. \ - (origin: {origin:?}, message: {message:?}, properties: {properties:?})", - ); - return Outcome::Error { error: XcmError::Barrier } - } - - *id = properties.message_id.unwrap_or(*id); - - let mut vm = Self::new(origin, *id); - - while !message.0.is_empty() { - let result = vm.process(message); - log::trace!(target: "xcm::execute", "result: {result:?}"); - message = if let Err(error) = result { - vm.total_surplus.saturating_accrue(error.weight); - vm.error = Some((error.index, error.xcm_error)); - vm.take_error_handler().or_else(|| vm.take_appendix()) - } else { - vm.drop_error_handler(); - vm.take_appendix() - } - } - - vm.post_process(xcm_weight) - } - - fn charge_fees(origin: impl Into, fees: Assets) -> XcmResult { - let origin = origin.into(); - if !Config::FeeManager::is_waived(Some(&origin), FeeReason::ChargeFees) { - for asset in fees.inner() { - Config::AssetTransactor::withdraw_asset(&asset, &origin, None)?; - } - Config::FeeManager::handle_fee(fees, None, FeeReason::ChargeFees); - } - Ok(()) - } -} - -impl XcmAssetTransfers for XcmExecutor { - type AssetTransactor = Config::AssetTransactor; - type IsReserve = Config::IsReserve; - type IsTeleporter = Config::IsTeleporter; -} - -#[derive(Debug)] -pub struct ExecutorError { - pub index: u32, - pub xcm_error: XcmError, - pub weight: Weight, -} - -#[cfg(feature = "runtime-benchmarks")] -impl From for frame_benchmarking::BenchmarkError { - fn from(error: ExecutorError) -> Self { - log::error!("XCM ERROR >> Index: {:?}, Error: {:?}, Weight: {:?}", error.index, error.xcm_error, error.weight); - Self::Stop("xcm executor error: see error logs") - } -} - -impl XcmExecutor { - pub fn new(origin: impl Into, message_id: XcmHash) -> Self { - let origin = origin.into(); - Self { - holding: AssetsInHolding::new(), - holding_limit: Config::MaxAssetsIntoHolding::get() as usize, - context: XcmContext { origin: Some(origin.clone()), message_id, topic: None }, - original_origin: origin, - trader: Config::Trader::new(), - error: None, - total_surplus: Weight::zero(), - total_refunded: Weight::zero(), - error_handler: Xcm(vec![]), - error_handler_weight: Weight::zero(), - appendix: Xcm(vec![]), - appendix_weight: Weight::zero(), - transact_status: Default::default(), - fees_mode: FeesMode { jit_withdraw: false }, - _config: PhantomData, - } - } - - /// Execute any final operations after having executed the XCM message. - /// This includes refunding surplus weight, trapping extra holding funds, and returning any - /// errors during execution. - pub fn post_process(mut self, xcm_weight: Weight) -> Outcome { - // We silently drop any error from our attempt to refund the surplus as it's a charitable - // thing so best-effort is all we will do. - let _ = self.refund_surplus(); - drop(self.trader); - - let mut weight_used = xcm_weight.saturating_sub(self.total_surplus); - - if !self.holding.is_empty() { - log::trace!( - target: "xcm::post_process", - "Trapping assets in holding register: {:?}, context: {:?} (original_origin: {:?})", - self.holding, self.context, self.original_origin, - ); - let effective_origin = self.context.origin.as_ref().unwrap_or(&self.original_origin); - let trap_weight = Config::AssetTrap::drop_assets(effective_origin, self.holding, &self.context); - weight_used.saturating_accrue(trap_weight); - }; - - match self.error { - None => Outcome::Complete { used: weight_used }, - // TODO: #2841 #REALWEIGHT We should deduct the cost of any instructions following - // the error which didn't end up being executed. - Some((_i, e)) => { - log::trace!(target: "xcm::post_process", "Execution errored at {:?}: {:?} (original_origin: {:?})", _i, e, self.original_origin); - Outcome::Incomplete { used: weight_used, error: e } - }, - } - } - - fn origin_ref(&self) -> Option<&Location> { - self.context.origin.as_ref() - } - - fn cloned_origin(&self) -> Option { - self.context.origin.clone() - } - - /// Send an XCM, charging fees from Holding as needed. - fn send(&mut self, dest: Location, msg: Xcm<()>, reason: FeeReason) -> Result { - log::trace!( - target: "xcm::send", "Sending msg: {msg:?}, to destination: {dest:?}, (reason: {reason:?})" - ); - let (ticket, fee) = validate_send::(dest, msg)?; - self.take_fee(fee, reason)?; - Config::XcmSender::deliver(ticket).map_err(Into::into) - } - - /// Remove the registered error handler and return it. Do not refund its weight. - fn take_error_handler(&mut self) -> Xcm { - let mut r = Xcm::(vec![]); - sp_std::mem::swap(&mut self.error_handler, &mut r); - self.error_handler_weight = Weight::zero(); - r - } - - /// Drop the registered error handler and refund its weight. - fn drop_error_handler(&mut self) { - self.error_handler = Xcm::(vec![]); - self.total_surplus.saturating_accrue(self.error_handler_weight); - self.error_handler_weight = Weight::zero(); - } - - /// Remove the registered appendix and return it. - fn take_appendix(&mut self) -> Xcm { - let mut r = Xcm::(vec![]); - sp_std::mem::swap(&mut self.appendix, &mut r); - self.appendix_weight = Weight::zero(); - r - } - - fn ensure_can_subsume_assets(&self, assets_length: usize) -> Result<(), XcmError> { - // worst-case, holding.len becomes 2 * holding_limit. - // this guarantees that if holding.len() == holding_limit and you have more than - // `holding_limit` items (which has a best case outcome of holding.len() == holding_limit), - // then the operation is guaranteed to succeed. - let worst_case_holding_len = self.holding.len() + assets_length; - log::trace!(target: "xcm::ensure_can_subsume_assets", "worst_case_holding_len: {:?}, holding_limit: {:?}", worst_case_holding_len, self.holding_limit); - ensure!(worst_case_holding_len <= self.holding_limit * 2, XcmError::HoldingWouldOverflow); - Ok(()) - } - - /// Refund any unused weight. - fn refund_surplus(&mut self) -> Result<(), XcmError> { - let current_surplus = self.total_surplus.saturating_sub(self.total_refunded); - log::trace!( - target: "xcm::refund_surplus", - "total_surplus: {:?}, total_refunded: {:?}, current_surplus: {:?}", - self.total_surplus, - self.total_refunded, - current_surplus, - ); - if current_surplus.any_gt(Weight::zero()) { - if let Some(w) = self.trader.refund_weight(current_surplus, &self.context) { - if !self.holding.contains_asset(&(w.id.clone(), 1).into()) && self.ensure_can_subsume_assets(1).is_err() - { - let _ = self - .trader - .buy_weight(current_surplus, w.into(), &self.context) - .defensive_proof("refund_weight returned an asset capable of buying weight; qed"); - log::error!( - target: "xcm::refund_surplus", - "error: HoldingWouldOverflow", - ); - return Err(XcmError::HoldingWouldOverflow) - } - self.total_refunded.saturating_accrue(current_surplus); - self.holding.subsume_assets(w.into()); - } - } - log::trace!( - target: "xcm::refund_surplus", - "total_refunded: {:?}", - self.total_refunded, - ); - Ok(()) - } - - fn take_fee(&mut self, fee: Assets, reason: FeeReason) -> XcmResult { - if Config::FeeManager::is_waived(self.origin_ref(), reason.clone()) { - return Ok(()) - } - log::trace!( - target: "xcm::fees", - "taking fee: {:?} from origin_ref: {:?} in fees_mode: {:?} for a reason: {:?}", - fee, - self.origin_ref(), - self.fees_mode, - reason, - ); - let paid = if self.fees_mode.jit_withdraw { - let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; - for asset in fee.inner() { - Config::AssetTransactor::withdraw_asset(&asset, origin, Some(&self.context))?; - } - fee - } else { - self.holding.try_take(fee.into()).map_err(|_| XcmError::NotHoldingFees)?.into() - }; - Config::FeeManager::handle_fee(paid, Some(&self.context), reason); - Ok(()) - } - - /// Calculates what `local_querier` would be from the perspective of `destination`. - fn to_querier(local_querier: Option, destination: &Location) -> Result, XcmError> { - Ok(match local_querier { - None => None, - Some(q) => Some( - q.reanchored(&destination, &Config::UniversalLocation::get()).map_err(|_| XcmError::ReanchorFailed)?, - ), - }) - } - - /// Send a bare `QueryResponse` message containing `response` informed by the given `info`. - /// - /// The `local_querier` argument is the querier (if any) specified from the *local* perspective. - fn respond( - &mut self, - local_querier: Option, - response: Response, - info: QueryResponseInfo, - fee_reason: FeeReason, - ) -> Result { - let querier = Self::to_querier(local_querier, &info.destination)?; - let QueryResponseInfo { destination, query_id, max_weight } = info; - let instruction = QueryResponse { query_id, response, max_weight, querier }; - let message = Xcm(vec![instruction]); - self.send(destination, message, fee_reason) - } - - fn try_reanchor( - reanchorable: T, - destination: &Location, - ) -> Result<(T, InteriorLocation), XcmError> { - let reanchor_context = Config::UniversalLocation::get(); - let reanchored = reanchorable.reanchored(&destination, &reanchor_context).map_err(|error| { - log::error!(target: "xcm::reanchor", "Failed reanchoring with error {error:?}"); - XcmError::ReanchorFailed - })?; - Ok((reanchored, reanchor_context)) - } - - /// NOTE: Any assets which were unable to be reanchored are introduced into `failed_bin`. - fn reanchored( - mut assets: AssetsInHolding, - dest: &Location, - maybe_failed_bin: Option<&mut AssetsInHolding>, - ) -> Assets { - let reanchor_context = Config::UniversalLocation::get(); - assets.reanchor(dest, &reanchor_context, maybe_failed_bin); - assets.into_assets_iter().collect::>().into() - } - - #[cfg(feature = "runtime-benchmarks")] - pub fn bench_process(&mut self, xcm: Xcm) -> Result<(), ExecutorError> { - self.process(xcm) - } - - fn process(&mut self, xcm: Xcm) -> Result<(), ExecutorError> { - log::trace!( - target: "xcm::process", - "origin: {:?}, total_surplus/refunded: {:?}/{:?}, error_handler_weight: {:?}", - self.origin_ref(), - self.total_surplus, - self.total_refunded, - self.error_handler_weight, - ); - let mut result = Ok(()); - for (i, instr) in xcm.0.into_iter().enumerate() { - match &mut result { - r @ Ok(()) => { - // Initialize the recursion count only the first time we hit this code in our - // potential recursive execution. - let inst_res = recursion_count::using_once(&mut 1, || { - recursion_count::with(|count| { - if *count > RECURSION_LIMIT { - return Err(XcmError::ExceedsStackLimit) - } - *count = count.saturating_add(1); - Ok(()) - }) - // This should always return `Some`, but let's play it safe. - .unwrap_or(Ok(()))?; - - // Ensure that we always decrement the counter whenever we finish processing - // the instruction. - defer! { - recursion_count::with(|count| { - *count = count.saturating_sub(1); - }); - } - - self.process_instruction(instr) - }); - if let Err(e) = inst_res { - log::trace!(target: "xcm::execute", "!!! ERROR: {:?}", e); - *r = Err(ExecutorError { index: i as u32, xcm_error: e, weight: Weight::zero() }); - } - }, - Err(ref mut error) => - if let Ok(x) = Config::Weigher::instr_weight(&instr) { - error.weight.saturating_accrue(x) - }, - } - } - result - } - - /// Process a single XCM instruction, mutating the state of the XCM virtual machine. - fn process_instruction(&mut self, instr: Instruction) -> Result<(), XcmError> { - log::trace!( - target: "xcm::process_instruction", - "=== {:?}", - instr - ); - match instr { - WithdrawAsset(assets) => { - let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; - self.ensure_can_subsume_assets(assets.len())?; - Config::TransactionalProcessor::process(|| { - // Take `assets` from the origin account (on-chain)... - for asset in assets.inner() { - Config::AssetTransactor::withdraw_asset(asset, origin, Some(&self.context))?; - } - Ok(()) - }) - .and_then(|_| { - // ...and place into holding. - self.holding.subsume_assets(assets.into()); - Ok(()) - }) - }, - ReserveAssetDeposited(assets) => { - // check whether we trust origin to be our reserve location for this asset. - let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; - self.ensure_can_subsume_assets(assets.len())?; - for asset in assets.inner() { - // Must ensure that we recognise the asset as being managed by the origin. - ensure!(Config::IsReserve::contains(asset, origin), XcmError::UntrustedReserveLocation); - } - self.holding.subsume_assets(assets.into()); - Ok(()) - }, - TransferAsset { assets, beneficiary } => { - Config::TransactionalProcessor::process(|| { - // Take `assets` from the origin account (on-chain) and place into dest account. - let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; - for asset in assets.inner() { - Config::AssetTransactor::transfer_asset(&asset, origin, &beneficiary, &self.context)?; - } - Ok(()) - }) - }, - TransferReserveAsset { mut assets, dest, xcm } => { - Config::TransactionalProcessor::process(|| { - let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; - // Take `assets` from the origin account (on-chain) and place into dest account. - for asset in assets.inner() { - Config::AssetTransactor::transfer_asset(asset, origin, &dest, &self.context)?; - } - let reanchor_context = Config::UniversalLocation::get(); - assets.reanchor(&dest, &reanchor_context).map_err(|()| XcmError::LocationFull)?; - let mut message = vec![ReserveAssetDeposited(assets), ClearOrigin]; - message.extend(xcm.0.into_iter()); - self.send(dest, Xcm(message), FeeReason::TransferReserveAsset)?; - Ok(()) - }) - }, - ReceiveTeleportedAsset(assets) => { - let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; - self.ensure_can_subsume_assets(assets.len())?; - Config::TransactionalProcessor::process(|| { - // check whether we trust origin to teleport this asset to us via config trait. - for asset in assets.inner() { - // We only trust the origin to send us assets that they identify as their - // sovereign assets. - ensure!(Config::IsTeleporter::contains(asset, origin), XcmError::UntrustedTeleportLocation); - // We should check that the asset can actually be teleported in (for this to - // be in error, there would need to be an accounting violation by one of the - // trusted chains, so it's unlikely, but we don't want to punish a possibly - // innocent chain/user). - Config::AssetTransactor::can_check_in(origin, asset, &self.context)?; - Config::AssetTransactor::check_in(origin, asset, &self.context); - } - Ok(()) - }) - .and_then(|_| { - self.holding.subsume_assets(assets.into()); - Ok(()) - }) - }, - Transact { origin_kind, require_weight_at_most, mut call } => { - // We assume that the Relay-chain is allowed to use transact on this parachain. - let origin = self.cloned_origin().ok_or_else(|| { - log::trace!( - target: "xcm::process_instruction::transact", - "No origin provided", - ); - - XcmError::BadOrigin - })?; - - // TODO: #2841 #TRANSACTFILTER allow the trait to issue filters for the relay-chain - let message_call = call.take_decoded().map_err(|_| { - log::trace!( - target: "xcm::process_instruction::transact", - "Failed to decode call", - ); - - XcmError::FailedToDecode - })?; - - log::trace!( - target: "xcm::process_instruction::transact", - "Processing call: {message_call:?}", - ); - - if !Config::SafeCallFilter::contains(&message_call) { - log::trace!( - target: "xcm::process_instruction::transact", - "Call filtered by `SafeCallFilter`", - ); - - return Err(XcmError::NoPermission) - } - - let dispatch_origin = - Config::OriginConverter::convert_origin(origin.clone(), origin_kind).map_err(|_| { - log::trace!( - target: "xcm::process_instruction::transact", - "Failed to convert origin {origin:?} and origin kind {origin_kind:?} to a local origin." - ); - - XcmError::BadOrigin - })?; - - log::trace!( - target: "xcm::process_instruction::transact", - "Dispatching with origin: {dispatch_origin:?}", - ); - - let weight = message_call.get_dispatch_info().weight; - - if !weight.all_lte(require_weight_at_most) { - log::trace!( - target: "xcm::process_instruction::transact", - "Max {weight} bigger than require at most {require_weight_at_most}", - ); - - return Err(XcmError::MaxWeightInvalid) - } - - let maybe_actual_weight = match Config::CallDispatcher::dispatch(message_call, dispatch_origin) { - Ok(post_info) => { - log::trace!( - target: "xcm::process_instruction::transact", - "Dispatch successful: {post_info:?}" - ); - self.transact_status = MaybeErrorCode::Success; - post_info.actual_weight - }, - Err(error_and_info) => { - log::trace!( - target: "xcm::process_instruction::transact", - "Dispatch failed {error_and_info:?}" - ); - - self.transact_status = error_and_info.error.encode().into(); - error_and_info.post_info.actual_weight - }, - }; - let actual_weight = maybe_actual_weight.unwrap_or(weight); - let surplus = weight.saturating_sub(actual_weight); - // We assume that the `Config::Weigher` will counts the `require_weight_at_most` - // for the estimate of how much weight this instruction will take. Now that we know - // that it's less, we credit it. - // - // We make the adjustment for the total surplus, which is used eventually - // reported back to the caller and this ensures that they account for the total - // weight consumed correctly (potentially allowing them to do more operations in a - // block than they otherwise would). - self.total_surplus.saturating_accrue(surplus); - Ok(()) - }, - QueryResponse { query_id, response, max_weight, querier } => { - let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; - Config::ResponseHandler::on_response( - origin, - query_id, - querier.as_ref(), - response, - max_weight, - &self.context, - ); - Ok(()) - }, - DescendOrigin(who) => self - .context - .origin - .as_mut() - .ok_or(XcmError::BadOrigin)? - .append_with(who) - .map_err(|_| XcmError::LocationFull), - ClearOrigin => { - self.context.origin = None; - Ok(()) - }, - ReportError(response_info) => { - // Report the given result by sending a QueryResponse XCM to a previously given - // outcome destination if one was registered. - self.respond( - self.cloned_origin(), - Response::ExecutionResult(self.error), - response_info, - FeeReason::Report, - )?; - Ok(()) - }, - DepositAsset { assets, beneficiary } => { - let old_holding = self.holding.clone(); - let result = Config::TransactionalProcessor::process(|| { - let deposited = self.holding.saturating_take(assets); - for asset in deposited.into_assets_iter() { - Config::AssetTransactor::deposit_asset(&asset, &beneficiary, Some(&self.context))?; - } - Ok(()) - }); - if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { - self.holding = old_holding; - } - result - }, - DepositReserveAsset { assets, dest, xcm } => { - let old_holding = self.holding.clone(); - let result = Config::TransactionalProcessor::process(|| { - // we need to do this take/put cycle to solve wildcards and get exact assets to - // be weighed - let to_weigh = self.holding.saturating_take(assets.clone()); - self.holding.subsume_assets(to_weigh.clone()); - let to_weigh_reanchored = Self::reanchored(to_weigh, &dest, None); - let mut message_to_weigh = vec![ReserveAssetDeposited(to_weigh_reanchored), ClearOrigin]; - message_to_weigh.extend(xcm.0.clone().into_iter()); - let (_, fee) = validate_send::(dest.clone(), Xcm(message_to_weigh))?; - // set aside fee to be charged by XcmSender - let transport_fee = self.holding.saturating_take(fee.into()); - - // now take assets to deposit (excluding transport_fee) - let deposited = self.holding.saturating_take(assets); - for asset in deposited.assets_iter() { - Config::AssetTransactor::deposit_asset(&asset, &dest, Some(&self.context))?; - } - // Note that we pass `None` as `maybe_failed_bin` and drop any assets which - // cannot be reanchored because we have already called `deposit_asset` on all - // assets. - let assets = Self::reanchored(deposited, &dest, None); - let mut message = vec![ReserveAssetDeposited(assets), ClearOrigin]; - message.extend(xcm.0.into_iter()); - // put back transport_fee in holding register to be charged by XcmSender - self.holding.subsume_assets(transport_fee); - self.send(dest, Xcm(message), FeeReason::DepositReserveAsset)?; - Ok(()) - }); - if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { - self.holding = old_holding; - } - result - }, - InitiateReserveWithdraw { assets, reserve, xcm } => { - let old_holding = self.holding.clone(); - let result = Config::TransactionalProcessor::process(|| { - // Note that here we are able to place any assets which could not be reanchored - // back into Holding. - let assets = - Self::reanchored(self.holding.saturating_take(assets), &reserve, Some(&mut self.holding)); - let mut message = vec![WithdrawAsset(assets), ClearOrigin]; - message.extend(xcm.0.into_iter()); - self.send(reserve, Xcm(message), FeeReason::InitiateReserveWithdraw)?; - Ok(()) - }); - if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { - self.holding = old_holding; - } - result - }, - InitiateTeleport { assets, dest, xcm } => { - let old_holding = self.holding.clone(); - let result = (|| -> Result<(), XcmError> { - // We must do this first in order to resolve wildcards. - let assets = self.holding.saturating_take(assets); - for asset in assets.assets_iter() { - // We should check that the asset can actually be teleported out (for this - // to be in error, there would need to be an accounting violation by - // ourselves, so it's unlikely, but we don't want to allow that kind of bug - // to leak into a trusted chain. - Config::AssetTransactor::can_check_out(&dest, &asset, &self.context)?; - } - // Note that we pass `None` as `maybe_failed_bin` and drop any assets which - // cannot be reanchored because we have already checked all assets out. - let reanchored_assets = Self::reanchored(assets.clone(), &dest, None); - let mut message = vec![ReceiveTeleportedAsset(reanchored_assets), ClearOrigin]; - message.extend(xcm.0.into_iter()); - self.send(dest.clone(), Xcm(message), FeeReason::InitiateTeleport)?; - - for asset in assets.assets_iter() { - Config::AssetTransactor::check_out(&dest, &asset, &self.context); - } - Ok(()) - })(); - if result.is_err() { - self.holding = old_holding; - } - result - }, - ReportHolding { response_info, assets } => { - // Note that we pass `None` as `maybe_failed_bin` since no assets were ever removed - // from Holding. - let assets = Self::reanchored(self.holding.min(&assets), &response_info.destination, None); - self.respond(self.cloned_origin(), Response::Assets(assets), response_info, FeeReason::Report)?; - Ok(()) - }, - BuyExecution { fees, weight_limit } => { - // There is no need to buy any weight if `weight_limit` is `Unlimited` since it - // would indicate that `AllowTopLevelPaidExecutionFrom` was unused for execution - // and thus there is some other reason why it has been determined that this XCM - // should be executed. - let Some(weight) = Option::::from(weight_limit) else { return Ok(()) }; - let old_holding = self.holding.clone(); - // pay for `weight` using up to `fees` of the holding register. - let max_fee = self.holding.try_take(fees.into()).map_err(|_| XcmError::NotHoldingFees)?; - log::trace!( - target: "xcm::execute", - "max_fee: {max_fee:?}", - ); - let result = || -> Result<(), XcmError> { - let unspent = self.trader.buy_weight(weight, max_fee, &self.context)?; - log::trace!( - target: "xcm::execute", - "unspent: {unspent:?}", - ); - self.holding.subsume_assets(unspent); - Ok(()) - }(); - if result.is_err() { - self.holding = old_holding; - } - result - }, - RefundSurplus => self.refund_surplus(), - SetErrorHandler(mut handler) => { - let handler_weight = - Config::Weigher::weight(&mut handler).map_err(|()| XcmError::WeightNotComputable)?; - self.total_surplus.saturating_accrue(self.error_handler_weight); - self.error_handler = handler; - self.error_handler_weight = handler_weight; - Ok(()) - }, - SetAppendix(mut appendix) => { - let appendix_weight = - Config::Weigher::weight(&mut appendix).map_err(|()| XcmError::WeightNotComputable)?; - self.total_surplus.saturating_accrue(self.appendix_weight); - self.appendix = appendix; - self.appendix_weight = appendix_weight; - Ok(()) - }, - ClearError => { - self.error = None; - Ok(()) - }, - ClaimAsset { assets, ticket } => { - let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; - self.ensure_can_subsume_assets(assets.len())?; - let ok = Config::AssetClaims::claim_assets(origin, &ticket, &assets, &self.context); - ensure!(ok, XcmError::UnknownClaim); - self.holding.subsume_assets(assets.into()); - Ok(()) - }, - Trap(code) => Err(XcmError::Trap(code)), - SubscribeVersion { query_id, max_response_weight } => { - let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; - // We don't allow derivative origins to subscribe since it would otherwise pose a - // DoS risk. - ensure!(&self.original_origin == origin, XcmError::BadOrigin); - Config::SubscriptionService::start(origin, query_id, max_response_weight, &self.context) - }, - UnsubscribeVersion => { - let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; - ensure!(&self.original_origin == origin, XcmError::BadOrigin); - Config::SubscriptionService::stop(origin, &self.context) - }, - BurnAsset(assets) => { - self.holding.saturating_take(assets.into()); - Ok(()) - }, - ExpectAsset(assets) => self.holding.ensure_contains(&assets).map_err(|_| XcmError::ExpectationFalse), - ExpectOrigin(origin) => { - ensure!(self.context.origin == origin, XcmError::ExpectationFalse); - Ok(()) - }, - ExpectError(error) => { - ensure!(self.error == error, XcmError::ExpectationFalse); - Ok(()) - }, - ExpectTransactStatus(transact_status) => { - ensure!(self.transact_status == transact_status, XcmError::ExpectationFalse); - Ok(()) - }, - QueryPallet { module_name, response_info } => { - let pallets = Config::PalletInstancesInfo::infos() - .into_iter() - .filter(|x| x.module_name.as_bytes() == &module_name[..]) - .map(|x| { - PalletInfo::new( - x.index as u32, - x.name.as_bytes().into(), - x.module_name.as_bytes().into(), - x.crate_version.major as u32, - x.crate_version.minor as u32, - x.crate_version.patch as u32, - ) - }) - .collect::, XcmError>>()?; - let QueryResponseInfo { destination, query_id, max_weight } = response_info; - let response = Response::PalletsInfo(pallets.try_into().map_err(|_| XcmError::Overflow)?); - let querier = Self::to_querier(self.cloned_origin(), &destination)?; - let instruction = QueryResponse { query_id, response, max_weight, querier }; - let message = Xcm(vec![instruction]); - self.send(destination, message, FeeReason::QueryPallet)?; - Ok(()) - }, - ExpectPallet { index, name, module_name, crate_major, min_crate_minor } => { - let pallet = Config::PalletInstancesInfo::infos() - .into_iter() - .find(|x| x.index == index as usize) - .ok_or(XcmError::PalletNotFound)?; - ensure!(pallet.name.as_bytes() == &name[..], XcmError::NameMismatch); - ensure!(pallet.module_name.as_bytes() == &module_name[..], XcmError::NameMismatch); - let major = pallet.crate_version.major as u32; - ensure!(major == crate_major, XcmError::VersionIncompatible); - let minor = pallet.crate_version.minor as u32; - ensure!(minor >= min_crate_minor, XcmError::VersionIncompatible); - Ok(()) - }, - ReportTransactStatus(response_info) => { - self.respond( - self.cloned_origin(), - Response::DispatchResult(self.transact_status.clone()), - response_info, - FeeReason::Report, - )?; - Ok(()) - }, - ClearTransactStatus => { - self.transact_status = Default::default(); - Ok(()) - }, - UniversalOrigin(new_global) => { - let universal_location = Config::UniversalLocation::get(); - ensure!(universal_location.first() != Some(&new_global), XcmError::InvalidLocation); - let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?; - let origin_xform = (origin, new_global); - let ok = Config::UniversalAliases::contains(&origin_xform); - ensure!(ok, XcmError::InvalidLocation); - let (_, new_global) = origin_xform; - let new_origin = Junctions::from([new_global]).relative_to(&universal_location); - self.context.origin = Some(new_origin); - Ok(()) - }, - ExportMessage { network, destination, xcm } => { - // The actual message sent to the bridge for forwarding is prepended with - // `UniversalOrigin` and `DescendOrigin` in order to ensure that the message is - // executed with this Origin. - // - // Prepend the desired message with instructions which effectively rewrite the - // origin. - // - // This only works because the remote chain empowers the bridge - // to speak for the local network. - let origin = self.context.origin.as_ref().ok_or(XcmError::BadOrigin)?.clone(); - let universal_source = - Config::UniversalLocation::get().within_global(origin).map_err(|()| XcmError::Unanchored)?; - let hash = (self.origin_ref(), &destination).using_encoded(blake2_128); - let channel = u32::decode(&mut hash.as_ref()).unwrap_or(0); - // Hash identifies the lane on the exporter which we use. We use the pairwise - // combination of the origin and destination to ensure origin/destination pairs - // will generally have their own lanes. - let (ticket, fee) = validate_export::( - network, - channel, - universal_source, - destination.clone(), - xcm, - )?; - let old_holding = self.holding.clone(); - let result = Config::TransactionalProcessor::process(|| { - self.take_fee(fee, FeeReason::Export { network, destination })?; - let _ = Config::MessageExporter::deliver(ticket).defensive_proof( - "`deliver` called immediately after `validate_export`; \ - `take_fee` does not affect the validity of the ticket; qed", - ); - Ok(()) - }); - if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { - self.holding = old_holding; - } - result - }, - LockAsset { asset, unlocker } => { - let old_holding = self.holding.clone(); - let result = Config::TransactionalProcessor::process(|| { - let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?; - let (remote_asset, context) = Self::try_reanchor(asset.clone(), &unlocker)?; - let lock_ticket = Config::AssetLocker::prepare_lock(unlocker.clone(), asset, origin.clone())?; - let owner = origin.reanchored(&unlocker, &context).map_err(|_| XcmError::ReanchorFailed)?; - let msg = Xcm::<()>(vec![NoteUnlockable { asset: remote_asset, owner }]); - let (ticket, price) = validate_send::(unlocker, msg)?; - self.take_fee(price, FeeReason::LockAsset)?; - lock_ticket.enact()?; - Config::XcmSender::deliver(ticket)?; - Ok(()) - }); - if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { - self.holding = old_holding; - } - result - }, - UnlockAsset { asset, target } => { - let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?; - Config::AssetLocker::prepare_unlock(origin, asset, target)?.enact()?; - Ok(()) - }, - NoteUnlockable { asset, owner } => { - let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?; - Config::AssetLocker::note_unlockable(origin, asset, owner)?; - Ok(()) - }, - RequestUnlock { asset, locker } => { - let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?; - let remote_asset = Self::try_reanchor(asset.clone(), &locker)?.0; - let remote_target = Self::try_reanchor(origin.clone(), &locker)?.0; - let reduce_ticket = - Config::AssetLocker::prepare_reduce_unlockable(locker.clone(), asset, origin.clone())?; - let msg = Xcm::<()>(vec![UnlockAsset { asset: remote_asset, target: remote_target }]); - let (ticket, price) = validate_send::(locker, msg)?; - let old_holding = self.holding.clone(); - let result = Config::TransactionalProcessor::process(|| { - self.take_fee(price, FeeReason::RequestUnlock)?; - reduce_ticket.enact()?; - Config::XcmSender::deliver(ticket)?; - Ok(()) - }); - if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { - self.holding = old_holding; - } - result - }, - ExchangeAsset { give, want, maximal } => { - let old_holding = self.holding.clone(); - let give = self.holding.saturating_take(give); - let result = (|| -> Result<(), XcmError> { - self.ensure_can_subsume_assets(want.len())?; - let exchange_result = - Config::AssetExchanger::exchange_asset(self.origin_ref(), give, &want, maximal); - if let Ok(received) = exchange_result { - self.holding.subsume_assets(received.into()); - Ok(()) - } else { - Err(XcmError::NoDeal) - } - })(); - if result.is_err() { - self.holding = old_holding; - } - result - }, - SetFeesMode { jit_withdraw } => { - self.fees_mode = FeesMode { jit_withdraw }; - Ok(()) - }, - SetTopic(topic) => { - self.context.topic = Some(topic); - Ok(()) - }, - ClearTopic => { - self.context.topic = None; - Ok(()) - }, - AliasOrigin(target) => { - let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; - if Config::Aliasers::contains(origin, &target) { - self.context.origin = Some(target); - Ok(()) - } else { - Err(XcmError::NoPermission) - } - }, - UnpaidExecution { check_origin, .. } => { - ensure!(check_origin.is_none() || self.context.origin == check_origin, XcmError::BadOrigin); - Ok(()) - }, - HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => - Config::TransactionalProcessor::process(|| { - Config::HrmpNewChannelOpenRequestHandler::handle(sender, max_message_size, max_capacity) - }), - HrmpChannelAccepted { recipient } => - Config::TransactionalProcessor::process(|| Config::HrmpChannelAcceptedHandler::handle(recipient)), - HrmpChannelClosing { initiator, sender, recipient } => Config::TransactionalProcessor::process(|| { - Config::HrmpChannelClosingHandler::handle(initiator, sender, recipient) - }), - } - } -} diff --git a/debug-executor/src/traits/asset_exchange.rs b/debug-executor/src/traits/asset_exchange.rs deleted file mode 100644 index 432a7498e..000000000 --- a/debug-executor/src/traits/asset_exchange.rs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (C) 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 . - -use crate::AssetsInHolding; -use xcm::prelude::*; - -/// A service for exchanging assets. -pub trait AssetExchange { - /// Handler for exchanging an asset. - /// - /// - `origin`: The location attempting the exchange; this should generally not matter. - /// - `give`: The assets which have been removed from the caller. - /// - `want`: The minimum amount of assets which should be given to the caller in case any - /// exchange happens. If more assets are provided, then they should generally be of the same - /// asset class if at all possible. - /// - `maximal`: If `true`, then as much as possible should be exchanged. - /// - /// `Ok` is returned along with the new set of assets which have been exchanged for `give`. At - /// least want must be in the set. Some assets originally in `give` may also be in this set. In - /// the case of returning an `Err`, then `give` is returned. - fn exchange_asset( - origin: Option<&Location>, - give: AssetsInHolding, - want: &Assets, - maximal: bool, - ) -> Result; -} - -#[impl_trait_for_tuples::impl_for_tuples(30)] -impl AssetExchange for Tuple { - fn exchange_asset( - origin: Option<&Location>, - give: AssetsInHolding, - want: &Assets, - maximal: bool, - ) -> Result { - for_tuples!( #( - let give = match Tuple::exchange_asset(origin, give, want, maximal) { - Ok(r) => return Ok(r), - Err(a) => a, - }; - )* ); - Err(give) - } -} diff --git a/debug-executor/src/traits/asset_lock.rs b/debug-executor/src/traits/asset_lock.rs deleted file mode 100644 index f344c2b7d..000000000 --- a/debug-executor/src/traits/asset_lock.rs +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (C) 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 . - -use sp_std::convert::Infallible; -use xcm::prelude::*; - -#[derive(Debug)] -pub enum LockError { - NotApplicable, - WouldClobber, - BadOrigin, - NotLocked, - NotEnoughLocked, - Unimplemented, - NotTrusted, - BadOwner, - UnknownAsset, - AssetNotOwned, - NoResources, - UnexpectedState, - InUse, -} - -impl From for XcmError { - fn from(e: LockError) -> XcmError { - use LockError::*; - match e { - NotApplicable => XcmError::AssetNotFound, - BadOrigin => XcmError::BadOrigin, - WouldClobber | NotLocked | NotEnoughLocked | Unimplemented | NotTrusted | BadOwner | UnknownAsset | - AssetNotOwned | NoResources | UnexpectedState | InUse => XcmError::LockError, - } - } -} - -pub trait Enact { - /// Enact a lock. This should generally be infallible if called immediately after being - /// received. - fn enact(self) -> Result<(), LockError>; -} - -impl Enact for Infallible { - fn enact(self) -> Result<(), LockError> { - unreachable!() - } -} - -/// Define a handler for notification of an asset being locked and for the unlock instruction. -pub trait AssetLock { - /// `Enact` implementer for `prepare_lock`. This type may be dropped safely to avoid doing the - /// lock. - type LockTicket: Enact; - - /// `Enact` implementer for `prepare_unlock`. This type may be dropped safely to avoid doing the - /// unlock. - type UnlockTicket: Enact; - - /// `Enact` implementer for `prepare_reduce_unlockable`. This type may be dropped safely to - /// avoid doing the unlock. - type ReduceTicket: Enact; - - /// Prepare to lock an asset. On success, a `Self::LockTicket` it returned, which can be used - /// to actually enact the lock. - /// - /// WARNING: Don't call this with an undropped instance of `Self::LockTicket` or - /// `Self::UnlockTicket`. - fn prepare_lock(unlocker: Location, asset: Asset, owner: Location) -> Result; - - /// Prepare to unlock an asset. On success, a `Self::UnlockTicket` it returned, which can be - /// used to actually enact the lock. - /// - /// WARNING: Don't call this with an undropped instance of `Self::LockTicket` or - /// `Self::UnlockTicket`. - fn prepare_unlock(locker: Location, asset: Asset, owner: Location) -> Result; - - /// Handler for when a location reports to us that an asset has been locked for us to unlock - /// at a later stage. - /// - /// If there is no way to handle the lock report, then this should return an error so that the - /// sending chain can ensure the lock does not remain. - /// - /// We should only act upon this message if we believe that the `origin` is honest. - fn note_unlockable(locker: Location, asset: Asset, owner: Location) -> Result<(), LockError>; - - /// Handler for when an owner wishes to unlock an asset on a remote chain. - /// - /// Returns a ticket which can be used to actually note the reduction in unlockable assets that - /// `owner` commands on `locker`. - /// - /// WARNING: Don't call this with an undropped instance of `Self::ReduceTicket`. - fn prepare_reduce_unlockable( - locker: Location, - asset: Asset, - owner: Location, - ) -> Result; -} - -impl AssetLock for () { - type LockTicket = Infallible; - type ReduceTicket = Infallible; - type UnlockTicket = Infallible; - - fn prepare_lock(_: Location, _: Asset, _: Location) -> Result { - Err(LockError::NotApplicable) - } - - fn prepare_unlock(_: Location, _: Asset, _: Location) -> Result { - Err(LockError::NotApplicable) - } - - fn note_unlockable(_: Location, _: Asset, _: Location) -> Result<(), LockError> { - Err(LockError::NotApplicable) - } - - fn prepare_reduce_unlockable(_: Location, _: Asset, _: Location) -> Result { - Err(LockError::NotApplicable) - } -} diff --git a/debug-executor/src/traits/asset_transfer.rs b/debug-executor/src/traits/asset_transfer.rs deleted file mode 100644 index ccb0a1b3e..000000000 --- a/debug-executor/src/traits/asset_transfer.rs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (C) 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 . - -use crate::traits::TransactAsset; -use frame_support::traits::ContainsPair; -use scale_info::TypeInfo; -use sp_runtime::codec::{Decode, Encode}; -use xcm::prelude::*; - -/// Errors related to determining asset transfer support. -#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo)] -pub enum Error { - /// Reserve chain could not be determined for assets. - UnknownReserve, -} - -/// Specify which type of asset transfer is required for a particular `(asset, dest)` combination. -#[derive(Clone, Encode, Decode, PartialEq, Debug, TypeInfo)] -pub enum TransferType { - /// should teleport `asset` to `dest` - Teleport, - /// should reserve-transfer `asset` to `dest`, using local chain as reserve - LocalReserve, - /// should reserve-transfer `asset` to `dest`, using `dest` as reserve - DestinationReserve, - /// should reserve-transfer `asset` to `dest`, using remote chain `Location` as reserve - RemoteReserve(VersionedLocation), -} - -/// A trait for identifying asset transfer type based on `IsTeleporter` and `IsReserve` -/// configurations. -pub trait XcmAssetTransfers { - /// Combinations of (Asset, Location) pairs which we trust as reserves. Meaning - /// reserve-based-transfers are to be used for assets matching this filter. - type IsReserve: ContainsPair; - - /// Combinations of (Asset, Location) pairs which we trust as teleporters. Meaning teleports are - /// to be used for assets matching this filter. - type IsTeleporter: ContainsPair; - - /// How to withdraw and deposit an asset. - type AssetTransactor: TransactAsset; - - /// Determine transfer type to be used for transferring `asset` from local chain to `dest`. - fn determine_for(asset: &Asset, dest: &Location) -> Result { - if Self::IsTeleporter::contains(asset, dest) { - // we trust destination for teleporting asset - return Ok(TransferType::Teleport) - } else if Self::IsReserve::contains(asset, dest) { - // we trust destination as asset reserve location - return Ok(TransferType::DestinationReserve) - } - - // try to determine reserve location based on asset id/location - let asset_location = asset.id.0.chain_location(); - if asset_location == Location::here() || Self::IsTeleporter::contains(asset, &asset_location) { - // if the asset is local, then it's a local reserve - // it's also a local reserve if the asset's location is not `here` but it's a location - // where it can be teleported to `here` => local reserve - Ok(TransferType::LocalReserve) - } else if Self::IsReserve::contains(asset, &asset_location) { - // remote location that is recognized as reserve location for asset - Ok(TransferType::RemoteReserve(asset_location.into())) - } else { - // remote location that is not configured either as teleporter or reserve => cannot - // determine asset reserve - Err(Error::UnknownReserve) - } - } -} - -impl XcmAssetTransfers for () { - type AssetTransactor = (); - type IsReserve = (); - type IsTeleporter = (); - - fn determine_for(_: &Asset, _: &Location) -> Result { - return Err(Error::UnknownReserve); - } -} diff --git a/debug-executor/src/traits/conversion.rs b/debug-executor/src/traits/conversion.rs deleted file mode 100644 index dcfdbec32..000000000 --- a/debug-executor/src/traits/conversion.rs +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (C) 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 . - -use frame_support::traits::{Contains, OriginTrait}; -use sp_runtime::{traits::Dispatchable, DispatchErrorWithPostInfo}; -use sp_std::{marker::PhantomData, result::Result}; -use xcm::latest::prelude::*; - -/// Means of converting a location into an account identifier. -pub trait ConvertLocation { - /// Convert the `location` into `Some` account ID, or `None` if not possible. - fn convert_location(location: &Location) -> Option; -} - -#[impl_trait_for_tuples::impl_for_tuples(30)] -impl ConvertLocation for Tuple { - fn convert_location(l: &Location) -> Option { - for_tuples!( #( - match Tuple::convert_location(l) { - Some(result) => return Some(result), - None => {}, - } - )* ); - None - } -} - -/// A converter `trait` for origin types. -/// -/// Can be amalgamated into tuples. If any of the tuple elements returns `Ok(_)`, it short circuits. -/// Else, the `Err(_)` of the last tuple item is returned. Each intermediate `Err(_)` might return a -/// different `origin` of type `Origin` which is passed to the next convert item. -/// -/// ```rust -/// # use xcm::latest::{Location, Junctions, Junction, OriginKind}; -/// # use staging_xcm_executor::traits::ConvertOrigin; -/// // A convertor that will bump the para id and pass it to the next one. -/// struct BumpParaId; -/// impl ConvertOrigin for BumpParaId { -/// fn convert_origin(origin: impl Into, _: OriginKind) -> Result { -/// match origin.into().unpack() { -/// (0, [Junction::Parachain(id)]) => { -/// Err([Junction::Parachain(id + 1)].into()) -/// } -/// _ => unreachable!() -/// } -/// } -/// } -/// -/// struct AcceptPara7; -/// impl ConvertOrigin for AcceptPara7 { -/// fn convert_origin(origin: impl Into, _: OriginKind) -> Result { -/// let origin = origin.into(); -/// match origin.unpack() { -/// (0, [Junction::Parachain(id)]) if *id == 7 => { -/// Ok(7) -/// } -/// _ => Err(origin) -/// } -/// } -/// } -/// # fn main() { -/// let origin: Location = [Junction::Parachain(6)].into(); -/// assert!( -/// <(BumpParaId, AcceptPara7) as ConvertOrigin>::convert_origin(origin, OriginKind::Native) -/// .is_ok() -/// ); -/// # } -/// ``` -pub trait ConvertOrigin { - /// Attempt to convert `origin` to the generic `Origin` whilst consuming it. - fn convert_origin(origin: impl Into, kind: OriginKind) -> Result; -} - -#[impl_trait_for_tuples::impl_for_tuples(30)] -impl ConvertOrigin for Tuple { - fn convert_origin(origin: impl Into, kind: OriginKind) -> Result { - for_tuples!( #( - let origin = match Tuple::convert_origin(origin, kind) { - Err(o) => o, - r => return r - }; - )* ); - let origin = origin.into(); - log::trace!( - target: "xcm::convert_origin", - "could not convert: origin: {:?}, kind: {:?}", - origin, - kind, - ); - Err(origin) - } -} - -/// Defines how a call is dispatched with given origin. -/// Allows to customize call dispatch, such as adapting the origin based on the call -/// or modifying the call. -pub trait CallDispatcher { - fn dispatch( - call: Call, - origin: Call::RuntimeOrigin, - ) -> Result>; -} - -pub struct WithOriginFilter(PhantomData); -impl CallDispatcher for WithOriginFilter -where - Call: Dispatchable, - Call::RuntimeOrigin: OriginTrait, - <::RuntimeOrigin as OriginTrait>::Call: 'static, - Filter: Contains<<::RuntimeOrigin as OriginTrait>::Call> + 'static, -{ - fn dispatch( - call: Call, - mut origin: ::RuntimeOrigin, - ) -> Result<::PostInfo, DispatchErrorWithPostInfo<::PostInfo>> { - origin.add_filter(Filter::contains); - call.dispatch(origin) - } -} - -// We implement it for every calls so they can dispatch themselves -// (without any change). -impl CallDispatcher for Call { - fn dispatch( - call: Call, - origin: Call::RuntimeOrigin, - ) -> Result> { - call.dispatch(origin) - } -} diff --git a/debug-executor/src/traits/drop_assets.rs b/debug-executor/src/traits/drop_assets.rs deleted file mode 100644 index 9cb13e0dd..000000000 --- a/debug-executor/src/traits/drop_assets.rs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (C) 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 . - -use crate::AssetsInHolding; -use core::marker::PhantomData; -use frame_support::traits::Contains; -use xcm::latest::{Assets, Location, Weight, XcmContext}; - -/// Define a handler for when some non-empty `AssetsInHolding` value should be dropped. -pub trait DropAssets { - /// Handler for receiving dropped assets. Returns the weight consumed by this operation. - fn drop_assets(origin: &Location, assets: AssetsInHolding, context: &XcmContext) -> Weight; -} -impl DropAssets for () { - fn drop_assets(_origin: &Location, _assets: AssetsInHolding, _context: &XcmContext) -> Weight { - Weight::zero() - } -} - -/// Morph a given `DropAssets` implementation into one which can filter based on assets. This can -/// be used to ensure that `AssetsInHolding` values which hold no value are ignored. -pub struct FilterAssets(PhantomData<(D, A)>); - -impl> DropAssets for FilterAssets { - fn drop_assets(origin: &Location, assets: AssetsInHolding, context: &XcmContext) -> Weight { - if A::contains(&assets) { - D::drop_assets(origin, assets, context) - } else { - Weight::zero() - } - } -} - -/// Morph a given `DropAssets` implementation into one which can filter based on origin. This can -/// be used to ban origins which don't have proper protections/policies against misuse of the -/// asset trap facility don't get to use it. -pub struct FilterOrigin(PhantomData<(D, O)>); - -impl> DropAssets for FilterOrigin { - fn drop_assets(origin: &Location, assets: AssetsInHolding, context: &XcmContext) -> Weight { - if O::contains(origin) { - D::drop_assets(origin, assets, context) - } else { - Weight::zero() - } - } -} - -/// Define any handlers for the `AssetClaim` instruction. -pub trait ClaimAssets { - /// Claim any assets available to `origin` and return them in a single `Assets` value, together - /// with the weight used by this operation. - fn claim_assets(origin: &Location, ticket: &Location, what: &Assets, context: &XcmContext) -> bool; -} - -#[impl_trait_for_tuples::impl_for_tuples(30)] -impl ClaimAssets for Tuple { - fn claim_assets(origin: &Location, ticket: &Location, what: &Assets, context: &XcmContext) -> bool { - for_tuples!( #( - if Tuple::claim_assets(origin, ticket, what, context) { - return true; - } - )* ); - false - } -} diff --git a/debug-executor/src/traits/export.rs b/debug-executor/src/traits/export.rs deleted file mode 100644 index 2ddfcff73..000000000 --- a/debug-executor/src/traits/export.rs +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright (C) 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 . - -use xcm::latest::prelude::*; - -/// Utility for delivering a message to a system under a different (non-local) consensus with a -/// spoofed origin. This essentially defines the behaviour of the `ExportMessage` XCM instruction. -/// -/// This is quite different to `SendXcm`; `SendXcm` assumes that the local side's location will be -/// preserved to be represented as the value of the Origin register in the messages execution. -/// -/// This trait on the other hand assumes that we do not necessarily want the Origin register to -/// contain the local (i.e. the caller chain's) location, since it will generally be exporting a -/// message on behalf of another consensus system. Therefore in addition to the message, the -/// destination must be given in two parts: the network and the interior location within it. -/// -/// We also require the caller to state exactly what location they purport to be representing. The -/// destination must accept the local location to represent that location or the operation will -/// fail. -pub trait ExportXcm { - /// Intermediate value which connects the two phases of the export operation. - type Ticket; - - /// Check whether the given `message` is deliverable to the given `destination` on `network`, - /// spoofing its source as `universal_source` and if so determine the cost which will be paid by - /// this chain to do so, returning a `Ticket` token which can be used to enact delivery. - /// - /// The `channel` to be used on the `network`'s export mechanism (bridge, probably) must also - /// be provided. - /// - /// The `destination` and `message` must be `Some` (or else an error will be returned) and they - /// may only be consumed if the `Err` is not `NotApplicable`. - /// - /// If it is not a destination which can be reached with this type but possibly could by others, - /// then this *MUST* return `NotApplicable`. Any other error will cause the tuple - /// implementation (used to compose routing systems from different delivery agents) to exit - /// early without trying alternative means of delivery. - fn validate( - network: NetworkId, - channel: u32, - universal_source: &mut Option, - destination: &mut Option, - message: &mut Option>, - ) -> SendResult; - - /// Actually carry out the delivery operation for a previously validated message sending. - /// - /// The implementation should do everything possible to ensure that this function is infallible - /// if called immediately after `validate`. Returning an error here would result in a price - /// paid without the service being delivered. - fn deliver(ticket: Self::Ticket) -> Result; -} - -#[impl_trait_for_tuples::impl_for_tuples(30)] -impl ExportXcm for Tuple { - for_tuples! { type Ticket = (#( Option ),* ); } - - fn validate( - network: NetworkId, - channel: u32, - universal_source: &mut Option, - destination: &mut Option, - message: &mut Option>, - ) -> SendResult { - let mut maybe_cost: Option = None; - let one_ticket: Self::Ticket = (for_tuples! { #( - if maybe_cost.is_some() { - None - } else { - match Tuple::validate(network, channel, universal_source, destination, message) { - Err(SendError::NotApplicable) => None, - Err(e) => { return Err(e) }, - Ok((v, c)) => { - maybe_cost = Some(c); - Some(v) - }, - } - } - ),* }); - if let Some(cost) = maybe_cost { - Ok((one_ticket, cost)) - } else { - Err(SendError::NotApplicable) - } - } - - fn deliver(mut one_ticket: Self::Ticket) -> Result { - for_tuples!( #( - if let Some(validated) = one_ticket.Tuple.take() { - return Tuple::deliver(validated); - } - )* ); - Err(SendError::Unroutable) - } -} - -/// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps -/// both in `Some` before passing them as as mutable references into `T::send_xcm`. -pub fn validate_export( - network: NetworkId, - channel: u32, - universal_source: InteriorLocation, - dest: InteriorLocation, - msg: Xcm<()>, -) -> SendResult { - T::validate(network, channel, &mut Some(universal_source), &mut Some(dest), &mut Some(msg)) -} - -/// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps -/// both in `Some` before passing them as as mutable references into `T::send_xcm`. -/// -/// Returns either `Ok` with the price of the delivery, or `Err` with the reason why the message -/// could not be sent. -/// -/// Generally you'll want to validate and get the price first to ensure that the sender can pay it -/// before actually doing the delivery. -pub fn export_xcm( - network: NetworkId, - channel: u32, - universal_source: InteriorLocation, - dest: InteriorLocation, - msg: Xcm<()>, -) -> Result<(XcmHash, Assets), SendError> { - let (ticket, price) = T::validate(network, channel, &mut Some(universal_source), &mut Some(dest), &mut Some(msg))?; - let hash = T::deliver(ticket)?; - Ok((hash, price)) -} diff --git a/debug-executor/src/traits/fee_manager.rs b/debug-executor/src/traits/fee_manager.rs deleted file mode 100644 index b6e303daa..000000000 --- a/debug-executor/src/traits/fee_manager.rs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (C) 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 . - -use xcm::prelude::*; - -/// Handle stuff to do with taking fees in certain XCM instructions. -pub trait FeeManager { - /// Determine if a fee should be waived. - fn is_waived(origin: Option<&Location>, r: FeeReason) -> bool; - - /// Do something with the fee which has been paid. Doing nothing here silently burns the - /// fees. - fn handle_fee(fee: Assets, context: Option<&XcmContext>, r: FeeReason); -} - -/// Context under which a fee is paid. -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum FeeReason { - /// When a reporting instruction is called. - Report, - /// When the `TransferReserveAsset` instruction is called. - TransferReserveAsset, - /// When the `DepositReserveAsset` instruction is called. - DepositReserveAsset, - /// When the `InitiateReserveWithdraw` instruction is called. - InitiateReserveWithdraw, - /// When the `InitiateTeleport` instruction is called. - InitiateTeleport, - /// When the `QueryPallet` instruction is called. - QueryPallet, - /// When the `ExportMessage` instruction is called (and includes the network ID). - Export { network: NetworkId, destination: InteriorLocation }, - /// The `charge_fees` API. - ChargeFees, - /// When the `LockAsset` instruction is called. - LockAsset, - /// When the `RequestUnlock` instruction is called. - RequestUnlock, -} - -impl FeeManager for () { - fn is_waived(_: Option<&Location>, _: FeeReason) -> bool { - false - } - - fn handle_fee(_: Assets, _: Option<&XcmContext>, _: FeeReason) {} -} diff --git a/debug-executor/src/traits/filter_asset_location.rs b/debug-executor/src/traits/filter_asset_location.rs deleted file mode 100644 index 5d0c32890..000000000 --- a/debug-executor/src/traits/filter_asset_location.rs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (C) 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 . - -use frame_support::traits::ContainsPair; -use xcm::latest::{Asset, Location}; - -/// Filters assets/location pairs. -/// -/// Can be amalgamated into tuples. If any item returns `true`, it short-circuits, else `false` is -/// returned. -#[deprecated = "Use `frame_support::traits::ContainsPair` instead"] -pub trait FilterAssetLocation { - /// A filter to distinguish between asset/location pairs. - fn contains(asset: &Asset, origin: &Location) -> bool; -} - -#[allow(deprecated)] -impl> FilterAssetLocation for T { - fn contains(asset: &Asset, origin: &Location) -> bool { - T::contains(asset, origin) - } -} diff --git a/debug-executor/src/traits/hrmp.rs b/debug-executor/src/traits/hrmp.rs deleted file mode 100644 index b6bbb9316..000000000 --- a/debug-executor/src/traits/hrmp.rs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (C) 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 . - -use xcm::latest::Result as XcmResult; - -/// Executes logic when a `HrmpNewChannelOpenRequest` XCM notification is received. -pub trait HandleHrmpNewChannelOpenRequest { - fn handle(sender: u32, max_message_size: u32, max_capacity: u32) -> XcmResult; -} - -/// Executes optional logic when a `HrmpChannelAccepted` XCM notification is received. -pub trait HandleHrmpChannelAccepted { - fn handle(recipient: u32) -> XcmResult; -} - -/// Executes optional logic when a `HrmpChannelClosing` XCM notification is received. -pub trait HandleHrmpChannelClosing { - fn handle(initiator: u32, sender: u32, recipient: u32) -> XcmResult; -} - -#[impl_trait_for_tuples::impl_for_tuples(30)] -impl HandleHrmpNewChannelOpenRequest for Tuple { - fn handle(sender: u32, max_message_size: u32, max_capacity: u32) -> XcmResult { - for_tuples!( #( Tuple::handle(sender, max_message_size, max_capacity)?; )* ); - Ok(()) - } -} - -#[impl_trait_for_tuples::impl_for_tuples(30)] -impl HandleHrmpChannelAccepted for Tuple { - fn handle(recipient: u32) -> XcmResult { - for_tuples!( #( Tuple::handle(recipient)?; )* ); - Ok(()) - } -} - -#[impl_trait_for_tuples::impl_for_tuples(30)] -impl HandleHrmpChannelClosing for Tuple { - fn handle(initiator: u32, sender: u32, recipient: u32) -> XcmResult { - for_tuples!( #( Tuple::handle(initiator, sender, recipient)?; )* ); - Ok(()) - } -} diff --git a/debug-executor/src/traits/mod.rs b/debug-executor/src/traits/mod.rs deleted file mode 100644 index 3095d2af8..000000000 --- a/debug-executor/src/traits/mod.rs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (C) 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 . - -//! Various traits used in configuring the executor. - -mod conversion; -pub use conversion::{CallDispatcher, ConvertLocation, ConvertOrigin, WithOriginFilter}; -mod drop_assets; -pub use drop_assets::{ClaimAssets, DropAssets}; -mod asset_exchange; -pub use asset_exchange::AssetExchange; -mod asset_lock; -pub use asset_lock::{AssetLock, Enact, LockError}; -mod asset_transfer; -pub use asset_transfer::{Error as AssetTransferError, TransferType, XcmAssetTransfers}; -mod export; -pub use export::{export_xcm, validate_export, ExportXcm}; -mod fee_manager; -pub use fee_manager::{FeeManager, FeeReason}; -mod filter_asset_location; -#[allow(deprecated)] -pub use filter_asset_location::FilterAssetLocation; -mod token_matching; -pub use token_matching::{Error, MatchesFungible, MatchesFungibles, MatchesNonFungible, MatchesNonFungibles}; -mod on_response; -pub use on_response::{OnResponse, QueryHandler, QueryResponseStatus, VersionChangeNotifier}; -mod process_transaction; -pub use process_transaction::ProcessTransaction; -mod should_execute; -pub use should_execute::{CheckSuspension, Properties, ShouldExecute}; -mod transact_asset; -pub use transact_asset::TransactAsset; -mod hrmp; -pub use hrmp::{HandleHrmpChannelAccepted, HandleHrmpChannelClosing, HandleHrmpNewChannelOpenRequest}; -mod record_xcm; -mod weight; -pub use record_xcm::RecordXcm; -#[deprecated = "Use `sp_runtime::traits::` instead"] -pub use sp_runtime::traits::{Identity, TryConvertInto as JustTry}; -pub use weight::{WeightBounds, WeightTrader}; - -pub mod prelude { - pub use super::{ - export_xcm, validate_export, AssetExchange, AssetLock, ClaimAssets, ConvertOrigin, DropAssets, Enact, Error, - ExportXcm, FeeManager, FeeReason, LockError, MatchesFungible, MatchesFungibles, MatchesNonFungible, - MatchesNonFungibles, OnResponse, ProcessTransaction, ShouldExecute, TransactAsset, VersionChangeNotifier, - WeightBounds, WeightTrader, WithOriginFilter, - }; - #[allow(deprecated)] - pub use super::{Identity, JustTry}; -} diff --git a/debug-executor/src/traits/on_response.rs b/debug-executor/src/traits/on_response.rs deleted file mode 100644 index fce5868ea..000000000 --- a/debug-executor/src/traits/on_response.rs +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright (C) 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 . - -use crate::{Junctions::Here, Xcm}; -use codec::{Decode, Encode}; -use core::result; -use frame_support::{pallet_prelude::Get, parameter_types}; -use sp_arithmetic::traits::Zero; -use sp_std::fmt::Debug; -use xcm::latest::{ - Error as XcmError, InteriorLocation, Location, QueryId, Response, Result as XcmResult, Weight, XcmContext, -}; - -/// Define what needs to be done upon receiving a query response. -pub trait OnResponse { - /// Returns `true` if we are expecting a response from `origin` for query `query_id` that was - /// queried by `querier`. - fn expecting_response(origin: &Location, query_id: u64, querier: Option<&Location>) -> bool; - /// Handler for receiving a `response` from `origin` relating to `query_id` initiated by - /// `querier`. - fn on_response( - origin: &Location, - query_id: u64, - querier: Option<&Location>, - response: Response, - max_weight: Weight, - context: &XcmContext, - ) -> Weight; -} -impl OnResponse for () { - fn expecting_response(_origin: &Location, _query_id: u64, _querier: Option<&Location>) -> bool { - false - } - - fn on_response( - _origin: &Location, - _query_id: u64, - _querier: Option<&Location>, - _response: Response, - _max_weight: Weight, - _context: &XcmContext, - ) -> Weight { - Weight::zero() - } -} - -/// Trait for a type which handles notifying a destination of XCM version changes. -pub trait VersionChangeNotifier { - /// Start notifying `location` should the XCM version of this chain change. - /// - /// When it does, this type should ensure a `QueryResponse` message is sent with the given - /// `query_id` & `max_weight` and with a `response` of `Response::Version`. This should happen - /// until/unless `stop` is called with the correct `query_id`. - /// - /// If the `location` has an ongoing notification and when this function is called, then an - /// error should be returned. - fn start(location: &Location, query_id: QueryId, max_weight: Weight, context: &XcmContext) -> XcmResult; - - /// Stop notifying `location` should the XCM change. Returns an error if there is no existing - /// notification set up. - fn stop(location: &Location, context: &XcmContext) -> XcmResult; - - /// Return true if a location is subscribed to XCM version changes. - fn is_subscribed(location: &Location) -> bool; -} - -impl VersionChangeNotifier for () { - fn start(_: &Location, _: QueryId, _: Weight, _: &XcmContext) -> XcmResult { - Err(XcmError::Unimplemented) - } - - fn stop(_: &Location, _: &XcmContext) -> XcmResult { - Err(XcmError::Unimplemented) - } - - fn is_subscribed(_: &Location) -> bool { - false - } -} - -/// The possible state of an XCM query response. -#[derive(Debug, PartialEq, Eq, Encode, Decode)] -pub enum QueryResponseStatus { - /// The response has arrived, and includes the inner Response and the block number it arrived - /// at. - Ready { response: Response, at: BlockNumber }, - /// The response has not yet arrived, the XCM might still be executing or the response might be - /// in transit. - Pending { timeout: BlockNumber }, - /// No response with the given `QueryId` was found, or the response was already queried and - /// removed from local storage. - NotFound, - /// Got an unexpected XCM version. - UnexpectedVersion, -} - -/// Provides methods to expect responses from XCMs and query their status. -pub trait QueryHandler { - type BlockNumber: Zero + Encode; - type Error; - type UniversalLocation: Get; - - /// Attempt to create a new query ID and register it as a query that is yet to respond. - fn new_query( - responder: impl Into, - timeout: Self::BlockNumber, - match_querier: impl Into, - ) -> QueryId; - - /// Consume `message` and return another which is equivalent to it except that it reports - /// back the outcome. - /// - /// - `message`: The message whose outcome should be reported. - /// - `responder`: The origin from which a response should be expected. - /// - `timeout`: The block number after which it is permissible to return `NotFound` from - /// `take_response`. - /// - /// `report_outcome` may return an error if the `responder` is not invertible. - /// - /// It is assumed that the querier of the response will be `Here`. - /// The response can be queried with `take_response`. - fn report_outcome( - message: &mut Xcm<()>, - responder: impl Into, - timeout: Self::BlockNumber, - ) -> result::Result; - - /// Attempt to remove and return the response of query with ID `query_id`. - fn take_response(id: QueryId) -> QueryResponseStatus; - - /// Makes sure to expect a response with the given id. - #[cfg(feature = "runtime-benchmarks")] - fn expect_response(id: QueryId, response: Response); -} - -parameter_types! { - pub UniversalLocation: InteriorLocation = Here; -} - -impl QueryHandler for () { - type BlockNumber = u64; - type Error = (); - type UniversalLocation = UniversalLocation; - - fn take_response(_query_id: QueryId) -> QueryResponseStatus { - QueryResponseStatus::NotFound - } - - fn new_query( - _responder: impl Into, - _timeout: Self::BlockNumber, - _match_querier: impl Into, - ) -> QueryId { - 0u64 - } - - fn report_outcome( - _message: &mut Xcm<()>, - _responder: impl Into, - _timeout: Self::BlockNumber, - ) -> Result { - Err(()) - } - - #[cfg(feature = "runtime-benchmarks")] - fn expect_response(_id: QueryId, _response: crate::Response) {} -} diff --git a/debug-executor/src/traits/process_transaction.rs b/debug-executor/src/traits/process_transaction.rs deleted file mode 100644 index 246ae5def..000000000 --- a/debug-executor/src/traits/process_transaction.rs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 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 . - -use xcm::latest::prelude::*; - -/// Provides mechanisms for transactional processing of XCM instructions. -/// -/// This trait defines the behavior required to process XCM instructions in a transactional -/// manner. Implementers of this trait can ensure that XCM instructions are executed -/// atomically, meaning they either fully succeed or fully fail without any partial effects. -/// -/// Implementers of this trait can also choose to not process XCM instructions transactionally. -/// This is useful for cases where the implementer is not able to provide transactional guarantees. -/// In this case the `IS_TRANSACTIONAL` constant should be set to `false`. -/// The `()` type implements this trait in a non-transactional manner. -pub trait ProcessTransaction { - /// Whether or not the implementor of the this trait is actually transactional. - const IS_TRANSACTIONAL: bool; - - /// Processes an XCM instruction encapsulated within the provided closure. Responsible for - /// processing an XCM instruction transactionally. If the closure returns an error, any - /// changes made during its execution should be rolled back. In the case where the - /// implementer is not able to provide transactional guarantees, the closure should be - /// executed as is. - /// # Parameters - /// - `f`: A closure that encapsulates the XCM instruction being processed. It will return a - /// `Result` indicating the success or failure of the instruction. - /// - /// # Returns - /// - A `Result` indicating the overall success or failure of the transactional process. - fn process(f: F) -> Result<(), XcmError> - where - F: FnOnce() -> Result<(), XcmError>; -} - -impl ProcessTransaction for () { - const IS_TRANSACTIONAL: bool = false; - - fn process(f: F) -> Result<(), XcmError> - where - F: FnOnce() -> Result<(), XcmError>, - { - f() - } -} diff --git a/debug-executor/src/traits/record_xcm.rs b/debug-executor/src/traits/record_xcm.rs deleted file mode 100644 index d0b5bf92d..000000000 --- a/debug-executor/src/traits/record_xcm.rs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (C) 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 . - -//! Trait for recording XCMs and a dummy implementation. - -use xcm::latest::Xcm; - -/// Trait for recording XCMs. -pub trait RecordXcm { - /// Whether or not we should record incoming XCMs. - fn should_record() -> bool; - /// Enable or disable recording. - fn set_record_xcm(enabled: bool); - /// Get recorded XCM. - /// Returns `None` if no message was sent, or if recording was off. - fn recorded_xcm() -> Option>; - /// Record `xcm`. - fn record(xcm: Xcm<()>); -} - -impl RecordXcm for () { - fn should_record() -> bool { - false - } - - fn set_record_xcm(_: bool) {} - - fn recorded_xcm() -> Option> { - None - } - - fn record(_: Xcm<()>) {} -} diff --git a/debug-executor/src/traits/should_execute.rs b/debug-executor/src/traits/should_execute.rs deleted file mode 100644 index e76d56bfe..000000000 --- a/debug-executor/src/traits/should_execute.rs +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (C) 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 . - -use frame_support::traits::ProcessMessageError; -use sp_std::result::Result; -use xcm::latest::{Instruction, Location, Weight, XcmHash}; - -/// Properties of an XCM message and its imminent execution. -#[derive(Clone, Eq, PartialEq, Debug)] -pub struct Properties { - /// The amount of weight that the system has determined this - /// message may utilize in its execution. Typically non-zero only because of prior fee - /// payment, but could in principle be due to other factors. - pub weight_credit: Weight, - /// The identity of the message, if one is known. If left as `None`, then it will generally - /// default to the hash of the message which may be non-unique. - pub message_id: Option, -} - -/// Trait to determine whether the execution engine should actually execute a given XCM. -/// -/// Can be amalgamated into a tuple to have multiple trials. If any of the tuple elements returns -/// `Ok(())`, the execution stops. Else, `Err(_)` is returned if all elements reject the message. -pub trait ShouldExecute { - /// Returns `Ok(())` if the given `message` may be executed. - /// - /// - `origin`: The origin (sender) of the message. - /// - `instructions`: The message itself. - /// - `max_weight`: The (possibly over-) estimation of the weight of execution of the message. - /// - `properties`: Various pre-established properties of the message which may be mutated by - /// this API. - fn should_execute( - origin: &Location, - instructions: &mut [Instruction], - max_weight: Weight, - properties: &mut Properties, - ) -> Result<(), ProcessMessageError>; -} - -#[impl_trait_for_tuples::impl_for_tuples(30)] -impl ShouldExecute for Tuple { - fn should_execute( - origin: &Location, - instructions: &mut [Instruction], - max_weight: Weight, - properties: &mut Properties, - ) -> Result<(), ProcessMessageError> { - for_tuples!( #( - match Tuple::should_execute(origin, instructions, max_weight, properties) { - Ok(()) => return Ok(()), - _ => (), - } - )* ); - log::trace!( - target: "xcm::should_execute", - "did not pass barrier: origin: {:?}, instructions: {:?}, max_weight: {:?}, properties: {:?}", - origin, - instructions, - max_weight, - properties, - ); - Err(ProcessMessageError::Unsupported) - } -} - -/// Trait to determine whether the execution engine is suspended from executing a given XCM. -/// -/// The trait method is given the same parameters as `ShouldExecute::should_execute`, so that the -/// implementer will have all the context necessary to determine whether or not to suspend the -/// XCM executor. -/// -/// Can be chained together in tuples to have multiple rounds of checks. If all of the tuple -/// elements returns false, then execution is not suspended. Otherwise, execution is suspended -/// if any of the tuple elements returns true. -pub trait CheckSuspension { - fn is_suspended( - origin: &Location, - instructions: &mut [Instruction], - max_weight: Weight, - properties: &mut Properties, - ) -> bool; -} - -#[impl_trait_for_tuples::impl_for_tuples(30)] -impl CheckSuspension for Tuple { - fn is_suspended( - origin: &Location, - instruction: &mut [Instruction], - max_weight: Weight, - properties: &mut Properties, - ) -> bool { - for_tuples!( #( - if Tuple::is_suspended(origin, instruction, max_weight, properties) { - return true - } - )* ); - - false - } -} diff --git a/debug-executor/src/traits/token_matching.rs b/debug-executor/src/traits/token_matching.rs deleted file mode 100644 index 6300b00aa..000000000 --- a/debug-executor/src/traits/token_matching.rs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (C) 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 . - -use sp_std::result; -use xcm::latest::prelude::*; - -pub trait MatchesFungible { - fn matches_fungible(a: &Asset) -> Option; -} - -#[impl_trait_for_tuples::impl_for_tuples(30)] -impl MatchesFungible for Tuple { - fn matches_fungible(a: &Asset) -> Option { - for_tuples!( #( - match Tuple::matches_fungible(a) { o @ Some(_) => return o, _ => () } - )* ); - log::trace!(target: "xcm::matches_fungible", "did not match fungible asset: {:?}", &a); - None - } -} - -pub trait MatchesNonFungible { - fn matches_nonfungible(a: &Asset) -> Option; -} - -#[impl_trait_for_tuples::impl_for_tuples(30)] -impl MatchesNonFungible for Tuple { - fn matches_nonfungible(a: &Asset) -> Option { - for_tuples!( #( - match Tuple::matches_nonfungible(a) { o @ Some(_) => return o, _ => () } - )* ); - log::trace!(target: "xcm::matches_non_fungible", "did not match non-fungible asset: {:?}", &a); - None - } -} - -/// Errors associated with [`MatchesFungibles`] operation. -#[derive(Debug, PartialEq, Eq)] -pub enum Error { - /// The given asset is not handled. (According to [`XcmError::AssetNotFound`]) - AssetNotHandled, - /// `Location` to `AccountId` conversion failed. - AccountIdConversionFailed, - /// `u128` amount to currency `Balance` conversion failed. - AmountToBalanceConversionFailed, - /// `Location` to `AssetId`/`ClassId` conversion failed. - AssetIdConversionFailed, - /// `AssetInstance` to non-fungibles instance ID conversion failed. - InstanceConversionFailed, -} - -impl From for XcmError { - fn from(e: Error) -> Self { - use XcmError::FailedToTransactAsset; - match e { - Error::AssetNotHandled => XcmError::AssetNotFound, - Error::AccountIdConversionFailed => FailedToTransactAsset("AccountIdConversionFailed"), - Error::AmountToBalanceConversionFailed => FailedToTransactAsset("AmountToBalanceConversionFailed"), - Error::AssetIdConversionFailed => FailedToTransactAsset("AssetIdConversionFailed"), - Error::InstanceConversionFailed => FailedToTransactAsset("InstanceConversionFailed"), - } - } -} - -pub trait MatchesFungibles { - fn matches_fungibles(a: &Asset) -> result::Result<(AssetId, Balance), Error>; -} - -#[impl_trait_for_tuples::impl_for_tuples(30)] -impl MatchesFungibles for Tuple { - fn matches_fungibles(a: &Asset) -> result::Result<(AssetId, Balance), Error> { - for_tuples!( #( - match Tuple::matches_fungibles(a) { o @ Ok(_) => return o, _ => () } - )* ); - log::trace!(target: "xcm::matches_fungibles", "did not match fungibles asset: {:?}", &a); - Err(Error::AssetNotHandled) - } -} - -pub trait MatchesNonFungibles { - fn matches_nonfungibles(a: &Asset) -> result::Result<(AssetId, Instance), Error>; -} - -#[impl_trait_for_tuples::impl_for_tuples(30)] -impl MatchesNonFungibles for Tuple { - fn matches_nonfungibles(a: &Asset) -> result::Result<(AssetId, Instance), Error> { - for_tuples!( #( - match Tuple::matches_nonfungibles(a) { o @ Ok(_) => return o, _ => () } - )* ); - log::trace!(target: "xcm::matches_non_fungibles", "did not match fungibles asset: {:?}", &a); - Err(Error::AssetNotHandled) - } -} diff --git a/debug-executor/src/traits/transact_asset.rs b/debug-executor/src/traits/transact_asset.rs deleted file mode 100644 index b2c14c694..000000000 --- a/debug-executor/src/traits/transact_asset.rs +++ /dev/null @@ -1,411 +0,0 @@ -// Copyright (C) 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 . - -use crate::AssetsInHolding; -use sp_std::result::Result; -use xcm::latest::{Asset, Error as XcmError, Location, Result as XcmResult, XcmContext}; - -/// Facility for asset transacting. -/// -/// This should work with as many asset/location combinations as possible. Locations to support may -/// include non-account locations such as a `[Junction::Parachain]`. Different -/// chains may handle them in different ways. -/// -/// Can be amalgamated as a tuple of items that implement this trait. In such executions, if any of -/// the transactors returns `Ok(())`, then it will short circuit. Else, execution is passed to the -/// next transactor. -pub trait TransactAsset { - /// Ensure that `check_in` will do as expected. - /// - /// When composed as a tuple, all type-items are called and at least one must result in `Ok`. - fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { - Err(XcmError::Unimplemented) - } - - /// An asset has been teleported in from the given origin. This should do whatever housekeeping - /// is needed. - /// - /// NOTE: This will make only a best-effort at bookkeeping. The caller should ensure that - /// `can_check_in` has returned with `Ok` in order to guarantee that this operation proceeds - /// properly. - /// - /// Implementation note: In general this will do one of two things: On chains where the asset is - /// native, it will reduce the assets from a special "teleported" account so that a) - /// total-issuance is preserved; and b) to ensure that no more assets can be teleported in than - /// were teleported out overall (this should not be needed if the teleporting chains are to be - /// trusted, but better to be safe than sorry). On chains where the asset is not native then it - /// will generally just be a no-op. - /// - /// When composed as a tuple, all type-items are called. It is up to the implementer that there - /// exists no value for `_what` which can cause side-effects for more than one of the - /// type-items. - fn check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) {} - - /// Ensure that `check_out` will do as expected. - /// - /// When composed as a tuple, all type-items are called and at least one must result in `Ok`. - fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { - Err(XcmError::Unimplemented) - } - - /// An asset has been teleported out to the given destination. This should do whatever - /// housekeeping is needed. - /// - /// Implementation note: In general this will do one of two things: On chains where the asset is - /// native, it will increase the assets in a special "teleported" account so that a) - /// total-issuance is preserved; and b) to ensure that no more assets can be teleported in than - /// were teleported out overall (this should not be needed if the teleporting chains are to be - /// trusted, but better to be safe than sorry). On chains where the asset is not native then it - /// will generally just be a no-op. - /// - /// When composed as a tuple, all type-items are called. It is up to the implementer that there - /// exists no value for `_what` which can cause side-effects for more than one of the - /// type-items. - fn check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) {} - - /// Deposit the `what` asset into the account of `who`. - /// - /// Implementations should return `XcmError::FailedToTransactAsset` if deposit failed. - fn deposit_asset(_what: &Asset, _who: &Location, _context: Option<&XcmContext>) -> XcmResult { - Err(XcmError::Unimplemented) - } - - /// Withdraw the given asset from the consensus system. Return the actual asset(s) withdrawn, - /// which should always be equal to `_what`. - /// - /// The XCM `_maybe_context` parameter may be `None` when the caller of `withdraw_asset` is - /// outside of the context of a currently-executing XCM. An example will be the `charge_fees` - /// method in the XCM executor. - /// - /// Implementations should return `XcmError::FailedToTransactAsset` if withdraw failed. - fn withdraw_asset( - _what: &Asset, - _who: &Location, - _maybe_context: Option<&XcmContext>, - ) -> Result { - Err(XcmError::Unimplemented) - } - - /// Move an `asset` `from` one location in `to` another location. - /// - /// Returns `XcmError::FailedToTransactAsset` if transfer failed. - /// - /// ## Notes - /// This function is meant to only be implemented by the type implementing `TransactAsset`, and - /// not be called directly. Most common API usages will instead call `transfer_asset`, which in - /// turn has a default implementation that calls `internal_transfer_asset`. As such, **please - /// do not call this method directly unless you know what you're doing**. - fn internal_transfer_asset( - _asset: &Asset, - _from: &Location, - _to: &Location, - _context: &XcmContext, - ) -> Result { - Err(XcmError::Unimplemented) - } - - /// Move an `asset` `from` one location in `to` another location. - /// - /// Attempts to use `internal_transfer_asset` and if not available then falls back to using a - /// two-part withdraw/deposit. - fn transfer_asset( - asset: &Asset, - from: &Location, - to: &Location, - context: &XcmContext, - ) -> Result { - match Self::internal_transfer_asset(asset, from, to, context) { - Err(XcmError::AssetNotFound | XcmError::Unimplemented) => { - let assets = Self::withdraw_asset(asset, from, Some(context))?; - // Not a very forgiving attitude; once we implement roll-backs then it'll be nicer. - Self::deposit_asset(asset, to, Some(context))?; - Ok(assets) - }, - result => result, - } - } -} - -#[impl_trait_for_tuples::impl_for_tuples(30)] -impl TransactAsset for Tuple { - fn can_check_in(origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult { - for_tuples!( #( - match Tuple::can_check_in(origin, what, context) { - Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (), - r => return r, - } - )* ); - log::trace!( - target: "xcm::TransactAsset::can_check_in", - "asset not found: what: {:?}, origin: {:?}, context: {:?}", - what, - origin, - context, - ); - Err(XcmError::AssetNotFound) - } - - fn check_in(origin: &Location, what: &Asset, context: &XcmContext) { - for_tuples!( #( - Tuple::check_in(origin, what, context); - )* ); - } - - fn can_check_out(dest: &Location, what: &Asset, context: &XcmContext) -> XcmResult { - for_tuples!( #( - match Tuple::can_check_out(dest, what, context) { - Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (), - r => return r, - } - )* ); - log::trace!( - target: "xcm::TransactAsset::can_check_out", - "asset not found: what: {:?}, dest: {:?}, context: {:?}", - what, - dest, - context, - ); - Err(XcmError::AssetNotFound) - } - - fn check_out(dest: &Location, what: &Asset, context: &XcmContext) { - for_tuples!( #( - Tuple::check_out(dest, what, context); - )* ); - } - - fn deposit_asset(what: &Asset, who: &Location, context: Option<&XcmContext>) -> XcmResult { - for_tuples!( #( - match Tuple::deposit_asset(what, who, context) { - Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (), - r => return r, - } - )* ); - log::trace!( - target: "xcm::TransactAsset::deposit_asset", - "did not deposit asset: what: {:?}, who: {:?}, context: {:?}", - what, - who, - context, - ); - Err(XcmError::AssetNotFound) - } - - fn withdraw_asset( - what: &Asset, - who: &Location, - maybe_context: Option<&XcmContext>, - ) -> Result { - for_tuples!( #( - match Tuple::withdraw_asset(what, who, maybe_context) { - Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (), - r => return r, - } - )* ); - log::trace!( - target: "xcm::TransactAsset::withdraw_asset", - "did not withdraw asset: what: {:?}, who: {:?}, maybe_context: {:?}", - what, - who, - maybe_context, - ); - Err(XcmError::AssetNotFound) - } - - fn internal_transfer_asset( - what: &Asset, - from: &Location, - to: &Location, - context: &XcmContext, - ) -> Result { - for_tuples!( #( - match Tuple::internal_transfer_asset(what, from, to, context) { - Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (), - r => return r, - } - )* ); - log::trace!( - target: "xcm::TransactAsset::internal_transfer_asset", - "did not transfer asset: what: {:?}, from: {:?}, to: {:?}, context: {:?}", - what, - from, - to, - context, - ); - Err(XcmError::AssetNotFound) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use xcm::latest::Junctions::Here; - - pub struct UnimplementedTransactor; - impl TransactAsset for UnimplementedTransactor {} - - pub struct NotFoundTransactor; - impl TransactAsset for NotFoundTransactor { - fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { - Err(XcmError::AssetNotFound) - } - - fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { - Err(XcmError::AssetNotFound) - } - - fn deposit_asset(_what: &Asset, _who: &Location, _context: Option<&XcmContext>) -> XcmResult { - Err(XcmError::AssetNotFound) - } - - fn withdraw_asset( - _what: &Asset, - _who: &Location, - _context: Option<&XcmContext>, - ) -> Result { - Err(XcmError::AssetNotFound) - } - - fn internal_transfer_asset( - _what: &Asset, - _from: &Location, - _to: &Location, - _context: &XcmContext, - ) -> Result { - Err(XcmError::AssetNotFound) - } - } - - pub struct OverflowTransactor; - impl TransactAsset for OverflowTransactor { - fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { - Err(XcmError::Overflow) - } - - fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { - Err(XcmError::Overflow) - } - - fn deposit_asset(_what: &Asset, _who: &Location, _context: Option<&XcmContext>) -> XcmResult { - Err(XcmError::Overflow) - } - - fn withdraw_asset( - _what: &Asset, - _who: &Location, - _context: Option<&XcmContext>, - ) -> Result { - Err(XcmError::Overflow) - } - - fn internal_transfer_asset( - _what: &Asset, - _from: &Location, - _to: &Location, - _context: &XcmContext, - ) -> Result { - Err(XcmError::Overflow) - } - } - - pub struct SuccessfulTransactor; - impl TransactAsset for SuccessfulTransactor { - fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { - Ok(()) - } - - fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { - Ok(()) - } - - fn deposit_asset(_what: &Asset, _who: &Location, _context: Option<&XcmContext>) -> XcmResult { - Ok(()) - } - - fn withdraw_asset( - _what: &Asset, - _who: &Location, - _context: Option<&XcmContext>, - ) -> Result { - Ok(AssetsInHolding::default()) - } - - fn internal_transfer_asset( - _what: &Asset, - _from: &Location, - _to: &Location, - _context: &XcmContext, - ) -> Result { - Ok(AssetsInHolding::default()) - } - } - - #[test] - fn defaults_to_asset_not_found() { - type MultiTransactor = (UnimplementedTransactor, NotFoundTransactor, UnimplementedTransactor); - - assert_eq!( - MultiTransactor::deposit_asset( - &(Here, 1u128).into(), - &Here.into(), - Some(&XcmContext::with_message_id([0; 32])), - ), - Err(XcmError::AssetNotFound) - ); - } - - #[test] - fn unimplemented_and_not_found_continue_iteration() { - type MultiTransactor = (UnimplementedTransactor, NotFoundTransactor, SuccessfulTransactor); - - assert_eq!( - MultiTransactor::deposit_asset( - &(Here, 1u128).into(), - &Here.into(), - Some(&XcmContext::with_message_id([0; 32])), - ), - Ok(()) - ); - } - - #[test] - fn unexpected_error_stops_iteration() { - type MultiTransactor = (OverflowTransactor, SuccessfulTransactor); - - assert_eq!( - MultiTransactor::deposit_asset( - &(Here, 1u128).into(), - &Here.into(), - Some(&XcmContext::with_message_id([0; 32])), - ), - Err(XcmError::Overflow) - ); - } - - #[test] - fn success_stops_iteration() { - type MultiTransactor = (SuccessfulTransactor, OverflowTransactor); - - assert_eq!( - MultiTransactor::deposit_asset( - &(Here, 1u128).into(), - &Here.into(), - Some(&XcmContext::with_message_id([0; 32])), - ), - Ok(()), - ); - } -} diff --git a/debug-executor/src/traits/weight.rs b/debug-executor/src/traits/weight.rs deleted file mode 100644 index df7be6668..000000000 --- a/debug-executor/src/traits/weight.rs +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (C) 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 . - -use crate::AssetsInHolding; -use sp_std::result::Result; -use xcm::latest::{prelude::*, Weight}; - -/// Determine the weight of an XCM message. -pub trait WeightBounds { - /// Return the maximum amount of weight that an attempted execution of this message could - /// consume. - fn weight(message: &mut Xcm) -> Result; - - /// Return the maximum amount of weight that an attempted execution of this instruction could - /// consume. - fn instr_weight(instruction: &Instruction) -> Result; -} - -#[allow(unused)] -/// A means of getting approximate weight consumption for a given destination message executor and a -/// message. -pub trait UniversalWeigher { - /// Get the upper limit of weight required for `dest` to execute `message`. - fn weigh(dest: impl Into, message: Xcm<()>) -> Result; -} - -/// Charge for weight in order to execute XCM. -/// -/// A `WeightTrader` may also be put into a tuple, in which case the default behavior of -/// `buy_weight` and `refund_weight` would be to attempt to call each tuple element's own -/// implementation of these two functions, in the order of which they appear in the tuple, -/// returning early when a successful result is returned. -pub trait WeightTrader: Sized { - /// Create a new trader instance. - fn new() -> Self; - - /// Purchase execution weight credit in return for up to a given `payment`. If less of the - /// payment is required then the surplus is returned. If the `payment` cannot be used to pay - /// for the `weight`, then an error is returned. - fn buy_weight( - &mut self, - weight: Weight, - payment: AssetsInHolding, - context: &XcmContext, - ) -> Result; - - /// Attempt a refund of `weight` into some asset. The caller does not guarantee that the weight - /// was purchased using `buy_weight`. - /// - /// Default implementation refunds nothing. - fn refund_weight(&mut self, _weight: Weight, _context: &XcmContext) -> Option { - None - } -} - -#[impl_trait_for_tuples::impl_for_tuples(30)] -impl WeightTrader for Tuple { - fn new() -> Self { - for_tuples!( ( #( Tuple::new() ),* ) ) - } - - fn buy_weight( - &mut self, - weight: Weight, - payment: AssetsInHolding, - context: &XcmContext, - ) -> Result { - let mut too_expensive_error_found = false; - let mut last_error = None; - for_tuples!( #( - match Tuple.buy_weight(weight, payment.clone(), context) { - Ok(assets) => return Ok(assets), - Err(e) => { - if let XcmError::TooExpensive = e { - too_expensive_error_found = true; - } - last_error = Some(e) - } - } - )* ); - - log::trace!(target: "xcm::buy_weight", "last_error: {:?}, too_expensive_error_found: {}", last_error, too_expensive_error_found); - - // if we have multiple traders, and first one returns `TooExpensive` and others fail e.g. - // `AssetNotFound` then it is more accurate to return `TooExpensive` then `AssetNotFound` - Err(if too_expensive_error_found { - XcmError::TooExpensive - } else { - last_error.unwrap_or(XcmError::TooExpensive) - }) - } - - fn refund_weight(&mut self, weight: Weight, context: &XcmContext) -> Option { - for_tuples!( #( - if let Some(asset) = Tuple.refund_weight(weight, context) { - return Some(asset); - } - )* ); - None - } -} diff --git a/integration-tests/chopsticks/overrides/polimec.ts b/integration-tests/chopsticks/overrides/polimec.ts index 79eded3a0..70d9007ed 100644 --- a/integration-tests/chopsticks/overrides/polimec.ts +++ b/integration-tests/chopsticks/overrides/polimec.ts @@ -23,25 +23,25 @@ const dot_location = { }, }; -const weth_location = { +export const weth_location = { parents: 2, interior: { x2: [ { globalConsensus: { ethereum: { - chainId: 1n - } - } + chainId: 1n, + }, + }, }, { accountKey20: { - key: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - } - } - ] - } -} + key: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + }, + }, + ], + }, +}; export const polimec_storage = { System: { @@ -85,57 +85,6 @@ export const polimec_storage = { ], ], Asset: [ - // [ - // [usdc_location], - // { - // owner: Accounts.ALICE, - // issuer: Accounts.ALICE, - // admin: Accounts.ALICE, - // freezer: Accounts.ALICE, - // supply: INITIAL_BALANCES.USDC, - // deposit: 0n, - // min_balance: 70000n, - // is_sufficient: true, - // accounts: 1, - // sufficients: 1, - // approvals: 0, - // status: 'Live', - // }, - // ], - // [ - // [usdt_location], - // { - // owner: Accounts.ALICE, - // issuer: Accounts.ALICE, - // admin: Accounts.ALICE, - // freezer: Accounts.ALICE, - // supply: INITIAL_BALANCES.USDT, - // deposit: 0n, - // min_balance: 70000n, - // is_sufficient: true, - // accounts: 1, - // sufficients: 1, - // approvals: 0, - // status: 'Live', - // }, - // ], - // [ - // [dot_location], - // { - // owner: Accounts.ALICE, - // issuer: Accounts.ALICE, - // admin: Accounts.ALICE, - // freezer: Accounts.ALICE, - // supply: INITIAL_BALANCES.DOT, - // deposit: 0n, - // min_balance: 100000000n, - // is_sufficient: true, - // accounts: 1, - // sufficients: 1, - // approvals: 0, - // status: 'Live', - // }, - // ], [ [weth_location], { @@ -143,9 +92,9 @@ export const polimec_storage = { issuer: Accounts.ALICE, admin: Accounts.ALICE, freezer: Accounts.ALICE, - supply: INITIAL_BALANCES.WETH, + supply: 100n * INITIAL_BALANCES.WETH, deposit: 0n, - min_balance: 1000000n, + min_balance: 15000000000000n, is_sufficient: true, accounts: 1, sufficients: 1, @@ -155,10 +104,7 @@ export const polimec_storage = { ], ], Metadata: [ - // [[usdc_location], { symbol: 'USDC', name: 'USDC', decimals: 6, isFrozen: false }], - // [[usdt_location], { symbol: 'USDT', name: 'USDC', decimals: 6, isFrozen: false }], - // [[dot_location], { symbol: 'DOT', name: 'DOT', decimals: 10, isFrozen: false }], - [[weth_location], { symbol: 'WETH', name: 'WETH', decimals: 18, isFrozen: false }], + [[weth_location], { symbol: 'Wrapped Ether', name: 'WETH', decimals: 18, isFrozen: false }], ], }, } as const; diff --git a/integration-tests/chopsticks/overrides/polkadot-hub.ts b/integration-tests/chopsticks/overrides/polkadot-hub.ts index 0bfbd4348..66a718b5e 100644 --- a/integration-tests/chopsticks/overrides/polkadot-hub.ts +++ b/integration-tests/chopsticks/overrides/polkadot-hub.ts @@ -1,7 +1,6 @@ import { INITIAL_BALANCES } from '@/constants'; import { Accounts, Asset } from '@/types'; - const weth_location = { parents: 2, interior: { @@ -9,18 +8,18 @@ const weth_location = { { globalConsensus: { ethereum: { - chainId: 1n - } - } + chainId: 1n, + }, + }, }, { accountKey20: { - key: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - } - } - ] - } -} + key: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + }, + }, + ], + }, +}; export const polkadot_hub_storage = { System: { @@ -53,11 +52,6 @@ export const polkadot_hub_storage = { ], }, ForeignAssets: { - Account: [ - [[weth_location, Accounts.ALICE], { balance: INITIAL_BALANCES.WETH }] - ], - Asset: [ - [[weth_location], { supply: INITIAL_BALANCES.WETH }] - ] - } + Account: [[[weth_location, Accounts.ALICE], { balance: INITIAL_BALANCES.WETH }]], + }, } as const; diff --git a/integration-tests/chopsticks/src/constants.ts b/integration-tests/chopsticks/src/constants.ts index 9c73d0b37..4c8b2f611 100644 --- a/integration-tests/chopsticks/src/constants.ts +++ b/integration-tests/chopsticks/src/constants.ts @@ -11,7 +11,7 @@ export const INITIAL_BALANCES = { export const TRANSFER_AMOUNTS = { TOKENS: 2n * 10n ** 6n, // e.g. 2 USDC NATIVE: 2n * 10n ** 10n, // e.g. 2 DOT - BRIDGED: 1n * 10n ** 10n, // e.g. 0.1 WETH + BRIDGED: 1n * 10n ** 17n, // e.g. 0.1 WETH } as const; export const DERIVE_PATHS = { diff --git a/integration-tests/chopsticks/src/managers/BaseManager.ts b/integration-tests/chopsticks/src/managers/BaseManager.ts index a01552486..7a1ebca39 100644 --- a/integration-tests/chopsticks/src/managers/BaseManager.ts +++ b/integration-tests/chopsticks/src/managers/BaseManager.ts @@ -1,12 +1,11 @@ import { DERIVE_PATHS } from '@/constants'; -import { - type Accounts, - type Asset, - AssetLocation, - type AssetSourceRelation, - type ChainClient, - type ChainToDefinition, - type Chains, +import type { + Accounts, + Asset, + AssetSourceRelation, + ChainClient, + ChainToDefinition, + Chains, } from '@/types'; import { sr25519CreateDerive } from '@polkadot-labs/hdkd'; import { DEV_PHRASE, entropyToMiniSecret, mnemonicToEntropy } from '@polkadot-labs/hdkd-helpers'; @@ -75,26 +74,9 @@ export abstract class BaseChainManager { return events[0]?.payload.actual_fee || 0n; } - // Make sure to override this in the other managers abstract getAssetSourceRelation(asset: Asset): AssetSourceRelation; - async getAssetBalanceOf(account: Accounts, asset: Asset): Promise { - const chain = this.getChainType(); - const api = this.getApi(chain); - const asset_source_relation = this.getAssetSourceRelation(asset); - const asset_location = AssetLocation(asset, asset_source_relation); - const account_balances_result = await api.apis.FungiblesApi.query_account_balances(account); - - if (account_balances_result.success === true && account_balances_result.value.type === 'V4') { - const assets = account_balances_result.value.value; - for (const asset of assets) { - if (asset.id === asset_location && asset.fun.type === 'Fungible') { - return asset.fun.value; - } - } - } - return 0n; - } + abstract getAssetBalanceOf(account: Accounts, asset: Asset): Promise; // @ts-expect-error - TODO: Not sure which is the correct type for this abstract getXcmPallet(); diff --git a/integration-tests/chopsticks/src/managers/PolimecManager.ts b/integration-tests/chopsticks/src/managers/PolimecManager.ts index fc75e9912..210dd3eef 100644 --- a/integration-tests/chopsticks/src/managers/PolimecManager.ts +++ b/integration-tests/chopsticks/src/managers/PolimecManager.ts @@ -1,10 +1,9 @@ import { type Accounts, Asset, AssetLocation, AssetSourceRelation, Chains } from '@/types'; +import { flatObject } from '@/utils.ts'; import { polimec } from '@polkadot-api/descriptors'; -import { isEqual } from 'lodash'; import { createClient } from 'polkadot-api'; import { getWsProvider } from 'polkadot-api/ws-provider/web'; import { BaseChainManager } from './BaseManager'; -import { normalizeForComparison } from '@/utils.ts'; export class PolimecManager extends BaseChainManager { connect() { @@ -55,25 +54,15 @@ export class PolimecManager extends BaseChainManager { const asset_source_relation = this.getAssetSourceRelation(asset); const asset_location = AssetLocation(asset, asset_source_relation).value; const account_balances_result = await api.apis.FungiblesApi.query_account_balances(account); - console.log('Requested asset location in PolimecManager'); - console.dir(asset_location, { depth: null, colors: true }); - console.log('\n\n'); if (account_balances_result.success === true && account_balances_result.value.type === 'V4') { const assets = account_balances_result.value.value; for (const asset of assets) { - if (Bun.deepEquals(normalizeForComparison(asset.id), normalizeForComparison(asset_location))) { - console.log('Found asset. Balance is: ', asset.fun.value); - console.dir(asset, { depth: null, colors: true }); - console.log('\n\n'); - return asset.fun.value; + if (Bun.deepEquals(flatObject(asset.id), flatObject(asset_location))) { + return asset.fun.value as bigint; } - console.log('Not it chief: \n'); - console.dir(asset, { depth: null, colors: true }); - console.log('\n\n'); } } - console.log('Asset not found'); - console.log('\n\n'); + console.log('Asset not found using query_account_balances Runtime API'); return 0n; } diff --git a/integration-tests/chopsticks/src/managers/PolkadotHubManager.ts b/integration-tests/chopsticks/src/managers/PolkadotHubManager.ts index f74d522a8..c72b29cbb 100644 --- a/integration-tests/chopsticks/src/managers/PolkadotHubManager.ts +++ b/integration-tests/chopsticks/src/managers/PolkadotHubManager.ts @@ -1,11 +1,9 @@ import { type Accounts, Asset, AssetLocation, AssetSourceRelation, Chains } from '@/types'; +import { flatObject } from '@/utils.ts'; import { pah } from '@polkadot-api/descriptors'; import { createClient } from 'polkadot-api'; import { getWsProvider } from 'polkadot-api/ws-provider/web'; import { BaseChainManager } from './BaseManager'; -import { normalizeForComparison } from '@/utils.ts'; - - export class PolkadotHubManager extends BaseChainManager { connect() { @@ -59,23 +57,12 @@ export class PolkadotHubManager extends BaseChainManager { if (account_balances_result.success === true && account_balances_result.value.type === 'V4') { const assets = account_balances_result.value.value; for (const asset of assets) { - if ( - Bun.deepEquals(normalizeForComparison(asset.id), normalizeForComparison(asset_location)) - ) { - console.log('Found asset. Balance is: ', asset.fun.value); - console.dir(normalizeForComparison(asset), { depth: null, colors: true }); - console.log('\n\n'); - return asset.fun.value; + if (Bun.deepEquals(flatObject(asset.id), flatObject(asset_location))) { + return asset.fun.value as bigint; } - console.log('Not it chief: \n'); - console.dir(normalizeForComparison(asset.id), { depth: null, colors: true }); - - console.log('\n\n'); - } } - console.log('Asset not found'); - console.log('\n\n'); + console.log('Asset not found using query_account_balances Runtime API'); return 0n; } diff --git a/integration-tests/chopsticks/src/managers/PolkadotManager.ts b/integration-tests/chopsticks/src/managers/PolkadotManager.ts index e1b9bead6..bc44e1b4d 100644 --- a/integration-tests/chopsticks/src/managers/PolkadotManager.ts +++ b/integration-tests/chopsticks/src/managers/PolkadotManager.ts @@ -1,4 +1,3 @@ -import { TRANSFER_AMOUNTS } from '@/constants.ts'; import { type Accounts, Asset, AssetSourceRelation, Chains } from '@/types'; import { polkadot } from '@polkadot-api/descriptors'; import { createClient } from 'polkadot-api'; diff --git a/integration-tests/chopsticks/src/setup.ts b/integration-tests/chopsticks/src/setup.ts index 7a6e50f2f..66bd2e8ed 100644 --- a/integration-tests/chopsticks/src/setup.ts +++ b/integration-tests/chopsticks/src/setup.ts @@ -1,3 +1,4 @@ +import { expect } from 'bun:test'; import { setupWithServer } from '@acala-network/chopsticks'; import { type Blockchain, @@ -47,7 +48,12 @@ export class ChainSetup { console.log('✅ HRMP channels created'); // Needed to execute storage migrations within the new WASM before running tests. - await this.polimec?.newBlock(); + const head = this.polimec.head; + console.log(`✅ Polimec chain is at block ${head.number}`); + console.log('✅ Running storage migrations...'); + const new_block = await this.polimec?.newBlock(); + console.log(`✅ Polimec chain is at block ${new_block.number}`); + expect(new_block.number === head.number + 1, 'Block number should be incremented by 1'); } async cleanup() { @@ -83,7 +89,6 @@ export class ChainSetup { 'wasm-override': POLIMEC_WASM, 'import-storage': polimec_storage, 'build-block-mode': BuildBlockMode.Instant, - 'runtime-log-level': 5, }); } @@ -93,7 +98,6 @@ export class ChainSetup { port: 8001, 'import-storage': polkadot_hub_storage, 'build-block-mode': BuildBlockMode.Instant, - 'runtime-log-level': 5, }); } diff --git a/integration-tests/chopsticks/src/tests/polimec.test.ts b/integration-tests/chopsticks/src/tests/polimec.test.ts index b053e10b7..51df14669 100644 --- a/integration-tests/chopsticks/src/tests/polimec.test.ts +++ b/integration-tests/chopsticks/src/tests/polimec.test.ts @@ -1,62 +1,62 @@ -import { afterAll, beforeAll, beforeEach, describe, test } from 'bun:test'; -import { TRANSFER_AMOUNTS } from '@/constants'; -import { createChainManager } from '@/managers/Factory'; -import { polimec_storage } from '@/polimec'; -import { ChainSetup } from '@/setup'; -import { PolimecToHubTransfer } from '@/transfers/PolimecToHub'; -import { Accounts, Asset, AssetSourceRelation, Chains } from '@/types'; +// import { afterAll, beforeAll, beforeEach, describe, test } from 'bun:test'; +// import { TRANSFER_AMOUNTS } from '@/constants'; +// import { createChainManager } from '@/managers/Factory'; +// import { polimec_storage } from '@/polimec'; +// import { ChainSetup } from '@/setup'; +// import { PolimecToHubTransfer } from '@/transfers/PolimecToHub'; +// import { Accounts, Asset, AssetSourceRelation, Chains } from '@/types'; -describe('Polimec -> Hub Transfer Tests', () => { - const sourceManager = createChainManager(Chains.Polimec); - const destManager = createChainManager(Chains.PolkadotHub); - const transferTest = new PolimecToHubTransfer(sourceManager, destManager); - const chainSetup = new ChainSetup(); +// describe('Polimec -> Hub Transfer Tests', () => { +// const sourceManager = createChainManager(Chains.Polimec); +// const destManager = createChainManager(Chains.PolkadotHub); +// const transferTest = new PolimecToHubTransfer(sourceManager, destManager); +// const chainSetup = new ChainSetup(); - beforeAll(async () => await chainSetup.initialize(polimec_storage)); - beforeEach(() => { - sourceManager.connect(); - destManager.connect(); - }); - afterAll(async () => await chainSetup.cleanup()); +// beforeAll(async () => await chainSetup.initialize(polimec_storage)); +// beforeEach(() => { +// sourceManager.connect(); +// destManager.connect(); +// }); +// afterAll(async () => await chainSetup.cleanup()); - async function getBalance(account: Accounts, asset: Asset) { - return await sourceManager.getAssetBalanceOf(account, asset); - } - test('Balance query', () => getBalance(Accounts.BOB, Asset.USDT), { timeout: 250000000 }); +// async function getBalance(account: Accounts, asset: Asset) { +// return await sourceManager.getAssetBalanceOf(account, asset); +// } +// test('Balance query', () => getBalance(Accounts.BOB, Asset.USDT), { timeout: 250000000 }); - test( - 'Send USDC to Hub', - () => - transferTest.testTransfer({ - amount: TRANSFER_AMOUNTS.TOKENS, - account: Accounts.BOB, - asset: Asset.USDC, - assetSourceRelation: AssetSourceRelation.Sibling, - }), - { timeout: 25000 }, - ); +// test( +// 'Send USDC to Hub', +// () => +// transferTest.testTransfer({ +// amount: TRANSFER_AMOUNTS.TOKENS, +// account: Accounts.BOB, +// asset: Asset.USDC, +// assetSourceRelation: AssetSourceRelation.Sibling, +// }), +// { timeout: 25000 }, +// ); - test( - 'Send USDT to Hub', - () => - transferTest.testTransfer({ - amount: TRANSFER_AMOUNTS.TOKENS, - account: Accounts.BOB, - asset: Asset.USDT, - assetSourceRelation: AssetSourceRelation.Sibling, - }), - { timeout: 25000 }, - ); +// test( +// 'Send USDT to Hub', +// () => +// transferTest.testTransfer({ +// amount: TRANSFER_AMOUNTS.TOKENS, +// account: Accounts.BOB, +// asset: Asset.USDT, +// assetSourceRelation: AssetSourceRelation.Sibling, +// }), +// { timeout: 25000 }, +// ); - test( - 'Send DOT to Hub', - () => - transferTest.testTransfer({ - amount: TRANSFER_AMOUNTS.NATIVE, - account: Accounts.BOB, - asset: Asset.DOT, - assetSourceRelation: AssetSourceRelation.Parent, - }), - { timeout: 25000 }, - ); -}); +// test( +// 'Send DOT to Hub', +// () => +// transferTest.testTransfer({ +// amount: TRANSFER_AMOUNTS.NATIVE, +// account: Accounts.BOB, +// asset: Asset.DOT, +// assetSourceRelation: AssetSourceRelation.Parent, +// }), +// { timeout: 25000 }, +// ); +// }); diff --git a/integration-tests/chopsticks/src/tests/polkadot.test.ts b/integration-tests/chopsticks/src/tests/polkadot.test.ts index 699ec86ad..72f99e8c1 100644 --- a/integration-tests/chopsticks/src/tests/polkadot.test.ts +++ b/integration-tests/chopsticks/src/tests/polkadot.test.ts @@ -1,32 +1,32 @@ -import { afterAll, beforeAll, beforeEach, describe, test } from 'bun:test'; -import { TRANSFER_AMOUNTS } from '@/constants'; -import { createChainManager } from '@/managers/Factory'; -import { ChainSetup } from '@/setup'; -import { PolkadotToPolimecTransfer } from '@/transfers/PolkadotToPolimec'; -import { Accounts, Asset, Chains } from '@/types'; +// import { afterAll, beforeAll, beforeEach, describe, test } from 'bun:test'; +// import { TRANSFER_AMOUNTS } from '@/constants'; +// import { createChainManager } from '@/managers/Factory'; +// import { ChainSetup } from '@/setup'; +// import { PolkadotToPolimecTransfer } from '@/transfers/PolkadotToPolimec'; +// import { Accounts, Asset, Chains } from '@/types'; -describe('Polkadot -> Polimec Transfer Tests', () => { - const chainSetup = new ChainSetup(); +// describe('Polkadot -> Polimec Transfer Tests', () => { +// const chainSetup = new ChainSetup(); - const sourceManager = createChainManager(Chains.Polkadot); - const destManager = createChainManager(Chains.Polimec); - const transferTest = new PolkadotToPolimecTransfer(sourceManager, destManager); +// const sourceManager = createChainManager(Chains.Polkadot); +// const destManager = createChainManager(Chains.Polimec); +// const transferTest = new PolkadotToPolimecTransfer(sourceManager, destManager); - beforeAll(async () => await chainSetup.initialize()); - beforeEach(() => { - sourceManager.connect(); - destManager.connect(); - }); - afterAll(async () => await chainSetup.cleanup()); +// beforeAll(async () => await chainSetup.initialize()); +// beforeEach(() => { +// sourceManager.connect(); +// destManager.connect(); +// }); +// afterAll(async () => await chainSetup.cleanup()); - test( - 'Send DOT to Polimec', - () => - transferTest.testTransfer({ - amount: TRANSFER_AMOUNTS.NATIVE, - account: Accounts.ALICE, - asset: Asset.DOT, - }), - { timeout: 25000 }, - ); -}); +// test( +// 'Send DOT to Polimec', +// () => +// transferTest.testTransfer({ +// amount: TRANSFER_AMOUNTS.NATIVE, +// account: Accounts.ALICE, +// asset: Asset.DOT, +// }), +// { timeout: 25000 }, +// ); +// }); diff --git a/integration-tests/chopsticks/src/transfers/BaseTransfer.ts b/integration-tests/chopsticks/src/transfers/BaseTransfer.ts index 6219e53a9..311a96300 100644 --- a/integration-tests/chopsticks/src/transfers/BaseTransfer.ts +++ b/integration-tests/chopsticks/src/transfers/BaseTransfer.ts @@ -1,13 +1,13 @@ import { expect } from 'bun:test'; import type { BaseChainManager } from '@/managers/BaseManager'; -import { - type Accounts, - type Asset, - type AssetSourceRelation, - type BalanceCheck, - Chains, - type TransferResult, +import type { + Accounts, + Asset, + AssetSourceRelation, + BalanceCheck, + TransferResult, } from '@/types'; +import { sleep } from 'bun'; export interface TransferOptions { account: Accounts; @@ -18,7 +18,7 @@ export abstract class BaseTransferTest { constructor( protected sourceManager: BaseChainManager, protected destManager: BaseChainManager, - ) {} + ) { } abstract executeTransfer(options: TransferOptions): Promise; abstract getBalances(options: TransferOptions): Promise<{ asset_balances: BalanceCheck[] }>; @@ -26,34 +26,30 @@ export abstract class BaseTransferTest { initialBalances: BalanceCheck[], finalBalances: BalanceCheck[], options: TransferOptions, - ): Promise; + ): void; async testTransfer(options: TransferOptions) { const { asset_balances: initialBalances } = await this.getBalances(options); + if (options.assets[0][1] > initialBalances[0].source) { + throw new Error(`Insufficient balance on Source chain for asset: ${options.assets[0][0]}`); + } const blockNumbers = await this.executeTransfer(options); - await this.waitForBlocks(blockNumbers); + await this.waitForBlocks(blockNumbers.destBlock); await this.verifyExecution(); const { asset_balances: finalBalances } = await this.getBalances(options); - await this.verifyFinalBalances(initialBalances, finalBalances, options); + this.verifyFinalBalances(initialBalances, finalBalances, options); } - protected async waitForBlocks({ sourceBlock, destBlock }: TransferResult) { - await Promise.all([ - this.sourceManager.waitForNextBlock(sourceBlock), - this.destManager.waitForNextBlock(destBlock), - ]); + protected async waitForBlocks(destBlockNumber: number) { + await sleep(2000); } protected async verifyExecution() { const events = await this.destManager.getMessageQueueEvents(); - const v = await this.destManager - .getApi(Chains.Polimec) - .event.MessageQueue.ProcessingFailed.pull(); - console.log('MsgQ Events:', v); expect(events).not.toBeEmpty(); expect(events).toBeArray(); expect(events).toHaveLength(1); expect(events[0].payload.success).toBeTrue(); } -} +} \ No newline at end of file diff --git a/integration-tests/chopsticks/src/transfers/HubToPolimec.ts b/integration-tests/chopsticks/src/transfers/HubToPolimec.ts index cc26cef4b..7e8d9c105 100644 --- a/integration-tests/chopsticks/src/transfers/HubToPolimec.ts +++ b/integration-tests/chopsticks/src/transfers/HubToPolimec.ts @@ -2,28 +2,21 @@ import { expect } from 'bun:test'; import type { PolimecManager } from '@/managers/PolimecManager'; import type { PolkadotHubManager } from '@/managers/PolkadotHubManager'; import { - type BalanceCheck, + Asset, + AssetSourceRelation, Chains, + ParaId, type PolimecBalanceCheck, getVersionedAssets, - AssetSourceRelation, - Asset, } from '@/types'; import { createTransferData, unwrap } from '@/utils'; import { DispatchRawOrigin, - XcmPalletOrigin, XcmVersionedAssetId, - XcmVersionedLocation, + type XcmVersionedLocation, type XcmVersionedXcm, } from '@polkadot-api/descriptors'; -import type { - I4c0s5cioidn76, - I5gi8h3e5lkbeq, - I47tkk5e5nm6g7, -} from '@polkadot-api/descriptors/dist/common-types'; - import { BaseTransferTest, type TransferOptions } from './BaseTransfer'; export class HubToPolimecTransfer extends BaseTransferTest { @@ -52,8 +45,7 @@ export class HubToPolimecTransfer extends BaseTransferTest { const transfer = api.tx.PolkadotXcm.transfer_assets(data); const res = await transfer.signAndSubmit(this.sourceManager.getSigner(account)); - console.log("Transfer result"); - console.dir(res, { depth: null, colors: true }); + console.log('Extrinsic result: ', res.ok); expect(res.ok).toBeTrue(); return { sourceBlock, destBlock }; @@ -85,20 +77,13 @@ export class HubToPolimecTransfer extends BaseTransferTest { const source_xcm_asset_fee_amount = await this.sourceManager.getXcmFee(); const dest_xcm_asset_fee_amount = await this.calculatePolimecXcmFee(transferOptions); - console.log('Native extrinsic fee amount: ', native_extrinsic_fee_amount); - console.log('Source xcm fee amount: ', source_xcm_asset_fee_amount); - console.log('Dest xcm fee amount: ', dest_xcm_asset_fee_amount); - const fee_asset = transferOptions.assets[0][0]; for (let i = 0; i < transferOptions.assets.length; i++) { const initialBalances = assetInitialBalances[i]; const finalBalances = assetFinalBalances[i]; const send_amount = transferOptions.assets[i][1]; - console.log('Send amount: ', send_amount); - const asset = transferOptions.assets[i][0]; - console.log('Asset: ', asset); let expectedSourceBalanceSpent = send_amount; let expectedDestBalanceSpent = 0n; @@ -112,13 +97,7 @@ export class HubToPolimecTransfer extends BaseTransferTest { expectedTreasuryBalanceGained += dest_xcm_asset_fee_amount; } - console.log('Expected source balance spent: ', expectedSourceBalanceSpent); - console.log('Expected dest balance spent: ', expectedDestBalanceSpent); - console.log('Expected treasury balance gained: ', expectedTreasuryBalanceGained); - - expect(finalBalances.source).toBe( - initialBalances.source - expectedSourceBalanceSpent, - ); + expect(finalBalances.source).toBe(initialBalances.source - expectedSourceBalanceSpent); expect(finalBalances.destination).toBe( initialBalances.destination + send_amount - expectedDestBalanceSpent, ); @@ -132,8 +111,6 @@ export class HubToPolimecTransfer extends BaseTransferTest { const sourceApi = this.sourceManager.getApi(Chains.PolkadotHub); const destApi = this.destManager.getApi(Chains.Polimec); - - const versioned_assets = getVersionedAssets(transferOptions.assets); const transferData = createTransferData({ toChain: Chains.Polimec, @@ -142,11 +119,11 @@ export class HubToPolimecTransfer extends BaseTransferTest { }); let remoteFeeAssetId: XcmVersionedAssetId; - let lastAsset = unwrap(transferOptions.assets.at(0)); + const lastAsset = unwrap(transferOptions.assets.at(0)); if (lastAsset[2] === AssetSourceRelation.Self) { lastAsset[2] = AssetSourceRelation.Sibling; } - let versioned_asset = getVersionedAssets([lastAsset]); + const versioned_asset = getVersionedAssets([lastAsset]); if (versioned_asset.type === 'V4') { remoteFeeAssetId = XcmVersionedAssetId.V4(unwrap(versioned_asset.value.at(0)).id); } else { @@ -158,7 +135,7 @@ export class HubToPolimecTransfer extends BaseTransferTest { { type: 'PolkadotXcm', value: { type: 'transfer_assets', value: transferData } }, ); - let forwardedXcms: I47tkk5e5nm6g7; + let forwardedXcms: [XcmVersionedLocation, XcmVersionedXcm[]][] = []; if (localDryRunResult.success && localDryRunResult.value.forwarded_xcms) { forwardedXcms = localDryRunResult.value.forwarded_xcms; } else { @@ -171,31 +148,19 @@ export class HubToPolimecTransfer extends BaseTransferTest { location.value.parents === 1 && location.value.interior.type === 'X1' && location.value.interior.value.type === 'Parachain' && - location.value.interior.value.value === 3344, // Polimec's ParaID. + location.value.interior.value.value === ParaId[Chains.Polimec], ); if (!xcmsToPolimec) { throw new Error('Could not find xcm to polimec'); } const messages = xcmsToPolimec[1]; const remoteXcm = messages[0]; - - console.log('Remote XCM:'); - console.dir(remoteXcm, { depth: null, colors: true }); - const assets = await destApi.apis.XcmPaymentApi.query_acceptable_payment_assets(4); - console.log("Acceptable payment assets") - console.dir(assets, { depth: null, colors: true }); const remoteXcmWeightResult = await destApi.apis.XcmPaymentApi.query_xcm_weight(remoteXcm); - console.log('XCM Weight:'); - console.dir(remoteXcmWeightResult, { depth: null, colors: true }); if (remoteXcmWeightResult.success) { - console.log("fee asset id"); - console.dir(remoteFeeAssetId, { depth: null, colors: true }); const remoteExecutionFeesResult = await destApi.apis.XcmPaymentApi.query_weight_to_asset_fee( remoteXcmWeightResult.value, remoteFeeAssetId, ); - console.log('remoteExecutionFeesResult'); - console.dir(remoteExecutionFeesResult, { depth: null, colors: true }); if (remoteExecutionFeesResult.success) { destinationExecutionFee = remoteExecutionFeesResult.value; } else { diff --git a/integration-tests/chopsticks/src/transfers/PolimecToHub.ts b/integration-tests/chopsticks/src/transfers/PolimecToHub.ts index c4376cd23..166fe50e5 100644 --- a/integration-tests/chopsticks/src/transfers/PolimecToHub.ts +++ b/integration-tests/chopsticks/src/transfers/PolimecToHub.ts @@ -2,17 +2,11 @@ import { expect } from 'bun:test'; import { INITIAL_BALANCES } from '@/constants'; import type { PolimecManager } from '@/managers/PolimecManager'; import type { PolkadotHubManager } from '@/managers/PolkadotHubManager'; -import { - Asset, - type AssetSourceRelation, - type BalanceCheck, - Chains, - getVersionedAssets, -} from '@/types'; +import { Asset, type BalanceCheck, Chains, getVersionedAssets } from '@/types'; import { createTransferData } from '@/utils'; -import { type BaseTransferOptions, BaseTransferTest } from './BaseTransfer'; +import { BaseTransferTest, type TransferOptions } from './BaseTransfer'; -export class PolimecToHubTransfer extends BaseTransferTest { +export class PolimecToHubTransfer extends BaseTransferTest { constructor( protected override sourceManager: PolimecManager, protected override destManager: PolkadotHubManager, @@ -20,7 +14,7 @@ export class PolimecToHubTransfer extends BaseTransferTest super(sourceManager, destManager); } - async executeTransfer({ account, assets }: BaseTransferOptions) { + async executeTransfer({ account, assets }: TransferOptions) { const [sourceBlock, destBlock] = await Promise.all([ this.sourceManager.getBlockNumber(), this.destManager.getBlockNumber(), @@ -42,34 +36,35 @@ export class PolimecToHubTransfer extends BaseTransferTest return { sourceBlock, destBlock }; } - async getBalances({ - account, - asset, - }: Omit): Promise<{ balances: BalanceCheck }> { - return { - balances: { - source: await this.sourceManager.getAssetBalanceOf(account, asset), - destination: await this.destManager.getAssetBalanceOf(account, asset), - }, - }; + async getBalances(options: TransferOptions): Promise<{ asset_balances: BalanceCheck[] }> { + const source = await this.sourceManager.getAssetBalanceOf( + options.account, + options.assets[0][0], + ); + const destination = await this.destManager.getAssetBalanceOf( + options.account, + options.assets[0][0], + ); + return { asset_balances: [{ source, destination }] }; } - async verifyFinalBalances( - initialBalances: BalanceCheck, - finalBalances: BalanceCheck, - { asset }: PolimecTransferOptions, + verifyFinalBalances( + initialBalances: BalanceCheck[], + finalBalances: BalanceCheck[], + options: TransferOptions, ) { // TODO: At the moment we exclude fees from the balance check since the PAPI team is wotking on some utilies to calculate fees. const initialBalance = - asset === Asset.DOT + options.assets[0][0] === Asset.DOT ? INITIAL_BALANCES.DOT - : asset === Asset.USDT + : options.assets[0][0] === Asset.USDT ? INITIAL_BALANCES.USDT : INITIAL_BALANCES.USDC; - - expect(initialBalances.destination).toBe(0n); - expect(initialBalances.source).toBe(initialBalance); - expect(finalBalances.source).toBeLessThan(initialBalances.source); - expect(finalBalances.destination).toBeGreaterThan(initialBalances.destination); + for (let i = 0; i < options.assets.length; i++) { + expect(initialBalances[i].destination).toBe(0n); + expect(initialBalances[i].source).toBe(initialBalance); + expect(finalBalances[i].source).toBeLessThan(initialBalances[i].source); + expect(finalBalances[i].destination).toBeGreaterThan(initialBalances[i].destination); + } } -} +} \ No newline at end of file diff --git a/integration-tests/chopsticks/src/transfers/PolkadotToPolimec.ts b/integration-tests/chopsticks/src/transfers/PolkadotToPolimec.ts index 54bbff0f4..b36fd31ba 100644 --- a/integration-tests/chopsticks/src/transfers/PolkadotToPolimec.ts +++ b/integration-tests/chopsticks/src/transfers/PolkadotToPolimec.ts @@ -1,11 +1,11 @@ import { expect } from 'bun:test'; import type { PolimecManager } from '@/managers/PolimecManager'; import type { PolkadotManager } from '@/managers/PolkadotManager'; -import { Asset, Chains, type PolimecBalanceCheck } from '@/types'; +import { Asset, type BalanceCheck, Chains, type PolimecBalanceCheck } from '@/types'; import { createDotMultiHopTransferData } from '@/utils'; -import { type BaseTransferOptions, BaseTransferTest } from './BaseTransfer'; +import { BaseTransferTest, type TransferOptions } from './BaseTransfer'; -export class PolkadotToPolimecTransfer extends BaseTransferTest { +export class PolkadotToPolimecTransfer extends BaseTransferTest { constructor( protected override sourceManager: PolkadotManager, protected override destManager: PolimecManager, @@ -13,7 +13,7 @@ export class PolkadotToPolimecTransfer extends BaseTransferTest): Promise<{ balances: PolimecBalanceCheck }> { - const treasuryAccount = this.destManager.getTreasuryAccount(); - return { - balances: { - source: await this.sourceManager.getAssetBalanceOf(account, Asset.DOT), - destination: await this.destManager.getAssetBalanceOf(account, Asset.DOT), - treasury: await this.destManager.getAssetBalanceOf(treasuryAccount, Asset.DOT), - }, - }; + async getBalances(options: TransferOptions): Promise<{ asset_balances: PolimecBalanceCheck[] }> { + throw new Error('Method not implemented.'); } async verifyFinalBalances( - initialBalances: PolimecBalanceCheck, - finalBalances: PolimecBalanceCheck, - ) { - // TODO: At the moment we exclude fees from the balance check since the PAPI team is wotking on some utilies to calculate fees. - expect(initialBalances.destination).toBe(0n); - expect(finalBalances.source).toBeLessThan(initialBalances.source); - expect(finalBalances.destination).toBeGreaterThan(initialBalances.destination); - expect(finalBalances.treasury).toBeGreaterThan(initialBalances.treasury); + initialBalances: PolimecBalanceCheck[], + finalBalances: PolimecBalanceCheck[], + options: TransferOptions, + ): Promise { + throw new Error('Method not implemented.'); } -} + + // async getBalances({ + // account, + // }: Omit): Promise<{ balances: PolimecBalanceCheck }> { + // const treasuryAccount = this.destManager.getTreasuryAccount(); + // return { + // balances: { + // source: await this.sourceManager.getAssetBalanceOf(account, Asset.DOT), + // destination: await this.destManager.getAssetBalanceOf(account, Asset.DOT), + // treasury: await this.destManager.getAssetBalanceOf(treasuryAccount, Asset.DOT), + // }, + // }; + // } + + // async verifyFinalBalances( + // initialBalances: PolimecBalanceCheck, + // finalBalances: PolimecBalanceCheck, + // ) { + // // TODO: At the moment we exclude fees from the balance check since the PAPI team is wotking on some utilies to calculate fees. + // expect(initialBalances.destination).toBe(0n); + // expect(finalBalances.source).toBeLessThan(initialBalances.source); + // expect(finalBalances.destination).toBeGreaterThan(initialBalances.destination); + // expect(finalBalances.treasury).toBeGreaterThan(initialBalances.treasury); + // } +} \ No newline at end of file diff --git a/integration-tests/chopsticks/src/types.ts b/integration-tests/chopsticks/src/types.ts index 6c75fed1e..4a534e7f4 100644 --- a/integration-tests/chopsticks/src/types.ts +++ b/integration-tests/chopsticks/src/types.ts @@ -2,7 +2,6 @@ import { XcmV3Junction, XcmV3JunctionNetworkId, XcmV3Junctions, - XcmV3MultiassetAssetId, XcmV3MultiassetFungibility, XcmVersionedAssets, XcmVersionedLocation, @@ -10,9 +9,8 @@ import { type polimec, type polkadot, } from '@polkadot-api/descriptors'; -import type { Ia5l7mu5a6v49o } from '@polkadot-api/descriptors/dist/common-types'; -import { hexToU8a } from '@polkadot/util'; import { FixedSizeBinary, type PolkadotClient, type TypedApi } from 'polkadot-api'; + type Polimec = typeof polimec; type PolkadotHub = typeof pah; type Polkadot = typeof polkadot; @@ -22,10 +20,12 @@ export enum Chains { PolkadotHub = 'ws://localhost:8001', Polkadot = 'ws://localhost:8002', } + export type ChainClient = { api: TypedApi; client: PolkadotClient; }; + export const ParaId = { [Chains.Polimec]: 3344, [Chains.PolkadotHub]: 1000, @@ -121,7 +121,7 @@ export function NativeAssetLocation( } return XcmVersionedLocation.V4({ parents: 1, - interior: XcmV3Junctions.X1([XcmV3Junction.Parachain(paraId)]), + interior: XcmV3Junctions.X1(XcmV3Junction.Parachain(paraId)), }); case AssetSourceRelation.Self: return XcmVersionedLocation.V4({ @@ -170,11 +170,18 @@ export function AssetLocation( export function getVersionedAssets( assets: [Asset, bigint, AssetSourceRelation][], ): XcmVersionedAssets { - const final_assets: Ia5l7mu5a6v49o[] = []; + const final_assets: { + id: { parents: number; interior: XcmV3Junctions }; + fun: XcmV3MultiassetFungibility; + }[] = []; for (const [asset, amount, asset_source_relation] of assets) { const location = AssetLocation(asset, asset_source_relation); + const id = { + parents: location.value.parents, + interior: location.value.interior as XcmV3Junctions, // We assume that this is not an XCM v2 MultiLocation. + }; final_assets.push({ - id: location.value, + id, fun: XcmV3MultiassetFungibility.Fungible(amount), }); } diff --git a/integration-tests/chopsticks/src/utils.ts b/integration-tests/chopsticks/src/utils.ts index 850dd08d8..5e7b9d16d 100644 --- a/integration-tests/chopsticks/src/utils.ts +++ b/integration-tests/chopsticks/src/utils.ts @@ -94,7 +94,7 @@ export const createDotMultiHopTransferData = (amount: bigint) => { return { dest, - assets: getVersionedAssets([[Asset.DOT, amount]], AssetSourceRelation.Self), + assets: getVersionedAssets([[Asset.DOT, amount, AssetSourceRelation.Self]]), assets_transfer_type: Enum('Teleport'), remote_fees_id: XcmVersionedAssetId.V3( XcmV3MultiassetAssetId.Concrete({ @@ -115,26 +115,22 @@ export function unwrap(value: T | undefined, errorMessage = 'Value is undefin return value; } -export function normalizeForComparison(obj: any): any { +export function flatObject(obj: unknown): unknown { if (obj === null || obj === undefined) { return obj; } - - if (obj instanceof Object && 'asHex' in obj) { - return obj.asHex(); + if (obj instanceof Object && typeof (obj as { asHex?: unknown }).asHex === 'function') { + return (obj as { asHex: () => unknown }).asHex(); } - if (typeof obj === 'object') { if (Array.isArray(obj)) { - return obj.map(normalizeForComparison); + return obj.map(flatObject); } - - const normalized: any = {}; + const normalized: Record = {}; for (const [key, value] of Object.entries(obj)) { - normalized[key] = normalizeForComparison(value); + normalized[key] = flatObject(value); } return normalized; } - return obj; -} \ No newline at end of file +} From a7f90e2f78364d9668be6b0f1b49bd1eb9b518de Mon Sep 17 00:00:00 2001 From: Leonardo Razovic <4128940+lrazovic@users.noreply.github.com> Date: Thu, 16 Jan 2025 15:22:58 +0100 Subject: [PATCH 04/11] chore: update doc --- integration-tests/chopsticks/src/setup.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration-tests/chopsticks/src/setup.ts b/integration-tests/chopsticks/src/setup.ts index 66bd2e8ed..ba7413117 100644 --- a/integration-tests/chopsticks/src/setup.ts +++ b/integration-tests/chopsticks/src/setup.ts @@ -50,7 +50,7 @@ export class ChainSetup { // Needed to execute storage migrations within the new WASM before running tests. const head = this.polimec.head; console.log(`✅ Polimec chain is at block ${head.number}`); - console.log('✅ Running storage migrations...'); + console.log('✅ Producing a new block...'); const new_block = await this.polimec?.newBlock(); console.log(`✅ Polimec chain is at block ${new_block.number}`); expect(new_block.number === head.number + 1, 'Block number should be incremented by 1'); @@ -69,7 +69,7 @@ export class ChainSetup { private async setupPolimec(polimec_storage: unknown) { const file = Bun.file(POLIMEC_WASM); - // Note: the tests are inteded to use a pre-production, locally compiled runtime, that's why we throw an error. + // Note: the tests are intended to use a pre-production, locally compiled runtime, that's why we throw an error. if (!(await file.exists())) { throw new Error( 'Polimec runtime not found! Please build it by running `cargo b -r -p polimec-runtime` before executing the tests.', From e6e3b59bca27ee1fa6f13b5defe4204014249b1a Mon Sep 17 00:00:00 2001 From: Leonardo Razovic <4128940+lrazovic@users.noreply.github.com> Date: Thu, 16 Jan 2025 15:54:50 +0100 Subject: [PATCH 05/11] feat: remove unwrap from the migration --- .../chopsticks/src/tests/hub.test.ts | 46 -------- .../chopsticks/src/tests/polimec.test.ts | 106 +++++++++--------- .../custom_migrations/asset_id_migration.rs | 100 ++++++++++------- 3 files changed, 108 insertions(+), 144 deletions(-) diff --git a/integration-tests/chopsticks/src/tests/hub.test.ts b/integration-tests/chopsticks/src/tests/hub.test.ts index 2d0ea2508..887c8cfce 100644 --- a/integration-tests/chopsticks/src/tests/hub.test.ts +++ b/integration-tests/chopsticks/src/tests/hub.test.ts @@ -61,50 +61,4 @@ describe('Polkadot Hub -> Polimec Transfer Tests', () => { // { timeout: 25000 }, // ); - // test('sandbox', async () => { - // console.log("hello"); - // const weth_1 = { - // parents: 2, - // interior: { - // x2: [ - // { - // globalConsensus: { - // ethereum: { - // chainId: 1n - // } - // } - // }, - // { - // accountKey20: { - // key: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - // } - // } - // ] - // } - // } - // - // const weth_2 = { - // parents: 2, - // interior: { - // x2: [ - // { - // globalConsensus: { - // ethereum: { - // chainId: 1n - // } - // } - // }, - // { - // accountKey20: { - // key: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - // } - // } - // ] - // } - // } - // - // const equals = Bun.deepEquals(weth_1, weth_2); - // expect(equals).toEqual(false); - // - // }, { timeout: 10000000 }); }); diff --git a/integration-tests/chopsticks/src/tests/polimec.test.ts b/integration-tests/chopsticks/src/tests/polimec.test.ts index 51df14669..d89373bf4 100644 --- a/integration-tests/chopsticks/src/tests/polimec.test.ts +++ b/integration-tests/chopsticks/src/tests/polimec.test.ts @@ -1,62 +1,56 @@ -// import { afterAll, beforeAll, beforeEach, describe, test } from 'bun:test'; -// import { TRANSFER_AMOUNTS } from '@/constants'; -// import { createChainManager } from '@/managers/Factory'; -// import { polimec_storage } from '@/polimec'; -// import { ChainSetup } from '@/setup'; -// import { PolimecToHubTransfer } from '@/transfers/PolimecToHub'; -// import { Accounts, Asset, AssetSourceRelation, Chains } from '@/types'; +import { afterAll, beforeAll, beforeEach, describe, test } from 'bun:test'; +import { TRANSFER_AMOUNTS } from '@/constants'; +import { createChainManager } from '@/managers/Factory'; +import { polimec_storage } from '@/polimec'; +import { ChainSetup } from '@/setup'; +import { PolimecToHubTransfer } from '@/transfers/PolimecToHub'; +import { Accounts, Asset, AssetSourceRelation, Chains } from '@/types'; -// describe('Polimec -> Hub Transfer Tests', () => { -// const sourceManager = createChainManager(Chains.Polimec); -// const destManager = createChainManager(Chains.PolkadotHub); -// const transferTest = new PolimecToHubTransfer(sourceManager, destManager); -// const chainSetup = new ChainSetup(); +describe('Polimec -> Hub Transfer Tests', () => { + const sourceManager = createChainManager(Chains.Polimec); + const destManager = createChainManager(Chains.PolkadotHub); + const transferTest = new PolimecToHubTransfer(sourceManager, destManager); + const chainSetup = new ChainSetup(); -// beforeAll(async () => await chainSetup.initialize(polimec_storage)); -// beforeEach(() => { -// sourceManager.connect(); -// destManager.connect(); -// }); -// afterAll(async () => await chainSetup.cleanup()); + beforeAll(async () => await chainSetup.initialize(polimec_storage)); + beforeEach(() => { + sourceManager.connect(); + destManager.connect(); + }); + afterAll(async () => await chainSetup.cleanup()); -// async function getBalance(account: Accounts, asset: Asset) { -// return await sourceManager.getAssetBalanceOf(account, asset); -// } -// test('Balance query', () => getBalance(Accounts.BOB, Asset.USDT), { timeout: 250000000 }); + async function getBalance(account: Accounts, asset: Asset) { + return await sourceManager.getAssetBalanceOf(account, asset); + } + test('Balance query', () => getBalance(Accounts.BOB, Asset.USDT), { timeout: 250000000 }); -// test( -// 'Send USDC to Hub', -// () => -// transferTest.testTransfer({ -// amount: TRANSFER_AMOUNTS.TOKENS, -// account: Accounts.BOB, -// asset: Asset.USDC, -// assetSourceRelation: AssetSourceRelation.Sibling, -// }), -// { timeout: 25000 }, -// ); + test( + 'Send USDC to Hub', + () => + transferTest.testTransfer({ + account: Accounts.BOB, + assets: [[Asset.USDC, TRANSFER_AMOUNTS.TOKENS, AssetSourceRelation.Sibling]] + }), + { timeout: 25000 }, + ); -// test( -// 'Send USDT to Hub', -// () => -// transferTest.testTransfer({ -// amount: TRANSFER_AMOUNTS.TOKENS, -// account: Accounts.BOB, -// asset: Asset.USDT, -// assetSourceRelation: AssetSourceRelation.Sibling, -// }), -// { timeout: 25000 }, -// ); + test( + 'Send USDT to Hub', + () => + transferTest.testTransfer({ + account: Accounts.BOB, + assets: [[Asset.USDT, TRANSFER_AMOUNTS.TOKENS, AssetSourceRelation.Sibling]] + }), + { timeout: 25000 }, + ); -// test( -// 'Send DOT to Hub', -// () => -// transferTest.testTransfer({ -// amount: TRANSFER_AMOUNTS.NATIVE, -// account: Accounts.BOB, -// asset: Asset.DOT, -// assetSourceRelation: AssetSourceRelation.Parent, -// }), -// { timeout: 25000 }, -// ); -// }); + test( + 'Send DOT to Hub', + () => + transferTest.testTransfer({ + account: Accounts.BOB, + assets: [[Asset.DOT, TRANSFER_AMOUNTS.NATIVE, AssetSourceRelation.Parent]] + }), + { timeout: 25000 }, + ); +}); \ No newline at end of file diff --git a/runtimes/polimec/src/custom_migrations/asset_id_migration.rs b/runtimes/polimec/src/custom_migrations/asset_id_migration.rs index 4f812ab4c..e567b41a0 100644 --- a/runtimes/polimec/src/custom_migrations/asset_id_migration.rs +++ b/runtimes/polimec/src/custom_migrations/asset_id_migration.rs @@ -9,12 +9,20 @@ use frame_support::{ use itertools::Itertools; use pallet_assets::{Approval, AssetAccount, AssetDetails, AssetMetadata}; use polimec_common::assets::AcceptedFundingAsset; -use sp_api::runtime_decl_for_core::CoreV5; use sp_runtime::BoundedVec; use xcm::v4::Location; -// Storage items of pallet-assets are set to private for some reason. So we have to redefine them to get the same storage -// encoding and call the `translate` methods. -_-' +#[cfg(feature = "try-runtime")] +use frame_support::migrations::VersionedPostUpgradeData; + +#[cfg(feature = "try-runtime")] +use parity_scale_codec::Encode; + +#[cfg(feature = "try-runtime")] +use sp_std::vec::Vec; + +// Storage items of pallet-assets are private. +// So we have to redefine them to get the same storage encoding and call the `translate` methods. pub mod pallet_assets_storage_items { use super::*; @@ -120,74 +128,82 @@ impl OnRuntimeUpgrade for FromOldAssetIdMigration { log::info!("AssetId Migration can be removed"); return ::DbWeight::get().reads(1) } - log::info!("Running AssetId Migration..."); let mut items = 0; if runtime_version.spec_version == 1_000_000 { + log::info!("Running AssetId Migration..."); let id_map = BTreeMap::from([ - (1984, AcceptedFundingAsset::USDT.id()), - (1337, AcceptedFundingAsset::USDC.id()), (10, AcceptedFundingAsset::DOT.id()), + (1337, AcceptedFundingAsset::USDC.id()), + (1984, AcceptedFundingAsset::USDT.id()), (3344, Location::here()), ]); let old_account_iterator = pallet_assets_storage_items::old_types::Account::iter().collect_vec(); for (old_asset_id, account, account_info) in old_account_iterator { - items += 1; - log::info!("old_account item {:?}", items); - - pallet_assets_storage_items::new_types::Account::insert( - id_map.get(&old_asset_id).unwrap(), - account.clone(), - account_info, - ); - pallet_assets_storage_items::old_types::Account::remove(old_asset_id, account); + if let Some(new_asset_id) = id_map.get(&old_asset_id) { + items += 1; + log::info!("old_account item {:?}", items); + + pallet_assets_storage_items::new_types::Account::insert( + new_asset_id, + account.clone(), + account_info, + ); + pallet_assets_storage_items::old_types::Account::remove(old_asset_id, account); + } } let old_asset_iterator = pallet_assets_storage_items::old_types::Asset::iter().collect_vec(); for (old_asset_id, asset_info) in old_asset_iterator { - items += 1; - log::info!("old_asset item {:?}", items); - pallet_assets_storage_items::new_types::Asset::insert(id_map.get(&old_asset_id).unwrap(), asset_info); - pallet_assets_storage_items::old_types::Asset::remove(old_asset_id); + if let Some(new_asset_id) = id_map.get(&old_asset_id) { + items += 1; + log::info!("old_asset item {:?}", items); + pallet_assets_storage_items::new_types::Asset::insert(new_asset_id, asset_info); + pallet_assets_storage_items::old_types::Asset::remove(old_asset_id); + } } let old_approvals_iterator = pallet_assets_storage_items::old_types::Approvals::iter().collect_vec(); for ((old_asset_id, owner, delegate), approval) in old_approvals_iterator { - items += 1; - log::info!("old_approvals item {:?}", items); - pallet_assets_storage_items::new_types::Approvals::insert( - (id_map.get(&old_asset_id).unwrap(), owner.clone(), delegate.clone()), - approval, - ); - pallet_assets_storage_items::old_types::Approvals::remove((old_asset_id, owner, delegate)); + if let Some(new_asset_id) = id_map.get(&old_asset_id) { + items += 1; + log::info!("old_approvals item {:?}", items); + pallet_assets_storage_items::new_types::Approvals::insert( + (new_asset_id, owner.clone(), delegate.clone()), + approval, + ); + pallet_assets_storage_items::old_types::Approvals::remove((old_asset_id, owner, delegate)); + } } let old_metadata_iterator = pallet_assets_storage_items::old_types::Metadata::iter().collect_vec(); for (old_asset_id, metadata) in old_metadata_iterator { - items += 1; - log::info!("old_metadata item {:?}", items); - pallet_assets_storage_items::new_types::Metadata::insert(id_map.get(&old_asset_id).unwrap(), metadata); - pallet_assets_storage_items::old_types::Metadata::remove(old_asset_id); + if let Some(new_asset_id) = id_map.get(&old_asset_id) { + items += 1; + log::info!("old_metadata item {:?}", items); + pallet_assets_storage_items::new_types::Metadata::insert(new_asset_id, metadata); + pallet_assets_storage_items::old_types::Metadata::remove(old_asset_id); + } } let old_oracle_raw_values_iterator = orml_oracle_storage_items::old_types::RawValues::iter().collect_vec(); for (account, old_asset_id, raw_values) in old_oracle_raw_values_iterator { - items += 1; - log::info!("old_oracle_raw_values item {:?}", items); - orml_oracle::RawValues::::insert( - account.clone(), - id_map.get(&old_asset_id).unwrap(), - raw_values, - ); - orml_oracle_storage_items::old_types::RawValues::remove(account, old_asset_id); + if let Some(new_asset_id) = id_map.get(&old_asset_id) { + items += 1; + log::info!("old_oracle_raw_values item {:?}", items); + orml_oracle::RawValues::::insert(account.clone(), new_asset_id, raw_values); + orml_oracle_storage_items::old_types::RawValues::remove(account, old_asset_id); + } } let old_oracle_values_iterator = orml_oracle_storage_items::old_types::Values::iter().collect_vec(); for (old_asset_id, value) in old_oracle_values_iterator { - items += 1; - log::info!("old_oracle_values item {:?}", items); - orml_oracle::Values::::insert(id_map.get(&old_asset_id).unwrap(), value); - orml_oracle_storage_items::old_types::Values::remove(old_asset_id); + if let Some(new_asset_id) = id_map.get(&old_asset_id) { + items += 1; + log::info!("old_oracle_values item {:?}", items); + orml_oracle::Values::::insert(new_asset_id, value); + orml_oracle_storage_items::old_types::Values::remove(old_asset_id); + } } } From 075cd67b0650c6e3de62ae4ad97b9875a675a2e6 Mon Sep 17 00:00:00 2001 From: Leonardo Razovic <4128940+lrazovic@users.noreply.github.com> Date: Thu, 16 Jan 2025 18:33:08 +0100 Subject: [PATCH 06/11] feat: close enough fee estimation --- .../chopsticks/.papi/polkadot-api.json | 6 +- .../chopsticks/src/managers/PolimecManager.ts | 4 +- .../src/managers/PolkadotHubManager.ts | 9 +- .../src/managers/PolkadotManager.ts | 9 +- .../chopsticks/src/tests/hub.test.ts | 3 +- .../chopsticks/src/tests/polimec.test.ts | 13 +- .../chopsticks/src/tests/polkadot.test.ts | 57 +++--- .../chopsticks/src/transfers/BaseTransfer.ts | 12 +- .../chopsticks/src/transfers/PolimecToHub.ts | 2 +- .../src/transfers/PolkadotToPolimec.ts | 181 ++++++++++++++---- integration-tests/chopsticks/src/types.ts | 2 +- integration-tests/chopsticks/src/utils.ts | 1 + 12 files changed, 203 insertions(+), 96 deletions(-) diff --git a/integration-tests/chopsticks/.papi/polkadot-api.json b/integration-tests/chopsticks/.papi/polkadot-api.json index 5eeeb6cdd..11a8fad67 100644 --- a/integration-tests/chopsticks/.papi/polkadot-api.json +++ b/integration-tests/chopsticks/.papi/polkadot-api.json @@ -7,12 +7,12 @@ "metadata": ".papi/metadata/polimec.scale" }, "polkadot": { - "wsUrl": "ws://localhost:8002", + "wsUrl": "wss://rpc.ibp.network/polkadot", "metadata": ".papi/metadata/polkadot.scale" }, "pah": { - "wsUrl": "ws://localhost:8001", + "wsUrl": "wss://sys.ibp.network/statemint", "metadata": ".papi/metadata/pah.scale" } } -} +} \ No newline at end of file diff --git a/integration-tests/chopsticks/src/managers/PolimecManager.ts b/integration-tests/chopsticks/src/managers/PolimecManager.ts index 210dd3eef..ebf41407f 100644 --- a/integration-tests/chopsticks/src/managers/PolimecManager.ts +++ b/integration-tests/chopsticks/src/managers/PolimecManager.ts @@ -2,12 +2,13 @@ import { type Accounts, Asset, AssetLocation, AssetSourceRelation, Chains } from import { flatObject } from '@/utils.ts'; import { polimec } from '@polkadot-api/descriptors'; import { createClient } from 'polkadot-api'; +import { withPolkadotSdkCompat } from 'polkadot-api/polkadot-sdk-compat'; import { getWsProvider } from 'polkadot-api/ws-provider/web'; import { BaseChainManager } from './BaseManager'; export class PolimecManager extends BaseChainManager { connect() { - const client = createClient(getWsProvider(this.getChainType())); + const client = createClient(withPolkadotSdkCompat(getWsProvider(this.getChainType()))); const api = client.getTypedApi(polimec); // Verify connection @@ -62,7 +63,6 @@ export class PolimecManager extends BaseChainManager { } } } - console.log('Asset not found using query_account_balances Runtime API'); return 0n; } diff --git a/integration-tests/chopsticks/src/managers/PolkadotHubManager.ts b/integration-tests/chopsticks/src/managers/PolkadotHubManager.ts index c72b29cbb..ceb1ccab4 100644 --- a/integration-tests/chopsticks/src/managers/PolkadotHubManager.ts +++ b/integration-tests/chopsticks/src/managers/PolkadotHubManager.ts @@ -2,12 +2,13 @@ import { type Accounts, Asset, AssetLocation, AssetSourceRelation, Chains } from import { flatObject } from '@/utils.ts'; import { pah } from '@polkadot-api/descriptors'; import { createClient } from 'polkadot-api'; +import { withPolkadotSdkCompat } from 'polkadot-api/polkadot-sdk-compat'; import { getWsProvider } from 'polkadot-api/ws-provider/web'; import { BaseChainManager } from './BaseManager'; export class PolkadotHubManager extends BaseChainManager { connect() { - const client = createClient(getWsProvider(this.getChainType())); + const client = createClient(withPolkadotSdkCompat(getWsProvider(this.getChainType()))); const api = client.getTypedApi(pah); // Verify connection @@ -50,10 +51,7 @@ export class PolkadotHubManager extends BaseChainManager { const asset_source_relation = this.getAssetSourceRelation(asset); const asset_location = AssetLocation(asset, asset_source_relation).value; const account_balances_result = await api.apis.FungiblesApi.query_account_balances(account); - console.log('Requested asset location in PolkadotHubManager'); - console.dir(asset_location, { depth: null, colors: true }); - console.log('\n\n'); if (account_balances_result.success === true && account_balances_result.value.type === 'V4') { const assets = account_balances_result.value.value; for (const asset of assets) { @@ -62,7 +60,6 @@ export class PolkadotHubManager extends BaseChainManager { } } } - console.log('Asset not found using query_account_balances Runtime API'); return 0n; } @@ -75,8 +72,6 @@ export class PolkadotHubManager extends BaseChainManager { async getXcmFee() { const api = this.getApi(Chains.PolkadotHub); const events = await api.event.PolkadotXcm.FeesPaid.pull(); - console.log('Fees paid event'); - console.dir(events, { depth: null, colors: true }); return (events[0]?.payload.fees[0].fun.value as bigint) || 0n; } diff --git a/integration-tests/chopsticks/src/managers/PolkadotManager.ts b/integration-tests/chopsticks/src/managers/PolkadotManager.ts index bc44e1b4d..c611025cb 100644 --- a/integration-tests/chopsticks/src/managers/PolkadotManager.ts +++ b/integration-tests/chopsticks/src/managers/PolkadotManager.ts @@ -1,12 +1,13 @@ import { type Accounts, Asset, AssetSourceRelation, Chains } from '@/types'; import { polkadot } from '@polkadot-api/descriptors'; import { createClient } from 'polkadot-api'; +import { withPolkadotSdkCompat } from 'polkadot-api/polkadot-sdk-compat'; import { getWsProvider } from 'polkadot-api/ws-provider/web'; import { BaseChainManager } from './BaseManager'; export class PolkadotManager extends BaseChainManager { connect() { - const client = createClient(getWsProvider(this.getChainType())); + const client = createClient(withPolkadotSdkCompat(getWsProvider(this.getChainType()))); const api = client.getTypedApi(polkadot); // Verify connection @@ -60,4 +61,10 @@ export class PolkadotManager extends BaseChainManager { const events = await api.event.XcmPallet.FeesPaid.pull(); return (events[0]?.payload.fees[0].fun.value as bigint) || 0n; } + + async getTransactionFee() { + const api = this.getApi(Chains.Polkadot); + const events = await api.event.TransactionPayment.TransactionFeePaid.pull(); + return (events[0]?.payload.actual_fee as bigint) || 0n; + } } diff --git a/integration-tests/chopsticks/src/tests/hub.test.ts b/integration-tests/chopsticks/src/tests/hub.test.ts index 887c8cfce..df0496d0c 100644 --- a/integration-tests/chopsticks/src/tests/hub.test.ts +++ b/integration-tests/chopsticks/src/tests/hub.test.ts @@ -1,4 +1,4 @@ -import { afterAll, beforeAll, beforeEach, describe, expect, test } from 'bun:test'; +import { afterAll, beforeAll, beforeEach, describe, test } from 'bun:test'; import { TRANSFER_AMOUNTS } from '@/constants'; import { createChainManager } from '@/managers/Factory'; import { ChainSetup } from '@/setup'; @@ -60,5 +60,4 @@ describe('Polkadot Hub -> Polimec Transfer Tests', () => { // }), // { timeout: 25000 }, // ); - }); diff --git a/integration-tests/chopsticks/src/tests/polimec.test.ts b/integration-tests/chopsticks/src/tests/polimec.test.ts index d89373bf4..23ad70a4e 100644 --- a/integration-tests/chopsticks/src/tests/polimec.test.ts +++ b/integration-tests/chopsticks/src/tests/polimec.test.ts @@ -19,17 +19,12 @@ describe('Polimec -> Hub Transfer Tests', () => { }); afterAll(async () => await chainSetup.cleanup()); - async function getBalance(account: Accounts, asset: Asset) { - return await sourceManager.getAssetBalanceOf(account, asset); - } - test('Balance query', () => getBalance(Accounts.BOB, Asset.USDT), { timeout: 250000000 }); - test( 'Send USDC to Hub', () => transferTest.testTransfer({ account: Accounts.BOB, - assets: [[Asset.USDC, TRANSFER_AMOUNTS.TOKENS, AssetSourceRelation.Sibling]] + assets: [[Asset.USDC, TRANSFER_AMOUNTS.TOKENS, AssetSourceRelation.Sibling]], }), { timeout: 25000 }, ); @@ -39,7 +34,7 @@ describe('Polimec -> Hub Transfer Tests', () => { () => transferTest.testTransfer({ account: Accounts.BOB, - assets: [[Asset.USDT, TRANSFER_AMOUNTS.TOKENS, AssetSourceRelation.Sibling]] + assets: [[Asset.USDT, TRANSFER_AMOUNTS.TOKENS, AssetSourceRelation.Sibling]], }), { timeout: 25000 }, ); @@ -49,8 +44,8 @@ describe('Polimec -> Hub Transfer Tests', () => { () => transferTest.testTransfer({ account: Accounts.BOB, - assets: [[Asset.DOT, TRANSFER_AMOUNTS.NATIVE, AssetSourceRelation.Parent]] + assets: [[Asset.DOT, TRANSFER_AMOUNTS.NATIVE, AssetSourceRelation.Parent]], }), { timeout: 25000 }, ); -}); \ No newline at end of file +}); diff --git a/integration-tests/chopsticks/src/tests/polkadot.test.ts b/integration-tests/chopsticks/src/tests/polkadot.test.ts index 72f99e8c1..6f9fd2522 100644 --- a/integration-tests/chopsticks/src/tests/polkadot.test.ts +++ b/integration-tests/chopsticks/src/tests/polkadot.test.ts @@ -1,32 +1,33 @@ -// import { afterAll, beforeAll, beforeEach, describe, test } from 'bun:test'; -// import { TRANSFER_AMOUNTS } from '@/constants'; -// import { createChainManager } from '@/managers/Factory'; -// import { ChainSetup } from '@/setup'; -// import { PolkadotToPolimecTransfer } from '@/transfers/PolkadotToPolimec'; -// import { Accounts, Asset, Chains } from '@/types'; +import { afterAll, beforeAll, beforeEach, describe, test } from 'bun:test'; +import { TRANSFER_AMOUNTS } from '@/constants'; +import { createChainManager } from '@/managers/Factory'; +import { ChainSetup } from '@/setup'; +import { PolkadotToPolimecTransfer } from '@/transfers/PolkadotToPolimec'; +import { Accounts, Asset, AssetSourceRelation, Chains } from '@/types'; -// describe('Polkadot -> Polimec Transfer Tests', () => { -// const chainSetup = new ChainSetup(); +describe('Polkadot -> Polimec Transfer Tests', () => { + const chainSetup = new ChainSetup(); -// const sourceManager = createChainManager(Chains.Polkadot); -// const destManager = createChainManager(Chains.Polimec); -// const transferTest = new PolkadotToPolimecTransfer(sourceManager, destManager); + const sourceManager = createChainManager(Chains.Polkadot); + const destManager = createChainManager(Chains.Polimec); + const hopManager = createChainManager(Chains.PolkadotHub); + const transferTest = new PolkadotToPolimecTransfer(sourceManager, destManager, hopManager); -// beforeAll(async () => await chainSetup.initialize()); -// beforeEach(() => { -// sourceManager.connect(); -// destManager.connect(); -// }); -// afterAll(async () => await chainSetup.cleanup()); + beforeAll(async () => await chainSetup.initialize()); + beforeEach(() => { + sourceManager.connect(); + hopManager.connect(); + destManager.connect(); + }); + afterAll(async () => await chainSetup.cleanup()); -// test( -// 'Send DOT to Polimec', -// () => -// transferTest.testTransfer({ -// amount: TRANSFER_AMOUNTS.NATIVE, -// account: Accounts.ALICE, -// asset: Asset.DOT, -// }), -// { timeout: 25000 }, -// ); -// }); + test( + 'Send DOT to Polimec, via AH', + () => + transferTest.testTransfer({ + account: Accounts.ALICE, + assets: [[Asset.DOT, TRANSFER_AMOUNTS.NATIVE, AssetSourceRelation.Self]], + }), + { timeout: 25000 }, + ); +}); diff --git a/integration-tests/chopsticks/src/transfers/BaseTransfer.ts b/integration-tests/chopsticks/src/transfers/BaseTransfer.ts index 311a96300..7d98074fc 100644 --- a/integration-tests/chopsticks/src/transfers/BaseTransfer.ts +++ b/integration-tests/chopsticks/src/transfers/BaseTransfer.ts @@ -1,12 +1,6 @@ import { expect } from 'bun:test'; import type { BaseChainManager } from '@/managers/BaseManager'; -import type { - Accounts, - Asset, - AssetSourceRelation, - BalanceCheck, - TransferResult, -} from '@/types'; +import type { Accounts, Asset, AssetSourceRelation, BalanceCheck, TransferResult } from '@/types'; import { sleep } from 'bun'; export interface TransferOptions { @@ -18,7 +12,7 @@ export abstract class BaseTransferTest { constructor( protected sourceManager: BaseChainManager, protected destManager: BaseChainManager, - ) { } + ) {} abstract executeTransfer(options: TransferOptions): Promise; abstract getBalances(options: TransferOptions): Promise<{ asset_balances: BalanceCheck[] }>; @@ -52,4 +46,4 @@ export abstract class BaseTransferTest { expect(events).toHaveLength(1); expect(events[0].payload.success).toBeTrue(); } -} \ No newline at end of file +} diff --git a/integration-tests/chopsticks/src/transfers/PolimecToHub.ts b/integration-tests/chopsticks/src/transfers/PolimecToHub.ts index 166fe50e5..c6e347414 100644 --- a/integration-tests/chopsticks/src/transfers/PolimecToHub.ts +++ b/integration-tests/chopsticks/src/transfers/PolimecToHub.ts @@ -67,4 +67,4 @@ export class PolimecToHubTransfer extends BaseTransferTest { expect(finalBalances[i].destination).toBeGreaterThan(initialBalances[i].destination); } } -} \ No newline at end of file +} diff --git a/integration-tests/chopsticks/src/transfers/PolkadotToPolimec.ts b/integration-tests/chopsticks/src/transfers/PolkadotToPolimec.ts index b36fd31ba..fbda1dcec 100644 --- a/integration-tests/chopsticks/src/transfers/PolkadotToPolimec.ts +++ b/integration-tests/chopsticks/src/transfers/PolkadotToPolimec.ts @@ -1,14 +1,29 @@ import { expect } from 'bun:test'; import type { PolimecManager } from '@/managers/PolimecManager'; +import type { PolkadotHubManager } from '@/managers/PolkadotHubManager'; import type { PolkadotManager } from '@/managers/PolkadotManager'; -import { Asset, type BalanceCheck, Chains, type PolimecBalanceCheck } from '@/types'; -import { createDotMultiHopTransferData } from '@/utils'; +import { + Asset, + AssetSourceRelation, + Chains, + ParaId, + type PolimecBalanceCheck, + getVersionedAssets, +} from '@/types'; +import { abs, createDotMultiHopTransferData, createTransferData, unwrap } from '@/utils'; +import { + DispatchRawOrigin, + XcmVersionedAssetId, + type XcmVersionedLocation, + type XcmVersionedXcm, +} from '@polkadot-api/descriptors'; import { BaseTransferTest, type TransferOptions } from './BaseTransfer'; export class PolkadotToPolimecTransfer extends BaseTransferTest { constructor( protected override sourceManager: PolkadotManager, protected override destManager: PolimecManager, + protected hopManager: PolkadotHubManager, ) { super(sourceManager, destManager); } @@ -17,6 +32,7 @@ export class PolkadotToPolimecTransfer extends BaseTransferTest { const [sourceBlock, destBlock] = await Promise.all([ this.sourceManager.getBlockNumber(), this.destManager.getBlockNumber(), + this.hopManager.getBlockNumber(), ]); const amount = assets[0][1]; @@ -31,39 +47,138 @@ export class PolkadotToPolimecTransfer extends BaseTransferTest { return { sourceBlock, destBlock }; } - async getBalances(options: TransferOptions): Promise<{ asset_balances: PolimecBalanceCheck[] }> { - throw new Error('Method not implemented.'); + async getBalances({ + account, + assets, + }: TransferOptions): Promise<{ asset_balances: PolimecBalanceCheck[] }> { + const asset_balances: PolimecBalanceCheck[] = []; + const treasury_account = this.destManager.getTreasuryAccount(); + for (const [asset] of assets) { + const balances: PolimecBalanceCheck = { + source: await this.sourceManager.getAssetBalanceOf(account, asset), + destination: await this.destManager.getAssetBalanceOf(account, asset), + treasury: await this.destManager.getAssetBalanceOf(treasury_account, asset), + }; + asset_balances.push(balances); + } + return { asset_balances }; } + // TODO: This is not accurate at the moment. + // TODO: We should improve the logic to handle the Polkadot -> Hub -> Polimec transfer fee. async verifyFinalBalances( - initialBalances: PolimecBalanceCheck[], - finalBalances: PolimecBalanceCheck[], - options: TransferOptions, - ): Promise { - throw new Error('Method not implemented.'); + assetInitialBalances: PolimecBalanceCheck[], + assetFinalBalances: PolimecBalanceCheck[], + transferOptions: TransferOptions, + ) { + const native_extrinsic_fee_amount = await this.sourceManager.getTransactionFee(); + const source_xcm_asset_fee_amount = await this.sourceManager.getXcmFee(); + const hop_xcm_asset_fee_amount = await this.calculateHubXcmFee(transferOptions); + + const fee_asset = transferOptions.assets[0][0]; + + for (let i = 0; i < transferOptions.assets.length; i++) { + const initialBalances = assetInitialBalances[i]; + const finalBalances = assetFinalBalances[i]; + const send_amount = transferOptions.assets[i][1]; + const asset = transferOptions.assets[i][0]; + + let expectedSourceBalanceSpent = send_amount; + let expectedDestBalanceSpent = 0n; + let expectedTreasuryBalanceGained = 0n; + + if (asset === Asset.DOT) { + expectedSourceBalanceSpent += native_extrinsic_fee_amount + source_xcm_asset_fee_amount; + } + if (asset === fee_asset) { + expectedDestBalanceSpent += hop_xcm_asset_fee_amount; + expectedTreasuryBalanceGained += hop_xcm_asset_fee_amount; + } + + expect(finalBalances.source).toBe(initialBalances.source - expectedSourceBalanceSpent); + const difference = + finalBalances.destination - + (initialBalances.destination + send_amount - expectedDestBalanceSpent); + const tolerance = (finalBalances.destination * 1_000_000n) / 1_000_000_000n; // 0.0001% + expect(abs(difference)).toBeLessThanOrEqual(tolerance); + } + } + + async calculateHubXcmFee(transferOptions: TransferOptions): Promise { + console.log('Calculating Polkadot -> Hub -> Polimec fees'); + return 422157353n; // TODO: Replace this with the actual fee calculation below } - // async getBalances({ - // account, - // }: Omit): Promise<{ balances: PolimecBalanceCheck }> { - // const treasuryAccount = this.destManager.getTreasuryAccount(); - // return { - // balances: { - // source: await this.sourceManager.getAssetBalanceOf(account, Asset.DOT), - // destination: await this.destManager.getAssetBalanceOf(account, Asset.DOT), - // treasury: await this.destManager.getAssetBalanceOf(treasuryAccount, Asset.DOT), - // }, - // }; - // } - - // async verifyFinalBalances( - // initialBalances: PolimecBalanceCheck, - // finalBalances: PolimecBalanceCheck, - // ) { - // // TODO: At the moment we exclude fees from the balance check since the PAPI team is wotking on some utilies to calculate fees. - // expect(initialBalances.destination).toBe(0n); - // expect(finalBalances.source).toBeLessThan(initialBalances.source); - // expect(finalBalances.destination).toBeGreaterThan(initialBalances.destination); - // expect(finalBalances.treasury).toBeGreaterThan(initialBalances.treasury); - // } -} \ No newline at end of file + async computeFee(transferOptions: TransferOptions) { + let destinationExecutionFee: bigint; + + const sourceApi = this.sourceManager.getApi(Chains.Polkadot); + // const polimecApi = this.destManager.getApi(Chains.Polimec); + const destApi = this.hopManager.getApi(Chains.PolkadotHub); + + const versioned_assets = getVersionedAssets(transferOptions.assets); + const transferData = createTransferData({ + toChain: Chains.PolkadotHub, + assets: versioned_assets, + recv: transferOptions.account, + }); + console.dir(transferData, { depth: null, colors: true }); + + let remoteFeeAssetId: XcmVersionedAssetId; + const lastAsset = unwrap(transferOptions.assets[0]); + if (lastAsset[0] === Asset.DOT) { + lastAsset[2] = AssetSourceRelation.Parent; + } else { + throw new Error('Invalid asset'); + } + const versioned_asset = getVersionedAssets([lastAsset]); + if (versioned_asset.type === 'V4') { + remoteFeeAssetId = XcmVersionedAssetId.V4(unwrap(versioned_asset.value[0]).id); + } else { + throw new Error('Invalid versioned assets'); + } + console.log('remoteFeeAssetId', remoteFeeAssetId); + const localDryRunResult = await sourceApi.apis.DryRunApi.dry_run_call( + { type: 'system', value: DispatchRawOrigin.Signed(transferOptions.account) }, + { type: 'XcmPallet', value: { type: 'transfer_assets', value: transferData } }, + ); + console.log('localDryRunResult', localDryRunResult); + + let forwardedXcms: [XcmVersionedLocation, XcmVersionedXcm[]][] = []; + if (localDryRunResult.success && localDryRunResult.value.forwarded_xcms) { + forwardedXcms = localDryRunResult.value.forwarded_xcms; + } else { + throw new Error('Dry run failed'); + } + + const xcmsToHub = forwardedXcms.find( + ([location, _]) => + location.type === 'V4' && + location.value.parents === 0 && + location.value.interior.type === 'X1' && + location.value.interior.value.type === 'Parachain' && + location.value.interior.value.value === ParaId[Chains.PolkadotHub], + ); + if (!xcmsToHub) { + throw new Error('Could not find xcm to Polkadot Hub'); + } + const messages = xcmsToHub[1]; + const remoteXcm = messages[0]; + const remoteXcmWeightResult = await destApi.apis.XcmPaymentApi.query_xcm_weight(remoteXcm); + if (remoteXcmWeightResult.success) { + const remoteExecutionFeesResult = await destApi.apis.XcmPaymentApi.query_weight_to_asset_fee( + remoteXcmWeightResult.value, + remoteFeeAssetId, + ); + if (remoteExecutionFeesResult.success) { + destinationExecutionFee = remoteExecutionFeesResult.value; + } else { + throw new Error('Could not calculate destination xcm fee'); + } + } else { + throw new Error('Could not calculate xcm weight'); + } + + return destinationExecutionFee; + } +} diff --git a/integration-tests/chopsticks/src/types.ts b/integration-tests/chopsticks/src/types.ts index 4a534e7f4..0fae64098 100644 --- a/integration-tests/chopsticks/src/types.ts +++ b/integration-tests/chopsticks/src/types.ts @@ -70,9 +70,9 @@ export interface TransferDataParams { } export enum Asset { - USDT = 1984, DOT = 10, USDC = 1337, + USDT = 1984, WETH = 10000, } diff --git a/integration-tests/chopsticks/src/utils.ts b/integration-tests/chopsticks/src/utils.ts index 5e7b9d16d..c11577659 100644 --- a/integration-tests/chopsticks/src/utils.ts +++ b/integration-tests/chopsticks/src/utils.ts @@ -134,3 +134,4 @@ export function flatObject(obj: unknown): unknown { } return obj; } +export const abs = (n: bigint) => (n < 0n ? -n : n); From 48aba6fcf1bdb0543757495fa625cae337cfe99b Mon Sep 17 00:00:00 2001 From: Leonardo Razovic <4128940+lrazovic@users.noreply.github.com> Date: Thu, 16 Jan 2025 18:40:36 +0100 Subject: [PATCH 07/11] chore: const WETH address --- .../chopsticks/overrides/polimec.ts | 6 ++--- .../chopsticks/overrides/polkadot-hub.ts | 23 ++----------------- integration-tests/chopsticks/src/constants.ts | 2 ++ integration-tests/chopsticks/src/types.ts | 4 ++-- 4 files changed, 9 insertions(+), 26 deletions(-) diff --git a/integration-tests/chopsticks/overrides/polimec.ts b/integration-tests/chopsticks/overrides/polimec.ts index 70d9007ed..507470cc4 100644 --- a/integration-tests/chopsticks/overrides/polimec.ts +++ b/integration-tests/chopsticks/overrides/polimec.ts @@ -1,5 +1,5 @@ -import { INITIAL_BALANCES } from '@/constants'; -import { Accounts, Asset, AssetLocation, AssetSourceRelation } from '@/types'; +import { INITIAL_BALANCES, WETH_ADDRESS } from '@/constants'; +import { Accounts } from '@/types'; export const POLIMEC_WASM = '../../target/release/wbuild/polimec-runtime/polimec_runtime.compact.compressed.wasm'; @@ -36,7 +36,7 @@ export const weth_location = { }, { accountKey20: { - key: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + key: WETH_ADDRESS, }, }, ], diff --git a/integration-tests/chopsticks/overrides/polkadot-hub.ts b/integration-tests/chopsticks/overrides/polkadot-hub.ts index 66a718b5e..023242a63 100644 --- a/integration-tests/chopsticks/overrides/polkadot-hub.ts +++ b/integration-tests/chopsticks/overrides/polkadot-hub.ts @@ -1,25 +1,6 @@ import { INITIAL_BALANCES } from '@/constants'; -import { Accounts, Asset } from '@/types'; - -const weth_location = { - parents: 2, - interior: { - x2: [ - { - globalConsensus: { - ethereum: { - chainId: 1n, - }, - }, - }, - { - accountKey20: { - key: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', - }, - }, - ], - }, -}; +import { Accounts, Asset } from '@/types';import { weth_location } from './polimec'; +; export const polkadot_hub_storage = { System: { diff --git a/integration-tests/chopsticks/src/constants.ts b/integration-tests/chopsticks/src/constants.ts index 4c8b2f611..331a98125 100644 --- a/integration-tests/chopsticks/src/constants.ts +++ b/integration-tests/chopsticks/src/constants.ts @@ -18,3 +18,5 @@ export const DERIVE_PATHS = { [Accounts.ALICE]: '//Alice', [Accounts.BOB]: '//Bob', }; + +export const WETH_ADDRESS = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" \ No newline at end of file diff --git a/integration-tests/chopsticks/src/types.ts b/integration-tests/chopsticks/src/types.ts index 0fae64098..66b5500eb 100644 --- a/integration-tests/chopsticks/src/types.ts +++ b/integration-tests/chopsticks/src/types.ts @@ -10,6 +10,7 @@ import { type polkadot, } from '@polkadot-api/descriptors'; import { FixedSizeBinary, type PolkadotClient, type TypedApi } from 'polkadot-api'; +import { WETH_ADDRESS } from './constants'; type Polimec = typeof polimec; type PolkadotHub = typeof pah; @@ -161,8 +162,7 @@ export function AssetLocation( return NativeAssetLocation(asset_source_relation); case Asset.WETH: { - const contract_hex = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; - return EthereumAssetLocation(FixedSizeBinary.fromHex(contract_hex)); + return EthereumAssetLocation(FixedSizeBinary.fromHex(WETH_ADDRESS)); } } } From 8acec82c5822d5c522273aefa4a84ad207888afc Mon Sep 17 00:00:00 2001 From: Leonardo Razovic <4128940+lrazovic@users.noreply.github.com> Date: Fri, 17 Jan 2025 17:20:05 +0100 Subject: [PATCH 08/11] chore: fix linter --- integration-tests/chopsticks/bun.lockb | Bin 199316 -> 199316 bytes .../chopsticks/overrides/polkadot-hub.ts | 4 ++-- integration-tests/chopsticks/src/constants.ts | 2 +- .../chopsticks/src/managers/BaseManager.ts | 6 +++--- .../chopsticks/src/tests/hub.test.ts | 5 +---- .../chopsticks/src/transfers/BaseTransfer.ts | 7 ++++--- .../src/transfers/PolkadotToPolimec.ts | 4 ++-- 7 files changed, 13 insertions(+), 15 deletions(-) diff --git a/integration-tests/chopsticks/bun.lockb b/integration-tests/chopsticks/bun.lockb index a421c147ce68ff8cffb0ead1ddd80ca6185f431f..5d4aed79a7750e834b9e1608a86faf59eb43d7da 100755 GIT binary patch delta 254 zcmVBGEt@3i4Fh_VmKPT+> zlNeu(Wi?plifdc~_m)o-f8v!)8_p<_> ER#04XkN^Mx delta 254 zcmVDY-eZuzUX6(Xp79a|A=i*Z%IoS1Ylu}&IR z0T7cBASaWMRu};*lMx^%lTaWCv)ER(u|N~+p6JsYpMh5kmcv9%R{RyzR?RhslP%L~ zhx7jj7%DOKsV;}YjW}0uy`&QLFyK#QW>X?q6%=X%troR8=GvFd2mu&CTTBAAQT}fM zY3`lbg3UJytV9lF@e=u_P|w8#s1_psQ}PU+qT9%fhR4p}(s9j$rWjE++WUl(!bg+r zrGojZhb0LCwfdc~_m)o-f8v!%7_p<_> ER Polimec Transfer Tests', () => { // () => // transferTest.testTransfer({ // account: Accounts.ALICE, - // assets: [ - // // [Asset.USDT, TRANSFER_AMOUNTS.TOKENS, AssetSourceRelation.Self], - // [Asset.WETH, TRANSFER_AMOUNTS.BRIDGED, AssetSourceRelation.Self], - // ], + // assets: [[Asset.WETH, TRANSFER_AMOUNTS.BRIDGED, AssetSourceRelation.Self]], // }), // { timeout: 25000 }, // ); diff --git a/integration-tests/chopsticks/src/transfers/BaseTransfer.ts b/integration-tests/chopsticks/src/transfers/BaseTransfer.ts index 7d98074fc..9e6df55a1 100644 --- a/integration-tests/chopsticks/src/transfers/BaseTransfer.ts +++ b/integration-tests/chopsticks/src/transfers/BaseTransfer.ts @@ -27,14 +27,15 @@ export abstract class BaseTransferTest { if (options.assets[0][1] > initialBalances[0].source) { throw new Error(`Insufficient balance on Source chain for asset: ${options.assets[0][0]}`); } - const blockNumbers = await this.executeTransfer(options); - await this.waitForBlocks(blockNumbers.destBlock); + const _blockNumbers = await this.executeTransfer(options); + await this.waitForBlocks(); await this.verifyExecution(); const { asset_balances: finalBalances } = await this.getBalances(options); this.verifyFinalBalances(initialBalances, finalBalances, options); } - protected async waitForBlocks(destBlockNumber: number) { + // TODO: Wait for the next block to be produced. + protected async waitForBlocks() { await sleep(2000); } diff --git a/integration-tests/chopsticks/src/transfers/PolkadotToPolimec.ts b/integration-tests/chopsticks/src/transfers/PolkadotToPolimec.ts index fbda1dcec..fa55eab38 100644 --- a/integration-tests/chopsticks/src/transfers/PolkadotToPolimec.ts +++ b/integration-tests/chopsticks/src/transfers/PolkadotToPolimec.ts @@ -104,9 +104,9 @@ export class PolkadotToPolimecTransfer extends BaseTransferTest { } } - async calculateHubXcmFee(transferOptions: TransferOptions): Promise { + async calculateHubXcmFee(_transferOptions: TransferOptions): Promise { console.log('Calculating Polkadot -> Hub -> Polimec fees'); - return 422157353n; // TODO: Replace this with the actual fee calculation below + return await Promise.resolve(422157353n); // TODO: Replace this with the actual fee calculation below } async computeFee(transferOptions: TransferOptions) { From 8d39a01a24f8c1c9c2f477555bb4b015c6561f16 Mon Sep 17 00:00:00 2001 From: Leonardo Razovic <4128940+lrazovic@users.noreply.github.com> Date: Mon, 20 Jan 2025 16:51:52 +0100 Subject: [PATCH 09/11] feat: inital BH setup --- .../chopsticks/.papi/descriptors/package.json | 2 +- .../chopsticks/.papi/polkadot-api.json | 4 + .../chopsticks/overrides/bridge-hub.ts | 18 ++++ .../chopsticks/overrides/index.ts | 3 +- .../src/managers/BridgeHubManager.ts | 77 +++++++++++++++++ .../chopsticks/src/managers/Factory.ts | 7 +- integration-tests/chopsticks/src/setup.ts | 28 ++++++- .../chopsticks/src/tests/bridge.test.ts | 33 ++++++++ .../chopsticks/src/transfers/BaseTransfer.ts | 23 +++--- .../src/transfers/BridgeToPolimec.ts | 82 +++++++++++++++++++ integration-tests/chopsticks/src/types.ts | 5 ++ 11 files changed, 266 insertions(+), 16 deletions(-) create mode 100644 integration-tests/chopsticks/overrides/bridge-hub.ts create mode 100644 integration-tests/chopsticks/src/managers/BridgeHubManager.ts create mode 100644 integration-tests/chopsticks/src/tests/bridge.test.ts create mode 100644 integration-tests/chopsticks/src/transfers/BridgeToPolimec.ts diff --git a/integration-tests/chopsticks/.papi/descriptors/package.json b/integration-tests/chopsticks/.papi/descriptors/package.json index 68407dbd1..50b7c07b8 100644 --- a/integration-tests/chopsticks/.papi/descriptors/package.json +++ b/integration-tests/chopsticks/.papi/descriptors/package.json @@ -1,5 +1,5 @@ { - "version": "0.1.0-autogenerated.575737038775881951", + "version": "0.1.0-autogenerated.697579702651170142", "name": "@polkadot-api/descriptors", "files": [ "dist" diff --git a/integration-tests/chopsticks/.papi/polkadot-api.json b/integration-tests/chopsticks/.papi/polkadot-api.json index 11a8fad67..f2d4ba115 100644 --- a/integration-tests/chopsticks/.papi/polkadot-api.json +++ b/integration-tests/chopsticks/.papi/polkadot-api.json @@ -13,6 +13,10 @@ "pah": { "wsUrl": "wss://sys.ibp.network/statemint", "metadata": ".papi/metadata/pah.scale" + }, + "bridge": { + "wsUrl": "wss://sys.ibp.network/bridgehub-polkadot", + "metadata": ".papi/metadata/bridge.scale" } } } \ No newline at end of file diff --git a/integration-tests/chopsticks/overrides/bridge-hub.ts b/integration-tests/chopsticks/overrides/bridge-hub.ts new file mode 100644 index 000000000..385b15a10 --- /dev/null +++ b/integration-tests/chopsticks/overrides/bridge-hub.ts @@ -0,0 +1,18 @@ +import { INITIAL_BALANCES } from '@/constants'; +import { Accounts } from '@/types'; + +export const bridge_storage = { + System: { + Account: [ + [ + [Accounts.ALICE], + { + providers: 1, + data: { + free: INITIAL_BALANCES.DOT, + }, + }, + ], + ], + }, +} as const; diff --git a/integration-tests/chopsticks/overrides/index.ts b/integration-tests/chopsticks/overrides/index.ts index f584a7a12..9c9288bb5 100644 --- a/integration-tests/chopsticks/overrides/index.ts +++ b/integration-tests/chopsticks/overrides/index.ts @@ -1,5 +1,6 @@ +import { bridge_storage } from '@/bridge-hub'; import { POLIMEC_WASM, polimec_storage } from '@/polimec'; import { polkadot_storage } from '@/polkadot'; import { polkadot_hub_storage } from '@/polkadot-hub'; -export { polkadot_storage, polkadot_hub_storage, POLIMEC_WASM, polimec_storage }; +export { polkadot_storage, polkadot_hub_storage, POLIMEC_WASM, polimec_storage, bridge_storage }; diff --git a/integration-tests/chopsticks/src/managers/BridgeHubManager.ts b/integration-tests/chopsticks/src/managers/BridgeHubManager.ts new file mode 100644 index 000000000..b9deed305 --- /dev/null +++ b/integration-tests/chopsticks/src/managers/BridgeHubManager.ts @@ -0,0 +1,77 @@ +import { type Accounts, Asset, AssetLocation, AssetSourceRelation, Chains } from '@/types'; +import { flatObject } from '@/utils.ts'; +import { bridge } from '@polkadot-api/descriptors'; +import { createClient } from 'polkadot-api'; +import { withPolkadotSdkCompat } from 'polkadot-api/polkadot-sdk-compat'; +import { getWsProvider } from 'polkadot-api/ws-provider/web'; +import { BaseChainManager } from './BaseManager'; + +export class BridgerHubManagaer extends BaseChainManager { + connect() { + const client = createClient(withPolkadotSdkCompat(getWsProvider(this.getChainType()))); + const api = client.getTypedApi(bridge); + + // Verify connection + if (!client || !api) { + throw new Error(`Failed to connect to ${this.getChainType()}`); + } + + this.clients.set(this.getChainType(), { client, api }); + } + + disconnect() { + this.clients.get(Chains.BridgeHub)?.client.destroy(); + } + + getChainType() { + return Chains.BridgeHub; + } + + getXcmPallet() { + const api = this.getApi(Chains.BridgeHub); + return api.tx.PolkadotXcm; + } + + getTreasuryAccount() { + return '58kXueYKLr5b8yCeY3Gd1nLQX2zSJLXjfMzTAuksNq25CFEL' as Accounts; + } + + getAssetSourceRelation(asset: Asset): AssetSourceRelation { + switch (asset) { + case Asset.DOT: + return AssetSourceRelation.Parent; + case Asset.USDT: + return AssetSourceRelation.Sibling; + case Asset.USDC: + return AssetSourceRelation.Sibling; + case Asset.WETH: + // TODO: Check it Placeholder + return AssetSourceRelation.Self; + } + } + + async getAssetBalanceOf(account: Accounts, asset: Asset): Promise { + const api = this.getApi(Chains.BridgeHub); + // const asset_source_relation = this.getAssetSourceRelation(asset); + // const asset_location = AssetLocation(asset, asset_source_relation).value; + // const account_balances_result = await api.apis.FungiblesApi.query_account_balances(account); + // if (account_balances_result.success === true && account_balances_result.value.type === 'V4') { + // const assets = account_balances_result.value.value; + // for (const asset of assets) { + // if (Bun.deepEquals(flatObject(asset.id), flatObject(asset_location))) { + // return asset.fun.value as bigint; + // } + // } + // } + return 0n; + } + + async getLocalXcmFee() { + const api = this.getApi(Chains.BridgeHub); + const events = await api.event.PolkadotXcm.FeesPaid.pull(); + if (!events.length) return 0n; + const fees = events[0]?.payload?.fees; + if (!fees?.length) return 0n; + return (fees[0]?.fun?.value as bigint) || 0n; + } +} diff --git a/integration-tests/chopsticks/src/managers/Factory.ts b/integration-tests/chopsticks/src/managers/Factory.ts index 4734f8fbf..13dc6d5e7 100644 --- a/integration-tests/chopsticks/src/managers/Factory.ts +++ b/integration-tests/chopsticks/src/managers/Factory.ts @@ -1,4 +1,5 @@ import { Chains } from '@/types'; +import { BridgerHubManagaer } from './BridgeHubManager'; import { PolimecManager } from './PolimecManager'; import { PolkadotHubManager } from './PolkadotHubManager'; import { PolkadotManager } from './PolkadotManager'; @@ -7,7 +8,11 @@ const chainManagerMap = { [Chains.PolkadotHub]: PolkadotHubManager, [Chains.Polimec]: PolimecManager, [Chains.Polkadot]: PolkadotManager, -} satisfies Record PolkadotHubManager | PolimecManager | PolkadotManager>; + [Chains.BridgeHub]: BridgerHubManagaer, +} satisfies Record< + Chains, + new () => PolkadotHubManager | PolimecManager | PolkadotManager | BridgerHubManagaer +>; export function createChainManager( chain: T, diff --git a/integration-tests/chopsticks/src/setup.ts b/integration-tests/chopsticks/src/setup.ts index ba7413117..ade85c7e8 100644 --- a/integration-tests/chopsticks/src/setup.ts +++ b/integration-tests/chopsticks/src/setup.ts @@ -6,7 +6,7 @@ import { connectParachains, connectVertical, } from '@acala-network/chopsticks-core'; -import { POLIMEC_WASM, polkadot_hub_storage, polkadot_storage } from '../overrides'; +import { POLIMEC_WASM, bridge_storage, polkadot_hub_storage, polkadot_storage } from '../overrides'; type SetupResult = Awaited>; @@ -14,17 +14,20 @@ export class ChainSetup { private relaychain?: Blockchain; private polimec?: Blockchain; private assetHub?: Blockchain; + private bridgeHub?: Blockchain; // Store setup objects for cleanup private polimecSetup?: SetupResult; private assetHubSetup?: SetupResult; private relaychainSetup?: SetupResult; + private bridgeHubSetup?: SetupResult; async initialize(polimec_storage?: unknown) { - const [polimecSetup, assetHubSetup, relaychainSetup] = await Promise.all([ + const [polimecSetup, assetHubSetup, relaychainSetup, bridgeHubSetup] = await Promise.all([ this.setupPolimec(polimec_storage), this.setupAssetHub(), this.setupRelaychain(), + this.setupBridgeHub(), ]); console.log('✅ Local nodes instances are up'); @@ -33,16 +36,20 @@ export class ChainSetup { this.polimecSetup = polimecSetup; this.assetHubSetup = assetHubSetup; this.relaychainSetup = relaychainSetup; + this.bridgeHubSetup = bridgeHubSetup; // Store chain references this.polimec = polimecSetup.chain; this.assetHub = assetHubSetup.chain; this.relaychain = relaychainSetup.chain; + this.bridgeHub = bridgeHubSetup.chain; await Promise.all([ connectVertical(this.relaychain, this.polimec), connectVertical(this.relaychain, this.assetHub), + connectVertical(this.relaychain, this.bridgeHub), connectParachains([this.polimec, this.assetHub]), + connectParachains([this.bridgeHub, this.assetHub]), ]); console.log('✅ HRMP channels created'); @@ -57,11 +64,17 @@ export class ChainSetup { } async cleanup() { - await Promise.all([this.relaychain?.close(), this.polimec?.close(), this.assetHub?.close()]); + await Promise.all([ + this.relaychain?.close(), + this.polimec?.close(), + this.assetHub?.close(), + this.bridgeHub?.close(), + ]); await Promise.all([ this.relaychainSetup?.close(), this.polimecSetup?.close(), this.assetHubSetup?.close(), + this.bridgeHubSetup?.close(), ]); console.log('✅ Local nodes instances are down'); } @@ -109,4 +122,13 @@ export class ChainSetup { 'build-block-mode': BuildBlockMode.Instant, }); } + + private setupBridgeHub() { + return setupWithServer({ + endpoint: 'wss://sys.ibp.network/bridgehub-polkadot', + port: 8003, + 'import-storage': bridge_storage, + 'build-block-mode': BuildBlockMode.Instant, + }); + } } diff --git a/integration-tests/chopsticks/src/tests/bridge.test.ts b/integration-tests/chopsticks/src/tests/bridge.test.ts new file mode 100644 index 000000000..333d46938 --- /dev/null +++ b/integration-tests/chopsticks/src/tests/bridge.test.ts @@ -0,0 +1,33 @@ +import { afterAll, beforeAll, beforeEach, describe, test } from 'bun:test'; +import { TRANSFER_AMOUNTS } from '@/constants'; +import { createChainManager } from '@/managers/Factory'; +import { ChainSetup } from '@/setup'; +import { BridgeToPolimecTransfer } from '@/transfers/BridgeToPolimec'; +import { HubToPolimecTransfer } from '@/transfers/HubToPolimec'; +import { Accounts, Asset, AssetSourceRelation, Chains } from '@/types'; + +describe('Bridge Hub -> Polimec Transfer Tests', () => { + const sourceManager = createChainManager(Chains.BridgeHub); + const hopManager = createChainManager(Chains.PolkadotHub); + const destManager = createChainManager(Chains.Polimec); + const transferTest = new BridgeToPolimecTransfer(sourceManager, hopManager, destManager); + const chainSetup = new ChainSetup(); + + beforeAll(async () => await chainSetup.initialize()); + beforeEach(() => { + sourceManager.connect(); + hopManager.connect(); + destManager.connect(); + }); + afterAll(async () => await chainSetup.cleanup()); + + test( + 'Send WETH to Polimec', + () => + transferTest.testTransfer({ + account: Accounts.ALICE, + assets: [[Asset.WETH, TRANSFER_AMOUNTS.BRIDGED, AssetSourceRelation.Self]], + }), + { timeout: 25000 }, + ); +}); diff --git a/integration-tests/chopsticks/src/transfers/BaseTransfer.ts b/integration-tests/chopsticks/src/transfers/BaseTransfer.ts index 9e6df55a1..4c3f7a3bf 100644 --- a/integration-tests/chopsticks/src/transfers/BaseTransfer.ts +++ b/integration-tests/chopsticks/src/transfers/BaseTransfer.ts @@ -12,7 +12,10 @@ export abstract class BaseTransferTest { constructor( protected sourceManager: BaseChainManager, protected destManager: BaseChainManager, - ) {} + ) { + this.sourceManager = sourceManager; + this.destManager = destManager; + } abstract executeTransfer(options: TransferOptions): Promise; abstract getBalances(options: TransferOptions): Promise<{ asset_balances: BalanceCheck[] }>; @@ -23,15 +26,15 @@ export abstract class BaseTransferTest { ): void; async testTransfer(options: TransferOptions) { - const { asset_balances: initialBalances } = await this.getBalances(options); - if (options.assets[0][1] > initialBalances[0].source) { - throw new Error(`Insufficient balance on Source chain for asset: ${options.assets[0][0]}`); - } - const _blockNumbers = await this.executeTransfer(options); - await this.waitForBlocks(); - await this.verifyExecution(); - const { asset_balances: finalBalances } = await this.getBalances(options); - this.verifyFinalBalances(initialBalances, finalBalances, options); + // const { asset_balances: initialBalances } = await this.getBalances(options); + // if (options.assets[0][1] > initialBalances[0].source) { + // throw new Error(`Insufficient balance on Source chain for asset: ${options.assets[0][0]}`); + // } + const blockNumbers = await this.executeTransfer(options); + // await this.waitForBlocks(); + // await this.verifyExecution(); + // const { asset_balances: finalBalances } = await this.getBalances(options); + // this.verifyFinalBalances(initialBalances, finalBalances, options); } // TODO: Wait for the next block to be produced. diff --git a/integration-tests/chopsticks/src/transfers/BridgeToPolimec.ts b/integration-tests/chopsticks/src/transfers/BridgeToPolimec.ts new file mode 100644 index 000000000..7d95c19f0 --- /dev/null +++ b/integration-tests/chopsticks/src/transfers/BridgeToPolimec.ts @@ -0,0 +1,82 @@ +import { expect } from 'bun:test'; +import type { BridgerHubManagaer } from '@/managers/BridgeHubManager'; +import type { PolimecManager } from '@/managers/PolimecManager'; +import type { PolkadotHubManager } from '@/managers/PolkadotHubManager'; +import { + Asset, + AssetSourceRelation, + Chains, + ParaId, + type PolimecBalanceCheck, + getVersionedAssets, +} from '@/types'; +import { createTransferData, unwrap } from '@/utils'; +import { + DispatchRawOrigin, + XcmVersionedAssetId, + type XcmVersionedLocation, + type XcmVersionedXcm, +} from '@polkadot-api/descriptors'; + +import { BaseTransferTest, type TransferOptions } from './BaseTransfer'; + +export class BridgeToPolimecTransfer extends BaseTransferTest { + constructor( + protected override sourceManager: BridgerHubManagaer, + protected hopManager: PolkadotHubManager, + protected override destManager: PolimecManager, + ) { + super(sourceManager, destManager); + this.hopManager = hopManager; + } + + async executeTransfer({ account, assets }: TransferOptions) { + console.log('BridgeToPolimecTransfer executeTransfer'); + const [sourceBlock, hopManagerBlock, destBlock] = await Promise.all([ + this.sourceManager.getBlockNumber(), + this.hopManager.getBlockNumber(), + this.destManager.getBlockNumber(), + ]); + + console.log('sourceBlock', sourceBlock); + console.log('hopManagerBlock', hopManagerBlock); + console.log('destBlock', destBlock); + return { sourceBlock, destBlock }; + + // const versioned_assets = getVersionedAssets(assets); + + // const data = createTransferData({ + // toChain: Chains.Polimec, + // assets: versioned_assets, + // recv: account, + // }); + + // const api = this.sourceManager.getApi(Chains.PolkadotHub); + // const transfer = api.tx.PolkadotXcm.transfer_assets(data); + // const res = await transfer.signAndSubmit(this.sourceManager.getSigner(account)); + + // console.log('Extrinsic result: ', res.ok); + + // expect(res.ok).toBeTrue(); + // return { sourceBlock, destBlock }; + } + + async getBalances({ + account, + assets, + }: TransferOptions): Promise<{ asset_balances: PolimecBalanceCheck[] }> { + return { asset_balances: [{ source: 0n, destination: 0n, treasury: 0n }] }; + } + + async verifyFinalBalances( + assetInitialBalances: PolimecBalanceCheck[], + assetFinalBalances: PolimecBalanceCheck[], + transferOptions: TransferOptions, + ) { + expect(0n).toBe(0n); + } + + async calculatePolimecXcmFee(transferOptions: TransferOptions): Promise { + return 0n; + } +} diff --git a/integration-tests/chopsticks/src/types.ts b/integration-tests/chopsticks/src/types.ts index 66b5500eb..e29494434 100644 --- a/integration-tests/chopsticks/src/types.ts +++ b/integration-tests/chopsticks/src/types.ts @@ -5,6 +5,7 @@ import { XcmV3MultiassetFungibility, XcmVersionedAssets, XcmVersionedLocation, + type bridge, type pah, type polimec, type polkadot, @@ -15,11 +16,13 @@ import { WETH_ADDRESS } from './constants'; type Polimec = typeof polimec; type PolkadotHub = typeof pah; type Polkadot = typeof polkadot; +type BridgeHub = typeof bridge; export enum Chains { Polimec = 'ws://localhost:8000', PolkadotHub = 'ws://localhost:8001', Polkadot = 'ws://localhost:8002', + BridgeHub = 'ws://localhost:8003', } export type ChainClient = { @@ -30,6 +33,7 @@ export type ChainClient = { export const ParaId = { [Chains.Polimec]: 3344, [Chains.PolkadotHub]: 1000, + [Chains.BridgeHub]: 1001, }; export enum AssetSourceRelation { @@ -47,6 +51,7 @@ export type ChainToDefinition = { [Chains.Polimec]: Polimec; [Chains.PolkadotHub]: PolkadotHub; [Chains.Polkadot]: Polkadot; + [Chains.BridgeHub]: BridgeHub; }; export interface TransferResult { From a64e4e968a120e3c825a285b8c6008ca31d74e75 Mon Sep 17 00:00:00 2001 From: Leonardo Razovic <4128940+lrazovic@users.noreply.github.com> Date: Tue, 21 Jan 2025 18:44:45 +0100 Subject: [PATCH 10/11] dev: save --- integration-tests/chopsticks/bun.lockb | Bin 199316 -> 199316 bytes .../chopsticks/overrides/polkadot-hub.ts | 4 - .../chopsticks/src/managers/BaseManager.ts | 12 +- .../src/transfers/BridgeToPolimec.ts | 197 ++++++++++++++++-- integration-tests/chopsticks/src/types.ts | 2 +- 5 files changed, 185 insertions(+), 30 deletions(-) diff --git a/integration-tests/chopsticks/bun.lockb b/integration-tests/chopsticks/bun.lockb index 5d4aed79a7750e834b9e1608a86faf59eb43d7da..da76a833d6f759fce03d0ecbc4854ef84f72ffdf 100755 GIT binary patch delta 153 zcmV;K0A~M`l?;@X43I7$;LiJ6vbz;Pkv+<7%-E^8?$aNvyfK(u|R^{I&h2s@eX&;Jpr1ACyY$d>y)Ebt&(X$G&SWqwYj@M6D&=hwfxXc z5gSVURS9Z{yFM#kQiw|Imn&Qlq`!yF2m!av2mAt10A>9 HvjVwRI`K&T delta 155 zcmV;M0A&A^l?;@X43I7$FqomPL##90Cy0%fQxW1Z3FeG0v0s5tcU`_6e~uyju}&IR z0TGiCASbhsR{gO+&6{*7sz34&Ff`@i;km8ya+@$mctAfV?DvxxUyWroSmukDg-_F8 zxYUn<-qxPxfsdAUnP*c_dN3Vh*Sp=$hs_89x6KFw<*E!fE_7#lVs&mUba`->fdc~_ Jx7)J; } + getClient(chain: T): PolkadotClient { + const client = this.clients.get(chain); + if (!client) throw new Error(`Chain ${chain} not initialized`); + return client.client; + } + getSigner(account: Accounts) { const signer = this.signers.get(account); if (!signer) throw new Error(`Signer for ${account} not found`); @@ -57,9 +63,11 @@ export abstract class BaseChainManager { ); } - getBlockNumber() { + async getBlockNumber() { const chain = this.getChainType(); const api = this.getApi(chain); + // Note: Not sure why this is needed, but without it we cannot retrieve the block number. + await api.compatibilityToken; return api.query.System.Number.getValue(); } diff --git a/integration-tests/chopsticks/src/transfers/BridgeToPolimec.ts b/integration-tests/chopsticks/src/transfers/BridgeToPolimec.ts index 7d95c19f0..1e782eff4 100644 --- a/integration-tests/chopsticks/src/transfers/BridgeToPolimec.ts +++ b/integration-tests/chopsticks/src/transfers/BridgeToPolimec.ts @@ -3,6 +3,7 @@ import type { BridgerHubManagaer } from '@/managers/BridgeHubManager'; import type { PolimecManager } from '@/managers/PolimecManager'; import type { PolkadotHubManager } from '@/managers/PolkadotHubManager'; import { + Accounts, Asset, AssetSourceRelation, Chains, @@ -10,14 +11,23 @@ import { type PolimecBalanceCheck, getVersionedAssets, } from '@/types'; -import { createTransferData, unwrap } from '@/utils'; +import { createTransferData, flatObject, unwrap } from '@/utils'; import { DispatchRawOrigin, - XcmVersionedAssetId, - type XcmVersionedLocation, - type XcmVersionedXcm, + XcmV3Instruction, + XcmV3Junction, + XcmV3JunctionNetworkId, + XcmV3Junctions, + XcmV3MultiassetAssetId, + XcmV3MultiassetFungibility, + XcmV3MultiassetMultiAssetFilter, + XcmV3MultiassetWildMultiAsset, + XcmV3WeightLimit, + XcmVersionedLocation, + XcmVersionedXcm, } from '@polkadot-api/descriptors'; +import { FixedSizeBinary } from 'polkadot-api'; import { BaseTransferTest, type TransferOptions } from './BaseTransfer'; export class BridgeToPolimecTransfer extends BaseTransferTest { @@ -32,33 +42,174 @@ export class BridgeToPolimecTransfer extends BaseTransferTest { async executeTransfer({ account, assets }: TransferOptions) { console.log('BridgeToPolimecTransfer executeTransfer'); - const [sourceBlock, hopManagerBlock, destBlock] = await Promise.all([ - this.sourceManager.getBlockNumber(), - this.hopManager.getBlockNumber(), - this.destManager.getBlockNumber(), - ]); - + const sourceBlock = await this.sourceManager.getBlockNumber(); console.log('sourceBlock', sourceBlock); + const hopManagerBlock = await this.hopManager.getBlockNumber(); console.log('hopManagerBlock', hopManagerBlock); + const destBlock = await this.destManager.getBlockNumber(); console.log('destBlock', destBlock); - return { sourceBlock, destBlock }; - // const versioned_assets = getVersionedAssets(assets); + const sourceApi = this.sourceManager.getApi(Chains.BridgeHub); + + const dest: XcmVersionedLocation = XcmVersionedLocation.V4({ + parents: 1, + interior: XcmV3Junctions.X1(XcmV3Junction.Parachain(ParaId[Chains.PolkadotHub])), + }); + + const message: XcmVersionedXcm = XcmVersionedXcm.V3([ + // 1. Receive Teleported Assets + XcmV3Instruction.ReceiveTeleportedAsset([ + { + id: XcmV3MultiassetAssetId.Concrete({ + parents: 1, + interior: XcmV3Junctions.Here(), + }), + fun: XcmV3MultiassetFungibility.Fungible(80000000000n), + }, + ]), + + // 2. Buy Execution + XcmV3Instruction.BuyExecution({ + fees: { + id: XcmV3MultiassetAssetId.Concrete({ + parents: 1, + interior: XcmV3Junctions.Here(), + }), + fun: XcmV3MultiassetFungibility.Fungible(40000000000n), + }, + weight_limit: XcmV3WeightLimit.Unlimited(), + }), + + // 3. Descend Origin + XcmV3Instruction.DescendOrigin(XcmV3Junctions.X1(XcmV3Junction.PalletInstance(80))), + + // 4. Universal Origin + XcmV3Instruction.UniversalOrigin( + XcmV3Junction.GlobalConsensus(XcmV3JunctionNetworkId.Ethereum({ chain_id: 1n })), + ), - // const data = createTransferData({ - // toChain: Chains.Polimec, - // assets: versioned_assets, - // recv: account, - // }); + // 5. Reserve Asset Deposited + XcmV3Instruction.ReserveAssetDeposited([ + { + id: XcmV3MultiassetAssetId.Concrete({ + parents: 2, + interior: XcmV3Junctions.X2([ + XcmV3Junction.GlobalConsensus(XcmV3JunctionNetworkId.Ethereum({ chain_id: 1n })), + XcmV3Junction.AccountKey20({ + network: undefined, + key: FixedSizeBinary.fromHex('0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'), + }), + ]), + }), + fun: XcmV3MultiassetFungibility.Fungible(15000000000000n), + }, + ]), - // const api = this.sourceManager.getApi(Chains.PolkadotHub); - // const transfer = api.tx.PolkadotXcm.transfer_assets(data); - // const res = await transfer.signAndSubmit(this.sourceManager.getSigner(account)); + // 6. Clear Origin + XcmV3Instruction.ClearOrigin(), - // console.log('Extrinsic result: ', res.ok); + // 7. Set Appendix + XcmV3Instruction.SetAppendix([ + XcmV3Instruction.DepositAsset({ + assets: XcmV3MultiassetMultiAssetFilter.Wild(XcmV3MultiassetWildMultiAsset.AllCounted(2)), + beneficiary: { + parents: 2, + interior: XcmV3Junctions.X1( + XcmV3Junction.GlobalConsensus(XcmV3JunctionNetworkId.Ethereum({ chain_id: 1n })), + ), + }, + }), + ]), - // expect(res.ok).toBeTrue(); - // return { sourceBlock, destBlock }; + // 8. Deposit Reserve Asset + XcmV3Instruction.DepositReserveAsset({ + assets: XcmV3MultiassetMultiAssetFilter.Definite([ + { + id: XcmV3MultiassetAssetId.Concrete({ + parents: 1, + interior: XcmV3Junctions.Here(), + }), + fun: XcmV3MultiassetFungibility.Fungible(40000000000n), + }, + { + id: XcmV3MultiassetAssetId.Concrete({ + parents: 2, + interior: XcmV3Junctions.X2([ + XcmV3Junction.GlobalConsensus(XcmV3JunctionNetworkId.Ethereum({ chain_id: 1n })), + XcmV3Junction.AccountKey20({ + network: undefined, + key: FixedSizeBinary.fromHex('0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'), + }), + ]), + }), + fun: XcmV3MultiassetFungibility.Fungible(15000000000000n), + }, + ]), + dest: { + parents: 1, + interior: XcmV3Junctions.X1(XcmV3Junction.Parachain(3344)), + }, + xcm: [ + XcmV3Instruction.BuyExecution({ + fees: { + id: XcmV3MultiassetAssetId.Concrete({ + parents: 1, + interior: XcmV3Junctions.Here(), + }), + fun: XcmV3MultiassetFungibility.Fungible(40000000000n), + }, + weight_limit: XcmV3WeightLimit.Unlimited(), + }), + XcmV3Instruction.DepositAsset({ + assets: XcmV3MultiassetMultiAssetFilter.Wild( + XcmV3MultiassetWildMultiAsset.AllCounted(2), + ), + beneficiary: { + parents: 0, + interior: XcmV3Junctions.X1( + XcmV3Junction.AccountId32({ + network: undefined, + id: FixedSizeBinary.fromAccountId32(Accounts.ALICE), + }), + ), + }, + }), + XcmV3Instruction.SetTopic( + FixedSizeBinary.fromArray([ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, + ]), + ), + ], + }), + // 9. Set Topic + XcmV3Instruction.SetTopic( + FixedSizeBinary.fromArray([ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, + ]), + ), + ]); + + const xcm_res = sourceApi.tx.PolkadotXcm.send({ dest, message }).decodedCall; + + const dry_run_res = await sourceApi.apis.DryRunApi.dry_run_call( + { type: 'system', value: DispatchRawOrigin.Root() }, + xcm_res, + ); + console.log('dryRunCallOnBH SUCCESS?', dry_run_res.success); + + const hopApi = this.hopManager.getApi(Chains.PolkadotHub); + + const origin = XcmVersionedLocation.V4({ + parents: 1, + interior: XcmV3Junctions.X1(XcmV3Junction.Parachain(ParaId[Chains.BridgeHub])), + }); + + const dryRunonHop = await hopApi.apis.DryRunApi.dry_run_xcm(origin, message); + console.dir(flatObject(dryRunonHop.value), { depth: null, colors: true }); + + return { sourceBlock, destBlock }; } async getBalances({ diff --git a/integration-tests/chopsticks/src/types.ts b/integration-tests/chopsticks/src/types.ts index e29494434..bc0555b00 100644 --- a/integration-tests/chopsticks/src/types.ts +++ b/integration-tests/chopsticks/src/types.ts @@ -33,7 +33,7 @@ export type ChainClient = { export const ParaId = { [Chains.Polimec]: 3344, [Chains.PolkadotHub]: 1000, - [Chains.BridgeHub]: 1001, + [Chains.BridgeHub]: 1002, }; export enum AssetSourceRelation { From e22194df53c381547a0f863787f6d9355fc7a6d2 Mon Sep 17 00:00:00 2001 From: Leonardo Razovic <4128940+lrazovic@users.noreply.github.com> Date: Fri, 24 Jan 2025 16:30:18 +0100 Subject: [PATCH 11/11] refactor: cleanup bridge code --- .../chopsticks/.papi/descriptors/package.json | 2 +- .../chopsticks/.papi/metadata/polimec.scale | Bin 341498 -> 341742 bytes .../chopsticks/.papi/polkadot-api.json | 1 - integration-tests/chopsticks/README.md | 13 + integration-tests/chopsticks/bun.lock | 1084 +++++++++++++++++ integration-tests/chopsticks/bun.lockb | Bin 199316 -> 0 bytes .../chopsticks/overrides/polimec.ts | 7 +- .../chopsticks/overrides/polkadot-hub.ts | 11 + integration-tests/chopsticks/package.json | 8 +- integration-tests/chopsticks/src/constants.ts | 6 +- .../src/managers/BridgeHubManager.ts | 36 +- .../chopsticks/src/managers/PolimecManager.ts | 19 +- .../src/managers/PolkadotHubManager.ts | 19 +- .../src/managers/PolkadotManager.ts | 19 +- integration-tests/chopsticks/src/setup.ts | 12 +- .../chopsticks/src/tests/bridge.test.ts | 4 +- .../chopsticks/src/tests/hub.test.ts | 10 - .../chopsticks/src/transfers/BaseTransfer.ts | 38 +- .../src/transfers/BridgeToPolimec.ts | 374 +++--- .../chopsticks/src/transfers/HubToPolimec.ts | 3 - .../src/transfers/PolkadotToPolimec.ts | 2 +- integration-tests/chopsticks/src/types.ts | 64 +- integration-tests/chopsticks/src/utils.ts | 16 +- 23 files changed, 1471 insertions(+), 277 deletions(-) create mode 100644 integration-tests/chopsticks/bun.lock delete mode 100755 integration-tests/chopsticks/bun.lockb diff --git a/integration-tests/chopsticks/.papi/descriptors/package.json b/integration-tests/chopsticks/.papi/descriptors/package.json index 50b7c07b8..08cffa71c 100644 --- a/integration-tests/chopsticks/.papi/descriptors/package.json +++ b/integration-tests/chopsticks/.papi/descriptors/package.json @@ -1,5 +1,5 @@ { - "version": "0.1.0-autogenerated.697579702651170142", + "version": "0.1.0-autogenerated.14907376514540113357", "name": "@polkadot-api/descriptors", "files": [ "dist" diff --git a/integration-tests/chopsticks/.papi/metadata/polimec.scale b/integration-tests/chopsticks/.papi/metadata/polimec.scale index 59eb902922bdf30b3bbf16fbdde2eaa245f59c8c..8d2530c583378add24184a94748adf5747bfe5eb 100644 GIT binary patch delta 13270 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 delta 12860 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 [!NOTE] +> Sometimes you need to regenerate the Polimec descriptors. To do that, run: +> +> ```bash +> bun papi add polimec --wasm ../../target/release/wbuild/polimec-runtime/polimec_runtime.compact.compressed.wasm +> ``` + To start the chains: ```bash @@ -22,3 +29,9 @@ To run the tests: bun run test ``` + +> [!IMPORTANT] +> TODO: Add: +> - [ ] Polimec SA on AH: Add WETH balance to it in the Chopstick ovveride +> - [ ] Polimec to Asset Hub: WETH transfer. This is a "normal" transfer_asset call. +> - [ ] Polimec to Ethereum: WETH transfer. This is a bit more complex, example extrinsic: https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fhydration.ibp.network#/extrinsics/decode/0x6b0d04010100a10f040801000007464a69c7e002020907040300c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200130000e8890423c78a0204010002040816040d01000001010088ca48e3e1d0f1c50bd6b504e1312d21f5bd45ed147e3c30c77eb5e4d63bdc6310010102020907040300c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000201090704081300010300c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20004000d010204000103001501c1413e4178c38567ada8945a80351f7b849600 diff --git a/integration-tests/chopsticks/bun.lock b/integration-tests/chopsticks/bun.lock new file mode 100644 index 000000000..185dfec32 --- /dev/null +++ b/integration-tests/chopsticks/bun.lock @@ -0,0 +1,1084 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "chopsticks", + "dependencies": { + "@polkadot-api/descriptors": "file:.papi/descriptors", + "polkadot-api": "^1.9.0", + }, + "devDependencies": { + "@acala-network/chopsticks": "1.0.2", + "@biomejs/biome": "1.9.4", + "@polkadot-labs/hdkd": "0.0.11", + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5.7.3", + }, + }, + }, + "packages": { + "@acala-network/chopsticks": ["@acala-network/chopsticks@1.0.2", "", { "dependencies": { "@acala-network/chopsticks-core": "1.0.2", "@acala-network/chopsticks-db": "1.0.2", "@pnpm/npm-conf": "^2.3.1", "@polkadot/api": "^15.0", "@polkadot/api-augment": "^15.0", "@polkadot/rpc-provider": "^15.0", "@polkadot/types": "^15.0", "@polkadot/util": "^13.2", "@polkadot/util-crypto": "^13.2", "axios": "^1.7.9", "comlink": "^4.4.2", "dotenv": "^16.4.7", "global-agent": "^3.0.0", "js-yaml": "^4.1.0", "jsondiffpatch": "^0.5.0", "lodash": "^4.17.21", "ws": "^8.18.0", "yargs": "^17.7.2", "zod": "^3.24.1" }, "bin": "./chopsticks.cjs" }, "sha512-3CqyGAv2/G0yMiZrX+zZgBvyIFpxAtgm2gJGj/OZK2oVXcQBVXxUkQW5FQEoN9eftVIfMfzC+YlZN4t/TxkhoA=="], + + "@acala-network/chopsticks-core": ["@acala-network/chopsticks-core@1.0.2", "", { "dependencies": { "@acala-network/chopsticks-executor": "1.0.2", "@polkadot/rpc-provider": "^15.0", "@polkadot/types": "^15.0", "@polkadot/types-codec": "^15.0", "@polkadot/types-known": "^15.0", "@polkadot/util": "^13.2", "@polkadot/util-crypto": "^13.2", "comlink": "^4.4.2", "eventemitter3": "^5.0.1", "lodash": "^4.17.21", "lru-cache": "^11.0.2", "pino": "^9.5.0", "pino-pretty": "^13.0.0", "rxjs": "^7.8.1", "zod": "^3.24.1" } }, "sha512-j45pTfgamgI7YjJHI943GFoj15kjciIP0nAVfX1CVMXwNA5YewLY8t8SCoyKhnPvS9OskdVtC54jWjjJRttC+w=="], + + "@acala-network/chopsticks-db": ["@acala-network/chopsticks-db@1.0.2", "", { "dependencies": { "@acala-network/chopsticks-core": "1.0.2", "@polkadot/util": "^13.2", "idb": "^8.0.1", "sqlite3": "^5.1.7", "typeorm": "^0.3.20" } }, "sha512-ky94awHwhK6uHsPLRqryIhhriBWK9RQDXHplybU+icdMq9C9aAYMmejQsF2T7lAC8LwFoe3mI0tDYRMDK0zhJA=="], + + "@acala-network/chopsticks-executor": ["@acala-network/chopsticks-executor@1.0.2", "", { "dependencies": { "@polkadot/util": "^13.2", "@polkadot/wasm-util": "^7.4" } }, "sha512-I0fie9j9pcnQMO89Ix5ZoLwWRcsUj7L2ba1b4UeJB0JwVibcJcCwSmAMefmcceDZPEkwvWya1ukv0gy/AY93Lw=="], + + "@babel/code-frame": ["@babel/code-frame@7.26.2", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="], + + "@biomejs/biome": ["@biomejs/biome@1.9.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "1.9.4", "@biomejs/cli-darwin-x64": "1.9.4", "@biomejs/cli-linux-arm64": "1.9.4", "@biomejs/cli-linux-arm64-musl": "1.9.4", "@biomejs/cli-linux-x64": "1.9.4", "@biomejs/cli-linux-x64-musl": "1.9.4", "@biomejs/cli-win32-arm64": "1.9.4", "@biomejs/cli-win32-x64": "1.9.4" }, "bin": { "biome": "bin/biome" } }, "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog=="], + + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@1.9.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw=="], + + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@1.9.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg=="], + + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g=="], + + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA=="], + + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg=="], + + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg=="], + + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@1.9.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg=="], + + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@1.9.4", "", { "os": "win32", "cpu": "x64" }, "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA=="], + + "@commander-js/extra-typings": ["@commander-js/extra-typings@13.1.0", "", { "peerDependencies": { "commander": "~13.1.0" } }, "sha512-q5P52BYb1hwVWE6dtID7VvuJWrlfbCv4klj7BjUUOqMz4jbSZD4C9fJ9lRjL2jnBGTg+gDDlaXN51rkWcLk4fg=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.24.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.24.2", "", { "os": "android", "cpu": "arm" }, "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.24.2", "", { "os": "android", "cpu": "arm64" }, "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.24.2", "", { "os": "android", "cpu": "x64" }, "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.24.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.24.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.24.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.24.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.24.2", "", { "os": "linux", "cpu": "arm" }, "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.24.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.24.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.24.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.24.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.24.2", "", { "os": "linux", "cpu": "x64" }, "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.24.2", "", { "os": "none", "cpu": "arm64" }, "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.24.2", "", { "os": "none", "cpu": "x64" }, "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.24.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.24.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.24.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.24.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.24.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.24.2", "", { "os": "win32", "cpu": "x64" }, "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg=="], + + "@gar/promisify": ["@gar/promisify@1.1.3", "", {}, "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw=="], + + "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/set-array": ["@jridgewell/set-array@1.2.1", "", {}, "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="], + + "@noble/curves": ["@noble/curves@1.8.1", "", { "dependencies": { "@noble/hashes": "1.7.1" } }, "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ=="], + + "@noble/hashes": ["@noble/hashes@1.7.1", "", {}, "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ=="], + + "@npmcli/fs": ["@npmcli/fs@1.1.1", "", { "dependencies": { "@gar/promisify": "^1.0.1", "semver": "^7.3.5" } }, "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ=="], + + "@npmcli/move-file": ["@npmcli/move-file@1.1.2", "", { "dependencies": { "mkdirp": "^1.0.4", "rimraf": "^3.0.2" } }, "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg=="], + + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + + "@pnpm/config.env-replace": ["@pnpm/config.env-replace@1.1.0", "", {}, "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w=="], + + "@pnpm/network.ca-file": ["@pnpm/network.ca-file@1.0.2", "", { "dependencies": { "graceful-fs": "4.2.10" } }, "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA=="], + + "@pnpm/npm-conf": ["@pnpm/npm-conf@2.3.1", "", { "dependencies": { "@pnpm/config.env-replace": "^1.1.0", "@pnpm/network.ca-file": "^1.0.1", "config-chain": "^1.1.11" } }, "sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw=="], + + "@polkadot-api/cli": ["@polkadot-api/cli@0.11.0", "", { "dependencies": { "@commander-js/extra-typings": "^13.1.0", "@polkadot-api/codegen": "0.13.0", "@polkadot-api/ink-contracts": "0.2.5", "@polkadot-api/json-rpc-provider": "0.0.4", "@polkadot-api/known-chains": "0.7.0", "@polkadot-api/metadata-compatibility": "0.1.15", "@polkadot-api/observable-client": "0.8.0", "@polkadot-api/polkadot-sdk-compat": "2.3.1", "@polkadot-api/sm-provider": "0.1.7", "@polkadot-api/smoldot": "0.3.8", "@polkadot-api/substrate-bindings": "0.11.0", "@polkadot-api/substrate-client": "0.3.0", "@polkadot-api/utils": "0.1.2", "@polkadot-api/wasm-executor": "^0.1.2", "@polkadot-api/ws-provider": "0.3.6", "@types/node": "^22.13.1", "commander": "^13.1.0", "execa": "^9.5.2", "fs.promises.exists": "^1.1.4", "ora": "^8.2.0", "read-pkg": "^9.0.1", "rxjs": "^7.8.1", "tsc-prog": "^2.3.0", "tsup": "^8.3.6", "typescript": "^5.7.3", "write-package": "^7.1.0" }, "bin": { "papi": "dist/main.js", "polkadot-api": "dist/main.js" } }, "sha512-ygOwpjoE54rZ6zE3n6gXFzWoFz9XegQIXKyBc4tV6fjxALiuNAtg/p1x60rXl1iEh0+jrBd+xpxvj2rUbSmejA=="], + + "@polkadot-api/codegen": ["@polkadot-api/codegen@0.13.0", "", { "dependencies": { "@polkadot-api/ink-contracts": "0.2.5", "@polkadot-api/metadata-builders": "0.10.1", "@polkadot-api/metadata-compatibility": "0.1.15", "@polkadot-api/substrate-bindings": "0.11.0", "@polkadot-api/utils": "0.1.2" } }, "sha512-VU0juJz7TWohFa/tUqOSUdauGlGBJT562+POFerz1/DdXeJ4P1U7BKGM+s57PJlN8D6cHfIv1uoPvuuwnfazgg=="], + + "@polkadot-api/descriptors": ["@polkadot-api/descriptors@file:.papi/descriptors", { "peerDependencies": { "polkadot-api": "*" } }], + + "@polkadot-api/ink-contracts": ["@polkadot-api/ink-contracts@0.2.5", "", { "dependencies": { "@polkadot-api/metadata-builders": "0.10.1", "@polkadot-api/substrate-bindings": "0.11.0", "@polkadot-api/utils": "0.1.2", "scale-ts": "^1.6.1" } }, "sha512-EqEub18L8IB1Z/QwnAe9Kn3MeRbM9y0gUpbMUg71v0pQVqBPlumA+oboItNTjLBKzd77QcJUr6bKqHr9rA1Utw=="], + + "@polkadot-api/json-rpc-provider": ["@polkadot-api/json-rpc-provider@0.0.4", "", {}, "sha512-9cDijLIxzHOBuq6yHqpqjJ9jBmXrctjc1OFqU+tQrS96adQze3mTIH6DTgfb/0LMrqxzxffz1HQGrIlEH00WrA=="], + + "@polkadot-api/json-rpc-provider-proxy": ["@polkadot-api/json-rpc-provider-proxy@0.2.4", "", {}, "sha512-nuGoY9QpBAiRU7xmXN3nugFvPcnSu3IxTLm1OWcNTGlZ1LW5bvdQHz3JLk56+Jlyb3GJ971hqdg2DJsMXkKCOg=="], + + "@polkadot-api/known-chains": ["@polkadot-api/known-chains@0.7.0", "", {}, "sha512-hKkxFG8bR4gy5K1GK4HrTLLpJVLu2IJDQHY9Ezz9RbL8QFffwg4BIGDI6JXdKBFnCDnHE8oN6xhd6aUiLIppQw=="], + + "@polkadot-api/logs-provider": ["@polkadot-api/logs-provider@0.0.6", "", { "dependencies": { "@polkadot-api/json-rpc-provider": "0.0.4" } }, "sha512-4WgHlvy+xee1ADaaVf6+MlK/+jGMtsMgAzvbQOJZnP4PfQuagoTqaeayk8HYKxXGphogLlPbD06tANxcb+nvAg=="], + + "@polkadot-api/metadata-builders": ["@polkadot-api/metadata-builders@0.10.1", "", { "dependencies": { "@polkadot-api/substrate-bindings": "0.11.0", "@polkadot-api/utils": "0.1.2" } }, "sha512-0GQFph1vT0wWTfijM7eO+JICR7hwqJeVmil1P29uLjScBI4IH7gX032nOuNz/SG1Qlu0CtDFasFWVeazuPJ0pw=="], + + "@polkadot-api/metadata-compatibility": ["@polkadot-api/metadata-compatibility@0.1.15", "", { "dependencies": { "@polkadot-api/metadata-builders": "0.10.1", "@polkadot-api/substrate-bindings": "0.11.0" } }, "sha512-voOXicr3S/l/5bkNb8hCmTWpfXLOg6iGYulguMJgCI+xGBooJBx7twtVogaNJTzXfrIZtEbxrD+tUsKScIN6iw=="], + + "@polkadot-api/observable-client": ["@polkadot-api/observable-client@0.8.0", "", { "dependencies": { "@polkadot-api/metadata-builders": "0.10.1", "@polkadot-api/substrate-bindings": "0.11.0", "@polkadot-api/utils": "0.1.2" }, "peerDependencies": { "@polkadot-api/substrate-client": "0.3.0", "rxjs": ">=7.8.0" } }, "sha512-n7k79RPyVL/sc22+AzIEioZM4vQO9mp+spkW7s+7M0WLvxpAJ6OR0YNt5/MYPggZGKUTIgH4iWPsLNYNtAJ4MA=="], + + "@polkadot-api/pjs-signer": ["@polkadot-api/pjs-signer@0.6.4", "", { "dependencies": { "@polkadot-api/metadata-builders": "0.10.1", "@polkadot-api/polkadot-signer": "0.1.6", "@polkadot-api/signers-common": "0.1.5", "@polkadot-api/substrate-bindings": "0.11.0", "@polkadot-api/utils": "0.1.2" } }, "sha512-dXln8Par3I5dIX1xw59a8ovpsBj346ZKxsW9/uF9Cm9IQB2QffG3N0jctBUGHYZ+8dHRPug117o/xZCzqOAibw=="], + + "@polkadot-api/polkadot-sdk-compat": ["@polkadot-api/polkadot-sdk-compat@2.3.1", "", { "dependencies": { "@polkadot-api/json-rpc-provider": "0.0.4" } }, "sha512-rb8IWmPRhKWD9NG4zh2n4q0HlEAvq+Cv1CbD+8YxH0XAqIIiFA+ch5JeDCIxQYngkn/43B0Gs7Gtzh18yv2yoA=="], + + "@polkadot-api/polkadot-signer": ["@polkadot-api/polkadot-signer@0.1.6", "", {}, "sha512-X7ghAa4r7doETtjAPTb50IpfGtrBmy3BJM5WCfNKa1saK04VFY9w+vDn+hwEcM4p0PcDHt66Ts74hzvHq54d9A=="], + + "@polkadot-api/signer": ["@polkadot-api/signer@0.1.14", "", { "dependencies": { "@noble/hashes": "^1.6.1", "@polkadot-api/polkadot-signer": "0.1.6", "@polkadot-api/signers-common": "0.1.5", "@polkadot-api/substrate-bindings": "0.11.0", "@polkadot-api/utils": "0.1.2" } }, "sha512-AwAnu6THwwKIe93PLZlER7pdD4x68SCWoIX1Lnb857axgGgLWZgpLVbkneTa+lTej/dqs7R3fhsEuXZX2dR+hg=="], + + "@polkadot-api/signers-common": ["@polkadot-api/signers-common@0.1.5", "", { "dependencies": { "@polkadot-api/metadata-builders": "0.10.1", "@polkadot-api/polkadot-signer": "0.1.6", "@polkadot-api/substrate-bindings": "0.11.0", "@polkadot-api/utils": "0.1.2" } }, "sha512-lmVH3S+Au8q7gRgAnHFbrHBm/5V7hB9xqwftt7SfmCOnl8N093IUKMANXRrD05jcQ+91hjAykU97xOV0W5zdhw=="], + + "@polkadot-api/sm-provider": ["@polkadot-api/sm-provider@0.1.7", "", { "dependencies": { "@polkadot-api/json-rpc-provider": "0.0.4", "@polkadot-api/json-rpc-provider-proxy": "0.2.4" }, "peerDependencies": { "@polkadot-api/smoldot": ">=0.3" } }, "sha512-BhNKVeIFZdawpPVadXszLl8IP4EDjcLHe/GchfRRFkvoNFuwS2nNv/npYIqCviXV+dd2R8VnEELxwScsf380Og=="], + + "@polkadot-api/smoldot": ["@polkadot-api/smoldot@0.3.8", "", { "dependencies": { "@types/node": "^22.9.0", "smoldot": "2.0.34" } }, "sha512-dbJSMRFtELDW+rZIWRwKE/K8oy7+gYaGl+DvaOjARoBW2n80rJ7RAMOCCu+b5h2zgl3elftFBwMNAuAWgHT/Zg=="], + + "@polkadot-api/substrate-bindings": ["@polkadot-api/substrate-bindings@0.11.0", "", { "dependencies": { "@noble/hashes": "^1.6.1", "@polkadot-api/utils": "0.1.2", "@scure/base": "^1.2.1", "scale-ts": "^1.6.1" } }, "sha512-UGCa0xYtfcS/5LJAcro1vhIwxF31fsF5f42mzro9G85T854cbXPzqABDICYmJqdF72tBRMrnJlx6okGtLx9TKA=="], + + "@polkadot-api/substrate-client": ["@polkadot-api/substrate-client@0.3.0", "", { "dependencies": { "@polkadot-api/json-rpc-provider": "0.0.4", "@polkadot-api/utils": "0.1.2" } }, "sha512-0hEvQLKH2zhaFzE8DPkWehvJilec8u2O2wbIEUStm0OJ8jIFtJ40MFjXQfB01dXBWUz1KaVBqS6xd3sZA90Dpw=="], + + "@polkadot-api/utils": ["@polkadot-api/utils@0.1.2", "", {}, "sha512-yhs5k2a8N1SBJcz7EthZoazzLQUkZxbf+0271Xzu42C5AEM9K9uFLbsB+ojzHEM72O5X8lPtSwGKNmS7WQyDyg=="], + + "@polkadot-api/wasm-executor": ["@polkadot-api/wasm-executor@0.1.2", "", {}, "sha512-a5wGenltB3EFPdf72u8ewi6HsUg2qubUAf3ekJprZf24lTK3+w8a/GUF/y6r08LJF35MALZ32SAtLqtVTIOGnQ=="], + + "@polkadot-api/ws-provider": ["@polkadot-api/ws-provider@0.3.6", "", { "dependencies": { "@polkadot-api/json-rpc-provider": "0.0.4", "@polkadot-api/json-rpc-provider-proxy": "0.2.4", "ws": "^8.18.0" } }, "sha512-D2+rvcDc9smt24qUKqFoCuKKNhyBVDQEtnsqHiUN/Ym8UGP+Acegac3b9VOig70EpCcRBoYeXY2gEog2ybx1Kg=="], + + "@polkadot-labs/hdkd": ["@polkadot-labs/hdkd@0.0.11", "", { "dependencies": { "@polkadot-labs/hdkd-helpers": "0.0.11" } }, "sha512-1s375PlIup6cV7EYZ1+Y1eLGLQlYnv7Gdvo20aA5PzXH2VskKHSOOgXH1t1umJIAG8J0pGXlCDG0kNc7nm8+ww=="], + + "@polkadot-labs/hdkd-helpers": ["@polkadot-labs/hdkd-helpers@0.0.11", "", { "dependencies": { "@noble/curves": "^1.8.1", "@noble/hashes": "^1.7.1", "@scure/base": "^1.2.4", "micro-sr25519": "^0.1.0", "scale-ts": "^1.6.1" } }, "sha512-qPlWqC3NNV/2NYc5GEy+Ovi4UBAgkMGvMfyiYuj2BQN4lW59Q1T9coNx0Yp6XzsnJ1ddaF9PWaUtxj3LdM0IDw=="], + + "@polkadot/api": ["@polkadot/api@15.4.1", "", { "dependencies": { "@polkadot/api-augment": "15.4.1", "@polkadot/api-base": "15.4.1", "@polkadot/api-derive": "15.4.1", "@polkadot/keyring": "^13.3.1", "@polkadot/rpc-augment": "15.4.1", "@polkadot/rpc-core": "15.4.1", "@polkadot/rpc-provider": "15.4.1", "@polkadot/types": "15.4.1", "@polkadot/types-augment": "15.4.1", "@polkadot/types-codec": "15.4.1", "@polkadot/types-create": "15.4.1", "@polkadot/types-known": "15.4.1", "@polkadot/util": "^13.3.1", "@polkadot/util-crypto": "^13.3.1", "eventemitter3": "^5.0.1", "rxjs": "^7.8.1", "tslib": "^2.8.1" } }, "sha512-o+5WmEt38rs+Enk2XTE5Mn3Vne+gbolvca7nl+hB/VOr5cK+ZAwhMfEt/ZFXzdAQOA9ePO91FLRsS48mimZ8PA=="], + + "@polkadot/api-augment": ["@polkadot/api-augment@15.4.1", "", { "dependencies": { "@polkadot/api-base": "15.4.1", "@polkadot/rpc-augment": "15.4.1", "@polkadot/types": "15.4.1", "@polkadot/types-augment": "15.4.1", "@polkadot/types-codec": "15.4.1", "@polkadot/util": "^13.3.1", "tslib": "^2.8.1" } }, "sha512-mq/m5eC5hzxzsYfbYoLxdqRgH3/hf60DYoVN1f8P7m798cHXE/8DjCSwgtb5QDiWfp+CifaI5O/PcgL8YNj6jw=="], + + "@polkadot/api-base": ["@polkadot/api-base@15.4.1", "", { "dependencies": { "@polkadot/rpc-core": "15.4.1", "@polkadot/types": "15.4.1", "@polkadot/util": "^13.3.1", "rxjs": "^7.8.1", "tslib": "^2.8.1" } }, "sha512-7a0wsLPpnEDLXhPmaLds03XchCnj7oP7MbdoULVKzIjYDq0MjYasigAz0Vs/QR6O5qodZWmgS2gA+VG+Ga5OMA=="], + + "@polkadot/api-derive": ["@polkadot/api-derive@15.4.1", "", { "dependencies": { "@polkadot/api": "15.4.1", "@polkadot/api-augment": "15.4.1", "@polkadot/api-base": "15.4.1", "@polkadot/rpc-core": "15.4.1", "@polkadot/types": "15.4.1", "@polkadot/types-codec": "15.4.1", "@polkadot/util": "^13.3.1", "@polkadot/util-crypto": "^13.3.1", "rxjs": "^7.8.1", "tslib": "^2.8.1" } }, "sha512-0EFrp0kNNpDWqtuSKbNe8+V1iEz1cXAiL+G7UM0oWae/U2xpbi0zdCMeC7hstUoJS//py1vPnDo0nkVfrdhT/A=="], + + "@polkadot/keyring": ["@polkadot/keyring@13.3.1", "", { "dependencies": { "@polkadot/util": "13.3.1", "@polkadot/util-crypto": "13.3.1", "tslib": "^2.8.0" } }, "sha512-PT3uG9MqciPyoEz/f23RRMSlht77fo1hZaA1Vbcs1Rz7h7qFC0+7jFI9Ak30EJh9V0I2YugfzqAe3NjjyDxlvw=="], + + "@polkadot/networks": ["@polkadot/networks@13.3.1", "", { "dependencies": { "@polkadot/util": "13.3.1", "@substrate/ss58-registry": "^1.51.0", "tslib": "^2.8.0" } }, "sha512-g/0OmCMUrbbW4RQ/xajTYd2SMJvFKY4kmMvpxtNN57hWQpY7c5oDXSz57jGH2uwvcBWeDfaNokcS+9hJL1RBcA=="], + + "@polkadot/rpc-augment": ["@polkadot/rpc-augment@15.4.1", "", { "dependencies": { "@polkadot/rpc-core": "15.4.1", "@polkadot/types": "15.4.1", "@polkadot/types-codec": "15.4.1", "@polkadot/util": "^13.3.1", "tslib": "^2.8.1" } }, "sha512-DiNSSK+UFkAnF0UtVWr6HSCDio74LWjVjLsh9csAKfqy8bXzTVshl8VjZR2G9nuW9YxoJjQREN8wEcM9F+kL3Q=="], + + "@polkadot/rpc-core": ["@polkadot/rpc-core@15.4.1", "", { "dependencies": { "@polkadot/rpc-augment": "15.4.1", "@polkadot/rpc-provider": "15.4.1", "@polkadot/types": "15.4.1", "@polkadot/util": "^13.3.1", "rxjs": "^7.8.1", "tslib": "^2.8.1" } }, "sha512-llAtGpKQgtmsy5+320T0Dr8Lxse77eN0NVbpWr7cQo8R5If8YM9cAMNETMtrY1S9596aaLX/GrThp5Zvt6Z5Aw=="], + + "@polkadot/rpc-provider": ["@polkadot/rpc-provider@15.4.1", "", { "dependencies": { "@polkadot/keyring": "^13.3.1", "@polkadot/types": "15.4.1", "@polkadot/types-support": "15.4.1", "@polkadot/util": "^13.3.1", "@polkadot/util-crypto": "^13.3.1", "@polkadot/x-fetch": "^13.3.1", "@polkadot/x-global": "^13.3.1", "@polkadot/x-ws": "^13.3.1", "eventemitter3": "^5.0.1", "mock-socket": "^9.3.1", "nock": "^13.5.5", "tslib": "^2.8.1" }, "optionalDependencies": { "@substrate/connect": "0.8.11" } }, "sha512-GOtU8fBczbpEa3U4nQxBvwCtYyP1fYbi6vWBnA/YiwQY6RWqMBY2Tfo9fw0MfYqZVpYvbUoaER4akTrtVvCoVA=="], + + "@polkadot/types": ["@polkadot/types@15.4.1", "", { "dependencies": { "@polkadot/keyring": "^13.3.1", "@polkadot/types-augment": "15.4.1", "@polkadot/types-codec": "15.4.1", "@polkadot/types-create": "15.4.1", "@polkadot/util": "^13.3.1", "@polkadot/util-crypto": "^13.3.1", "rxjs": "^7.8.1", "tslib": "^2.8.1" } }, "sha512-5Oh6iwdtXg9/CN55c2IzNx90/ALK1DlP/OswN9vERp6rqZttAEGTQgSiYFHP0+7WhDB8H6v8jVutHadqv7lhMg=="], + + "@polkadot/types-augment": ["@polkadot/types-augment@15.4.1", "", { "dependencies": { "@polkadot/types": "15.4.1", "@polkadot/types-codec": "15.4.1", "@polkadot/util": "^13.3.1", "tslib": "^2.8.1" } }, "sha512-40X4UVEHmJhNV+gYS79RY38rv3shFEGd9H8xTas91IgZtT12mRw1kH5vjLHk+nYTVAAR1ml3D3IStILAwzikgQ=="], + + "@polkadot/types-codec": ["@polkadot/types-codec@15.4.1", "", { "dependencies": { "@polkadot/util": "^13.3.1", "@polkadot/x-bigint": "^13.3.1", "tslib": "^2.8.1" } }, "sha512-LksB8JBdu8ysYWsRbE1U+cv8svUpSddalR6mg0pP0H/ngj58PWrRUWJBRw2LDw65B4Dw1AIJ0QrbigmCjCyz+A=="], + + "@polkadot/types-create": ["@polkadot/types-create@15.4.1", "", { "dependencies": { "@polkadot/types-codec": "15.4.1", "@polkadot/util": "^13.3.1", "tslib": "^2.8.1" } }, "sha512-NP1YGsLdGii0FNAKeKHNxpvLZCusEugs+g21vHHmdtFLC08ngU7pNJGERteo6vDNr5JDejzVbB+i94Y9RzKC0Q=="], + + "@polkadot/types-known": ["@polkadot/types-known@15.4.1", "", { "dependencies": { "@polkadot/networks": "^13.3.1", "@polkadot/types": "15.4.1", "@polkadot/types-codec": "15.4.1", "@polkadot/types-create": "15.4.1", "@polkadot/util": "^13.3.1", "tslib": "^2.8.1" } }, "sha512-LF9estF7y7sXHQ7tA9QoVzAmtkglJdcqMbj70H70V+CBZjiyAgd84PvBtQ6mcIywJ54iCyBBLbhlqcbH/zgUdw=="], + + "@polkadot/types-support": ["@polkadot/types-support@15.4.1", "", { "dependencies": { "@polkadot/util": "^13.3.1", "tslib": "^2.8.1" } }, "sha512-BeMi+780cP0jb4HTwovjcNt/Yf/lgKkXGlfu/gZhLb6eu7Sz3VzAxI8z2WdMWE/NiXMEHeC0YpwOowhRS2tEVA=="], + + "@polkadot/util": ["@polkadot/util@13.3.1", "", { "dependencies": { "@polkadot/x-bigint": "13.3.1", "@polkadot/x-global": "13.3.1", "@polkadot/x-textdecoder": "13.3.1", "@polkadot/x-textencoder": "13.3.1", "@types/bn.js": "^5.1.6", "bn.js": "^5.2.1", "tslib": "^2.8.0" } }, "sha512-5crLP/rUZOJzuo/W8t73J8PxpibJ5vrxY57rR6V+mIpCZd1ORiw0wxeHcV5F9Adpn7yJyuGBwxPbueNR5Rr1Zw=="], + + "@polkadot/util-crypto": ["@polkadot/util-crypto@13.3.1", "", { "dependencies": { "@noble/curves": "^1.3.0", "@noble/hashes": "^1.3.3", "@polkadot/networks": "13.3.1", "@polkadot/util": "13.3.1", "@polkadot/wasm-crypto": "^7.4.1", "@polkadot/wasm-util": "^7.4.1", "@polkadot/x-bigint": "13.3.1", "@polkadot/x-randomvalues": "13.3.1", "@scure/base": "^1.1.7", "tslib": "^2.8.0" } }, "sha512-FU6yf3IY++DKlf0eqO9/obe2y1zuZ5rbqRs75fyOME/5VXio1fA3GIpW7aFphyneFRd78G8QLh8kn0oIwBGMNg=="], + + "@polkadot/wasm-bridge": ["@polkadot/wasm-bridge@7.4.1", "", { "dependencies": { "@polkadot/wasm-util": "7.4.1", "tslib": "^2.7.0" }, "peerDependencies": { "@polkadot/util": "*", "@polkadot/x-randomvalues": "*" } }, "sha512-tdkJaV453tezBxhF39r4oeG0A39sPKGDJmN81LYLf+Fihb7astzwju+u75BRmDrHZjZIv00un3razJEWCxze6g=="], + + "@polkadot/wasm-crypto": ["@polkadot/wasm-crypto@7.4.1", "", { "dependencies": { "@polkadot/wasm-bridge": "7.4.1", "@polkadot/wasm-crypto-asmjs": "7.4.1", "@polkadot/wasm-crypto-init": "7.4.1", "@polkadot/wasm-crypto-wasm": "7.4.1", "@polkadot/wasm-util": "7.4.1", "tslib": "^2.7.0" }, "peerDependencies": { "@polkadot/util": "*", "@polkadot/x-randomvalues": "*" } }, "sha512-kHN/kF7hYxm1y0WeFLWeWir6oTzvcFmR4N8fJJokR+ajYbdmrafPN+6iLgQVbhZnDdxyv9jWDuRRsDnBx8tPMQ=="], + + "@polkadot/wasm-crypto-asmjs": ["@polkadot/wasm-crypto-asmjs@7.4.1", "", { "dependencies": { "tslib": "^2.7.0" }, "peerDependencies": { "@polkadot/util": "*" } }, "sha512-pwU8QXhUW7IberyHJIQr37IhbB6DPkCG5FhozCiNTq4vFBsFPjm9q8aZh7oX1QHQaiAZa2m2/VjIVE+FHGbvHQ=="], + + "@polkadot/wasm-crypto-init": ["@polkadot/wasm-crypto-init@7.4.1", "", { "dependencies": { "@polkadot/wasm-bridge": "7.4.1", "@polkadot/wasm-crypto-asmjs": "7.4.1", "@polkadot/wasm-crypto-wasm": "7.4.1", "@polkadot/wasm-util": "7.4.1", "tslib": "^2.7.0" }, "peerDependencies": { "@polkadot/util": "*", "@polkadot/x-randomvalues": "*" } }, "sha512-AVka33+f7MvXEEIGq5U0dhaA2SaXMXnxVCQyhJTaCnJ5bRDj0Xlm3ijwDEQUiaDql7EikbkkRtmlvs95eSUWYQ=="], + + "@polkadot/wasm-crypto-wasm": ["@polkadot/wasm-crypto-wasm@7.4.1", "", { "dependencies": { "@polkadot/wasm-util": "7.4.1", "tslib": "^2.7.0" }, "peerDependencies": { "@polkadot/util": "*" } }, "sha512-PE1OAoupFR0ZOV2O8tr7D1FEUAwaggzxtfs3Aa5gr+yxlSOaWUKeqsOYe1KdrcjmZVV3iINEAXxgrbzCmiuONg=="], + + "@polkadot/wasm-util": ["@polkadot/wasm-util@7.4.1", "", { "dependencies": { "tslib": "^2.7.0" }, "peerDependencies": { "@polkadot/util": "*" } }, "sha512-RAcxNFf3zzpkr+LX/ItAsvj+QyM56TomJ0xjUMo4wKkHjwsxkz4dWJtx5knIgQz/OthqSDMR59VNEycQeNuXzA=="], + + "@polkadot/x-bigint": ["@polkadot/x-bigint@13.3.1", "", { "dependencies": { "@polkadot/x-global": "13.3.1", "tslib": "^2.8.0" } }, "sha512-ewc708a7LUdrT92v9DsSAIbcJQBn3aR9/LavF/iyMOq5lZJyPXDSjAnskfMs818R3RLCrKVKfs+aKkxt2eqo8g=="], + + "@polkadot/x-fetch": ["@polkadot/x-fetch@13.3.1", "", { "dependencies": { "@polkadot/x-global": "13.3.1", "node-fetch": "^3.3.2", "tslib": "^2.8.0" } }, "sha512-J+HM42j0KGqdC/eo7vmsdLPz74MR7+0My4km6TG9HGjKqqztwygtenpopPod2SbRnL4nHiEG0wZzpVOW6HN2gw=="], + + "@polkadot/x-global": ["@polkadot/x-global@13.3.1", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-861TeIw49a3JvkwlUWrddfG+JaUqtFZDsemYxxZIjjcRJLrKOsoKNqHbiHi2OPrwlX8PwAA/wc5I9Q4XRQ7KEg=="], + + "@polkadot/x-randomvalues": ["@polkadot/x-randomvalues@13.3.1", "", { "dependencies": { "@polkadot/x-global": "13.3.1", "tslib": "^2.8.0" }, "peerDependencies": { "@polkadot/util": "13.3.1", "@polkadot/wasm-util": "*" } }, "sha512-GIb0au3vIX2U/DRH0PRckM+1I4EIbU8PLX1roGJgN1MAYKWiylJTKPVoBMafMM87o8qauOevJ46uYB/qlfbiWg=="], + + "@polkadot/x-textdecoder": ["@polkadot/x-textdecoder@13.3.1", "", { "dependencies": { "@polkadot/x-global": "13.3.1", "tslib": "^2.8.0" } }, "sha512-g2R9O1p0ZsNDhZ3uEBZh6fQaVLlo3yFr0YNqt15v7e9lBI4APvTJ202EINlo2jB5lz/R438/BdjEA3AL+0zUtQ=="], + + "@polkadot/x-textencoder": ["@polkadot/x-textencoder@13.3.1", "", { "dependencies": { "@polkadot/x-global": "13.3.1", "tslib": "^2.8.0" } }, "sha512-DnHLUdoKDYxekfxopuUuPB+j5Mu7Jemejcduu5gz3/89GP/sYPAu0CAVbq9B+hK1yGjBBj31eA4wkAV1oktYmg=="], + + "@polkadot/x-ws": ["@polkadot/x-ws@13.3.1", "", { "dependencies": { "@polkadot/x-global": "13.3.1", "tslib": "^2.8.0", "ws": "^8.18.0" } }, "sha512-ytqkC7FwVs4BlzNFAmPMFp+xD1KIdMMP/mvCSOrnxjlsyM5DVGop4x4c2ZgDUBmrFqmIiVkWDfMIZeOxui2OLQ=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.31.0", "", { "os": "android", "cpu": "arm" }, "sha512-9NrR4033uCbUBRgvLcBrJofa2KY9DzxL2UKZ1/4xA/mnTNyhZCWBuD8X3tPm1n4KxcgaraOYgrFKSgwjASfmlA=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.31.0", "", { "os": "android", "cpu": "arm64" }, "sha512-iBbODqT86YBFHajxxF8ebj2hwKm1k8PTBQSojSt3d1FFt1gN+xf4CowE47iN0vOSdnd+5ierMHBbu/rHc7nq5g=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.31.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-WHIZfXgVBX30SWuTMhlHPXTyN20AXrLH4TEeH/D0Bolvx9PjgZnn4H677PlSGvU6MKNsjCQJYczkpvBbrBnG6g=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.31.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-hrWL7uQacTEF8gdrQAqcDy9xllQ0w0zuL1wk1HV8wKGSGbKPVjVUv/DEwT2+Asabf8Dh/As+IvfdU+H8hhzrQQ=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.31.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-S2oCsZ4hJviG1QjPY1h6sVJLBI6ekBeAEssYKad1soRFv3SocsQCzX6cwnk6fID6UQQACTjeIMB+hyYrFacRew=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.31.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-pCANqpynRS4Jirn4IKZH4tnm2+2CqCNLKD7gAdEjzdLGbH1iO0zouHz4mxqg0uEMpO030ejJ0aA6e1PJo2xrPA=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.31.0", "", { "os": "linux", "cpu": "arm" }, "sha512-0O8ViX+QcBd3ZmGlcFTnYXZKGbFu09EhgD27tgTdGnkcYXLat4KIsBBQeKLR2xZDCXdIBAlWLkiXE1+rJpCxFw=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.31.0", "", { "os": "linux", "cpu": "arm" }, "sha512-w5IzG0wTVv7B0/SwDnMYmbr2uERQp999q8FMkKG1I+j8hpPX2BYFjWe69xbhbP6J9h2gId/7ogesl9hwblFwwg=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.31.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-JyFFshbN5xwy6fulZ8B/8qOqENRmDdEkcIMF0Zz+RsfamEW+Zabl5jAb0IozP/8UKnJ7g2FtZZPEUIAlUSX8cA=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.31.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-kpQXQ0UPFeMPmPYksiBL9WS/BDiQEjRGMfklVIsA0Sng347H8W2iexch+IEwaR7OVSKtr2ZFxggt11zVIlZ25g=="], + + "@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.31.0", "", { "os": "linux", "cpu": "none" }, "sha512-pMlxLjt60iQTzt9iBb3jZphFIl55a70wexvo8p+vVFK+7ifTRookdoXX3bOsRdmfD+OKnMozKO6XM4zR0sHRrQ=="], + + "@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.31.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-D7TXT7I/uKEuWiRkEFbed1UUYZwcJDU4vZQdPTcepK7ecPhzKOYk4Er2YR4uHKme4qDeIh6N3XrLfpuM7vzRWQ=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.31.0", "", { "os": "linux", "cpu": "none" }, "sha512-wal2Tc8O5lMBtoePLBYRKj2CImUCJ4UNGJlLwspx7QApYny7K1cUYlzQ/4IGQBLmm+y0RS7dwc3TDO/pmcneTw=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.31.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-O1o5EUI0+RRMkK9wiTVpk2tyzXdXefHtRTIjBbmFREmNMy7pFeYXCFGbhKFwISA3UOExlo5GGUuuj3oMKdK6JQ=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.31.0", "", { "os": "linux", "cpu": "x64" }, "sha512-zSoHl356vKnNxwOWnLd60ixHNPRBglxpv2g7q0Cd3Pmr561gf0HiAcUBRL3S1vPqRC17Zo2CX/9cPkqTIiai1g=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.31.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ypB/HMtcSGhKUQNiFwqgdclWNRrAYDH8iMYH4etw/ZlGwiTVxBz2tDrGRrPlfZu6QjXwtd+C3Zib5pFqID97ZA=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.31.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-JuhN2xdI/m8Hr+aVO3vspO7OQfUFO6bKLIRTAy0U15vmWjnZDLrEgCZ2s6+scAYaQVpYSh9tZtRijApw9IXyMw=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.31.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-U1xZZXYkvdf5MIWmftU8wrM5PPXzyaY1nGCI4KI4BFfoZxHamsIe+BtnPLIvvPykvQWlVbqUXdLa4aJUuilwLQ=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.31.0", "", { "os": "win32", "cpu": "x64" }, "sha512-ul8rnCsUumNln5YWwz0ted2ZHFhzhRRnkpBZ+YRuHoRAlUji9KChpOUOndY7uykrPEPXVbHLlsdo6v5yXo/TXw=="], + + "@rx-state/core": ["@rx-state/core@0.1.4", "", { "peerDependencies": { "rxjs": ">=7" } }, "sha512-Z+3hjU2xh1HisLxt+W5hlYX/eGSDaXXP+ns82gq/PLZpkXLu0uwcNUh9RLY3Clq4zT+hSsA3vcpIGt6+UAb8rQ=="], + + "@scure/base": ["@scure/base@1.2.4", "", {}, "sha512-5Yy9czTO47mqz+/J8GM6GIId4umdCk1wc1q8rKERQulIoc8VP9pzDcghv10Tl2E7R96ZUx/PhND3ESYUQX8NuQ=="], + + "@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="], + + "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="], + + "@sqltools/formatter": ["@sqltools/formatter@1.2.5", "", {}, "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw=="], + + "@substrate/connect": ["@substrate/connect@0.8.11", "", { "dependencies": { "@substrate/connect-extension-protocol": "^2.0.0", "@substrate/connect-known-chains": "^1.1.5", "@substrate/light-client-extension-helpers": "^1.0.0", "smoldot": "2.0.26" } }, "sha512-ofLs1PAO9AtDdPbdyTYj217Pe+lBfTLltdHDs3ds8no0BseoLeAGxpz1mHfi7zB4IxI3YyAiLjH6U8cw4pj4Nw=="], + + "@substrate/connect-extension-protocol": ["@substrate/connect-extension-protocol@2.2.1", "", {}, "sha512-GoafTgm/Jey9E4Xlj4Z5ZBt/H4drH2CNq8VrAro80rtoznrXnFDNVivLQzZN0Xaj2g8YXSn9pC9Oc9IovYZJXw=="], + + "@substrate/connect-known-chains": ["@substrate/connect-known-chains@1.9.0", "", {}, "sha512-R7yE0kIRUnvNlMiYramQ+dQwSY0ZpqRJ1mK8hLKlvCbEMqjSFa0n/WYYG6/bst9nNA1O6OZLWpvMso6yhENe3A=="], + + "@substrate/light-client-extension-helpers": ["@substrate/light-client-extension-helpers@1.0.0", "", { "dependencies": { "@polkadot-api/json-rpc-provider": "^0.0.1", "@polkadot-api/json-rpc-provider-proxy": "^0.1.0", "@polkadot-api/observable-client": "^0.3.0", "@polkadot-api/substrate-client": "^0.1.2", "@substrate/connect-extension-protocol": "^2.0.0", "@substrate/connect-known-chains": "^1.1.5", "rxjs": "^7.8.1" }, "peerDependencies": { "smoldot": "2.x" } }, "sha512-TdKlni1mBBZptOaeVrKnusMg/UBpWUORNDv5fdCaJklP4RJiFOzBCrzC+CyVI5kQzsXBisZ+2pXm+rIjS38kHg=="], + + "@substrate/ss58-registry": ["@substrate/ss58-registry@1.51.0", "", {}, "sha512-TWDurLiPxndFgKjVavCniytBIw+t4ViOi7TYp9h/D0NMmkEc9klFTo+827eyEJ0lELpqO207Ey7uGxUa+BS1jQ=="], + + "@tootallnate/once": ["@tootallnate/once@1.1.2", "", {}, "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw=="], + + "@types/bn.js": ["@types/bn.js@5.1.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-Xh8vSwUeMKeYYrj3cX4lGQgFSF/N03r+tv4AiLl1SucqV+uTQpxRcnM8AkXKHwYP9ZPXOYXRr2KPXpVlIvqh9w=="], + + "@types/bun": ["@types/bun@1.2.2", "", { "dependencies": { "bun-types": "1.2.2" } }, "sha512-tr74gdku+AEDN5ergNiBnplr7hpDp3V1h7fqI2GcR/rsUaM39jpSeKH0TFibRvU0KwniRx5POgaYnaXbk0hU+w=="], + + "@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="], + + "@types/node": ["@types/node@22.13.1", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew=="], + + "@types/normalize-package-data": ["@types/normalize-package-data@2.4.4", "", {}, "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA=="], + + "@types/ws": ["@types/ws@8.5.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA=="], + + "abbrev": ["abbrev@1.1.1", "", {}, "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="], + + "agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + + "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="], + + "aggregate-error": ["aggregate-error@3.1.0", "", { "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], + + "app-root-path": ["app-root-path@3.1.0", "", {}, "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA=="], + + "aproba": ["aproba@2.0.0", "", {}, "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ=="], + + "are-we-there-yet": ["are-we-there-yet@3.0.1", "", { "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" } }, "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="], + + "axios": ["axios@1.7.9", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="], + + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + + "bn.js": ["bn.js@5.2.1", "", {}, "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ=="], + + "boolean": ["boolean@3.2.0", "", {}, "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw=="], + + "brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], + + "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + + "bun-types": ["bun-types@1.2.2", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-RCbMH5elr9gjgDGDhkTTugA21XtJAy/9jkKe/G3WR2q17VPGhcquf9Sir6uay9iW+7P/BV0CAHA1XlHXMAVKHg=="], + + "bundle-require": ["bundle-require@5.1.0", "", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="], + + "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + + "cacache": ["cacache@15.3.0", "", { "dependencies": { "@npmcli/fs": "^1.0.0", "@npmcli/move-file": "^1.0.1", "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "glob": "^7.1.4", "infer-owner": "^1.0.4", "lru-cache": "^6.0.0", "minipass": "^3.1.1", "minipass-collect": "^1.0.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.2", "mkdirp": "^1.0.3", "p-map": "^4.0.0", "promise-inflight": "^1.0.1", "rimraf": "^3.0.2", "ssri": "^8.0.1", "tar": "^6.0.2", "unique-filename": "^1.1.1" } }, "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ=="], + + "chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="], + + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + + "chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="], + + "clean-stack": ["clean-stack@2.2.0", "", {}, "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="], + + "cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="], + + "cli-highlight": ["cli-highlight@2.1.11", "", { "dependencies": { "chalk": "^4.0.0", "highlight.js": "^10.7.1", "mz": "^2.4.0", "parse5": "^5.1.1", "parse5-htmlparser2-tree-adapter": "^6.0.0", "yargs": "^16.0.0" }, "bin": { "highlight": "bin/highlight" } }, "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg=="], + + "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], + + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "color-support": ["color-support@1.1.3", "", { "bin": { "color-support": "bin.js" } }, "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg=="], + + "colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="], + + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + + "comlink": ["comlink@4.4.2", "", {}, "sha512-OxGdvBmJuNKSCMO4NTl1L47VRp6xn2wG4F/2hYzB6tiCb709otOxtEYCSvK80PtjODfXXZu8ds+Nw5kVCjqd2g=="], + + "commander": ["commander@13.1.0", "", {}, "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "config-chain": ["config-chain@1.1.13", "", { "dependencies": { "ini": "^1.3.4", "proto-list": "~1.2.1" } }, "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ=="], + + "consola": ["consola@3.4.0", "", {}, "sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA=="], + + "console-control-strings": ["console-control-strings@1.1.0", "", {}, "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], + + "dateformat": ["dateformat@4.6.3", "", {}, "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA=="], + + "dayjs": ["dayjs@1.11.13", "", {}, "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="], + + "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + + "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], + + "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], + + "deepmerge-ts": ["deepmerge-ts@7.1.4", "", {}, "sha512-fxqo6nHGQ9zOVgI4KXqtWXJR/yCLtC7aXIVq+6jc8tHPFUxlFmuUcm2kC4vztQ+LJxQ3gER/XAWearGYQ8niGA=="], + + "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], + + "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="], + + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + + "delegates": ["delegates@1.0.0", "", {}, "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="], + + "detect-indent": ["detect-indent@7.0.1", "", {}, "sha512-Mc7QhQ8s+cLrnUfU/Ji94vG/r8M26m8f++vyres4ZoojaRDpZ1eSIh/EpzLNwlWuvzSZ3UbDFspjFvTDXe6e/g=="], + + "detect-libc": ["detect-libc@2.0.3", "", {}, "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="], + + "detect-node": ["detect-node@2.1.0", "", {}, "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="], + + "diff-match-patch": ["diff-match-patch@1.0.5", "", {}, "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw=="], + + "dotenv": ["dotenv@16.4.7", "", {}, "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ=="], + + "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "encoding": ["encoding@0.1.13", "", { "dependencies": { "iconv-lite": "^0.6.2" } }, "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A=="], + + "end-of-stream": ["end-of-stream@1.4.4", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q=="], + + "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], + + "err-code": ["err-code@2.0.3", "", {}, "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es6-error": ["es6-error@4.1.1", "", {}, "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="], + + "esbuild": ["esbuild@0.24.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.24.2", "@esbuild/android-arm": "0.24.2", "@esbuild/android-arm64": "0.24.2", "@esbuild/android-x64": "0.24.2", "@esbuild/darwin-arm64": "0.24.2", "@esbuild/darwin-x64": "0.24.2", "@esbuild/freebsd-arm64": "0.24.2", "@esbuild/freebsd-x64": "0.24.2", "@esbuild/linux-arm": "0.24.2", "@esbuild/linux-arm64": "0.24.2", "@esbuild/linux-ia32": "0.24.2", "@esbuild/linux-loong64": "0.24.2", "@esbuild/linux-mips64el": "0.24.2", "@esbuild/linux-ppc64": "0.24.2", "@esbuild/linux-riscv64": "0.24.2", "@esbuild/linux-s390x": "0.24.2", "@esbuild/linux-x64": "0.24.2", "@esbuild/netbsd-arm64": "0.24.2", "@esbuild/netbsd-x64": "0.24.2", "@esbuild/openbsd-arm64": "0.24.2", "@esbuild/openbsd-x64": "0.24.2", "@esbuild/sunos-x64": "0.24.2", "@esbuild/win32-arm64": "0.24.2", "@esbuild/win32-ia32": "0.24.2", "@esbuild/win32-x64": "0.24.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="], + + "execa": ["execa@9.5.2", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.3", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.0", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.0.0" } }, "sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q=="], + + "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], + + "fast-copy": ["fast-copy@3.0.2", "", {}, "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ=="], + + "fast-redact": ["fast-redact@3.5.0", "", {}, "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A=="], + + "fast-safe-stringify": ["fast-safe-stringify@2.1.1", "", {}, "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="], + + "fdir": ["fdir@6.4.3", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw=="], + + "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], + + "figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="], + + "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="], + + "follow-redirects": ["follow-redirects@1.15.9", "", {}, "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="], + + "foreground-child": ["foreground-child@3.3.0", "", { "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" } }, "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg=="], + + "form-data": ["form-data@4.0.1", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "mime-types": "^2.1.12" } }, "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw=="], + + "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], + + "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], + + "fs-minipass": ["fs-minipass@2.1.0", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg=="], + + "fs.promises.exists": ["fs.promises.exists@1.1.4", "", {}, "sha512-lJzUGWbZn8vhGWBedA+RYjB/BeJ+3458ljUfmplqhIeb6ewzTFWNPCR1HCiYCkXV9zxcHz9zXkJzMsEgDLzh3Q=="], + + "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "gauge": ["gauge@4.0.4", "", { "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.3", "console-control-strings": "^1.1.0", "has-unicode": "^2.0.1", "signal-exit": "^3.0.7", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "wide-align": "^1.1.5" } }, "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg=="], + + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "get-east-asian-width": ["get-east-asian-width@1.3.0", "", {}, "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ=="], + + "get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="], + + "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], + + "glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], + + "global-agent": ["global-agent@3.0.0", "", { "dependencies": { "boolean": "^3.0.1", "es6-error": "^4.1.1", "matcher": "^3.0.0", "roarr": "^2.15.3", "semver": "^7.3.2", "serialize-error": "^7.0.1" } }, "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q=="], + + "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "graceful-fs": ["graceful-fs@4.2.10", "", {}, "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], + + "has-unicode": ["has-unicode@2.0.1", "", {}, "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="], + + "help-me": ["help-me@5.0.0", "", {}, "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg=="], + + "highlight.js": ["highlight.js@10.7.3", "", {}, "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="], + + "hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="], + + "http-cache-semantics": ["http-cache-semantics@4.1.1", "", {}, "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ=="], + + "http-proxy-agent": ["http-proxy-agent@4.0.1", "", { "dependencies": { "@tootallnate/once": "1", "agent-base": "6", "debug": "4" } }, "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg=="], + + "https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], + + "human-signals": ["human-signals@8.0.0", "", {}, "sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA=="], + + "humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="], + + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + + "idb": ["idb@8.0.1", "", {}, "sha512-EkBCzUZSdhJV8PxMSbeEV//xguVKZu9hZZulM+2gHXI0t2hGVU3eYE6/XnH77DS6FM2FY8wl17aDcu9vXpvLWQ=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="], + + "index-to-position": ["index-to-position@0.1.2", "", {}, "sha512-MWDKS3AS1bGCHLBA2VLImJz42f7bJh8wQsTGCzI3j519/CASStoDONUBVz2I/VID0MpiX3SGSnbOD2xUalbE5g=="], + + "infer-owner": ["infer-owner@1.0.4", "", {}, "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A=="], + + "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + + "ip-address": ["ip-address@9.0.5", "", { "dependencies": { "jsbn": "1.1.0", "sprintf-js": "^1.1.3" } }, "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-interactive": ["is-interactive@2.0.0", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="], + + "is-lambda": ["is-lambda@1.0.1", "", {}, "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ=="], + + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], + + "is-stream": ["is-stream@4.0.1", "", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="], + + "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + + "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], + + "jsbn": ["jsbn@1.1.0", "", {}, "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A=="], + + "json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="], + + "jsondiffpatch": ["jsondiffpatch@0.5.0", "", { "dependencies": { "chalk": "^3.0.0", "diff-match-patch": "^1.0.0" }, "bin": { "jsondiffpatch": "bin/jsondiffpatch" } }, "sha512-Quz3MvAwHxVYNXsOByL7xI5EB2WYOeFswqaHIA3qOK3isRWTxiplBEocmmru6XmxDB2L7jDNYtYA4FyimoAFEw=="], + + "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], + + "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + + "load-tsconfig": ["load-tsconfig@0.2.5", "", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="], + + "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], + + "lodash.sortby": ["lodash.sortby@4.7.0", "", {}, "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA=="], + + "log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="], + + "lru-cache": ["lru-cache@11.0.2", "", {}, "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA=="], + + "make-fetch-happen": ["make-fetch-happen@9.1.0", "", { "dependencies": { "agentkeepalive": "^4.1.3", "cacache": "^15.2.0", "http-cache-semantics": "^4.1.0", "http-proxy-agent": "^4.0.1", "https-proxy-agent": "^5.0.0", "is-lambda": "^1.0.1", "lru-cache": "^6.0.0", "minipass": "^3.1.3", "minipass-collect": "^1.0.2", "minipass-fetch": "^1.3.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^0.6.2", "promise-retry": "^2.0.1", "socks-proxy-agent": "^6.0.0", "ssri": "^8.0.0" } }, "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg=="], + + "matcher": ["matcher@3.0.0", "", { "dependencies": { "escape-string-regexp": "^4.0.0" } }, "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng=="], + + "micro-sr25519": ["micro-sr25519@0.1.0", "", { "dependencies": { "@noble/curves": "~1.7.0", "@noble/hashes": "~1.6.0" } }, "sha512-at5zfxiKNhh07v4GPb8Sc6wCW+jd18FMMgPM0ACIQMcgvMfB9a34mfOlXr5B04J4yFZ6imlvJfRaFbOxMA7ytw=="], + + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], + + "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], + + "minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="], + + "minipass-collect": ["minipass-collect@1.0.2", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA=="], + + "minipass-fetch": ["minipass-fetch@1.4.1", "", { "dependencies": { "minipass": "^3.1.0", "minipass-sized": "^1.0.3", "minizlib": "^2.0.0" }, "optionalDependencies": { "encoding": "^0.1.12" } }, "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw=="], + + "minipass-flush": ["minipass-flush@1.0.5", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw=="], + + "minipass-pipeline": ["minipass-pipeline@1.2.4", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A=="], + + "minipass-sized": ["minipass-sized@1.0.3", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g=="], + + "minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="], + + "mkdirp": ["mkdirp@2.1.6", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A=="], + + "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], + + "mock-socket": ["mock-socket@9.3.1", "", {}, "sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], + + "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], + + "negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="], + + "nock": ["nock@13.5.6", "", { "dependencies": { "debug": "^4.1.0", "json-stringify-safe": "^5.0.1", "propagate": "^2.0.0" } }, "sha512-o2zOYiCpzRqSzPj0Zt/dQ/DqZeYoaQ7TUonc/xUPjCGl9WeHpNbxgVvOquXYAaJzI0M9BXV3HTzG0p8IUAbBTQ=="], + + "node-abi": ["node-abi@3.73.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-z8iYzQGBu35ZkTQ9mtR8RqugJZ9RCLn8fv3d7LsgDBzOijGQP3RdKTX4LA7LXw03ZhU5z0l4xfhIMgSES31+cg=="], + + "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], + + "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], + + "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + + "node-gyp": ["node-gyp@8.4.1", "", { "dependencies": { "env-paths": "^2.2.0", "glob": "^7.1.4", "graceful-fs": "^4.2.6", "make-fetch-happen": "^9.1.0", "nopt": "^5.0.0", "npmlog": "^6.0.0", "rimraf": "^3.0.2", "semver": "^7.3.5", "tar": "^6.1.2", "which": "^2.0.2" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w=="], + + "nopt": ["nopt@5.0.0", "", { "dependencies": { "abbrev": "1" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ=="], + + "normalize-package-data": ["normalize-package-data@6.0.2", "", { "dependencies": { "hosted-git-info": "^7.0.0", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4" } }, "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g=="], + + "npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="], + + "npmlog": ["npmlog@6.0.2", "", { "dependencies": { "are-we-there-yet": "^3.0.0", "console-control-strings": "^1.1.0", "gauge": "^4.0.3", "set-blocking": "^2.0.0" } }, "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg=="], + + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + + "object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="], + + "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], + + "ora": ["ora@8.2.0", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="], + + "p-map": ["p-map@4.0.0", "", { "dependencies": { "aggregate-error": "^3.0.0" } }, "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ=="], + + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + + "parse-json": ["parse-json@8.1.0", "", { "dependencies": { "@babel/code-frame": "^7.22.13", "index-to-position": "^0.1.2", "type-fest": "^4.7.1" } }, "sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA=="], + + "parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="], + + "parse5": ["parse5@5.1.1", "", {}, "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug=="], + + "parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@6.0.1", "", { "dependencies": { "parse5": "^6.0.1" } }, "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA=="], + + "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="], + + "pino": ["pino@9.6.0", "", { "dependencies": { "atomic-sleep": "^1.0.0", "fast-redact": "^3.1.1", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^4.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^3.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg=="], + + "pino-abstract-transport": ["pino-abstract-transport@2.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw=="], + + "pino-pretty": ["pino-pretty@13.0.0", "", { "dependencies": { "colorette": "^2.0.7", "dateformat": "^4.6.3", "fast-copy": "^3.0.2", "fast-safe-stringify": "^2.1.1", "help-me": "^5.0.0", "joycon": "^3.1.1", "minimist": "^1.2.6", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pump": "^3.0.0", "secure-json-parse": "^2.4.0", "sonic-boom": "^4.0.1", "strip-json-comments": "^3.1.1" }, "bin": { "pino-pretty": "bin.js" } }, "sha512-cQBBIVG3YajgoUjo1FdKVRX6t9XPxwB9lcNJVD5GCnNM4Y6T12YYx8c6zEejxQsU0wrg9TwmDulcE9LR7qcJqA=="], + + "pino-std-serializers": ["pino-std-serializers@7.0.0", "", {}, "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA=="], + + "pirates": ["pirates@4.0.6", "", {}, "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg=="], + + "polkadot-api": ["polkadot-api@1.9.0", "", { "dependencies": { "@polkadot-api/cli": "0.11.0", "@polkadot-api/ink-contracts": "0.2.5", "@polkadot-api/json-rpc-provider": "0.0.4", "@polkadot-api/known-chains": "0.7.0", "@polkadot-api/logs-provider": "0.0.6", "@polkadot-api/metadata-builders": "0.10.1", "@polkadot-api/metadata-compatibility": "0.1.15", "@polkadot-api/observable-client": "0.8.0", "@polkadot-api/pjs-signer": "0.6.4", "@polkadot-api/polkadot-sdk-compat": "2.3.1", "@polkadot-api/polkadot-signer": "0.1.6", "@polkadot-api/signer": "0.1.14", "@polkadot-api/sm-provider": "0.1.7", "@polkadot-api/smoldot": "0.3.8", "@polkadot-api/substrate-bindings": "0.11.0", "@polkadot-api/substrate-client": "0.3.0", "@polkadot-api/utils": "0.1.2", "@polkadot-api/ws-provider": "0.3.6", "@rx-state/core": "^0.1.4" }, "peerDependencies": { "rxjs": ">=7.8.0" }, "bin": { "papi": "bin/cli.mjs", "polkadot-api": "bin/cli.mjs" } }, "sha512-k454jsoJfDdr69ua9aYQ9EA9DpZ8nqarwLEmJiG1DFm9zy7uhkrJUk3QnDInkPtgQMNU0JC+MyGoTlFUMXMxlA=="], + + "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], + + "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], + + "pretty-ms": ["pretty-ms@9.2.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg=="], + + "process-warning": ["process-warning@4.0.1", "", {}, "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q=="], + + "promise-inflight": ["promise-inflight@1.0.1", "", {}, "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g=="], + + "promise-retry": ["promise-retry@2.0.1", "", { "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g=="], + + "propagate": ["propagate@2.0.1", "", {}, "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag=="], + + "proto-list": ["proto-list@1.2.4", "", {}, "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA=="], + + "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], + + "pump": ["pump@3.0.2", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="], + + "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], + + "read-pkg": ["read-pkg@9.0.1", "", { "dependencies": { "@types/normalize-package-data": "^2.4.3", "normalize-package-data": "^6.0.0", "parse-json": "^8.0.0", "type-fest": "^4.6.0", "unicorn-magic": "^0.1.0" } }, "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA=="], + + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + + "readdirp": ["readdirp@4.1.1", "", {}, "sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw=="], + + "real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="], + + "reflect-metadata": ["reflect-metadata@0.2.2", "", {}, "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="], + + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + + "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], + + "restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], + + "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], + + "rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], + + "roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="], + + "rollup": ["rollup@4.31.0", "", { "dependencies": { "@types/estree": "1.0.6" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.31.0", "@rollup/rollup-android-arm64": "4.31.0", "@rollup/rollup-darwin-arm64": "4.31.0", "@rollup/rollup-darwin-x64": "4.31.0", "@rollup/rollup-freebsd-arm64": "4.31.0", "@rollup/rollup-freebsd-x64": "4.31.0", "@rollup/rollup-linux-arm-gnueabihf": "4.31.0", "@rollup/rollup-linux-arm-musleabihf": "4.31.0", "@rollup/rollup-linux-arm64-gnu": "4.31.0", "@rollup/rollup-linux-arm64-musl": "4.31.0", "@rollup/rollup-linux-loongarch64-gnu": "4.31.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.31.0", "@rollup/rollup-linux-riscv64-gnu": "4.31.0", "@rollup/rollup-linux-s390x-gnu": "4.31.0", "@rollup/rollup-linux-x64-gnu": "4.31.0", "@rollup/rollup-linux-x64-musl": "4.31.0", "@rollup/rollup-win32-arm64-msvc": "4.31.0", "@rollup/rollup-win32-ia32-msvc": "4.31.0", "@rollup/rollup-win32-x64-msvc": "4.31.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-9cCE8P4rZLx9+PjoyqHLs31V9a9Vpvfo4qNcs6JCiGWYhw2gijSetFbH6SSy1whnkgcefnUwr8sad7tgqsGvnw=="], + + "rxjs": ["rxjs@7.8.1", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "scale-ts": ["scale-ts@1.6.1", "", {}, "sha512-PBMc2AWc6wSEqJYBDPcyCLUj9/tMKnLX70jLOSndMtcUoLQucP/DM0vnQo1wJAYjTrQiq8iG9rD0q6wFzgjH7g=="], + + "secure-json-parse": ["secure-json-parse@2.7.0", "", {}, "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="], + + "semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], + + "semver-compare": ["semver-compare@1.0.0", "", {}, "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="], + + "serialize-error": ["serialize-error@7.0.1", "", { "dependencies": { "type-fest": "^0.13.1" } }, "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw=="], + + "set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="], + + "sha.js": ["sha.js@2.4.11", "", { "dependencies": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" }, "bin": { "sha.js": "./bin.js" } }, "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], + + "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], + + "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], + + "smoldot": ["smoldot@2.0.34", "", { "dependencies": { "ws": "^8.8.1" } }, "sha512-mw9tCbGEhEp0koMqLL0jBEixVY1MIN/xI3pE6ZY1TuOPU+LnYy8FloODVyzkvzQPaBYrETXJdRlmA/+k6g3gow=="], + + "socks": ["socks@2.8.3", "", { "dependencies": { "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" } }, "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw=="], + + "socks-proxy-agent": ["socks-proxy-agent@6.2.1", "", { "dependencies": { "agent-base": "^6.0.2", "debug": "^4.3.3", "socks": "^2.6.2" } }, "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ=="], + + "sonic-boom": ["sonic-boom@4.2.0", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww=="], + + "sort-keys": ["sort-keys@5.1.0", "", { "dependencies": { "is-plain-obj": "^4.0.0" } }, "sha512-aSbHV0DaBcr7u0PVHXzM6NbZNAtrr9sF6+Qfs9UUVG7Ll3jQ6hHi8F/xqIIcn2rvIVbr0v/2zyjSdwSV47AgLQ=="], + + "source-map": ["source-map@0.8.0-beta.0", "", { "dependencies": { "whatwg-url": "^7.0.0" } }, "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA=="], + + "spdx-correct": ["spdx-correct@3.2.0", "", { "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" } }, "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA=="], + + "spdx-exceptions": ["spdx-exceptions@2.5.0", "", {}, "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="], + + "spdx-expression-parse": ["spdx-expression-parse@3.0.1", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q=="], + + "spdx-license-ids": ["spdx-license-ids@3.0.21", "", {}, "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg=="], + + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + + "sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="], + + "sqlite3": ["sqlite3@5.1.7", "", { "dependencies": { "bindings": "^1.5.0", "node-addon-api": "^7.0.0", "prebuild-install": "^7.1.1", "tar": "^6.1.11" }, "optionalDependencies": { "node-gyp": "8.x" } }, "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog=="], + + "ssri": ["ssri@8.0.1", "", { "dependencies": { "minipass": "^3.1.1" } }, "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ=="], + + "stdin-discarder": ["stdin-discarder@0.2.2", "", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="], + + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-final-newline": ["strip-final-newline@4.0.0", "", {}, "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw=="], + + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + + "sucrase": ["sucrase@3.35.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="], + + "tar-fs": ["tar-fs@2.1.2", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA=="], + + "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], + + "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], + + "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], + + "thread-stream": ["thread-stream@3.1.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A=="], + + "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], + + "tinyglobby": ["tinyglobby@0.2.10", "", { "dependencies": { "fdir": "^6.4.2", "picomatch": "^4.0.2" } }, "sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew=="], + + "tr46": ["tr46@1.0.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA=="], + + "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], + + "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], + + "tsc-prog": ["tsc-prog@2.3.0", "", { "peerDependencies": { "typescript": ">=4" } }, "sha512-ycET2d75EgcX7y8EmG4KiZkLAwUzbY4xRhA6NU0uVbHkY4ZjrAAuzTMxXI85kOwATqPnBI5C/7y7rlpY0xdqHA=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "tsup": ["tsup@8.3.6", "", { "dependencies": { "bundle-require": "^5.0.0", "cac": "^6.7.14", "chokidar": "^4.0.1", "consola": "^3.2.3", "debug": "^4.3.7", "esbuild": "^0.24.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.24.0", "source-map": "0.8.0-beta.0", "sucrase": "^3.35.0", "tinyexec": "^0.3.1", "tinyglobby": "^0.2.9", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core", "postcss", "typescript"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-XkVtlDV/58S9Ye0JxUUTcrQk4S+EqlOHKzg6Roa62rdjL1nGWNUstG0xgI4vanHdfIpjP448J8vlN0oK6XOJ5g=="], + + "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], + + "type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="], + + "typeorm": ["typeorm@0.3.20", "", { "dependencies": { "@sqltools/formatter": "^1.2.5", "app-root-path": "^3.1.0", "buffer": "^6.0.3", "chalk": "^4.1.2", "cli-highlight": "^2.1.11", "dayjs": "^1.11.9", "debug": "^4.3.4", "dotenv": "^16.0.3", "glob": "^10.3.10", "mkdirp": "^2.1.3", "reflect-metadata": "^0.2.1", "sha.js": "^2.4.11", "tslib": "^2.5.0", "uuid": "^9.0.0", "yargs": "^17.6.2" }, "peerDependencies": { "@google-cloud/spanner": "^5.18.0", "@sap/hana-client": "^2.12.25", "better-sqlite3": "^7.1.2 || ^8.0.0 || ^9.0.0", "hdb-pool": "^0.1.6", "ioredis": "^5.0.4", "mongodb": "^5.8.0", "mssql": "^9.1.1 || ^10.0.1", "mysql2": "^2.2.5 || ^3.0.1", "oracledb": "^6.3.0", "pg": "^8.5.1", "pg-native": "^3.0.0", "pg-query-stream": "^4.0.0", "redis": "^3.1.1 || ^4.0.0", "sql.js": "^1.4.0", "sqlite3": "^5.0.3", "ts-node": "^10.7.0", "typeorm-aurora-data-api-driver": "^2.0.0" }, "optionalPeers": ["@google-cloud/spanner", "@sap/hana-client", "better-sqlite3", "hdb-pool", "ioredis", "mongodb", "mssql", "mysql2", "oracledb", "pg", "pg-native", "pg-query-stream", "redis", "sql.js", "sqlite3", "ts-node", "typeorm-aurora-data-api-driver"], "bin": { "typeorm": "cli.js", "typeorm-ts-node-esm": "cli-ts-node-esm.js", "typeorm-ts-node-commonjs": "cli-ts-node-commonjs.js" } }, "sha512-sJ0T08dV5eoZroaq9uPKBoNcGslHBR4E4y+EBHs//SiGbblGe7IeduP/IH4ddCcj0qp3PHwDwGnuvqEAnKlq/Q=="], + + "typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], + + "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], + + "unicorn-magic": ["unicorn-magic@0.1.0", "", {}, "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ=="], + + "unique-filename": ["unique-filename@1.1.1", "", { "dependencies": { "unique-slug": "^2.0.0" } }, "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ=="], + + "unique-slug": ["unique-slug@2.0.2", "", { "dependencies": { "imurmurhash": "^0.1.4" } }, "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], + + "validate-npm-package-license": ["validate-npm-package-license@3.0.4", "", { "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew=="], + + "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], + + "webidl-conversions": ["webidl-conversions@4.0.2", "", {}, "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="], + + "whatwg-url": ["whatwg-url@7.1.0", "", { "dependencies": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", "webidl-conversions": "^4.0.2" } }, "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "wide-align": ["wide-align@1.1.5", "", { "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } }, "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg=="], + + "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "write-file-atomic": ["write-file-atomic@5.0.1", "", { "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^4.0.1" } }, "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw=="], + + "write-json-file": ["write-json-file@6.0.0", "", { "dependencies": { "detect-indent": "^7.0.1", "is-plain-obj": "^4.1.0", "sort-keys": "^5.0.0", "write-file-atomic": "^5.0.1" } }, "sha512-MNHcU3f9WxnNyR6MxsYSj64Jz0+dwIpisWKWq9gqLj/GwmA9INg3BZ3vt70/HB3GEwrnDQWr4RPrywnhNzmUFA=="], + + "write-package": ["write-package@7.1.0", "", { "dependencies": { "deepmerge-ts": "^7.1.0", "read-pkg": "^9.0.1", "sort-keys": "^5.0.0", "type-fest": "^4.23.0", "write-json-file": "^6.0.0" } }, "sha512-DqUx8GI3r9BFWwU2DPKddL1E7xWfbFED82mLVhGXKlFEPe8IkBftzO7WfNwHtk7oGDHDeuH/o8VMpzzfMwmLUA=="], + + "ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="], + + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "yoctocolors": ["yoctocolors@2.1.1", "", {}, "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ=="], + + "zod": ["zod@3.24.1", "", {}, "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A=="], + + "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], + + "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + + "@npmcli/move-file/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], + + "@polkadot-api/smoldot/@types/node": ["@types/node@22.10.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-Ir6hwgsKyNESl/gLOcEz3krR4CBGgliDqBQ2ma4wIhEx0w+xnoeTq3tdrNw15kU3SxogDjOgv9sqdtLW8mIHaw=="], + + "@substrate/connect/smoldot": ["smoldot@2.0.26", "", { "dependencies": { "ws": "^8.8.1" } }, "sha512-F+qYmH4z2s2FK+CxGj8moYcd1ekSIKH8ywkdqlOz88Dat35iB1DIYL11aILN46YSGMzQW/lbJNS307zBSDN5Ig=="], + + "@substrate/light-client-extension-helpers/@polkadot-api/json-rpc-provider": ["@polkadot-api/json-rpc-provider@0.0.1", "", {}, "sha512-/SMC/l7foRjpykLTUTacIH05H3mr9ip8b5xxfwXlVezXrNVLp3Cv0GX6uItkKd+ZjzVPf3PFrDF2B2/HLSNESA=="], + + "@substrate/light-client-extension-helpers/@polkadot-api/json-rpc-provider-proxy": ["@polkadot-api/json-rpc-provider-proxy@0.1.0", "", {}, "sha512-8GSFE5+EF73MCuLQm8tjrbCqlgclcHBSRaswvXziJ0ZW7iw3UEMsKkkKvELayWyBuOPa2T5i1nj6gFOeIsqvrg=="], + + "@substrate/light-client-extension-helpers/@polkadot-api/observable-client": ["@polkadot-api/observable-client@0.3.2", "", { "dependencies": { "@polkadot-api/metadata-builders": "0.3.2", "@polkadot-api/substrate-bindings": "0.6.0", "@polkadot-api/utils": "0.1.0" }, "peerDependencies": { "@polkadot-api/substrate-client": "0.1.4", "rxjs": ">=7.8.0" } }, "sha512-HGgqWgEutVyOBXoGOPp4+IAq6CNdK/3MfQJmhCJb8YaJiaK4W6aRGrdQuQSTPHfERHCARt9BrOmEvTXAT257Ug=="], + + "@substrate/light-client-extension-helpers/@polkadot-api/substrate-client": ["@polkadot-api/substrate-client@0.1.4", "", { "dependencies": { "@polkadot-api/json-rpc-provider": "0.0.1", "@polkadot-api/utils": "0.1.0" } }, "sha512-MljrPobN0ZWTpn++da9vOvt+Ex+NlqTlr/XT7zi9sqPtDJiQcYl+d29hFAgpaeTqbeQKZwz3WDE9xcEfLE8c5A=="], + + "@types/bn.js/@types/node": ["@types/node@22.10.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-Ir6hwgsKyNESl/gLOcEz3krR4CBGgliDqBQ2ma4wIhEx0w+xnoeTq3tdrNw15kU3SxogDjOgv9sqdtLW8mIHaw=="], + + "@types/ws/@types/node": ["@types/node@22.10.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-Ir6hwgsKyNESl/gLOcEz3krR4CBGgliDqBQ2ma4wIhEx0w+xnoeTq3tdrNw15kU3SxogDjOgv9sqdtLW8mIHaw=="], + + "bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + + "cacache/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + + "cacache/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + + "cacache/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "cacache/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], + + "cli-highlight/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "cli-highlight/yargs": ["yargs@16.2.0", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw=="], + + "fs-minipass/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "gauge/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "glob/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + + "hosted-git-info/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "log-symbols/chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="], + + "log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="], + + "make-fetch-happen/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + + "make-fetch-happen/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "micro-sr25519/@noble/curves": ["@noble/curves@1.7.0", "", { "dependencies": { "@noble/hashes": "1.6.0" } }, "sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw=="], + + "micro-sr25519/@noble/hashes": ["@noble/hashes@1.6.1", "", {}, "sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w=="], + + "minipass-collect/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-fetch/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-sized/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "node-gyp/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + + "node-gyp/graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], + + "npm-run-path/unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], + + "ora/chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="], + + "ora/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "ora/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], + + "parse-json/type-fest": ["type-fest@4.33.0", "", {}, "sha512-s6zVrxuyKbbAsSAD5ZPTB77q4YIdRctkTbJ2/Dqlinwz+8ooH2gd+YA7VA6Pa93KML9GockVvoxjZ2vHP+mu8g=="], + + "parse5-htmlparser2-tree-adapter/parse5": ["parse5@6.0.1", "", {}, "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="], + + "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "path-scurry/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + + "rc/strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], + + "read-pkg/type-fest": ["type-fest@4.33.0", "", {}, "sha512-s6zVrxuyKbbAsSAD5ZPTB77q4YIdRctkTbJ2/Dqlinwz+8ooH2gd+YA7VA6Pa93KML9GockVvoxjZ2vHP+mu8g=="], + + "rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + + "ssri/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], + + "tar/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], + + "tar-fs/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], + + "typeorm/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "write-package/type-fest": ["type-fest@4.33.0", "", {}, "sha512-s6zVrxuyKbbAsSAD5ZPTB77q4YIdRctkTbJ2/Dqlinwz+8ooH2gd+YA7VA6Pa93KML9GockVvoxjZ2vHP+mu8g=="], + + "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + + "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], + + "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], + + "@substrate/light-client-extension-helpers/@polkadot-api/observable-client/@polkadot-api/metadata-builders": ["@polkadot-api/metadata-builders@0.3.2", "", { "dependencies": { "@polkadot-api/substrate-bindings": "0.6.0", "@polkadot-api/utils": "0.1.0" } }, "sha512-TKpfoT6vTb+513KDzMBTfCb/ORdgRnsS3TDFpOhAhZ08ikvK+hjHMt5plPiAX/OWkm1Wc9I3+K6W0hX5Ab7MVg=="], + + "@substrate/light-client-extension-helpers/@polkadot-api/observable-client/@polkadot-api/substrate-bindings": ["@polkadot-api/substrate-bindings@0.6.0", "", { "dependencies": { "@noble/hashes": "^1.3.1", "@polkadot-api/utils": "0.1.0", "@scure/base": "^1.1.1", "scale-ts": "^1.6.0" } }, "sha512-lGuhE74NA1/PqdN7fKFdE5C1gNYX357j1tWzdlPXI0kQ7h3kN0zfxNOpPUN7dIrPcOFZ6C0tRRVrBylXkI6xPw=="], + + "@substrate/light-client-extension-helpers/@polkadot-api/observable-client/@polkadot-api/utils": ["@polkadot-api/utils@0.1.0", "", {}, "sha512-MXzWZeuGxKizPx2Xf/47wx9sr/uxKw39bVJUptTJdsaQn/TGq+z310mHzf1RCGvC1diHM8f593KrnDgc9oNbJA=="], + + "@substrate/light-client-extension-helpers/@polkadot-api/substrate-client/@polkadot-api/utils": ["@polkadot-api/utils@0.1.0", "", {}, "sha512-MXzWZeuGxKizPx2Xf/47wx9sr/uxKw39bVJUptTJdsaQn/TGq+z310mHzf1RCGvC1diHM8f593KrnDgc9oNbJA=="], + + "cacache/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "cli-highlight/yargs/cliui": ["cliui@7.0.4", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ=="], + + "cli-highlight/yargs/yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="], + + "micro-sr25519/@noble/curves/@noble/hashes": ["@noble/hashes@1.6.0", "", {}, "sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ=="], + + "node-gyp/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "ora/string-width/emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="], + + "ora/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], + + "rimraf/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "cacache/glob/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], + + "node-gyp/glob/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], + + "rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], + } +} diff --git a/integration-tests/chopsticks/bun.lockb b/integration-tests/chopsticks/bun.lockb deleted file mode 100755 index da76a833d6f759fce03d0ecbc4854ef84f72ffdf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 199316 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 diff --git a/integration-tests/chopsticks/overrides/polimec.ts b/integration-tests/chopsticks/overrides/polimec.ts index 507470cc4..7223dbb8b 100644 --- a/integration-tests/chopsticks/overrides/polimec.ts +++ b/integration-tests/chopsticks/overrides/polimec.ts @@ -77,13 +77,8 @@ export const polimec_storage = { balance: INITIAL_BALANCES.DOT, }, ], - [ - [weth_location, Accounts.BOB], - { - balance: INITIAL_BALANCES.WETH, - }, - ], ], + // Note: We can remove Asset and Metadata from the storage override as soon we set them on-chain. Asset: [ [ [weth_location], diff --git a/integration-tests/chopsticks/overrides/polkadot-hub.ts b/integration-tests/chopsticks/overrides/polkadot-hub.ts index 433ea064f..573217498 100644 --- a/integration-tests/chopsticks/overrides/polkadot-hub.ts +++ b/integration-tests/chopsticks/overrides/polkadot-hub.ts @@ -1,5 +1,6 @@ import { INITIAL_BALANCES } from '@/constants'; import { Accounts, Asset } from '@/types'; +import { weth_location } from './polimec'; export const polkadot_hub_storage = { System: { @@ -31,4 +32,14 @@ export const polkadot_hub_storage = { ], ], }, + ForeignAssets: { + Account: [ + [ + [weth_location, Accounts.POLIMEC], + { + balance: INITIAL_BALANCES.WETH, + }, + ], + ], + }, } as const; diff --git a/integration-tests/chopsticks/package.json b/integration-tests/chopsticks/package.json index 9a0c09ebd..1c621f4fe 100644 --- a/integration-tests/chopsticks/package.json +++ b/integration-tests/chopsticks/package.json @@ -4,12 +4,12 @@ "type": "module", "scripts": { "lint": "biome check --write src overrides", - "test": "env LOG_LEVEL=error bun test --timeout 25000" + "test": "env LOG_LEVEL=error bun test" }, "devDependencies": { - "@acala-network/chopsticks": "1.0.1", + "@acala-network/chopsticks": "1.0.2", "@biomejs/biome": "1.9.4", - "@polkadot-labs/hdkd": "0.0.10", + "@polkadot-labs/hdkd": "0.0.11", "@types/bun": "latest" }, "peerDependencies": { @@ -17,6 +17,6 @@ }, "dependencies": { "@polkadot-api/descriptors": "file:.papi/descriptors", - "polkadot-api": "^1.8.2" + "polkadot-api": "^1.9.0" } } diff --git a/integration-tests/chopsticks/src/constants.ts b/integration-tests/chopsticks/src/constants.ts index 597bef471..043e5f127 100644 --- a/integration-tests/chopsticks/src/constants.ts +++ b/integration-tests/chopsticks/src/constants.ts @@ -1,4 +1,5 @@ import { Accounts } from '@/types'; +import { FixedSizeBinary } from 'polkadot-api'; export const INITIAL_BALANCES = { USDT: 52000n * 10n ** 6n, @@ -17,6 +18,9 @@ export const TRANSFER_AMOUNTS = { export const DERIVE_PATHS = { [Accounts.ALICE]: '//Alice', [Accounts.BOB]: '//Bob', -}; +} as const; export const WETH_ADDRESS = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; +export const DEFAULT_TOPIC = FixedSizeBinary.fromArray(Array(32).fill(1)); +export const FEE_AMOUNT = 40_000_000_000n; +export const WETH_AMOUNT = 15_000_000_000_000n; diff --git a/integration-tests/chopsticks/src/managers/BridgeHubManager.ts b/integration-tests/chopsticks/src/managers/BridgeHubManager.ts index b9deed305..d5f09eaa2 100644 --- a/integration-tests/chopsticks/src/managers/BridgeHubManager.ts +++ b/integration-tests/chopsticks/src/managers/BridgeHubManager.ts @@ -1,5 +1,4 @@ -import { type Accounts, Asset, AssetLocation, AssetSourceRelation, Chains } from '@/types'; -import { flatObject } from '@/utils.ts'; +import { type Accounts, Asset, AssetSourceRelation, Chains } from '@/types'; import { bridge } from '@polkadot-api/descriptors'; import { createClient } from 'polkadot-api'; import { withPolkadotSdkCompat } from 'polkadot-api/polkadot-sdk-compat'; @@ -7,28 +6,31 @@ import { getWsProvider } from 'polkadot-api/ws-provider/web'; import { BaseChainManager } from './BaseManager'; export class BridgerHubManagaer extends BaseChainManager { + private chain = Chains.BridgeHub; + connect() { - const client = createClient(withPolkadotSdkCompat(getWsProvider(this.getChainType()))); - const api = client.getTypedApi(bridge); + const provider = withPolkadotSdkCompat(getWsProvider(this.chain)); + const client = createClient(provider); // Verify connection - if (!client || !api) { - throw new Error(`Failed to connect to ${this.getChainType()}`); + if (!client) { + throw new Error(`Failed to connect to ${this.chain}`); } - this.clients.set(this.getChainType(), { client, api }); + const api = client.getTypedApi(bridge); + this.clients.set(this.chain, { client, api }); } disconnect() { - this.clients.get(Chains.BridgeHub)?.client.destroy(); + this.clients.get(this.chain)?.client.destroy(); } getChainType() { - return Chains.BridgeHub; + return this.chain; } getXcmPallet() { - const api = this.getApi(Chains.BridgeHub); + const api = this.getApi(this.chain); return api.tx.PolkadotXcm; } @@ -50,19 +52,9 @@ export class BridgerHubManagaer extends BaseChainManager { } } + // Note: On BridgeHub, there should be no balance for any asset. + // There is DOT, but we are not tracking it. async getAssetBalanceOf(account: Accounts, asset: Asset): Promise { - const api = this.getApi(Chains.BridgeHub); - // const asset_source_relation = this.getAssetSourceRelation(asset); - // const asset_location = AssetLocation(asset, asset_source_relation).value; - // const account_balances_result = await api.apis.FungiblesApi.query_account_balances(account); - // if (account_balances_result.success === true && account_balances_result.value.type === 'V4') { - // const assets = account_balances_result.value.value; - // for (const asset of assets) { - // if (Bun.deepEquals(flatObject(asset.id), flatObject(asset_location))) { - // return asset.fun.value as bigint; - // } - // } - // } return 0n; } diff --git a/integration-tests/chopsticks/src/managers/PolimecManager.ts b/integration-tests/chopsticks/src/managers/PolimecManager.ts index ebf41407f..44d9ff46c 100644 --- a/integration-tests/chopsticks/src/managers/PolimecManager.ts +++ b/integration-tests/chopsticks/src/managers/PolimecManager.ts @@ -7,28 +7,31 @@ import { getWsProvider } from 'polkadot-api/ws-provider/web'; import { BaseChainManager } from './BaseManager'; export class PolimecManager extends BaseChainManager { + private chain = Chains.Polimec; + connect() { - const client = createClient(withPolkadotSdkCompat(getWsProvider(this.getChainType()))); - const api = client.getTypedApi(polimec); + const provider = withPolkadotSdkCompat(getWsProvider(this.chain)); + const client = createClient(provider); // Verify connection - if (!client || !api) { - throw new Error(`Failed to connect to ${this.getChainType()}`); + if (!client) { + throw new Error(`Failed to connect to ${this.chain}`); } - this.clients.set(this.getChainType(), { client, api }); + const api = client.getTypedApi(polimec); + this.clients.set(this.chain, { client, api }); } disconnect() { - this.clients.get(Chains.Polimec)?.client.destroy(); + this.clients.get(this.chain)?.client.destroy(); } getChainType() { - return Chains.Polimec; + return this.chain; } getXcmPallet() { - const api = this.getApi(Chains.Polimec); + const api = this.getApi(this.chain); return api.tx.PolkadotXcm; } diff --git a/integration-tests/chopsticks/src/managers/PolkadotHubManager.ts b/integration-tests/chopsticks/src/managers/PolkadotHubManager.ts index ceb1ccab4..dc6c7d894 100644 --- a/integration-tests/chopsticks/src/managers/PolkadotHubManager.ts +++ b/integration-tests/chopsticks/src/managers/PolkadotHubManager.ts @@ -7,28 +7,31 @@ import { getWsProvider } from 'polkadot-api/ws-provider/web'; import { BaseChainManager } from './BaseManager'; export class PolkadotHubManager extends BaseChainManager { + private chain = Chains.PolkadotHub; + connect() { - const client = createClient(withPolkadotSdkCompat(getWsProvider(this.getChainType()))); - const api = client.getTypedApi(pah); + const provider = withPolkadotSdkCompat(getWsProvider(this.chain)); + const client = createClient(provider); // Verify connection - if (!client || !api) { - throw new Error(`Failed to connect to ${this.getChainType()}`); + if (!client) { + throw new Error(`Failed to connect to ${this.chain}`); } - this.clients.set(this.getChainType(), { client, api }); + const api = client.getTypedApi(pah); + this.clients.set(this.chain, { client, api }); } disconnect() { - this.clients.get(Chains.PolkadotHub)?.client.destroy(); + this.clients.get(this.chain)?.client.destroy(); } getChainType() { - return Chains.PolkadotHub; + return this.chain; } getXcmPallet() { - const api = this.getApi(Chains.PolkadotHub); + const api = this.getApi(this.chain); return api.tx.PolkadotXcm; } diff --git a/integration-tests/chopsticks/src/managers/PolkadotManager.ts b/integration-tests/chopsticks/src/managers/PolkadotManager.ts index c611025cb..83a1e94eb 100644 --- a/integration-tests/chopsticks/src/managers/PolkadotManager.ts +++ b/integration-tests/chopsticks/src/managers/PolkadotManager.ts @@ -6,28 +6,31 @@ import { getWsProvider } from 'polkadot-api/ws-provider/web'; import { BaseChainManager } from './BaseManager'; export class PolkadotManager extends BaseChainManager { + private chain = Chains.Polkadot; + connect() { - const client = createClient(withPolkadotSdkCompat(getWsProvider(this.getChainType()))); - const api = client.getTypedApi(polkadot); + const provider = withPolkadotSdkCompat(getWsProvider(this.chain)); + const client = createClient(provider); // Verify connection - if (!client || !api) { - throw new Error(`Failed to connect to ${this.getChainType()}`); + if (!client) { + throw new Error(`Failed to connect to ${this.chain}`); } - this.clients.set(this.getChainType(), { client, api }); + const api = client.getTypedApi(polkadot); + this.clients.set(this.chain, { client, api }); } disconnect() { - this.clients.get(Chains.Polkadot)?.client.destroy(); + this.clients.get(this.chain)?.client.destroy(); } getChainType() { - return Chains.Polkadot; + return this.chain; } getXcmPallet() { - const api = this.getApi(Chains.Polkadot); + const api = this.getApi(this.chain); return api.tx.XcmPallet; } diff --git a/integration-tests/chopsticks/src/setup.ts b/integration-tests/chopsticks/src/setup.ts index ade85c7e8..42d9bbaa6 100644 --- a/integration-tests/chopsticks/src/setup.ts +++ b/integration-tests/chopsticks/src/setup.ts @@ -44,13 +44,11 @@ export class ChainSetup { this.relaychain = relaychainSetup.chain; this.bridgeHub = bridgeHubSetup.chain; - await Promise.all([ - connectVertical(this.relaychain, this.polimec), - connectVertical(this.relaychain, this.assetHub), - connectVertical(this.relaychain, this.bridgeHub), - connectParachains([this.polimec, this.assetHub]), - connectParachains([this.bridgeHub, this.assetHub]), - ]); + const parachains = [this.polimec, this.assetHub, this.bridgeHub]; + for (const parachain of parachains) { + await connectVertical(this.relaychain, parachain); + } + await connectParachains(parachains); console.log('✅ HRMP channels created'); diff --git a/integration-tests/chopsticks/src/tests/bridge.test.ts b/integration-tests/chopsticks/src/tests/bridge.test.ts index 333d46938..44db3f479 100644 --- a/integration-tests/chopsticks/src/tests/bridge.test.ts +++ b/integration-tests/chopsticks/src/tests/bridge.test.ts @@ -1,9 +1,9 @@ import { afterAll, beforeAll, beforeEach, describe, test } from 'bun:test'; import { TRANSFER_AMOUNTS } from '@/constants'; import { createChainManager } from '@/managers/Factory'; +import { polimec_storage } from '@/polimec'; import { ChainSetup } from '@/setup'; import { BridgeToPolimecTransfer } from '@/transfers/BridgeToPolimec'; -import { HubToPolimecTransfer } from '@/transfers/HubToPolimec'; import { Accounts, Asset, AssetSourceRelation, Chains } from '@/types'; describe('Bridge Hub -> Polimec Transfer Tests', () => { @@ -13,7 +13,7 @@ describe('Bridge Hub -> Polimec Transfer Tests', () => { const transferTest = new BridgeToPolimecTransfer(sourceManager, hopManager, destManager); const chainSetup = new ChainSetup(); - beforeAll(async () => await chainSetup.initialize()); + beforeAll(async () => await chainSetup.initialize(polimec_storage)); beforeEach(() => { sourceManager.connect(); hopManager.connect(); diff --git a/integration-tests/chopsticks/src/tests/hub.test.ts b/integration-tests/chopsticks/src/tests/hub.test.ts index 95305ea89..a1ac12088 100644 --- a/integration-tests/chopsticks/src/tests/hub.test.ts +++ b/integration-tests/chopsticks/src/tests/hub.test.ts @@ -47,14 +47,4 @@ describe('Polkadot Hub -> Polimec Transfer Tests', () => { }), { timeout: 25000 }, ); - - // test( - // 'Send WETH to Polimec', - // () => - // transferTest.testTransfer({ - // account: Accounts.ALICE, - // assets: [[Asset.WETH, TRANSFER_AMOUNTS.BRIDGED, AssetSourceRelation.Self]], - // }), - // { timeout: 25000 }, - // ); }); diff --git a/integration-tests/chopsticks/src/transfers/BaseTransfer.ts b/integration-tests/chopsticks/src/transfers/BaseTransfer.ts index 4c3f7a3bf..efbfbe4b5 100644 --- a/integration-tests/chopsticks/src/transfers/BaseTransfer.ts +++ b/integration-tests/chopsticks/src/transfers/BaseTransfer.ts @@ -1,6 +1,13 @@ import { expect } from 'bun:test'; import type { BaseChainManager } from '@/managers/BaseManager'; -import type { Accounts, Asset, AssetSourceRelation, BalanceCheck, TransferResult } from '@/types'; +import { + type Accounts, + type Asset, + type AssetSourceRelation, + type BalanceCheck, + Chains, + type TransferResult, +} from '@/types'; import { sleep } from 'bun'; export interface TransferOptions { @@ -26,15 +33,26 @@ export abstract class BaseTransferTest { ): void; async testTransfer(options: TransferOptions) { - // const { asset_balances: initialBalances } = await this.getBalances(options); - // if (options.assets[0][1] > initialBalances[0].source) { - // throw new Error(`Insufficient balance on Source chain for asset: ${options.assets[0][0]}`); - // } - const blockNumbers = await this.executeTransfer(options); - // await this.waitForBlocks(); - // await this.verifyExecution(); - // const { asset_balances: finalBalances } = await this.getBalances(options); - // this.verifyFinalBalances(initialBalances, finalBalances, options); + // Note: For the bridged tests we use the dry-run Runtime API, so we don't write any data to the chain. + const isBridged = this.sourceManager.getChainType() === Chains.BridgeHub; + + let initialBalances: BalanceCheck[] = []; + if (!isBridged) { + const { asset_balances } = await this.getBalances(options); + initialBalances = asset_balances; // Assign within the block + if (options.assets[0][1] > asset_balances[0].source) { + throw new Error(`Insufficient balance on Source chain for asset: ${options.assets[0][0]}`); + } + } + + await this.executeTransfer(options); + + if (!isBridged) { + await this.waitForBlocks(); + await this.verifyExecution(); + const { asset_balances: finalBalances } = await this.getBalances(options); + this.verifyFinalBalances(initialBalances, finalBalances, options); + } } // TODO: Wait for the next block to be produced. diff --git a/integration-tests/chopsticks/src/transfers/BridgeToPolimec.ts b/integration-tests/chopsticks/src/transfers/BridgeToPolimec.ts index 1e782eff4..7a996d46a 100644 --- a/integration-tests/chopsticks/src/transfers/BridgeToPolimec.ts +++ b/integration-tests/chopsticks/src/transfers/BridgeToPolimec.ts @@ -1,17 +1,10 @@ import { expect } from 'bun:test'; +import { DEFAULT_TOPIC, FEE_AMOUNT, WETH_ADDRESS, WETH_AMOUNT } from '@/constants'; import type { BridgerHubManagaer } from '@/managers/BridgeHubManager'; import type { PolimecManager } from '@/managers/PolimecManager'; import type { PolkadotHubManager } from '@/managers/PolkadotHubManager'; -import { - Accounts, - Asset, - AssetSourceRelation, - Chains, - ParaId, - type PolimecBalanceCheck, - getVersionedAssets, -} from '@/types'; -import { createTransferData, flatObject, unwrap } from '@/utils'; +import { Accounts, type BalanceCheck, Chains, ParaId } from '@/types'; +import { unwrap_api } from '@/utils'; import { DispatchRawOrigin, XcmV3Instruction, @@ -26,11 +19,20 @@ import { XcmVersionedLocation, XcmVersionedXcm, } from '@polkadot-api/descriptors'; - import { FixedSizeBinary } from 'polkadot-api'; import { BaseTransferTest, type TransferOptions } from './BaseTransfer'; export class BridgeToPolimecTransfer extends BaseTransferTest { + getBalances(_options: TransferOptions): Promise<{ asset_balances: BalanceCheck[] }> { + throw new Error('Method not allowed.'); + } + verifyFinalBalances( + _initialBalances: BalanceCheck[], + _finalBalances: BalanceCheck[], + _options: TransferOptions, + ): void { + throw new Error('Method not allowed.'); + } constructor( protected override sourceManager: BridgerHubManagaer, protected hopManager: PolkadotHubManager, @@ -41,96 +43,107 @@ export class BridgeToPolimecTransfer extends BaseTransferTest { } async executeTransfer({ account, assets }: TransferOptions) { - console.log('BridgeToPolimecTransfer executeTransfer'); const sourceBlock = await this.sourceManager.getBlockNumber(); - console.log('sourceBlock', sourceBlock); - const hopManagerBlock = await this.hopManager.getBlockNumber(); - console.log('hopManagerBlock', hopManagerBlock); const destBlock = await this.destManager.getBlockNumber(); - console.log('destBlock', destBlock); - const sourceApi = this.sourceManager.getApi(Chains.BridgeHub); + const sourceApi = this.sourceManager.getApi(Chains.BridgeHub); + const hopApi = this.hopManager.getApi(Chains.PolkadotHub); + const destApi = this.destManager.getApi(Chains.Polimec); const dest: XcmVersionedLocation = XcmVersionedLocation.V4({ parents: 1, interior: XcmV3Junctions.X1(XcmV3Junction.Parachain(ParaId[Chains.PolkadotHub])), }); - const message: XcmVersionedXcm = XcmVersionedXcm.V3([ - // 1. Receive Teleported Assets - XcmV3Instruction.ReceiveTeleportedAsset([ - { - id: XcmV3MultiassetAssetId.Concrete({ - parents: 1, - interior: XcmV3Junctions.Here(), - }), - fun: XcmV3MultiassetFungibility.Fungible(80000000000n), - }, - ]), - - // 2. Buy Execution - XcmV3Instruction.BuyExecution({ - fees: { - id: XcmV3MultiassetAssetId.Concrete({ - parents: 1, - interior: XcmV3Junctions.Here(), - }), - fun: XcmV3MultiassetFungibility.Fungible(40000000000n), - }, - weight_limit: XcmV3WeightLimit.Unlimited(), - }), - - // 3. Descend Origin - XcmV3Instruction.DescendOrigin(XcmV3Junctions.X1(XcmV3Junction.PalletInstance(80))), - - // 4. Universal Origin - XcmV3Instruction.UniversalOrigin( - XcmV3Junction.GlobalConsensus(XcmV3JunctionNetworkId.Ethereum({ chain_id: 1n })), - ), - - // 5. Reserve Asset Deposited - XcmV3Instruction.ReserveAssetDeposited([ - { - id: XcmV3MultiassetAssetId.Concrete({ - parents: 2, - interior: XcmV3Junctions.X2([ - XcmV3Junction.GlobalConsensus(XcmV3JunctionNetworkId.Ethereum({ chain_id: 1n })), - XcmV3Junction.AccountKey20({ - network: undefined, - key: FixedSizeBinary.fromHex('0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'), - }), - ]), - }), - fun: XcmV3MultiassetFungibility.Fungible(15000000000000n), - }, - ]), + const messageFromEthereum = this.getEthereumMessage(Chains.PolkadotHub); - // 6. Clear Origin - XcmV3Instruction.ClearOrigin(), + const origin = XcmVersionedLocation.V4({ + parents: 1, + interior: XcmV3Junctions.X1(XcmV3Junction.Parachain(ParaId[Chains.BridgeHub])), + }); - // 7. Set Appendix - XcmV3Instruction.SetAppendix([ - XcmV3Instruction.DepositAsset({ - assets: XcmV3MultiassetMultiAssetFilter.Wild(XcmV3MultiassetWildMultiAsset.AllCounted(2)), - beneficiary: { - parents: 2, - interior: XcmV3Junctions.X1( - XcmV3Junction.GlobalConsensus(XcmV3JunctionNetworkId.Ethereum({ chain_id: 1n })), - ), - }, - }), - ]), + // Execute the XCM message + const xcm_res = sourceApi.tx.PolkadotXcm.send({ + dest, + message: messageFromEthereum, + }).decodedCall; + const rootOrigin = { type: 'system' as const, value: DispatchRawOrigin.Root() }; + + // Parallelize dry run calls + const [dryRunOnSource, dryRunOnHop] = await Promise.all([ + sourceApi.apis.DryRunApi.dry_run_call(rootOrigin, xcm_res).then(unwrap_api), + hopApi.apis.DryRunApi.dry_run_xcm(origin, messageFromEthereum).then(unwrap_api), + ]); + + // Validate results + expect(dryRunOnSource.success).toBeTrue(); + expect(dryRunOnSource.value.execution_result.success).toBeTrue(); + expect(dryRunOnHop.success).toBeTrue(); + expect(dryRunOnHop.value.execution_result.type).toBe('Complete'); + + const issuedEvents = dryRunOnHop.value.emitted_events.filter( + (event) => event.type === 'ForeignAssets' && event.value.type === 'Issued', + ); + expect(issuedEvents.length).toBe(1); + + const messageOnPolimec = this.getEthereumMessage(Chains.Polimec); + + const hopOrigin = XcmVersionedLocation.V4({ + parents: 1, + interior: XcmV3Junctions.X1(XcmV3Junction.Parachain(ParaId[Chains.PolkadotHub])), + }); - // 8. Deposit Reserve Asset - XcmV3Instruction.DepositReserveAsset({ - assets: XcmV3MultiassetMultiAssetFilter.Definite([ + const dryRunOnDest = await destApi.apis.DryRunApi.dry_run_xcm(hopOrigin, messageOnPolimec).then( + unwrap_api, + ); + expect(dryRunOnDest.success).toBeTrue(); + + const issuedEventsOnDest = dryRunOnDest.value.emitted_events.filter( + (event) => event.type === 'ForeignAssets' && event.value.type === 'Issued', + ); + + // TODO: Check why we have 3 events instead of 2 (WETH + DOT). Curently we have 3 events (WETH + DOT + DOT) + expect(issuedEventsOnDest.length).toBe(3); + + return { sourceBlock, destBlock }; + } + + private getEthereumMessage(on: Chains): XcmVersionedXcm { + if (on === Chains.PolkadotHub) + return XcmVersionedXcm.V3([ + // 1. Receive Teleported Assets + XcmV3Instruction.ReceiveTeleportedAsset([ { id: XcmV3MultiassetAssetId.Concrete({ parents: 1, interior: XcmV3Junctions.Here(), }), - fun: XcmV3MultiassetFungibility.Fungible(40000000000n), + fun: XcmV3MultiassetFungibility.Fungible(FEE_AMOUNT * 2n), }, + ]), + + // 2. Buy Execution + XcmV3Instruction.BuyExecution({ + fees: { + id: XcmV3MultiassetAssetId.Concrete({ + parents: 1, + interior: XcmV3Junctions.Here(), + }), + fun: XcmV3MultiassetFungibility.Fungible(FEE_AMOUNT), + }, + weight_limit: XcmV3WeightLimit.Unlimited(), + }), + + // 3. Descend Origin + XcmV3Instruction.DescendOrigin(XcmV3Junctions.X1(XcmV3Junction.PalletInstance(80))), + + // 4. Universal Origin + XcmV3Instruction.UniversalOrigin( + XcmV3Junction.GlobalConsensus(XcmV3JunctionNetworkId.Ethereum({ chain_id: 1n })), + ), + + // 5. Reserve Asset Deposited + XcmV3Instruction.ReserveAssetDeposited([ { id: XcmV3MultiassetAssetId.Concrete({ parents: 2, @@ -138,96 +151,149 @@ export class BridgeToPolimecTransfer extends BaseTransferTest { XcmV3Junction.GlobalConsensus(XcmV3JunctionNetworkId.Ethereum({ chain_id: 1n })), XcmV3Junction.AccountKey20({ network: undefined, - key: FixedSizeBinary.fromHex('0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'), + key: FixedSizeBinary.fromHex(WETH_ADDRESS), }), ]), }), - fun: XcmV3MultiassetFungibility.Fungible(15000000000000n), + fun: XcmV3MultiassetFungibility.Fungible(WETH_AMOUNT), }, ]), - dest: { - parents: 1, - interior: XcmV3Junctions.X1(XcmV3Junction.Parachain(3344)), - }, - xcm: [ - XcmV3Instruction.BuyExecution({ - fees: { - id: XcmV3MultiassetAssetId.Concrete({ - parents: 1, - interior: XcmV3Junctions.Here(), - }), - fun: XcmV3MultiassetFungibility.Fungible(40000000000n), - }, - weight_limit: XcmV3WeightLimit.Unlimited(), - }), + + // 6. Clear Origin + XcmV3Instruction.ClearOrigin(), + + // 7. Set Appendix + XcmV3Instruction.SetAppendix([ XcmV3Instruction.DepositAsset({ assets: XcmV3MultiassetMultiAssetFilter.Wild( XcmV3MultiassetWildMultiAsset.AllCounted(2), ), beneficiary: { - parents: 0, + parents: 2, interior: XcmV3Junctions.X1( - XcmV3Junction.AccountId32({ - network: undefined, - id: FixedSizeBinary.fromAccountId32(Accounts.ALICE), - }), + XcmV3Junction.GlobalConsensus(XcmV3JunctionNetworkId.Ethereum({ chain_id: 1n })), ), }, }), - XcmV3Instruction.SetTopic( - FixedSizeBinary.fromArray([ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, - ]), - ), - ], - }), - // 9. Set Topic - XcmV3Instruction.SetTopic( - FixedSizeBinary.fromArray([ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, ]), - ), - ]); - - const xcm_res = sourceApi.tx.PolkadotXcm.send({ dest, message }).decodedCall; - - const dry_run_res = await sourceApi.apis.DryRunApi.dry_run_call( - { type: 'system', value: DispatchRawOrigin.Root() }, - xcm_res, - ); - console.log('dryRunCallOnBH SUCCESS?', dry_run_res.success); - - const hopApi = this.hopManager.getApi(Chains.PolkadotHub); - - const origin = XcmVersionedLocation.V4({ - parents: 1, - interior: XcmV3Junctions.X1(XcmV3Junction.Parachain(ParaId[Chains.BridgeHub])), - }); - const dryRunonHop = await hopApi.apis.DryRunApi.dry_run_xcm(origin, message); - console.dir(flatObject(dryRunonHop.value), { depth: null, colors: true }); + // 8. Deposit Reserve Asset + XcmV3Instruction.DepositReserveAsset({ + assets: XcmV3MultiassetMultiAssetFilter.Definite([ + { + id: XcmV3MultiassetAssetId.Concrete({ + parents: 1, + interior: XcmV3Junctions.Here(), + }), + fun: XcmV3MultiassetFungibility.Fungible(FEE_AMOUNT), + }, + { + id: XcmV3MultiassetAssetId.Concrete({ + parents: 2, + interior: XcmV3Junctions.X2([ + XcmV3Junction.GlobalConsensus(XcmV3JunctionNetworkId.Ethereum({ chain_id: 1n })), + XcmV3Junction.AccountKey20({ + network: undefined, + key: FixedSizeBinary.fromHex(WETH_ADDRESS), + }), + ]), + }), + fun: XcmV3MultiassetFungibility.Fungible(WETH_AMOUNT), + }, + ]), + dest: { + parents: 1, + interior: XcmV3Junctions.X1(XcmV3Junction.Parachain(ParaId[Chains.Polimec])), + }, + xcm: [ + XcmV3Instruction.BuyExecution({ + fees: { + id: XcmV3MultiassetAssetId.Concrete({ + parents: 1, + interior: XcmV3Junctions.Here(), + }), + fun: XcmV3MultiassetFungibility.Fungible(FEE_AMOUNT), + }, + weight_limit: XcmV3WeightLimit.Unlimited(), + }), + XcmV3Instruction.DepositAsset({ + assets: XcmV3MultiassetMultiAssetFilter.Wild( + XcmV3MultiassetWildMultiAsset.AllCounted(2), + ), + beneficiary: { + parents: 0, + interior: XcmV3Junctions.X1( + XcmV3Junction.AccountId32({ + network: undefined, + id: FixedSizeBinary.fromAccountId32(Accounts.ALICE), + }), + ), + }, + }), + XcmV3Instruction.SetTopic(DEFAULT_TOPIC), + ], + }), + // 9. Set Topic + XcmV3Instruction.SetTopic(DEFAULT_TOPIC), + ]); + if (on === Chains.Polimec) + return XcmVersionedXcm.V3([ + // Reserve Asset Deposited + XcmV3Instruction.ReserveAssetDeposited([ + { + id: XcmV3MultiassetAssetId.Concrete({ + parents: 1, + interior: XcmV3Junctions.Here(), + }), + fun: XcmV3MultiassetFungibility.Fungible(FEE_AMOUNT), + }, + { + id: XcmV3MultiassetAssetId.Concrete({ + parents: 2, + interior: XcmV3Junctions.X2([ + XcmV3Junction.GlobalConsensus(XcmV3JunctionNetworkId.Ethereum({ chain_id: 1n })), + XcmV3Junction.AccountKey20({ + network: undefined, + key: FixedSizeBinary.fromHex(WETH_ADDRESS), + }), + ]), + }), + fun: XcmV3MultiassetFungibility.Fungible(WETH_AMOUNT), + }, + ]), - return { sourceBlock, destBlock }; - } + // Clear Origin + XcmV3Instruction.ClearOrigin(), - async getBalances({ - account, - assets, - }: TransferOptions): Promise<{ asset_balances: PolimecBalanceCheck[] }> { - return { asset_balances: [{ source: 0n, destination: 0n, treasury: 0n }] }; - } + // Buy Execution + XcmV3Instruction.BuyExecution({ + fees: { + id: XcmV3MultiassetAssetId.Concrete({ + parents: 1, + interior: XcmV3Junctions.Here(), + }), + fun: XcmV3MultiassetFungibility.Fungible(FEE_AMOUNT), + }, + weight_limit: XcmV3WeightLimit.Unlimited(), + }), - async verifyFinalBalances( - assetInitialBalances: PolimecBalanceCheck[], - assetFinalBalances: PolimecBalanceCheck[], - transferOptions: TransferOptions, - ) { - expect(0n).toBe(0n); - } + // Deposit Asset + XcmV3Instruction.DepositAsset({ + assets: XcmV3MultiassetMultiAssetFilter.Wild(XcmV3MultiassetWildMultiAsset.AllCounted(2)), + beneficiary: { + parents: 0, + interior: XcmV3Junctions.X1( + XcmV3Junction.AccountId32({ + network: undefined, + id: FixedSizeBinary.fromAccountId32(Accounts.ALICE), + }), + ), + }, + }), - async calculatePolimecXcmFee(transferOptions: TransferOptions): Promise { - return 0n; + // Set Topic + XcmV3Instruction.SetTopic(DEFAULT_TOPIC), + ]); + throw new Error('Unsupported chain'); } } diff --git a/integration-tests/chopsticks/src/transfers/HubToPolimec.ts b/integration-tests/chopsticks/src/transfers/HubToPolimec.ts index 7e8d9c105..7116ebcfa 100644 --- a/integration-tests/chopsticks/src/transfers/HubToPolimec.ts +++ b/integration-tests/chopsticks/src/transfers/HubToPolimec.ts @@ -44,9 +44,6 @@ export class HubToPolimecTransfer extends BaseTransferTest { const api = this.sourceManager.getApi(Chains.PolkadotHub); const transfer = api.tx.PolkadotXcm.transfer_assets(data); const res = await transfer.signAndSubmit(this.sourceManager.getSigner(account)); - - console.log('Extrinsic result: ', res.ok); - expect(res.ok).toBeTrue(); return { sourceBlock, destBlock }; } diff --git a/integration-tests/chopsticks/src/transfers/PolkadotToPolimec.ts b/integration-tests/chopsticks/src/transfers/PolkadotToPolimec.ts index fa55eab38..8108943e3 100644 --- a/integration-tests/chopsticks/src/transfers/PolkadotToPolimec.ts +++ b/integration-tests/chopsticks/src/transfers/PolkadotToPolimec.ts @@ -99,7 +99,7 @@ export class PolkadotToPolimecTransfer extends BaseTransferTest { const difference = finalBalances.destination - (initialBalances.destination + send_amount - expectedDestBalanceSpent); - const tolerance = (finalBalances.destination * 1_000_000n) / 1_000_000_000n; // 0.0001% + const tolerance = (finalBalances.destination * 1_000_000n) / 1_000_000_00n; // 0.001% expect(abs(difference)).toBeLessThanOrEqual(tolerance); } } diff --git a/integration-tests/chopsticks/src/types.ts b/integration-tests/chopsticks/src/types.ts index bc0555b00..60fdddc60 100644 --- a/integration-tests/chopsticks/src/types.ts +++ b/integration-tests/chopsticks/src/types.ts @@ -18,23 +18,38 @@ type PolkadotHub = typeof pah; type Polkadot = typeof polkadot; type BridgeHub = typeof bridge; -export enum Chains { - Polimec = 'ws://localhost:8000', - PolkadotHub = 'ws://localhost:8001', - Polkadot = 'ws://localhost:8002', - BridgeHub = 'ws://localhost:8003', -} +export const Relay = { + Polkadot: 'ws://localhost:8002', +} as const; + +export const Parachains = { + Polimec: 'ws://localhost:8000', + PolkadotHub: 'ws://localhost:8001', + BridgeHub: 'ws://localhost:8003', +} as const; + +export const Chains = { + ...Relay, + ...Parachains, +} as const; + +// Create a type for the combined object +export type Chains = (typeof Chains)[keyof typeof Chains]; + +export type Parachains = (typeof Parachains)[keyof typeof Parachains]; export type ChainClient = { api: TypedApi; client: PolkadotClient; }; -export const ParaId = { - [Chains.Polimec]: 3344, - [Chains.PolkadotHub]: 1000, - [Chains.BridgeHub]: 1002, -}; +const PARACHAIN_IDS = { + [Parachains.Polimec]: 3344, + [Parachains.PolkadotHub]: 1000, + [Parachains.BridgeHub]: 1002, +} as const; + +export const ParaId = PARACHAIN_IDS; export enum AssetSourceRelation { Parent = 0, @@ -45,6 +60,7 @@ export enum AssetSourceRelation { export enum Accounts { BOB = '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty', ALICE = '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY', + POLIMEC = '5Eg2fnsdZ37LtdXnum4Lp9M1pAmBeThPxbqGMnCe4Lh77jdD', } export type ChainToDefinition = { @@ -79,7 +95,7 @@ export enum Asset { DOT = 10, USDC = 1337, USDT = 1984, - WETH = 10000, + WETH = 10000, // Note: This is not the real Asset ID - we should improve this. } export function AssetHubAssetLocation( @@ -154,22 +170,16 @@ export function EthereumAssetLocation(contract_address: FixedSizeBinary<20>): Xc export function AssetLocation( asset: Asset, - asset_source_relation: AssetSourceRelation, + assetSourceRelation: AssetSourceRelation, ): XcmVersionedLocation { - switch (asset) { - case Asset.USDT: - return AssetHubAssetLocation(1984n, asset_source_relation); - - case Asset.USDC: - return AssetHubAssetLocation(1337n, asset_source_relation); - - case Asset.DOT: - return NativeAssetLocation(asset_source_relation); - - case Asset.WETH: { - return EthereumAssetLocation(FixedSizeBinary.fromHex(WETH_ADDRESS)); - } - } + const baseLocation = + asset === Asset.WETH + ? EthereumAssetLocation(FixedSizeBinary.fromHex(WETH_ADDRESS)) + : asset === Asset.DOT + ? NativeAssetLocation(assetSourceRelation) + : AssetHubAssetLocation(BigInt(asset), assetSourceRelation); + + return baseLocation; } export function getVersionedAssets( diff --git a/integration-tests/chopsticks/src/utils.ts b/integration-tests/chopsticks/src/utils.ts index c11577659..c45495330 100644 --- a/integration-tests/chopsticks/src/utils.ts +++ b/integration-tests/chopsticks/src/utils.ts @@ -21,7 +21,6 @@ import { XcmVersionedXcm, } from '@polkadot-api/descriptors'; import { Enum, FixedSizeBinary } from 'polkadot-api'; - const custom_xcm_on_dest = (): XcmVersionedXcm => { return XcmVersionedXcm.V3([ XcmV3Instruction.DepositReserveAsset({ @@ -109,12 +108,21 @@ export const createDotMultiHopTransferData = (amount: bigint) => { }; export function unwrap(value: T | undefined, errorMessage = 'Value is undefined'): T { - if (value === undefined) { - throw new Error(errorMessage); - } + if (value === undefined) throw new Error(errorMessage); + return value; } +export function unwrap_api( + value: T, + errorMessage = 'Value is undefined', +): T & { success: true } { + if (value === undefined) throw new Error(errorMessage); + if (value === null) throw new Error(errorMessage); + if (!value.success) throw new Error('Dry run failed'); + return value as T & { success: true }; +} + export function flatObject(obj: unknown): unknown { if (obj === null || obj === undefined) { return obj;