feat: initialize Kurdistan SDK - independent fork of Polkadot SDK

This commit is contained in:
2025-12-13 15:44:15 +03:00
commit e4778b4576
6838 changed files with 1847450 additions and 0 deletions
+868
View File
@@ -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)]);
}
}
+137
View File
@@ -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");
}
+331
View File
@@ -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 chains 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 = ();
}
+26
View File
@@ -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
}
}