feat: initialize Kurdistan SDK - independent fork of Polkadot SDK
This commit is contained in:
@@ -0,0 +1,868 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use alloc::{
|
||||
collections::{btree_map::BTreeMap, btree_set::BTreeSet},
|
||||
vec::Vec,
|
||||
};
|
||||
use core::mem;
|
||||
use sp_runtime::{traits::Saturating, RuntimeDebug};
|
||||
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<AssetId, u128>,
|
||||
|
||||
/// The non-fungible assets.
|
||||
// TODO: Consider BTreeMap<AssetId, BTreeSet<AssetInstance>>
|
||||
// or even BTreeMap<AssetId, SortedVec<AssetInstance>>
|
||||
pub non_fungible: BTreeSet<(AssetId, AssetInstance)>,
|
||||
}
|
||||
|
||||
impl From<Asset> for AssetsInHolding {
|
||||
fn from(asset: Asset) -> AssetsInHolding {
|
||||
let mut result = Self::default();
|
||||
result.subsume(asset);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Asset>> for AssetsInHolding {
|
||||
fn from(assets: Vec<Asset>) -> AssetsInHolding {
|
||||
let mut result = Self::default();
|
||||
for asset in assets.into_iter() {
|
||||
result.subsume(asset)
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Assets> for AssetsInHolding {
|
||||
fn from(assets: Assets) -> AssetsInHolding {
|
||||
assets.into_inner().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AssetsInHolding> for Vec<Asset> {
|
||||
fn from(a: AssetsInHolding) -> Self {
|
||||
a.into_assets_iter().collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AssetsInHolding> for Assets {
|
||||
fn from(a: AssetsInHolding) -> Self {
|
||||
a.into_assets_iter().collect::<Vec<Asset>>().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<Item = Asset> + '_ {
|
||||
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<Item = Asset> + '_ {
|
||||
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<Item = Asset> {
|
||||
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<Item = Asset> + '_ {
|
||||
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
|
||||
pub fn subsume_assets(&mut self, mut assets: AssetsInHolding) {
|
||||
// for fungibles, find matching fungibles and sum their amounts so we end-up having just
|
||||
// single such fungible but with increased amount inside
|
||||
for (asset_id, asset_amount) in assets.fungible {
|
||||
self.fungible
|
||||
.entry(asset_id)
|
||||
.and_modify(|current_asset_amount| {
|
||||
current_asset_amount.saturating_accrue(asset_amount)
|
||||
})
|
||||
.or_insert(asset_amount);
|
||||
}
|
||||
// for non-fungibles, every entry is unique so there is no notion of amount to sum-up
|
||||
// together if there is the same non-fungible in both holdings (same instance_id) these
|
||||
// will be collapsed into just single one
|
||||
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<AssetsInHolding, TakeError> {
|
||||
let mut taken = AssetsInHolding::new();
|
||||
let maybe_limit = mask.limit().map(|x| x as usize);
|
||||
match mask {
|
||||
AssetFilter::Wild(All) | AssetFilter::Wild(AllCounted(_)) => match maybe_limit {
|
||||
None => return Ok(self.swapped(AssetsInHolding::new())),
|
||||
Some(limit) if self.len() <= limit =>
|
||||
return Ok(self.swapped(AssetsInHolding::new())),
|
||||
Some(0) => return Ok(AssetsInHolding::new()),
|
||||
Some(limit) => {
|
||||
let fungible = mem::replace(&mut self.fungible, Default::default());
|
||||
fungible.into_iter().for_each(|(c, amount)| {
|
||||
if taken.len() < limit {
|
||||
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 taken.len() < limit {
|
||||
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<AssetsInHolding, TakeError> {
|
||||
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<AssetsInHolding, AssetsInHolding> {
|
||||
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<_>>(), 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 alloc::vec;
|
||||
use xcm::latest::prelude::*;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
/// Concrete fungible constructor
|
||||
fn CF(amount: u128) -> Asset {
|
||||
(Here, amount).into()
|
||||
}
|
||||
#[allow(non_snake_case)]
|
||||
/// Concrete fungible constructor with index for GeneralIndex
|
||||
fn CFG(index: u128, amount: u128) -> Asset {
|
||||
(GeneralIndex(index), amount).into()
|
||||
}
|
||||
#[allow(non_snake_case)]
|
||||
/// Concrete fungible constructor (parent=1)
|
||||
fn CFP(amount: u128) -> Asset {
|
||||
(Parent, amount).into()
|
||||
}
|
||||
#[allow(non_snake_case)]
|
||||
/// Concrete fungible constructor (parent=2)
|
||||
fn CFPP(amount: u128) -> Asset {
|
||||
((Parent, Parent), 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 assets_in_holding_order_works() {
|
||||
// populate assets in non-ordered fashion
|
||||
let mut assets = AssetsInHolding::new();
|
||||
assets.subsume(CFPP(300));
|
||||
assets.subsume(CFP(200));
|
||||
assets.subsume(CNF(2));
|
||||
assets.subsume(CF(100));
|
||||
assets.subsume(CNF(1));
|
||||
assets.subsume(CFG(10, 400));
|
||||
assets.subsume(CFG(15, 500));
|
||||
|
||||
// following is the order we expect from AssetsInHolding
|
||||
// - fungibles before non-fungibles
|
||||
// - for fungibles, sort by parent first, if parents match, then by other components like
|
||||
// general index
|
||||
// - for non-fungibles, sort by instance_id
|
||||
let mut iter = assets.clone().into_assets_iter();
|
||||
// fungible, order by parent, parent=0
|
||||
assert_eq!(Some(CF(100)), iter.next());
|
||||
// fungible, order by parent then by general index, parent=0, general index=10
|
||||
assert_eq!(Some(CFG(10, 400)), iter.next());
|
||||
// fungible, order by parent then by general index, parent=0, general index=15
|
||||
assert_eq!(Some(CFG(15, 500)), iter.next());
|
||||
// fungible, order by parent, parent=1
|
||||
assert_eq!(Some(CFP(200)), iter.next());
|
||||
// fungible, order by parent, parent=2
|
||||
assert_eq!(Some(CFPP(300)), iter.next());
|
||||
// non-fungible, after fungibles, order by instance id, id=1
|
||||
assert_eq!(Some(CNF(1)), iter.next());
|
||||
// non-fungible, after fungibles, order by instance id, id=2
|
||||
assert_eq!(Some(CNF(2)), iter.next());
|
||||
// nothing else in the assets
|
||||
assert_eq!(None, iter.next());
|
||||
|
||||
// lets add copy of the assets to the assets itself, just to check if order stays the same
|
||||
// we also expect 2x amount for every fungible and collapsed non-fungibles
|
||||
let assets_same = assets.clone();
|
||||
assets.subsume_assets(assets_same);
|
||||
|
||||
let mut iter = assets.into_assets_iter();
|
||||
assert_eq!(Some(CF(200)), iter.next());
|
||||
assert_eq!(Some(CFG(10, 800)), iter.next());
|
||||
assert_eq!(Some(CFG(15, 1000)), iter.next());
|
||||
assert_eq!(Some(CFP(400)), iter.next());
|
||||
assert_eq!(Some(CFPP(600)), iter.next());
|
||||
assert_eq!(Some(CNF(1)), iter.next());
|
||||
assert_eq!(Some(CNF(2)), iter.next());
|
||||
assert_eq!(None, iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subsume_assets_equal_length_holdings() {
|
||||
let mut t1 = test_assets();
|
||||
let mut t2 = AssetsInHolding::new();
|
||||
t2.subsume(CF(300));
|
||||
t2.subsume(CNF(50));
|
||||
|
||||
let t1_clone = t1.clone();
|
||||
let mut t2_clone = t2.clone();
|
||||
|
||||
// ensure values for same fungibles are summed up together
|
||||
// and order is also ok (see assets_in_holding_order_works())
|
||||
t1.subsume_assets(t2.clone());
|
||||
let mut iter = t1.into_assets_iter();
|
||||
assert_eq!(Some(CF(600)), iter.next());
|
||||
assert_eq!(Some(CNF(40)), iter.next());
|
||||
assert_eq!(Some(CNF(50)), iter.next());
|
||||
assert_eq!(None, iter.next());
|
||||
|
||||
// try the same initial holdings but other way around
|
||||
// expecting same exact result as above
|
||||
t2_clone.subsume_assets(t1_clone.clone());
|
||||
let mut iter = t2_clone.into_assets_iter();
|
||||
assert_eq!(Some(CF(600)), iter.next());
|
||||
assert_eq!(Some(CNF(40)), iter.next());
|
||||
assert_eq!(Some(CNF(50)), iter.next());
|
||||
assert_eq!(None, iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subsume_assets_different_length_holdings() {
|
||||
let mut t1 = AssetsInHolding::new();
|
||||
t1.subsume(CFP(400));
|
||||
t1.subsume(CFPP(100));
|
||||
|
||||
let mut t2 = AssetsInHolding::new();
|
||||
t2.subsume(CF(100));
|
||||
t2.subsume(CNF(50));
|
||||
t2.subsume(CNF(40));
|
||||
t2.subsume(CFP(100));
|
||||
t2.subsume(CFPP(100));
|
||||
|
||||
let t1_clone = t1.clone();
|
||||
let mut t2_clone = t2.clone();
|
||||
|
||||
// ensure values for same fungibles are summed up together
|
||||
// and order is also ok (see assets_in_holding_order_works())
|
||||
t1.subsume_assets(t2);
|
||||
let mut iter = t1.into_assets_iter();
|
||||
assert_eq!(Some(CF(100)), iter.next());
|
||||
assert_eq!(Some(CFP(500)), iter.next());
|
||||
assert_eq!(Some(CFPP(200)), iter.next());
|
||||
assert_eq!(Some(CNF(40)), iter.next());
|
||||
assert_eq!(Some(CNF(50)), iter.next());
|
||||
assert_eq!(None, iter.next());
|
||||
|
||||
// try the same initial holdings but other way around
|
||||
// expecting same exact result as above
|
||||
t2_clone.subsume_assets(t1_clone);
|
||||
let mut iter = t2_clone.into_assets_iter();
|
||||
assert_eq!(Some(CF(100)), iter.next());
|
||||
assert_eq!(Some(CFP(500)), iter.next());
|
||||
assert_eq!(Some(CFPP(200)), iter.next());
|
||||
assert_eq!(Some(CNF(40)), iter.next());
|
||||
assert_eq!(Some(CNF(50)), iter.next());
|
||||
assert_eq!(None, iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subsume_assets_empty_holding() {
|
||||
let mut t1 = AssetsInHolding::new();
|
||||
let t2 = AssetsInHolding::new();
|
||||
t1.subsume_assets(t2.clone());
|
||||
let mut iter = t1.clone().into_assets_iter();
|
||||
assert_eq!(None, iter.next());
|
||||
|
||||
t1.subsume(CFP(400));
|
||||
t1.subsume(CNF(40));
|
||||
t1.subsume(CFPP(100));
|
||||
|
||||
let t1_clone = t1.clone();
|
||||
let mut t2_clone = t2.clone();
|
||||
|
||||
// ensure values for same fungibles are summed up together
|
||||
// and order is also ok (see assets_in_holding_order_works())
|
||||
t1.subsume_assets(t2.clone());
|
||||
let mut iter = t1.into_assets_iter();
|
||||
assert_eq!(Some(CFP(400)), iter.next());
|
||||
assert_eq!(Some(CFPP(100)), iter.next());
|
||||
assert_eq!(Some(CNF(40)), iter.next());
|
||||
assert_eq!(None, iter.next());
|
||||
|
||||
// try the same initial holdings but other way around
|
||||
// expecting same exact result as above
|
||||
t2_clone.subsume_assets(t1_clone.clone());
|
||||
let mut iter = t2_clone.into_assets_iter();
|
||||
assert_eq!(Some(CFP(400)), iter.next());
|
||||
assert_eq!(Some(CFPP(100)), iter.next());
|
||||
assert_eq!(Some(CNF(40)), iter.next());
|
||||
assert_eq!(None, iter.next());
|
||||
}
|
||||
|
||||
#[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<Asset> = 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::<Vec<_>>();
|
||||
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::<Vec<_>>();
|
||||
assert_eq!(fungible, vec![CF(300)]);
|
||||
let non_fungible = assets.min(&non_fungible);
|
||||
let non_fungible = non_fungible.assets_iter().collect::<Vec<_>>();
|
||||
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::<Vec<_>>();
|
||||
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::<Vec<_>>();
|
||||
assert_eq!(fungible, vec![CF(300)]);
|
||||
let non_fungible = assets.saturating_take(non_fungible);
|
||||
let non_fungible = non_fungible.assets_iter().collect::<Vec<_>>();
|
||||
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::<Vec<_>>();
|
||||
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)]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::traits::{
|
||||
AssetExchange, AssetLock, CallDispatcher, ClaimAssets, ConvertOrigin, DropAssets, EventEmitter,
|
||||
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<PostInfo = PostDispatchInfo> + GetDispatchInfo;
|
||||
|
||||
/// How to send an onward XCM message.
|
||||
///
|
||||
/// The sender is tasked with returning the assets it needs to pay for delivery fees.
|
||||
/// Only one asset should be returned as delivery fees, any other will be ignored by
|
||||
/// the executor.
|
||||
type XcmSender: SendXcm;
|
||||
|
||||
/// How to emit XCM events.
|
||||
type XcmEventEmitter: EventEmitter;
|
||||
|
||||
/// How to withdraw and deposit an asset.
|
||||
type AssetTransactor: TransactAsset;
|
||||
|
||||
/// How to get a call origin from a `OriginKind` value.
|
||||
type OriginConverter: ConvertOrigin<<Self::RuntimeCall as Dispatchable>::RuntimeOrigin>;
|
||||
|
||||
/// Combinations of (Asset, Location) pairs which we trust as reserves.
|
||||
type IsReserve: ContainsPair<Asset, Location>;
|
||||
|
||||
/// Combinations of (Asset, Location) pairs which we trust as teleporters.
|
||||
type IsTeleporter: ContainsPair<Asset, Location>;
|
||||
|
||||
/// A list of (Origin, Target) pairs allowing a given Origin to be substituted with its
|
||||
/// corresponding Target pair.
|
||||
type Aliasers: ContainsPair<Location, Location>;
|
||||
|
||||
/// This chain's Universal Location.
|
||||
type UniversalLocation: Get<InteriorLocation>;
|
||||
|
||||
/// Whether we should execute the given XCM at all.
|
||||
type Barrier: ShouldExecute;
|
||||
|
||||
/// The means of determining an XCM message's weight.
|
||||
type Weigher: WeightBounds<Self::RuntimeCall>;
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// This is used in the executor to swap the asset wanted for fees with the asset needed for
|
||||
/// delivery fees.
|
||||
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<u32>;
|
||||
|
||||
/// 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<Self::RuntimeCall>;
|
||||
|
||||
/// 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<Self::RuntimeCall>;
|
||||
|
||||
/// 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;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,302 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Unit tests related to the `InitiateTransfer` instruction.
|
||||
//!
|
||||
//! See [Fellowship RFC 100](https://github.com/polkadot-fellows/rfCs/pull/100),
|
||||
//! [Fellowship RFC 122](https://github.com/polkadot-fellows/rfCs/pull/122), and the
|
||||
//! [specification](https://github.com/polkadot-fellows/xcm-format) for more information.
|
||||
|
||||
use codec::Encode;
|
||||
use frame_support::BoundedVec;
|
||||
use xcm::{latest::AssetTransferFilter, prelude::*};
|
||||
|
||||
use super::mock::*;
|
||||
|
||||
// The sender and recipient we use across these tests.
|
||||
const SENDER: [u8; 32] = [0; 32];
|
||||
const RECIPIENT: [u8; 32] = [1; 32];
|
||||
const RECIPIENT2: [u8; 32] = [2; 32];
|
||||
|
||||
#[test]
|
||||
fn clears_origin() {
|
||||
// Make sure the sender has enough funds to withdraw.
|
||||
add_asset(SENDER, (Here, 100u128));
|
||||
|
||||
let xcm_on_dest =
|
||||
Xcm(vec![RefundSurplus, DepositAsset { assets: Wild(All), beneficiary: RECIPIENT.into() }]);
|
||||
let assets: Assets = (Here, 90u128).into();
|
||||
let xcm = Xcm::<TestCall>(vec![
|
||||
WithdrawAsset((Here, 100u128).into()),
|
||||
PayFees { asset: (Here, 10u128).into() },
|
||||
InitiateTransfer {
|
||||
destination: Parent.into(),
|
||||
remote_fees: Some(AssetTransferFilter::ReserveDeposit(assets.into())),
|
||||
preserve_origin: false,
|
||||
assets: BoundedVec::new(),
|
||||
remote_xcm: xcm_on_dest,
|
||||
},
|
||||
]);
|
||||
|
||||
let (mut vm, _) = instantiate_executor(SENDER, xcm.clone());
|
||||
|
||||
// Program runs successfully.
|
||||
let res = vm.bench_process(xcm);
|
||||
assert!(res.is_ok(), "execution error {:?}", res);
|
||||
|
||||
let (dest, sent_message) = sent_xcm().pop().unwrap();
|
||||
assert_eq!(dest, Parent.into());
|
||||
assert_eq!(sent_message.len(), 6);
|
||||
let mut instr = sent_message.inner().iter();
|
||||
assert!(matches!(instr.next().unwrap(), ReserveAssetDeposited(..)));
|
||||
assert!(matches!(instr.next().unwrap(), PayFees { .. }));
|
||||
assert!(matches!(instr.next().unwrap(), ClearOrigin));
|
||||
assert!(matches!(instr.next().unwrap(), RefundSurplus));
|
||||
assert!(matches!(instr.next().unwrap(), DepositAsset { .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preserves_origin() {
|
||||
// Make sure the sender has enough funds to withdraw.
|
||||
add_asset(SENDER, (Here, 100u128));
|
||||
|
||||
let xcm_on_dest =
|
||||
Xcm(vec![RefundSurplus, DepositAsset { assets: Wild(All), beneficiary: RECIPIENT.into() }]);
|
||||
let assets: Assets = (Here, 90u128).into();
|
||||
let xcm = Xcm::<TestCall>(vec![
|
||||
WithdrawAsset((Here, 100u128).into()),
|
||||
PayFees { asset: (Here, 10u128).into() },
|
||||
InitiateTransfer {
|
||||
destination: Parent.into(),
|
||||
remote_fees: Some(AssetTransferFilter::ReserveDeposit(assets.into())),
|
||||
preserve_origin: true,
|
||||
assets: BoundedVec::new(),
|
||||
remote_xcm: xcm_on_dest,
|
||||
},
|
||||
]);
|
||||
|
||||
let (mut vm, _) = instantiate_executor(SENDER, xcm.clone());
|
||||
|
||||
// Program runs successfully.
|
||||
let res = vm.bench_process(xcm);
|
||||
assert!(res.is_ok(), "execution error {:?}", res);
|
||||
|
||||
let (dest, sent_message) = sent_xcm().pop().unwrap();
|
||||
assert_eq!(dest, Parent.into());
|
||||
assert_eq!(sent_message.len(), 6);
|
||||
let mut instr = sent_message.inner().iter();
|
||||
assert!(matches!(instr.next().unwrap(), ReserveAssetDeposited(..)));
|
||||
assert!(matches!(instr.next().unwrap(), PayFees { .. }));
|
||||
assert!(matches!(
|
||||
instr.next().unwrap(),
|
||||
AliasOrigin(origin) if matches!(origin.unpack(), (0, [Teyrchain(1000), AccountId32 { id: SENDER, network: None }]))
|
||||
));
|
||||
assert!(matches!(instr.next().unwrap(), RefundSurplus));
|
||||
assert!(matches!(instr.next().unwrap(), DepositAsset { .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unpaid_execution_goes_after_origin_alteration() {
|
||||
// Make sure the sender has enough funds to withdraw.
|
||||
add_asset(SENDER, (Here, 100u128));
|
||||
|
||||
let xcm_on_destination =
|
||||
Xcm::builder_unsafe().refund_surplus().deposit_asset(All, RECIPIENT).build();
|
||||
let asset: Asset = (Here, 90u128).into();
|
||||
let xcm = Xcm::builder()
|
||||
.withdraw_asset((Here, 100u128))
|
||||
.pay_fees((Here, 10u128))
|
||||
.initiate_transfer(
|
||||
Parent,
|
||||
None, // We specify no remote fees.
|
||||
true, // Preserve origin, necessary for `UnpaidExecution`.
|
||||
vec![AssetTransferFilter::ReserveDeposit(asset.into())],
|
||||
xcm_on_destination,
|
||||
)
|
||||
.build();
|
||||
|
||||
// We initialize the executor with the SENDER origin, which is not waived.
|
||||
let (mut vm, _) = instantiate_executor(SENDER, xcm.clone());
|
||||
|
||||
// Program fails with `BadOrigin`.
|
||||
let result = vm.bench_process(xcm);
|
||||
assert!(result.is_ok(), "execution error {:?}", result);
|
||||
|
||||
let (destination, sent_message) = sent_xcm().pop().unwrap();
|
||||
assert_eq!(destination, Parent.into());
|
||||
assert_eq!(sent_message.len(), 6);
|
||||
let mut instructions = sent_message.inner().iter();
|
||||
assert!(matches!(instructions.next().unwrap(), ReserveAssetDeposited(..)));
|
||||
assert!(matches!(
|
||||
instructions.next().unwrap(),
|
||||
AliasOrigin(origin) if matches!(origin.unpack(), (0, [Teyrchain(1000), AccountId32 { id: SENDER, network: None }]))
|
||||
));
|
||||
assert!(matches!(instructions.next().unwrap(), UnpaidExecution { .. }));
|
||||
assert!(matches!(instructions.next().unwrap(), RefundSurplus));
|
||||
assert!(matches!(instructions.next().unwrap(), DepositAsset { .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_alias_origin_if_root() {
|
||||
// Make sure the sender has enough funds to withdraw.
|
||||
add_asset(Here, (Here, 100u128));
|
||||
|
||||
let xcm_on_destination =
|
||||
Xcm::builder_unsafe().refund_surplus().deposit_asset(All, RECIPIENT).build();
|
||||
let asset: Asset = (Here, 90u128).into();
|
||||
let xcm = Xcm::builder()
|
||||
.withdraw_asset((Here, 100u128))
|
||||
.pay_fees((Here, 10u128))
|
||||
.initiate_transfer(
|
||||
Parent,
|
||||
None, // We specify no remote fees.
|
||||
true, // Preserve origin, necessary for `UnpaidExecution`.
|
||||
vec![AssetTransferFilter::ReserveDeposit(asset.into())],
|
||||
xcm_on_destination,
|
||||
)
|
||||
.build();
|
||||
|
||||
// We initialize the executor with the SENDER origin, which is not waived.
|
||||
let (mut vm, _) = instantiate_executor(Here, xcm.clone());
|
||||
|
||||
// Program fails with `BadOrigin`.
|
||||
let result = vm.bench_process(xcm);
|
||||
assert!(result.is_ok(), "execution error {:?}", result);
|
||||
|
||||
let (destination, sent_message) = sent_xcm().pop().unwrap();
|
||||
assert_eq!(destination, Parent.into());
|
||||
assert_eq!(sent_message.len(), 5);
|
||||
let mut instructions = sent_message.inner().iter();
|
||||
assert!(matches!(instructions.next().unwrap(), ReserveAssetDeposited(..)));
|
||||
assert!(matches!(instructions.next().unwrap(), UnpaidExecution { .. }));
|
||||
assert!(matches!(instructions.next().unwrap(), RefundSurplus));
|
||||
assert!(matches!(instructions.next().unwrap(), DepositAsset { .. }));
|
||||
}
|
||||
|
||||
// We simulate going from one system teyrchain to another without
|
||||
// having to pay remote fees.
|
||||
#[test]
|
||||
fn unpaid_transact() {
|
||||
let to_another_system_para: Location = (Parent, Teyrchain(1001)).into();
|
||||
// We want to execute some call in the receiving chain.
|
||||
let xcm_on_destination = Xcm::builder_unsafe()
|
||||
.transact(OriginKind::Superuser, None, b"".encode())
|
||||
.build();
|
||||
let xcm = Xcm::builder_unsafe()
|
||||
.initiate_transfer(
|
||||
to_another_system_para.clone(),
|
||||
None, // We specify no remote fees.
|
||||
true, // Preserve necessary for `UnpaidExecution`.
|
||||
vec![], // No need for assets.
|
||||
xcm_on_destination,
|
||||
)
|
||||
.build();
|
||||
|
||||
// We initialize the executor with the root origin, which is waived.
|
||||
let (mut vm, _) = instantiate_executor(Here, xcm.clone());
|
||||
|
||||
// Program executes successfully.
|
||||
let result = vm.bench_process(xcm.clone());
|
||||
assert!(result.is_ok(), "execution error: {:?}", result);
|
||||
|
||||
let (destination, sent_message) = sent_xcm().pop().unwrap();
|
||||
assert_eq!(destination, to_another_system_para);
|
||||
assert_eq!(sent_message.len(), 3);
|
||||
let mut instructions = sent_message.inner().iter();
|
||||
assert!(matches!(instructions.next().unwrap(), UnpaidExecution { .. }));
|
||||
assert!(matches!(instructions.next().unwrap(), Transact { .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deposit_assets_with_retry_burns_dust_and_deposits_rest() {
|
||||
// fund sender
|
||||
add_asset(SENDER, (Here, 200u128));
|
||||
|
||||
// dust amount (< ED=2)
|
||||
let dust: Asset = (Here, 1u128).into();
|
||||
|
||||
// non-dust amount (> ED=2)
|
||||
let legit: Asset = (Here, 100u128).into();
|
||||
|
||||
let xcm = Xcm::<TestCall>(vec![
|
||||
WithdrawAsset((Here, 101u128).into()),
|
||||
DepositAsset {
|
||||
assets: Definite(Assets::from(vec![dust.clone()])),
|
||||
beneficiary: RECIPIENT.into(),
|
||||
},
|
||||
DepositAsset {
|
||||
assets: Definite(Assets::from(vec![legit.clone()])),
|
||||
beneficiary: RECIPIENT.into(),
|
||||
},
|
||||
]);
|
||||
|
||||
let (mut vm, weight) = instantiate_executor(SENDER, xcm.clone());
|
||||
|
||||
let result = vm.bench_process(xcm);
|
||||
|
||||
assert!(result.is_ok(), "XCM execution must succeed even if one deposit is dust");
|
||||
let outcome = vm.bench_post_process(weight);
|
||||
assert!(matches!(outcome, Outcome::Complete { .. }), "Expected Complete, got {:?}", outcome);
|
||||
|
||||
let here_assets = asset_list(RECIPIENT);
|
||||
assert_eq!(here_assets, vec![legit], "only the ≥ED asset (100) should end up in `Here`");
|
||||
|
||||
// dust is burned, so nothing lands in the trap account
|
||||
let trapped = asset_list(TRAPPED_ASSETS);
|
||||
assert!(trapped.is_empty(), "dust assets should be silently burned, not trapped");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deposit_assets_with_retry_all_dust_are_burned() {
|
||||
// fund sender
|
||||
add_asset(SENDER, (Here, 20u128));
|
||||
|
||||
// two dust amounts, both < ED=2
|
||||
let d1: Asset = (Here, 1u128).into();
|
||||
let d2: Asset = (Here, 1u128).into();
|
||||
|
||||
let xcm = Xcm::<TestCall>(vec![
|
||||
// withdraw 1+1 so it succeeds
|
||||
WithdrawAsset((Here, (1u128 + 1u128)).into()),
|
||||
DepositAsset {
|
||||
assets: Definite(Assets::from(vec![d1.clone()])),
|
||||
beneficiary: RECIPIENT.into(),
|
||||
},
|
||||
DepositAsset {
|
||||
assets: Definite(Assets::from(vec![d2.clone()])),
|
||||
beneficiary: RECIPIENT2.into(),
|
||||
},
|
||||
]);
|
||||
|
||||
let (mut vm, weight) = instantiate_executor(SENDER, xcm.clone());
|
||||
let result = vm.bench_process(xcm);
|
||||
|
||||
assert!(result.is_ok(), "all-dust deposit must not abort");
|
||||
let outcome = vm.bench_post_process(weight);
|
||||
assert!(matches!(outcome, Outcome::Complete { .. }));
|
||||
|
||||
// none of the two dust deposits should land in either recipient
|
||||
let received = asset_list(RECIPIENT);
|
||||
assert!(received.is_empty(), "no ≥ED assets, so recipient must get nothing");
|
||||
|
||||
// none of the two dust deposits should land in either recipient
|
||||
let received = asset_list(RECIPIENT2);
|
||||
assert!(received.is_empty(), "no ≥ED assets, so recipient must get nothing");
|
||||
|
||||
// all dust is burned, trap account stays empty
|
||||
let trapped = asset_list(TRAPPED_ASSETS);
|
||||
assert!(trapped.is_empty(), "all dust assets must be burned, not trapped");
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Mock types and XcmConfig for all executor unit tests.
|
||||
|
||||
use alloc::collections::btree_map::BTreeMap;
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode};
|
||||
use core::cell::RefCell;
|
||||
use frame_support::{
|
||||
dispatch::{DispatchInfo, DispatchResultWithPostInfo, GetDispatchInfo, PostDispatchInfo},
|
||||
parameter_types,
|
||||
traits::{Everything, Nothing, ProcessMessageError},
|
||||
weights::Weight,
|
||||
};
|
||||
use sp_runtime::traits::Dispatchable;
|
||||
use xcm::prelude::*;
|
||||
|
||||
use crate::{
|
||||
traits::{
|
||||
DropAssets, FeeManager, ProcessTransaction, Properties, ShouldExecute, TransactAsset,
|
||||
WeightBounds, WeightTrader,
|
||||
},
|
||||
AssetsInHolding, Config, FeeReason, XcmExecutor,
|
||||
};
|
||||
|
||||
/// We create an XCVM instance instead of calling `XcmExecutor::<_>::prepare_and_execute` so we
|
||||
/// can inspect its fields.
|
||||
pub fn instantiate_executor(
|
||||
origin: impl Into<Location>,
|
||||
message: Xcm<<XcmConfig as Config>::RuntimeCall>,
|
||||
) -> (XcmExecutor<XcmConfig>, Weight) {
|
||||
let mut vm =
|
||||
XcmExecutor::<XcmConfig>::new(origin, message.using_encoded(sp_io::hashing::blake2_256));
|
||||
let weight = XcmExecutor::<XcmConfig>::prepare(message.clone(), Weight::MAX)
|
||||
.unwrap()
|
||||
.weight_of();
|
||||
vm.message_weight = weight;
|
||||
(vm, weight)
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const MaxAssetsIntoHolding: u32 = 10;
|
||||
pub const BaseXcmWeight: Weight = Weight::from_parts(1, 1);
|
||||
pub const MaxInstructions: u32 = 10;
|
||||
pub UniversalLocation: InteriorLocation = [GlobalConsensus(ByGenesis([0; 32])), Teyrchain(1000)].into();
|
||||
/// Simulate the chain’s existential deposit.
|
||||
pub const ExistentialDeposit: u128 = 2;
|
||||
}
|
||||
|
||||
/// Test origin.
|
||||
#[derive(Debug)]
|
||||
pub struct TestOrigin;
|
||||
|
||||
/// Test call.
|
||||
///
|
||||
/// Doesn't dispatch anything, has an empty implementation of [`Dispatchable`] that
|
||||
/// just returns `Ok` with an empty [`PostDispatchInfo`].
|
||||
#[derive(
|
||||
Debug, Encode, Decode, DecodeWithMemTracking, Eq, PartialEq, Clone, Copy, scale_info::TypeInfo,
|
||||
)]
|
||||
pub struct TestCall;
|
||||
impl Dispatchable for TestCall {
|
||||
type RuntimeOrigin = TestOrigin;
|
||||
type Config = ();
|
||||
type Info = ();
|
||||
type PostInfo = PostDispatchInfo;
|
||||
|
||||
fn dispatch(self, _origin: Self::RuntimeOrigin) -> DispatchResultWithPostInfo {
|
||||
Ok(PostDispatchInfo::default())
|
||||
}
|
||||
}
|
||||
impl GetDispatchInfo for TestCall {
|
||||
fn get_dispatch_info(&self) -> DispatchInfo {
|
||||
DispatchInfo::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Test weigher that just returns a fixed weight for every program.
|
||||
pub struct TestWeigher;
|
||||
impl<C> WeightBounds<C> for TestWeigher {
|
||||
fn weight(_message: &mut Xcm<C>, _weight_limit: Weight) -> Result<Weight, InstructionError> {
|
||||
Ok(Weight::from_parts(2, 2))
|
||||
}
|
||||
|
||||
fn instr_weight(_instruction: &mut Instruction<C>) -> Result<Weight, XcmError> {
|
||||
Ok(Weight::from_parts(2, 2))
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
pub static ASSETS: RefCell<BTreeMap<Location, AssetsInHolding>> = RefCell::new(BTreeMap::new());
|
||||
pub static SENT_XCM: RefCell<Vec<(Location, Xcm<()>)>> = RefCell::new(Vec::new());
|
||||
}
|
||||
|
||||
pub fn add_asset(who: impl Into<Location>, what: impl Into<Asset>) {
|
||||
ASSETS.with(|a| {
|
||||
a.borrow_mut()
|
||||
.entry(who.into())
|
||||
.or_insert(AssetsInHolding::new())
|
||||
.subsume(what.into())
|
||||
});
|
||||
}
|
||||
|
||||
pub fn asset_list(who: impl Into<Location>) -> Vec<Asset> {
|
||||
Assets::from(assets(who)).into_inner()
|
||||
}
|
||||
|
||||
pub fn assets(who: impl Into<Location>) -> AssetsInHolding {
|
||||
ASSETS.with(|a| a.borrow().get(&who.into()).cloned()).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn get_first_fungible(assets: &AssetsInHolding) -> Option<Asset> {
|
||||
assets.fungible_assets_iter().next()
|
||||
}
|
||||
|
||||
/// Test asset transactor that withdraws from and deposits to a thread local assets storage.
|
||||
pub struct TestAssetTransactor;
|
||||
impl TransactAsset for TestAssetTransactor {
|
||||
fn deposit_asset(
|
||||
what: &Asset,
|
||||
who: &Location,
|
||||
_context: Option<&XcmContext>,
|
||||
) -> Result<(), XcmError> {
|
||||
if let Fungibility::Fungible(amount) = what.fun {
|
||||
// fail if below the configured existential deposit
|
||||
if amount < ExistentialDeposit::get() {
|
||||
return Err(XcmError::FailedToTransactAsset(
|
||||
sp_runtime::TokenError::BelowMinimum.into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
add_asset(who.clone(), what.clone());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn withdraw_asset(
|
||||
what: &Asset,
|
||||
who: &Location,
|
||||
_context: Option<&XcmContext>,
|
||||
) -> Result<AssetsInHolding, XcmError> {
|
||||
ASSETS.with(|a| {
|
||||
a.borrow_mut()
|
||||
.get_mut(who)
|
||||
.ok_or(XcmError::NotWithdrawable)?
|
||||
.try_take(what.clone().into())
|
||||
.map_err(|_| XcmError::NotWithdrawable)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Test barrier that just lets everything through.
|
||||
pub struct TestBarrier;
|
||||
impl ShouldExecute for TestBarrier {
|
||||
fn should_execute<Call>(
|
||||
_origin: &Location,
|
||||
_instructions: &mut [Instruction<Call>],
|
||||
_max_weight: Weight,
|
||||
_properties: &mut Properties,
|
||||
) -> Result<(), ProcessMessageError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Test weight to fee that just multiplies `Weight.ref_time` and `Weight.proof_size`.
|
||||
pub struct WeightToFee;
|
||||
impl WeightToFee {
|
||||
pub fn weight_to_fee(weight: &Weight) -> u128 {
|
||||
weight.ref_time() as u128 * weight.proof_size() as u128
|
||||
}
|
||||
}
|
||||
|
||||
/// Test weight trader that just buys weight with the native asset (`Here`) and
|
||||
/// uses the test `WeightToFee`.
|
||||
pub struct TestTrader {
|
||||
weight_bought_so_far: Weight,
|
||||
}
|
||||
impl WeightTrader for TestTrader {
|
||||
fn new() -> Self {
|
||||
Self { weight_bought_so_far: Weight::zero() }
|
||||
}
|
||||
|
||||
fn buy_weight(
|
||||
&mut self,
|
||||
weight: Weight,
|
||||
payment: AssetsInHolding,
|
||||
_context: &XcmContext,
|
||||
) -> Result<AssetsInHolding, XcmError> {
|
||||
let amount = WeightToFee::weight_to_fee(&weight);
|
||||
let required: Asset = (Here, amount).into();
|
||||
let unused = payment.checked_sub(required).map_err(|_| XcmError::TooExpensive)?;
|
||||
self.weight_bought_so_far.saturating_add(weight);
|
||||
Ok(unused)
|
||||
}
|
||||
|
||||
fn refund_weight(&mut self, weight: Weight, _context: &XcmContext) -> Option<Asset> {
|
||||
let weight = weight.min(self.weight_bought_so_far);
|
||||
let amount = WeightToFee::weight_to_fee(&weight);
|
||||
self.weight_bought_so_far -= weight;
|
||||
if amount > 0 {
|
||||
Some((Here, amount).into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Account where all dropped assets are deposited.
|
||||
pub const TRAPPED_ASSETS: [u8; 32] = [255; 32];
|
||||
|
||||
/// Test asset trap that moves all dropped assets to the `TRAPPED_ASSETS` account.
|
||||
pub struct TestAssetTrap;
|
||||
impl DropAssets for TestAssetTrap {
|
||||
fn drop_assets(_origin: &Location, assets: AssetsInHolding, _context: &XcmContext) -> Weight {
|
||||
ASSETS.with(|a| {
|
||||
a.borrow_mut()
|
||||
.entry(TRAPPED_ASSETS.into())
|
||||
.or_insert(AssetsInHolding::new())
|
||||
.subsume_assets(assets)
|
||||
});
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
/// Test sender that always succeeds and puts messages in a dummy queue.
|
||||
///
|
||||
/// It charges `1` for the delivery fee.
|
||||
pub struct TestSender;
|
||||
impl SendXcm for TestSender {
|
||||
type Ticket = (Location, Xcm<()>);
|
||||
|
||||
fn validate(
|
||||
destination: &mut Option<Location>,
|
||||
message: &mut Option<Xcm<()>>,
|
||||
) -> SendResult<Self::Ticket> {
|
||||
let ticket = (destination.take().unwrap(), message.take().unwrap());
|
||||
let delivery_fee: Asset = (Here, 1u128).into();
|
||||
Ok((ticket, delivery_fee.into()))
|
||||
}
|
||||
|
||||
fn deliver(ticket: Self::Ticket) -> Result<XcmHash, SendError> {
|
||||
SENT_XCM.with(|q| q.borrow_mut().push(ticket));
|
||||
Ok([0; 32])
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets queued test messages.
|
||||
pub fn sent_xcm() -> Vec<(Location, Xcm<()>)> {
|
||||
SENT_XCM.with(|q| (*q.borrow()).clone())
|
||||
}
|
||||
|
||||
/// A mock contract address that doesn't need to pay for fees.
|
||||
pub const WAIVED_CONTRACT_ADDRESS: [u8; 20] = [128; 20];
|
||||
|
||||
/// Test fee manager that will waive the fee for some origins.
|
||||
///
|
||||
/// Doesn't do anything with the fee, which effectively burns it.
|
||||
pub struct TestFeeManager;
|
||||
impl FeeManager for TestFeeManager {
|
||||
fn is_waived(origin: Option<&Location>, _: FeeReason) -> bool {
|
||||
let Some(origin) = origin else { return false };
|
||||
// Match the root origin and a particular smart contract account.
|
||||
matches!(
|
||||
origin.unpack(),
|
||||
(0, []) | (0, [AccountKey20 { network: None, key: WAIVED_CONTRACT_ADDRESS }])
|
||||
)
|
||||
}
|
||||
|
||||
fn handle_fee(_: Assets, _: Option<&XcmContext>, _: FeeReason) {}
|
||||
}
|
||||
|
||||
/// Dummy transactional processor that doesn't rollback storage changes, just
|
||||
/// aims to rollback executor state.
|
||||
pub struct TestTransactionalProcessor;
|
||||
impl ProcessTransaction for TestTransactionalProcessor {
|
||||
const IS_TRANSACTIONAL: bool = true;
|
||||
|
||||
fn process<F>(f: F) -> Result<(), XcmError>
|
||||
where
|
||||
F: FnOnce() -> Result<(), XcmError>,
|
||||
{
|
||||
f()
|
||||
}
|
||||
}
|
||||
|
||||
/// Test XcmConfig that uses all the test implementations in this file.
|
||||
pub struct XcmConfig;
|
||||
impl Config for XcmConfig {
|
||||
type RuntimeCall = TestCall;
|
||||
type XcmSender = TestSender;
|
||||
type XcmEventEmitter = ();
|
||||
type AssetTransactor = TestAssetTransactor;
|
||||
type OriginConverter = ();
|
||||
type IsReserve = ();
|
||||
type IsTeleporter = ();
|
||||
type UniversalLocation = UniversalLocation;
|
||||
type Barrier = TestBarrier;
|
||||
type Weigher = TestWeigher;
|
||||
type Trader = TestTrader;
|
||||
type ResponseHandler = ();
|
||||
type AssetTrap = TestAssetTrap;
|
||||
type AssetLocker = ();
|
||||
type AssetExchanger = ();
|
||||
type AssetClaims = ();
|
||||
type SubscriptionService = ();
|
||||
type PalletInstancesInfo = ();
|
||||
type MaxAssetsIntoHolding = MaxAssetsIntoHolding;
|
||||
type FeeManager = TestFeeManager;
|
||||
type MessageExporter = ();
|
||||
type UniversalAliases = Nothing;
|
||||
type CallDispatcher = Self::RuntimeCall;
|
||||
type SafeCallFilter = Everything;
|
||||
type Aliasers = Nothing;
|
||||
type TransactionalProcessor = TestTransactionalProcessor;
|
||||
type HrmpNewChannelOpenRequestHandler = ();
|
||||
type HrmpChannelAcceptedHandler = ();
|
||||
type HrmpChannelClosingHandler = ();
|
||||
type XcmRecorder = ();
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Unit tests for the XCM executor.
|
||||
//!
|
||||
//! These exclude any cross-chain functionality. For those, look at the
|
||||
//! `xcm-emulator` based tests in the cumulus folder.
|
||||
//! These tests deal with internal state changes of the XCVM.
|
||||
|
||||
mod initiate_transfer;
|
||||
mod mock;
|
||||
mod pay_fees;
|
||||
mod set_asset_claimer;
|
||||
@@ -0,0 +1,346 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Unit tests related to the `fees` register and `PayFees` instruction.
|
||||
//!
|
||||
//! See [Fellowship RFC 105](https://github.com/polkadot-fellows/rfCs/pull/105)
|
||||
//! and the [specification](https://github.com/polkadot-fellows/xcm-format) for more information.
|
||||
|
||||
use xcm::prelude::*;
|
||||
|
||||
use super::mock::*;
|
||||
|
||||
// The sender and recipient we use across these tests.
|
||||
const SENDER: [u8; 32] = [0; 32];
|
||||
const RECIPIENT: [u8; 32] = [1; 32];
|
||||
|
||||
// ===== Happy path =====
|
||||
|
||||
// This is a sort of backwards compatibility test.
|
||||
// Since `PayFees` is a replacement for `BuyExecution`, we need to make sure it at least
|
||||
// manages to do the same thing, paying for execution fees.
|
||||
#[test]
|
||||
fn works_for_execution_fees() {
|
||||
// Make sure the sender has enough funds to withdraw.
|
||||
add_asset(SENDER, (Here, 100u128));
|
||||
|
||||
// Build xcm.
|
||||
let xcm = Xcm::<TestCall>::builder()
|
||||
.withdraw_asset((Here, 100u128))
|
||||
.pay_fees((Here, 10u128)) // 10% destined for fees, not more.
|
||||
.deposit_asset(All, RECIPIENT)
|
||||
.build();
|
||||
|
||||
let (mut vm, weight) = instantiate_executor(SENDER, xcm.clone());
|
||||
|
||||
// Program runs successfully.
|
||||
assert!(vm.bench_process(xcm).is_ok());
|
||||
|
||||
// Nothing is left in the `holding` register.
|
||||
assert_eq!(get_first_fungible(vm.holding()), None);
|
||||
// Execution fees were 4, so we still have 6 left in the `fees` register.
|
||||
assert_eq!(get_first_fungible(vm.fees()).unwrap(), (Here, 6u128).into());
|
||||
|
||||
// The recipient received all the assets in the holding register, so `100` that
|
||||
// were withdrawn, minus the `10` that were destinated for fee payment.
|
||||
assert_eq!(asset_list(RECIPIENT), [(Here, 90u128).into()]);
|
||||
|
||||
// Leftover fees get trapped.
|
||||
assert!(vm.bench_post_process(weight).ensure_complete().is_ok());
|
||||
assert_eq!(asset_list(TRAPPED_ASSETS), [(Here, 6u128).into()])
|
||||
}
|
||||
|
||||
// This tests the new functionality provided by `PayFees`, being able to pay for
|
||||
// delivery fees from the `fees` register.
|
||||
#[test]
|
||||
fn works_for_delivery_fees() {
|
||||
// Make sure the sender has enough funds to withdraw.
|
||||
add_asset(SENDER, (Here, 100u128));
|
||||
|
||||
// Information to send messages.
|
||||
// We don't care about the specifics since we're not actually sending them.
|
||||
let query_response_info =
|
||||
QueryResponseInfo { destination: Parent.into(), query_id: 0, max_weight: Weight::zero() };
|
||||
|
||||
// Build xcm.
|
||||
let xcm = Xcm::<TestCall>::builder()
|
||||
.withdraw_asset((Here, 100u128))
|
||||
// We load `10` plancks to pay for fees.
|
||||
.pay_fees((Here, 10u128))
|
||||
// Send a bunch of messages, each charging delivery fees.
|
||||
.report_error(query_response_info.clone())
|
||||
.report_error(query_response_info.clone())
|
||||
.report_error(query_response_info)
|
||||
.deposit_asset(All, RECIPIENT)
|
||||
.build();
|
||||
|
||||
let (mut vm, _) = instantiate_executor(SENDER, xcm.clone());
|
||||
|
||||
// Program runs successfully.
|
||||
assert!(vm.bench_process(xcm).is_ok());
|
||||
|
||||
// Nothing is left in the `holding` register.
|
||||
assert_eq!(get_first_fungible(vm.holding()), None);
|
||||
// Execution fees were 4, delivery were 3, so we are left with only 3 in the `fees` register.
|
||||
assert_eq!(get_first_fungible(vm.fees()).unwrap(), (Here, 3u128).into());
|
||||
|
||||
// The recipient received all the assets in the holding register, so `100` that
|
||||
// were withdrawn, minus the `10` that were destinated for fee payment.
|
||||
assert_eq!(asset_list(RECIPIENT), [(Here, 90u128).into()]);
|
||||
|
||||
let querier: Location =
|
||||
(Teyrchain(1000), AccountId32 { id: SENDER.into(), network: None }).into();
|
||||
let sent_message = Xcm(vec![
|
||||
QueryResponse {
|
||||
query_id: 0,
|
||||
response: Response::ExecutionResult(None),
|
||||
max_weight: Weight::zero(),
|
||||
querier: Some(querier),
|
||||
},
|
||||
SetTopic(vm.context.topic_or_message_id()),
|
||||
]);
|
||||
|
||||
// The messages were "sent" successfully.
|
||||
assert_eq!(
|
||||
sent_xcm(),
|
||||
vec![
|
||||
(Parent.into(), sent_message.clone()),
|
||||
(Parent.into(), sent_message.clone()),
|
||||
(Parent.into(), sent_message.clone())
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// Tests the support for `BuyExecution` while the ecosystem transitions to `PayFees`.
|
||||
#[test]
|
||||
fn buy_execution_works_as_before() {
|
||||
// Make sure the sender has enough funds to withdraw.
|
||||
add_asset(SENDER, (Here, 100u128));
|
||||
|
||||
// Build xcm.
|
||||
let xcm = Xcm::<TestCall>::builder()
|
||||
.withdraw_asset((Here, 100u128))
|
||||
// We can put everything here, since excess will be returned to holding.
|
||||
// We have to specify `Limited` here to actually work, it's normally
|
||||
// set in the `AllowTopLevelPaidExecutionFrom` barrier.
|
||||
.buy_execution((Here, 100u128), Limited(Weight::from_parts(2, 2)))
|
||||
.deposit_asset(All, RECIPIENT)
|
||||
.build();
|
||||
|
||||
let (mut vm, _) = instantiate_executor(SENDER, xcm.clone());
|
||||
|
||||
// Program runs successfully.
|
||||
assert!(vm.bench_process(xcm).is_ok());
|
||||
|
||||
// Nothing is left in the `holding` register.
|
||||
assert_eq!(get_first_fungible(vm.holding()), None);
|
||||
// `BuyExecution` does not interact with the `fees` register.
|
||||
assert_eq!(get_first_fungible(vm.fees()), None);
|
||||
|
||||
// The recipient received all the assets in the holding register, so `100` that
|
||||
// were withdrawn, minus the `4` from paying the execution fees.
|
||||
assert_eq!(asset_list(RECIPIENT), [(Here, 96u128).into()]);
|
||||
}
|
||||
|
||||
// Tests the interaction between `PayFees` and `RefundSurplus`.
|
||||
#[test]
|
||||
fn fees_can_be_refunded() {
|
||||
// Make sure the sender has enough funds to withdraw.
|
||||
add_asset(SENDER, (Here, 100u128));
|
||||
|
||||
// Build xcm.
|
||||
let xcm = Xcm::<TestCall>::builder()
|
||||
.withdraw_asset((Here, 100u128))
|
||||
.pay_fees((Here, 10u128)) // 10% destined for fees, not more.
|
||||
.deposit_asset(All, RECIPIENT)
|
||||
.refund_surplus()
|
||||
.deposit_asset(All, SENDER)
|
||||
.build();
|
||||
|
||||
let (mut vm, _) = instantiate_executor(SENDER, xcm.clone());
|
||||
|
||||
// Program runs successfully.
|
||||
assert!(vm.bench_process(xcm).is_ok());
|
||||
|
||||
// Nothing is left in the `holding` register.
|
||||
assert_eq!(get_first_fungible(vm.holding()), None);
|
||||
// Nothing was left in the `fees` register since it was refunded.
|
||||
assert_eq!(get_first_fungible(vm.fees()), None);
|
||||
|
||||
// The recipient received all the assets in the holding register, so `100` that
|
||||
// were withdrawn, minus the `10` that were destinated for fee payment.
|
||||
assert_eq!(asset_list(RECIPIENT), [(Here, 90u128).into()]);
|
||||
|
||||
// The sender got back `6` from unused assets.
|
||||
assert_eq!(asset_list(SENDER), [(Here, 6u128).into()]);
|
||||
}
|
||||
|
||||
// ===== Unhappy path =====
|
||||
|
||||
#[test]
|
||||
fn putting_all_assets_in_pay_fees() {
|
||||
// Make sure the sender has enough funds to withdraw.
|
||||
add_asset(SENDER, (Here, 100u128));
|
||||
|
||||
// Build xcm.
|
||||
let xcm = Xcm::<TestCall>::builder()
|
||||
.withdraw_asset((Here, 100u128))
|
||||
.pay_fees((Here, 100u128)) // 100% destined for fees, this is not going to end well...
|
||||
.deposit_asset(All, RECIPIENT)
|
||||
.build();
|
||||
|
||||
let (mut vm, _) = instantiate_executor(SENDER, xcm.clone());
|
||||
|
||||
// Program runs successfully.
|
||||
assert!(vm.bench_process(xcm).is_ok());
|
||||
|
||||
// Nothing is left in the `holding` register.
|
||||
assert_eq!(get_first_fungible(vm.holding()), None);
|
||||
// We destined `100` for fee payment, after `4` for execution fees, we are left with `96`.
|
||||
assert_eq!(get_first_fungible(vm.fees()).unwrap(), (Here, 96u128).into());
|
||||
|
||||
// The recipient received no assets since they were all destined for fee payment.
|
||||
assert_eq!(asset_list(RECIPIENT), []);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn putting_more_than_available_fails() {
|
||||
// Make sure the sender has enough funds to withdraw.
|
||||
add_asset(SENDER, (Here, 100u128));
|
||||
|
||||
// Build xcm.
|
||||
let xcm = Xcm::<TestCall>::builder()
|
||||
.withdraw_asset((Here, 100u128))
|
||||
.pay_fees((Here, 200u128)) // 200% destined for fees, there's not even that much!
|
||||
.deposit_asset(All, RECIPIENT)
|
||||
.build();
|
||||
|
||||
let (mut vm, weight) = instantiate_executor(SENDER, xcm.clone());
|
||||
|
||||
// Program fails.
|
||||
assert!(vm.bench_process(xcm).is_err());
|
||||
|
||||
// Everything is left in the `holding` register.
|
||||
assert_eq!(get_first_fungible(vm.holding()).unwrap(), (Here, 100u128).into());
|
||||
// Nothing in the `fees` register.
|
||||
assert_eq!(get_first_fungible(vm.fees()), None);
|
||||
|
||||
// The recipient received no assets since message failed.
|
||||
assert_eq!(asset_list(RECIPIENT), []);
|
||||
|
||||
// Leftover assets get trapped.
|
||||
assert!(vm.bench_post_process(weight).ensure_complete().is_ok());
|
||||
assert_eq!(asset_list(TRAPPED_ASSETS), [(Here, 100u128).into()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refunding_too_early() {
|
||||
// Make sure the sender has enough funds to withdraw.
|
||||
add_asset(SENDER, (Here, 100u128));
|
||||
|
||||
// Information to send messages.
|
||||
// We don't care about the specifics since we're not actually sending them.
|
||||
let query_response_info =
|
||||
QueryResponseInfo { destination: Parent.into(), query_id: 0, max_weight: Weight::zero() };
|
||||
|
||||
// Build xcm.
|
||||
let xcm = Xcm::<TestCall>::builder()
|
||||
.withdraw_asset((Here, 100u128))
|
||||
.pay_fees((Here, 10u128)) // 10% destined for fees, not more.
|
||||
.deposit_asset(All, RECIPIENT)
|
||||
.refund_surplus()
|
||||
.deposit_asset(All, SENDER)
|
||||
// `refund_surplus` cleared the `fees` register.
|
||||
// `holding` is used as a fallback, but we also cleared that.
|
||||
// The instruction will error and the message won't be sent :(.
|
||||
.report_error(query_response_info)
|
||||
.build();
|
||||
|
||||
let (mut vm, _) = instantiate_executor(SENDER, xcm.clone());
|
||||
|
||||
// Program fails to run.
|
||||
assert!(vm.bench_process(xcm).is_err());
|
||||
|
||||
// Nothing is left in the `holding` register.
|
||||
assert_eq!(get_first_fungible(vm.holding()), None);
|
||||
// Nothing was left in the `fees` register since it was refunded.
|
||||
assert_eq!(get_first_fungible(vm.fees()), None);
|
||||
|
||||
// The recipient received all the assets in the holding register, so `100` that
|
||||
// were withdrawn, minus the `10` that were destinated for fee payment.
|
||||
assert_eq!(asset_list(RECIPIENT), [(Here, 90u128).into()]);
|
||||
|
||||
// The sender got back `6` from unused assets.
|
||||
assert_eq!(asset_list(SENDER), [(Here, 6u128).into()]);
|
||||
|
||||
// No messages were "sent".
|
||||
assert_eq!(sent_xcm(), Vec::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pay_fees_is_processed_only_once() {
|
||||
// Make sure the sender has enough funds to withdraw.
|
||||
add_asset(SENDER, (Here, 100u128));
|
||||
|
||||
let xcm = Xcm::<TestCall>::builder()
|
||||
.withdraw_asset((Here, 100u128))
|
||||
.pay_fees((Here, 10u128))
|
||||
// Will both be a noop.
|
||||
.pay_fees((Parent, 10u128))
|
||||
.pay_fees((Here, 200u128))
|
||||
.deposit_asset(All, RECIPIENT)
|
||||
.build();
|
||||
|
||||
let (mut vm, weight) = instantiate_executor(SENDER, xcm.clone());
|
||||
|
||||
// Program runs successfully.
|
||||
assert!(vm.bench_process(xcm).is_ok());
|
||||
|
||||
// Nothing left in the `holding` register.
|
||||
assert_eq!(get_first_fungible(vm.holding()), None);
|
||||
// Only the first `PayFees` was executed and execution fees are 4,
|
||||
// so there are 6 fees left in the `fees` register.
|
||||
assert_eq!(get_first_fungible(vm.fees()).unwrap(), (Here, 6u128).into());
|
||||
|
||||
// Leftover fees get trapped.
|
||||
assert!(vm.bench_post_process(weight).ensure_complete().is_ok());
|
||||
assert_eq!(asset_list(TRAPPED_ASSETS), [(Here, 6u128).into()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn already_paid_fees_rolls_back_on_error() {
|
||||
// Make sure the sender has enough funds to withdraw.
|
||||
add_asset(SENDER, (Here, 100u128));
|
||||
|
||||
let xcm = Xcm::<TestCall>::builder()
|
||||
.withdraw_asset((Here, 100u128))
|
||||
.pay_fees((Here, 200u128))
|
||||
.deposit_asset(All, RECIPIENT)
|
||||
.build();
|
||||
|
||||
let (mut vm, _) = instantiate_executor(SENDER, xcm.clone());
|
||||
|
||||
// Program fails.
|
||||
assert!(vm.bench_process(xcm).is_err());
|
||||
|
||||
// Everything left in the `holding` register.
|
||||
assert_eq!(get_first_fungible(vm.holding()).unwrap(), (Here, 100u128).into());
|
||||
// Nothing in the `fees` register.
|
||||
assert_eq!(get_first_fungible(vm.fees()), None);
|
||||
|
||||
// Already paid fees is false.
|
||||
assert_eq!(vm.already_paid_fees(), false);
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Unit tests related to the `fees` register and `PayFees` instruction.
|
||||
//!
|
||||
//! See [Fellowship RFC 105](https://github.com/polkadot-fellows/rfCs/pull/105)
|
||||
//! and the [specification](https://github.com/polkadot-fellows/xcm-format) for more information.
|
||||
|
||||
use codec::Encode;
|
||||
use xcm::prelude::*;
|
||||
|
||||
use super::mock::*;
|
||||
use crate::XcmExecutor;
|
||||
|
||||
#[test]
|
||||
fn set_asset_claimer() {
|
||||
let sender = Location::new(0, [AccountId32 { id: [0; 32], network: None }]);
|
||||
let bob = Location::new(0, [AccountId32 { id: [2; 32], network: None }]);
|
||||
|
||||
// Make sure the user has enough funds to withdraw.
|
||||
add_asset(sender.clone(), (Here, 100u128));
|
||||
|
||||
// Build xcm.
|
||||
let xcm = Xcm::<TestCall>::builder_unsafe()
|
||||
// if withdrawing fails we're not missing any corner case.
|
||||
.withdraw_asset((Here, 100u128))
|
||||
.clear_origin()
|
||||
.set_hints(vec![AssetClaimer { location: bob.clone() }])
|
||||
.pay_fees((Here, 10u128)) // 10% destined for fees, not more.
|
||||
.build();
|
||||
|
||||
// We create an XCVM instance instead of calling `XcmExecutor::<_>::prepare_and_execute` so we
|
||||
// can inspect its fields.
|
||||
let mut vm =
|
||||
XcmExecutor::<XcmConfig>::new(sender, xcm.using_encoded(sp_io::hashing::blake2_256));
|
||||
vm.message_weight =
|
||||
XcmExecutor::<XcmConfig>::prepare(xcm.clone(), Weight::MAX).unwrap().weight_of();
|
||||
|
||||
let result = vm.bench_process(xcm);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(vm.asset_claimer(), Some(bob));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn do_not_set_asset_claimer_none() {
|
||||
let sender = Location::new(0, [AccountId32 { id: [0; 32], network: None }]);
|
||||
|
||||
// Make sure the user has enough funds to withdraw.
|
||||
add_asset(sender.clone(), (Here, 100u128));
|
||||
|
||||
// Build xcm.
|
||||
let xcm = Xcm::<TestCall>::builder_unsafe()
|
||||
// if withdrawing fails we're not missing any corner case.
|
||||
.withdraw_asset((Here, 100u128))
|
||||
.clear_origin()
|
||||
.pay_fees((Here, 10u128)) // 10% destined for fees, not more.
|
||||
.build();
|
||||
|
||||
// We create an XCVM instance instead of calling `XcmExecutor::<_>::prepare_and_execute` so we
|
||||
// can inspect its fields.
|
||||
let mut vm =
|
||||
XcmExecutor::<XcmConfig>::new(sender, xcm.using_encoded(sp_io::hashing::blake2_256));
|
||||
vm.message_weight =
|
||||
XcmExecutor::<XcmConfig>::prepare(xcm.clone(), Weight::MAX).unwrap().weight_of();
|
||||
|
||||
let result = vm.bench_process(xcm);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(vm.asset_claimer(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trap_then_set_asset_claimer() {
|
||||
let sender = Location::new(0, [AccountId32 { id: [0; 32], network: None }]);
|
||||
let bob = Location::new(0, [AccountId32 { id: [2; 32], network: None }]);
|
||||
|
||||
// Make sure the user has enough funds to withdraw.
|
||||
add_asset(sender.clone(), (Here, 100u128));
|
||||
|
||||
// Build xcm.
|
||||
let xcm = Xcm::<TestCall>::builder_unsafe()
|
||||
// if withdrawing fails we're not missing any corner case.
|
||||
.withdraw_asset((Here, 100u128))
|
||||
.clear_origin()
|
||||
.trap(0u64)
|
||||
.set_hints(vec![AssetClaimer { location: bob }])
|
||||
.pay_fees((Here, 10u128)) // 10% destined for fees, not more.
|
||||
.build();
|
||||
|
||||
// We create an XCVM instance instead of calling `XcmExecutor::<_>::prepare_and_execute` so we
|
||||
// can inspect its fields.
|
||||
let mut vm =
|
||||
XcmExecutor::<XcmConfig>::new(sender, xcm.using_encoded(sp_io::hashing::blake2_256));
|
||||
vm.message_weight =
|
||||
XcmExecutor::<XcmConfig>::prepare(xcm.clone(), Weight::MAX).unwrap().weight_of();
|
||||
|
||||
let result = vm.bench_process(xcm);
|
||||
assert!(result.is_err());
|
||||
assert_eq!(vm.asset_claimer(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_asset_claimer_then_trap() {
|
||||
let sender = Location::new(0, [AccountId32 { id: [0; 32], network: None }]);
|
||||
let bob = Location::new(0, [AccountId32 { id: [2; 32], network: None }]);
|
||||
|
||||
// Make sure the user has enough funds to withdraw.
|
||||
add_asset(sender.clone(), (Here, 100u128));
|
||||
|
||||
// Build xcm.
|
||||
let xcm = Xcm::<TestCall>::builder_unsafe()
|
||||
// if withdrawing fails we're not missing any corner case.
|
||||
.withdraw_asset((Here, 100u128))
|
||||
.clear_origin()
|
||||
.set_hints(vec![AssetClaimer { location: bob.clone() }])
|
||||
.trap(0u64)
|
||||
.pay_fees((Here, 10u128)) // 10% destined for fees, not more.
|
||||
.build();
|
||||
|
||||
// We create an XCVM instance instead of calling `XcmExecutor::<_>::prepare_and_execute` so we
|
||||
// can inspect its fields.
|
||||
let mut vm =
|
||||
XcmExecutor::<XcmConfig>::new(sender, xcm.using_encoded(sp_io::hashing::blake2_256));
|
||||
vm.message_weight =
|
||||
XcmExecutor::<XcmConfig>::prepare(xcm.clone(), Weight::MAX).unwrap().weight_of();
|
||||
|
||||
let result = vm.bench_process(xcm);
|
||||
assert!(result.is_err());
|
||||
assert_eq!(vm.asset_claimer(), Some(bob));
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::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<AssetsInHolding, AssetsInHolding>;
|
||||
|
||||
/// Handler for quoting the exchange price of two asset collections.
|
||||
///
|
||||
/// It's useful before calling `exchange_asset`, to get some information on whether or not the
|
||||
/// exchange will be successful.
|
||||
///
|
||||
/// Arguments:
|
||||
/// - `give` The asset(s) that are going to be given.
|
||||
/// - `want` The asset(s) that are wanted.
|
||||
/// - `maximal`:
|
||||
/// - If `true`, then the return value is the resulting amount of `want` obtained by swapping
|
||||
/// `give`.
|
||||
/// - If `false`, then the return value is the required amount of `give` needed to get `want`.
|
||||
///
|
||||
/// The return value is `Assets` since it comprises both which assets and how much of them.
|
||||
///
|
||||
/// The relationship between this function and `exchange_asset` is the following:
|
||||
/// - quote(give, want, maximal) = resulting_want -> exchange(give, resulting_want, maximal) ✅
|
||||
/// - quote(give, want, minimal) = required_give -> exchange(required_give_amount, want,
|
||||
/// minimal) ✅
|
||||
fn quote_exchange_price(_give: &Assets, _want: &Assets, _maximal: bool) -> Option<Assets>;
|
||||
}
|
||||
|
||||
#[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<AssetsInHolding, AssetsInHolding> {
|
||||
for_tuples!( #(
|
||||
let give = match Tuple::exchange_asset(origin, give, want, maximal) {
|
||||
Ok(r) => return Ok(r),
|
||||
Err(a) => a,
|
||||
};
|
||||
)* );
|
||||
Err(give)
|
||||
}
|
||||
|
||||
fn quote_exchange_price(give: &Assets, want: &Assets, maximal: bool) -> Option<Assets> {
|
||||
for_tuples!( #(
|
||||
match Tuple::quote_exchange_price(give, want, maximal) {
|
||||
Some(assets) => return Some(assets),
|
||||
None => {}
|
||||
}
|
||||
)* );
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use core::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<LockError> 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<Self::LockTicket, LockError>;
|
||||
|
||||
/// 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<Self::UnlockTicket, LockError>;
|
||||
|
||||
/// 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<Self::ReduceTicket, LockError>;
|
||||
}
|
||||
|
||||
impl AssetLock for () {
|
||||
type LockTicket = Infallible;
|
||||
type UnlockTicket = Infallible;
|
||||
type ReduceTicket = Infallible;
|
||||
fn prepare_lock(_: Location, _: Asset, _: Location) -> Result<Self::LockTicket, LockError> {
|
||||
Err(LockError::NotApplicable)
|
||||
}
|
||||
fn prepare_unlock(_: Location, _: Asset, _: Location) -> Result<Self::UnlockTicket, LockError> {
|
||||
Err(LockError::NotApplicable)
|
||||
}
|
||||
fn note_unlockable(_: Location, _: Asset, _: Location) -> Result<(), LockError> {
|
||||
Err(LockError::NotApplicable)
|
||||
}
|
||||
fn prepare_reduce_unlockable(
|
||||
_: Location,
|
||||
_: Asset,
|
||||
_: Location,
|
||||
) -> Result<Self::ReduceTicket, LockError> {
|
||||
Err(LockError::NotApplicable)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::traits::TransactAsset;
|
||||
use frame_support::traits::ContainsPair;
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::codec::{Decode, DecodeWithMemTracking, 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, DecodeWithMemTracking, 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<Asset, Location>;
|
||||
|
||||
/// Combinations of (Asset, Location) pairs which we trust as teleporters. Meaning teleports are
|
||||
/// to be used for assets matching this filter.
|
||||
type IsTeleporter: ContainsPair<Asset, Location>;
|
||||
|
||||
/// 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<TransferType, Error> {
|
||||
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 IsReserve = ();
|
||||
type IsTeleporter = ();
|
||||
type AssetTransactor = ();
|
||||
fn determine_for(_: &Asset, _: &Location) -> Result<TransferType, Error> {
|
||||
return Err(Error::UnknownReserve);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use core::{marker::PhantomData, result::Result};
|
||||
use frame_support::traits::{Contains, OriginTrait};
|
||||
use sp_runtime::{traits::Dispatchable, DispatchErrorWithPostInfo};
|
||||
use xcm::latest::prelude::*;
|
||||
|
||||
/// Means of converting a location into an account identifier.
|
||||
pub trait ConvertLocation<AccountId> {
|
||||
/// Convert the `location` into `Some` account ID, or `None` if not possible.
|
||||
fn convert_location(location: &Location) -> Option<AccountId>;
|
||||
}
|
||||
|
||||
#[impl_trait_for_tuples::impl_for_tuples(30)]
|
||||
impl<AccountId> ConvertLocation<AccountId> for Tuple {
|
||||
fn convert_location(l: &Location) -> Option<AccountId> {
|
||||
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<u32> for BumpParaId {
|
||||
/// fn convert_origin(origin: impl Into<Location>, _: OriginKind) -> Result<u32, Location> {
|
||||
/// match origin.into().unpack() {
|
||||
/// (0, [Junction::Teyrchain(id)]) => {
|
||||
/// Err([Junction::Teyrchain(id + 1)].into())
|
||||
/// }
|
||||
/// _ => unreachable!()
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// struct AcceptPara7;
|
||||
/// impl ConvertOrigin<u32> for AcceptPara7 {
|
||||
/// fn convert_origin(origin: impl Into<Location>, _: OriginKind) -> Result<u32, Location> {
|
||||
/// let origin = origin.into();
|
||||
/// match origin.unpack() {
|
||||
/// (0, [Junction::Teyrchain(id)]) if *id == 7 => {
|
||||
/// Ok(7)
|
||||
/// }
|
||||
/// _ => Err(origin)
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// # fn main() {
|
||||
/// let origin: Location = [Junction::Teyrchain(6)].into();
|
||||
/// assert!(
|
||||
/// <(BumpParaId, AcceptPara7) as ConvertOrigin<u32>>::convert_origin(origin, OriginKind::Native)
|
||||
/// .is_ok()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub trait ConvertOrigin<Origin> {
|
||||
/// Attempt to convert `origin` to the generic `Origin` whilst consuming it.
|
||||
fn convert_origin(origin: impl Into<Location>, kind: OriginKind) -> Result<Origin, Location>;
|
||||
}
|
||||
|
||||
#[impl_trait_for_tuples::impl_for_tuples(30)]
|
||||
impl<O> ConvertOrigin<O> for Tuple {
|
||||
fn convert_origin(origin: impl Into<Location>, kind: OriginKind) -> Result<O, Location> {
|
||||
let origin = origin.into();
|
||||
|
||||
tracing::trace!(
|
||||
target: "xcm::convert_origin",
|
||||
?origin,
|
||||
?kind,
|
||||
"Converting origin",
|
||||
);
|
||||
|
||||
for_tuples!( #(
|
||||
let convert_origin = core::any::type_name::<Tuple>();
|
||||
|
||||
let origin = match Tuple::convert_origin(origin, kind) {
|
||||
Err(o) => {
|
||||
tracing::trace!(
|
||||
target: "xcm::convert_origin",
|
||||
%convert_origin,
|
||||
"Convert origin step failed",
|
||||
);
|
||||
|
||||
o
|
||||
},
|
||||
Ok(o) => {
|
||||
tracing::trace!(
|
||||
target: "xcm::convert_origin",
|
||||
%convert_origin,
|
||||
"Convert origin step succeeded",
|
||||
);
|
||||
|
||||
return Ok(o)
|
||||
}
|
||||
};
|
||||
)* );
|
||||
|
||||
tracing::trace!(
|
||||
target: "xcm::convert_origin",
|
||||
"Converting origin failed",
|
||||
);
|
||||
|
||||
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<Call: Dispatchable> {
|
||||
fn dispatch(
|
||||
call: Call,
|
||||
origin: Call::RuntimeOrigin,
|
||||
) -> Result<Call::PostInfo, DispatchErrorWithPostInfo<Call::PostInfo>>;
|
||||
}
|
||||
|
||||
pub struct WithOriginFilter<Filter>(PhantomData<Filter>);
|
||||
impl<Call, Filter> CallDispatcher<Call> for WithOriginFilter<Filter>
|
||||
where
|
||||
Call: Dispatchable,
|
||||
Call::RuntimeOrigin: OriginTrait,
|
||||
<<Call as Dispatchable>::RuntimeOrigin as OriginTrait>::Call: 'static,
|
||||
Filter: Contains<<<Call as Dispatchable>::RuntimeOrigin as OriginTrait>::Call> + 'static,
|
||||
{
|
||||
fn dispatch(
|
||||
call: Call,
|
||||
mut origin: <Call as Dispatchable>::RuntimeOrigin,
|
||||
) -> Result<
|
||||
<Call as Dispatchable>::PostInfo,
|
||||
DispatchErrorWithPostInfo<<Call as Dispatchable>::PostInfo>,
|
||||
> {
|
||||
origin.add_filter(Filter::contains);
|
||||
call.dispatch(origin)
|
||||
}
|
||||
}
|
||||
|
||||
// We implement it for every calls so they can dispatch themselves
|
||||
// (without any change).
|
||||
impl<Call: Dispatchable> CallDispatcher<Call> for Call {
|
||||
fn dispatch(
|
||||
call: Call,
|
||||
origin: Call::RuntimeOrigin,
|
||||
) -> Result<Call::PostInfo, DispatchErrorWithPostInfo<Call::PostInfo>> {
|
||||
call.dispatch(origin)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::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.
|
||||
#[allow(dead_code)]
|
||||
pub struct FilterAssets<D, A>(PhantomData<(D, A)>);
|
||||
|
||||
impl<D: DropAssets, A: Contains<AssetsInHolding>> DropAssets for FilterAssets<D, A> {
|
||||
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.
|
||||
#[allow(dead_code)]
|
||||
pub struct FilterOrigin<D, O>(PhantomData<(D, O)>);
|
||||
|
||||
impl<D: DropAssets, O: Contains<Location>> DropAssets for FilterOrigin<D, O> {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use xcm::{
|
||||
latest::{Location, SendError, Xcm, XcmHash},
|
||||
prelude::XcmError,
|
||||
};
|
||||
|
||||
/// Defines the event emitter for the XCM executor.
|
||||
/// This trait allows implementations to emit events related to XCM handling, including successful
|
||||
/// sends and failure cases.
|
||||
pub trait EventEmitter {
|
||||
/// Emits an event when an XCM is successfully sent.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `origin`: The origin location of the XCM.
|
||||
/// - `destination`: The target location where the message is sent.
|
||||
/// - `message`: `Some(Xcm)` for `pallet_xcm::Event::Sent`, `None` for other events to reduce
|
||||
/// storage.
|
||||
/// - `message_id`: A unique identifier for the XCM.
|
||||
fn emit_sent_event(
|
||||
origin: Location,
|
||||
destination: Location,
|
||||
message: Option<Xcm<()>>,
|
||||
message_id: XcmHash,
|
||||
);
|
||||
|
||||
/// Emits an event when an XCM fails to send.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `origin`: The origin location of the XCM.
|
||||
/// - `destination`: The intended target location.
|
||||
/// - `error`: The error encountered while sending.
|
||||
/// - `message_id`: The unique identifier for the failed message.
|
||||
fn emit_send_failure_event(
|
||||
origin: Location,
|
||||
destination: Location,
|
||||
error: SendError,
|
||||
message_id: XcmHash,
|
||||
);
|
||||
|
||||
/// Emits an event when an XCM fails to process.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `origin`: The origin location of the message.
|
||||
/// - `error`: The error encountered while processing.
|
||||
/// - `message_id`: The unique identifier for the failed message.
|
||||
fn emit_process_failure_event(origin: Location, error: XcmError, message_id: XcmHash);
|
||||
}
|
||||
|
||||
/// A no-op implementation of `EventEmitter` for unit type `()`.
|
||||
/// This can be used when event emission is not required.
|
||||
impl EventEmitter for () {
|
||||
fn emit_sent_event(
|
||||
_origin: Location,
|
||||
_destination: Location,
|
||||
_message: Option<Xcm<()>>,
|
||||
_message_id: XcmHash,
|
||||
) {
|
||||
}
|
||||
|
||||
fn emit_send_failure_event(
|
||||
_origin: Location,
|
||||
_destination: Location,
|
||||
_error: SendError,
|
||||
_message_id: XcmHash,
|
||||
) {
|
||||
}
|
||||
|
||||
fn emit_process_failure_event(_origin: Location, _error: XcmError, _message_id: XcmHash) {}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use xcm::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 during the message's 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 that can be reached with this type, but possibly could be with
|
||||
/// 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<InteriorLocation>,
|
||||
destination: &mut Option<InteriorLocation>,
|
||||
message: &mut Option<Xcm<()>>,
|
||||
) -> SendResult<Self::Ticket>;
|
||||
|
||||
/// 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<XcmHash, SendError>;
|
||||
}
|
||||
|
||||
#[impl_trait_for_tuples::impl_for_tuples(30)]
|
||||
impl ExportXcm for Tuple {
|
||||
for_tuples! { type Ticket = (#( Option<Tuple::Ticket> ),* ); }
|
||||
|
||||
fn validate(
|
||||
network: NetworkId,
|
||||
channel: u32,
|
||||
universal_source: &mut Option<InteriorLocation>,
|
||||
destination: &mut Option<InteriorLocation>,
|
||||
message: &mut Option<Xcm<()>>,
|
||||
) -> SendResult<Self::Ticket> {
|
||||
let mut maybe_cost: Option<Assets> = None;
|
||||
let one_ticket: Self::Ticket = (for_tuples! { #(
|
||||
if maybe_cost.is_some() {
|
||||
None
|
||||
} else {
|
||||
match Tuple::validate(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<XcmHash, SendError> {
|
||||
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 mutable references into `T::send_xcm`.
|
||||
pub fn validate_export<T: ExportXcm>(
|
||||
network: NetworkId,
|
||||
channel: u32,
|
||||
universal_source: InteriorLocation,
|
||||
dest: InteriorLocation,
|
||||
msg: Xcm<()>,
|
||||
) -> SendResult<T::Ticket> {
|
||||
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 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<T: ExportXcm>(
|
||||
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))
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use xcm::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 `InitiateTransfer` instruction is called.
|
||||
InitiateTransfer,
|
||||
/// 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) {}
|
||||
}
|
||||
|
||||
pub struct WaiveDeliveryFees;
|
||||
|
||||
impl FeeManager for WaiveDeliveryFees {
|
||||
fn is_waived(_: Option<&Location>, _: FeeReason) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn handle_fee(_: Assets, _: Option<&XcmContext>, _: FeeReason) {}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use 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<Asset, Location>` instead"]
|
||||
pub trait FilterAssetLocation {
|
||||
/// A filter to distinguish between asset/location pairs.
|
||||
fn contains(asset: &Asset, origin: &Location) -> bool;
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T: ContainsPair<Asset, Location>> FilterAssetLocation for T {
|
||||
fn contains(asset: &Asset, origin: &Location) -> bool {
|
||||
T::contains(asset, origin)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use xcm::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(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Various 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, WaiveDeliveryFees};
|
||||
mod filter_asset_location;
|
||||
#[allow(deprecated)]
|
||||
pub use filter_asset_location::FilterAssetLocation;
|
||||
mod token_matching;
|
||||
pub use token_matching::{
|
||||
Error, MatchesFungible, MatchesFungibles, MatchesInstance, 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, DenyExecution, Properties, ShouldExecute};
|
||||
mod transact_asset;
|
||||
pub use transact_asset::TransactAsset;
|
||||
mod hrmp;
|
||||
pub use hrmp::{
|
||||
HandleHrmpChannelAccepted, HandleHrmpChannelClosing, HandleHrmpNewChannelOpenRequest,
|
||||
};
|
||||
mod event_emitter;
|
||||
mod record_xcm;
|
||||
mod weight;
|
||||
pub use event_emitter::EventEmitter;
|
||||
|
||||
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, EventEmitter, ExportXcm, FeeManager, FeeReason, LockError,
|
||||
MatchesFungible, MatchesFungibles, MatchesInstance, MatchesNonFungible,
|
||||
MatchesNonFungibles, OnResponse, ProcessTransaction, ShouldExecute, TransactAsset,
|
||||
VersionChangeNotifier, WeightBounds, WeightTrader, WithOriginFilter,
|
||||
};
|
||||
#[allow(deprecated)]
|
||||
pub use super::{Identity, JustTry};
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{Junctions::Here, Xcm};
|
||||
use codec::{Decode, Encode};
|
||||
use core::{fmt::Debug, result};
|
||||
use frame_support::{pallet_prelude::Get, parameter_types};
|
||||
use sp_arithmetic::traits::Zero;
|
||||
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<BlockNumber> {
|
||||
/// 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<InteriorLocation>;
|
||||
|
||||
/// Attempt to create a new query ID and register it as a query that is yet to respond.
|
||||
fn new_query(
|
||||
responder: impl Into<Location>,
|
||||
timeout: Self::BlockNumber,
|
||||
match_querier: impl Into<Location>,
|
||||
) -> 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<Location>,
|
||||
timeout: Self::BlockNumber,
|
||||
) -> result::Result<QueryId, Self::Error>;
|
||||
|
||||
/// Attempt to remove and return the response of query with ID `query_id`.
|
||||
fn take_response(id: QueryId) -> QueryResponseStatus<Self::BlockNumber>;
|
||||
|
||||
/// 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<Self::BlockNumber> {
|
||||
QueryResponseStatus::NotFound
|
||||
}
|
||||
fn new_query(
|
||||
_responder: impl Into<Location>,
|
||||
_timeout: Self::BlockNumber,
|
||||
_match_querier: impl Into<Location>,
|
||||
) -> QueryId {
|
||||
0u64
|
||||
}
|
||||
|
||||
fn report_outcome(
|
||||
_message: &mut Xcm<()>,
|
||||
_responder: impl Into<Location>,
|
||||
_timeout: Self::BlockNumber,
|
||||
) -> Result<QueryId, Self::Error> {
|
||||
Err(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn expect_response(_id: QueryId, _response: crate::Response) {}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use xcm::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: F) -> Result<(), XcmError>
|
||||
where
|
||||
F: FnOnce() -> Result<(), XcmError>;
|
||||
}
|
||||
|
||||
impl ProcessTransaction for () {
|
||||
const IS_TRANSACTIONAL: bool = false;
|
||||
fn process<F>(f: F) -> Result<(), XcmError>
|
||||
where
|
||||
F: FnOnce() -> Result<(), XcmError>,
|
||||
{
|
||||
f()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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<Xcm<()>>;
|
||||
/// Record `xcm`.
|
||||
fn record(xcm: Xcm<()>);
|
||||
}
|
||||
|
||||
impl RecordXcm for () {
|
||||
fn should_record() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn set_record_xcm(_: bool) {}
|
||||
|
||||
fn recorded_xcm() -> Option<Xcm<()>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn record(_: Xcm<()>) {}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use core::result::Result;
|
||||
use frame_support::traits::ProcessMessageError;
|
||||
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<XcmHash>,
|
||||
}
|
||||
|
||||
/// 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<RuntimeCall>(
|
||||
origin: &Location,
|
||||
instructions: &mut [Instruction<RuntimeCall>],
|
||||
max_weight: Weight,
|
||||
properties: &mut Properties,
|
||||
) -> Result<(), ProcessMessageError>;
|
||||
}
|
||||
|
||||
#[impl_trait_for_tuples::impl_for_tuples(30)]
|
||||
impl ShouldExecute for Tuple {
|
||||
fn should_execute<RuntimeCall>(
|
||||
origin: &Location,
|
||||
instructions: &mut [Instruction<RuntimeCall>],
|
||||
max_weight: Weight,
|
||||
properties: &mut Properties,
|
||||
) -> Result<(), ProcessMessageError> {
|
||||
for_tuples!( #(
|
||||
let barrier = core::any::type_name::<Tuple>();
|
||||
match Tuple::should_execute(origin, instructions, max_weight, properties) {
|
||||
Ok(()) => {
|
||||
tracing::trace!(
|
||||
target: "xcm::should_execute",
|
||||
?origin,
|
||||
?instructions,
|
||||
?max_weight,
|
||||
?properties,
|
||||
%barrier,
|
||||
"pass barrier",
|
||||
);
|
||||
return Ok(())
|
||||
},
|
||||
Err(error) => {
|
||||
tracing::trace!(
|
||||
target: "xcm::should_execute",
|
||||
?origin,
|
||||
?instructions,
|
||||
?max_weight,
|
||||
?properties,
|
||||
?error,
|
||||
%barrier,
|
||||
"did not pass barrier",
|
||||
);
|
||||
},
|
||||
}
|
||||
)* );
|
||||
|
||||
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<Call>(
|
||||
origin: &Location,
|
||||
instructions: &mut [Instruction<Call>],
|
||||
max_weight: Weight,
|
||||
properties: &mut Properties,
|
||||
) -> bool;
|
||||
}
|
||||
|
||||
#[impl_trait_for_tuples::impl_for_tuples(30)]
|
||||
impl CheckSuspension for Tuple {
|
||||
fn is_suspended<Call>(
|
||||
origin: &Location,
|
||||
instruction: &mut [Instruction<Call>],
|
||||
max_weight: Weight,
|
||||
properties: &mut Properties,
|
||||
) -> bool {
|
||||
for_tuples!( #(
|
||||
if Tuple::is_suspended(origin, instruction, max_weight, properties) {
|
||||
return true
|
||||
}
|
||||
)* );
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait to determine whether the execution engine should not execute a given XCM.
|
||||
///
|
||||
/// Can be amalgamated into a tuple to have multiple traits. If any of the tuple elements returns
|
||||
/// `Err(ProcessMessageError)`, the execution stops. Else, `Ok(())` is returned if all elements
|
||||
/// accept the message.
|
||||
pub trait DenyExecution {
|
||||
/// Returns `Ok(())` if there is no reason to deny execution,
|
||||
/// while `Err(ProcessMessageError)` indicates there is a reason to deny execution.
|
||||
///
|
||||
/// - `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 deny_execution<RuntimeCall>(
|
||||
origin: &Location,
|
||||
instructions: &mut [Instruction<RuntimeCall>],
|
||||
max_weight: Weight,
|
||||
properties: &mut Properties,
|
||||
) -> Result<(), ProcessMessageError>;
|
||||
}
|
||||
|
||||
#[impl_trait_for_tuples::impl_for_tuples(10)]
|
||||
impl DenyExecution for Tuple {
|
||||
fn deny_execution<RuntimeCall>(
|
||||
origin: &Location,
|
||||
instructions: &mut [Instruction<RuntimeCall>],
|
||||
max_weight: Weight,
|
||||
properties: &mut Properties,
|
||||
) -> Result<(), ProcessMessageError> {
|
||||
for_tuples!( #(
|
||||
let barrier = core::any::type_name::<Tuple>();
|
||||
match Tuple::deny_execution(origin, instructions, max_weight, properties) {
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
target: "xcm::deny_execution",
|
||||
?origin,
|
||||
?instructions,
|
||||
?max_weight,
|
||||
?properties,
|
||||
?error,
|
||||
%barrier,
|
||||
"did not pass barrier",
|
||||
);
|
||||
return Err(error);
|
||||
},
|
||||
Ok(()) => {
|
||||
tracing::trace!(
|
||||
target: "xcm::deny_execution",
|
||||
?origin,
|
||||
?instructions,
|
||||
?max_weight,
|
||||
?properties,
|
||||
%barrier,
|
||||
"pass barrier",
|
||||
);
|
||||
},
|
||||
}
|
||||
)* );
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use core::result;
|
||||
use xcm::latest::prelude::*;
|
||||
|
||||
pub trait MatchesFungible<Balance> {
|
||||
fn matches_fungible(a: &Asset) -> Option<Balance>;
|
||||
}
|
||||
|
||||
#[impl_trait_for_tuples::impl_for_tuples(30)]
|
||||
impl<Balance> MatchesFungible<Balance> for Tuple {
|
||||
fn matches_fungible(a: &Asset) -> Option<Balance> {
|
||||
for_tuples!( #(
|
||||
match Tuple::matches_fungible(a) { o @ Some(_) => return o, _ => () }
|
||||
)* );
|
||||
tracing::trace!(target: "xcm::matches_fungible", asset = ?a, "did not match fungible asset");
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MatchesNonFungible<Instance> {
|
||||
fn matches_nonfungible(a: &Asset) -> Option<Instance>;
|
||||
}
|
||||
|
||||
#[impl_trait_for_tuples::impl_for_tuples(30)]
|
||||
impl<Instance> MatchesNonFungible<Instance> for Tuple {
|
||||
fn matches_nonfungible(a: &Asset) -> Option<Instance> {
|
||||
for_tuples!( #(
|
||||
match Tuple::matches_nonfungible(a) { o @ Some(_) => return o, _ => () }
|
||||
)* );
|
||||
tracing::trace!(target: "xcm::matches_non_fungible", asset = ?a, "did not match non-fungible asset");
|
||||
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<Error> 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<AssetId, Balance> {
|
||||
fn matches_fungibles(a: &Asset) -> result::Result<(AssetId, Balance), Error>;
|
||||
}
|
||||
|
||||
#[impl_trait_for_tuples::impl_for_tuples(30)]
|
||||
impl<AssetId, Balance> MatchesFungibles<AssetId, Balance> for Tuple {
|
||||
fn matches_fungibles(a: &Asset) -> result::Result<(AssetId, Balance), Error> {
|
||||
for_tuples!( #(
|
||||
match Tuple::matches_fungibles(a) { o @ Ok(_) => return o, _ => () }
|
||||
)* );
|
||||
tracing::trace!(target: "xcm::matches_fungibles", asset = ?a, "did not match fungibles asset");
|
||||
Err(Error::AssetNotHandled)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MatchesNonFungibles<AssetId, Instance> {
|
||||
fn matches_nonfungibles(a: &Asset) -> result::Result<(AssetId, Instance), Error>;
|
||||
}
|
||||
|
||||
#[impl_trait_for_tuples::impl_for_tuples(30)]
|
||||
impl<AssetId, Instance> MatchesNonFungibles<AssetId, Instance> for Tuple {
|
||||
fn matches_nonfungibles(a: &Asset) -> result::Result<(AssetId, Instance), Error> {
|
||||
for_tuples!( #(
|
||||
match Tuple::matches_nonfungibles(a) { o @ Ok(_) => return o, _ => () }
|
||||
)* );
|
||||
tracing::trace!(target: "xcm::matches_non_fungibles", asset = ?a, "did not match non-fungibles asset");
|
||||
Err(Error::AssetNotHandled)
|
||||
}
|
||||
}
|
||||
|
||||
/// Unique instances matcher trait.
|
||||
///
|
||||
/// The `Id` type should be defined in such a way so that its value can unambigiously identify an
|
||||
/// instance. I.e., if instances are grouped (e.g., as tokens in an NFT collection), the `Id` should
|
||||
/// contain both the group ID and the item group-local ID.
|
||||
///
|
||||
/// This unified interface allows us to avoid duplicating the XCM adapters for non-grouped and
|
||||
/// grouped instances.
|
||||
///
|
||||
/// NOTE: The trait implementors should follow the convention of identifying the collection-less
|
||||
/// NFTs by an XCM `Asset` of the form `{ asset_id: NFT_ID, fun:
|
||||
/// Fungibility::NonFungible(AssetInstance::Undefined) }`.
|
||||
pub trait MatchesInstance<Id> {
|
||||
fn matches_instance(a: &Asset) -> result::Result<Id, Error>;
|
||||
}
|
||||
|
||||
#[impl_trait_for_tuples::impl_for_tuples(30)]
|
||||
impl<Id> MatchesInstance<Id> for Tuple {
|
||||
fn matches_instance(a: &Asset) -> result::Result<Id, Error> {
|
||||
for_tuples!( #(
|
||||
match Tuple::matches_instance(a) { o @ Ok(_) => return o, _ => () }
|
||||
)* );
|
||||
tracing::trace!(target: "xcm::matches_instance", asset = ?a, "did not match an asset instance");
|
||||
Err(Error::AssetNotHandled)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,551 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{AssetsInHolding, Weight};
|
||||
use core::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::Teyrchain]`. 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)
|
||||
}
|
||||
|
||||
/// Identical to `deposit_asset` but returning the surplus, if any.
|
||||
///
|
||||
/// Return the difference between the worst-case weight and the actual weight consumed.
|
||||
/// This can be zero most of the time unless there's some metering involved.
|
||||
fn deposit_asset_with_surplus(
|
||||
what: &Asset,
|
||||
who: &Location,
|
||||
context: Option<&XcmContext>,
|
||||
) -> Result<Weight, XcmError> {
|
||||
Self::deposit_asset(what, who, context).map(|()| Weight::zero())
|
||||
}
|
||||
|
||||
/// 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<AssetsInHolding, XcmError> {
|
||||
Err(XcmError::Unimplemented)
|
||||
}
|
||||
|
||||
/// Withdraw assets returning surplus.
|
||||
///
|
||||
/// The surplus is the difference between the worst-case weight and the actual weight consumed.
|
||||
/// This can be zero most of the time unless there's some metering involved.
|
||||
fn withdraw_asset_with_surplus(
|
||||
what: &Asset,
|
||||
who: &Location,
|
||||
maybe_context: Option<&XcmContext>,
|
||||
) -> Result<(AssetsInHolding, Weight), XcmError> {
|
||||
Self::withdraw_asset(what, who, maybe_context).map(|assets| (assets, Weight::zero()))
|
||||
}
|
||||
|
||||
/// 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<AssetsInHolding, XcmError> {
|
||||
Err(XcmError::Unimplemented)
|
||||
}
|
||||
|
||||
/// Identical to `internal_transfer_asset` but returning the surplus, if any.
|
||||
///
|
||||
/// The surplus is the difference between the worst-case weight and the actual
|
||||
/// consumed weight.
|
||||
/// This can be zero usually if there's no metering involved.
|
||||
fn internal_transfer_asset_with_surplus(
|
||||
asset: &Asset,
|
||||
from: &Location,
|
||||
to: &Location,
|
||||
context: &XcmContext,
|
||||
) -> Result<(AssetsInHolding, Weight), XcmError> {
|
||||
Self::internal_transfer_asset(asset, from, to, context)
|
||||
.map(|assets| (assets, Weight::zero()))
|
||||
}
|
||||
|
||||
/// 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<AssetsInHolding, XcmError> {
|
||||
match Self::internal_transfer_asset(asset, from, to, context) {
|
||||
Err(XcmError::AssetNotFound | XcmError::Unimplemented) => {
|
||||
let assets = Self::withdraw_asset(asset, from, Some(context))?;
|
||||
Self::deposit_asset(asset, to, Some(context))?;
|
||||
Ok(assets)
|
||||
},
|
||||
result => result,
|
||||
}
|
||||
}
|
||||
|
||||
/// Identical to `transfer_asset` but returning the surplus, if any.
|
||||
///
|
||||
/// The surplus is the difference between the worst-case weight and the actual
|
||||
/// consumed weight.
|
||||
/// This can be zero usually if there's no metering involved.
|
||||
fn transfer_asset_with_surplus(
|
||||
asset: &Asset,
|
||||
from: &Location,
|
||||
to: &Location,
|
||||
context: &XcmContext,
|
||||
) -> Result<(AssetsInHolding, Weight), XcmError> {
|
||||
match Self::internal_transfer_asset_with_surplus(asset, from, to, context) {
|
||||
Err(XcmError::AssetNotFound | XcmError::Unimplemented) => {
|
||||
let (assets, withdraw_surplus) =
|
||||
Self::withdraw_asset_with_surplus(asset, from, Some(context))?;
|
||||
let deposit_surplus = Self::deposit_asset_with_surplus(asset, to, Some(context))?;
|
||||
let total_surplus = withdraw_surplus.saturating_add(deposit_surplus);
|
||||
Ok((assets, total_surplus))
|
||||
},
|
||||
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,
|
||||
}
|
||||
)* );
|
||||
tracing::trace!(
|
||||
target: "xcm::TransactAsset::can_check_in",
|
||||
?what,
|
||||
?origin,
|
||||
?context,
|
||||
"asset not found",
|
||||
);
|
||||
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,
|
||||
}
|
||||
)* );
|
||||
tracing::trace!(
|
||||
target: "xcm::TransactAsset::can_check_out",
|
||||
?what,
|
||||
?dest,
|
||||
?context,
|
||||
"asset not found",
|
||||
);
|
||||
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,
|
||||
}
|
||||
)* );
|
||||
tracing::trace!(
|
||||
target: "xcm::TransactAsset::deposit_asset",
|
||||
?what,
|
||||
?who,
|
||||
?context,
|
||||
"did not deposit asset",
|
||||
);
|
||||
Err(XcmError::AssetNotFound)
|
||||
}
|
||||
|
||||
fn deposit_asset_with_surplus(
|
||||
what: &Asset,
|
||||
who: &Location,
|
||||
context: Option<&XcmContext>,
|
||||
) -> Result<Weight, XcmError> {
|
||||
for_tuples!( #(
|
||||
match Tuple::deposit_asset_with_surplus(what, who, context) {
|
||||
Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (),
|
||||
r => return r,
|
||||
}
|
||||
)* );
|
||||
tracing::trace!(
|
||||
target: "xcm::TransactAsset::deposit_asset",
|
||||
?what,
|
||||
?who,
|
||||
?context,
|
||||
"did not deposit asset",
|
||||
);
|
||||
Err(XcmError::AssetNotFound)
|
||||
}
|
||||
|
||||
fn withdraw_asset(
|
||||
what: &Asset,
|
||||
who: &Location,
|
||||
maybe_context: Option<&XcmContext>,
|
||||
) -> Result<AssetsInHolding, XcmError> {
|
||||
for_tuples!( #(
|
||||
match Tuple::withdraw_asset(what, who, maybe_context) {
|
||||
Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (),
|
||||
r => return r,
|
||||
}
|
||||
)* );
|
||||
tracing::trace!(
|
||||
target: "xcm::TransactAsset::withdraw_asset",
|
||||
?what,
|
||||
?who,
|
||||
?maybe_context,
|
||||
"did not withdraw asset",
|
||||
);
|
||||
Err(XcmError::AssetNotFound)
|
||||
}
|
||||
|
||||
fn withdraw_asset_with_surplus(
|
||||
what: &Asset,
|
||||
who: &Location,
|
||||
maybe_context: Option<&XcmContext>,
|
||||
) -> Result<(AssetsInHolding, Weight), XcmError> {
|
||||
for_tuples!( #(
|
||||
match Tuple::withdraw_asset_with_surplus(what, who, maybe_context) {
|
||||
Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (),
|
||||
r => return r,
|
||||
}
|
||||
)* );
|
||||
tracing::trace!(
|
||||
target: "xcm::TransactAsset::withdraw_asset",
|
||||
?what,
|
||||
?who,
|
||||
?maybe_context,
|
||||
"did not withdraw asset",
|
||||
);
|
||||
Err(XcmError::AssetNotFound)
|
||||
}
|
||||
|
||||
fn internal_transfer_asset(
|
||||
what: &Asset,
|
||||
from: &Location,
|
||||
to: &Location,
|
||||
context: &XcmContext,
|
||||
) -> Result<AssetsInHolding, XcmError> {
|
||||
for_tuples!( #(
|
||||
match Tuple::internal_transfer_asset(what, from, to, context) {
|
||||
Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (),
|
||||
r => return r,
|
||||
}
|
||||
)* );
|
||||
tracing::trace!(
|
||||
target: "xcm::TransactAsset::internal_transfer_asset",
|
||||
?what,
|
||||
?from,
|
||||
?to,
|
||||
?context,
|
||||
"did not transfer asset",
|
||||
);
|
||||
Err(XcmError::AssetNotFound)
|
||||
}
|
||||
|
||||
fn internal_transfer_asset_with_surplus(
|
||||
what: &Asset,
|
||||
from: &Location,
|
||||
to: &Location,
|
||||
context: &XcmContext,
|
||||
) -> Result<(AssetsInHolding, Weight), XcmError> {
|
||||
for_tuples!( #(
|
||||
match Tuple::internal_transfer_asset_with_surplus(what, from, to, context) {
|
||||
Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (),
|
||||
r => return r,
|
||||
}
|
||||
)* );
|
||||
tracing::trace!(
|
||||
target: "xcm::TransactAsset::internal_transfer_asset",
|
||||
?what,
|
||||
?from,
|
||||
?to,
|
||||
?context,
|
||||
"did not transfer asset",
|
||||
);
|
||||
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<AssetsInHolding, XcmError> {
|
||||
Err(XcmError::AssetNotFound)
|
||||
}
|
||||
|
||||
fn internal_transfer_asset(
|
||||
_what: &Asset,
|
||||
_from: &Location,
|
||||
_to: &Location,
|
||||
_context: &XcmContext,
|
||||
) -> Result<AssetsInHolding, XcmError> {
|
||||
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<AssetsInHolding, XcmError> {
|
||||
Err(XcmError::Overflow)
|
||||
}
|
||||
|
||||
fn internal_transfer_asset(
|
||||
_what: &Asset,
|
||||
_from: &Location,
|
||||
_to: &Location,
|
||||
_context: &XcmContext,
|
||||
) -> Result<AssetsInHolding, XcmError> {
|
||||
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<AssetsInHolding, XcmError> {
|
||||
Ok(AssetsInHolding::default())
|
||||
}
|
||||
|
||||
fn internal_transfer_asset(
|
||||
_what: &Asset,
|
||||
_from: &Location,
|
||||
_to: &Location,
|
||||
_context: &XcmContext,
|
||||
) -> Result<AssetsInHolding, XcmError> {
|
||||
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(()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::AssetsInHolding;
|
||||
use core::result::Result;
|
||||
use xcm::latest::{prelude::*, Weight};
|
||||
|
||||
/// Determine the weight of an XCM message.
|
||||
pub trait WeightBounds<RuntimeCall> {
|
||||
/// Return the maximum amount of weight that an attempted execution of this message could
|
||||
/// consume.
|
||||
fn weight(
|
||||
message: &mut Xcm<RuntimeCall>,
|
||||
weight_limit: Weight,
|
||||
) -> Result<Weight, InstructionError>;
|
||||
|
||||
/// Return the maximum amount of weight that an attempted execution of this instruction could
|
||||
/// consume.
|
||||
fn instr_weight(instruction: &mut Instruction<RuntimeCall>) -> Result<Weight, XcmError>;
|
||||
}
|
||||
|
||||
/// 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<AssetsInHolding, XcmError>;
|
||||
|
||||
/// 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<Asset> {
|
||||
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<AssetsInHolding, XcmError> {
|
||||
let mut too_expensive_error_found = false;
|
||||
let mut last_error = None;
|
||||
for_tuples!( #(
|
||||
let weight_trader = core::any::type_name::<Tuple>();
|
||||
|
||||
match Tuple.buy_weight(weight, payment.clone(), context) {
|
||||
Ok(assets) => {
|
||||
tracing::trace!(
|
||||
target: "xcm::buy_weight",
|
||||
%weight_trader,
|
||||
"Buy weight succeeded",
|
||||
);
|
||||
|
||||
return Ok(assets)
|
||||
},
|
||||
Err(error) => {
|
||||
if let XcmError::TooExpensive = error {
|
||||
too_expensive_error_found = true;
|
||||
}
|
||||
last_error = Some(error);
|
||||
|
||||
tracing::trace!(
|
||||
target: "xcm::buy_weight",
|
||||
?error,
|
||||
%weight_trader,
|
||||
"Weight trader failed",
|
||||
);
|
||||
}
|
||||
}
|
||||
)* );
|
||||
|
||||
tracing::trace!(
|
||||
target: "xcm::buy_weight",
|
||||
"Buy weight failed",
|
||||
);
|
||||
|
||||
// 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<Asset> {
|
||||
for_tuples!( #(
|
||||
if let Some(asset) = Tuple.refund_weight(weight, context) {
|
||||
return Some(asset);
|
||||
}
|
||||
)* );
|
||||
None
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user