Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Reanchor should return canonical location #4470

Merged
merged 13 commits into from
Dec 14, 2021
3 changes: 3 additions & 0 deletions runtime/test-runtime/src/xcm_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ impl InvertLocation for InvertNothing {
fn invert_location(_: &MultiLocation) -> sp_std::result::Result<MultiLocation, ()> {
Ok(Here.into())
}
fn ancestry() -> MultiLocation {
Here.into()
}
}

pub struct XcmConfig;
Expand Down
10 changes: 4 additions & 6 deletions xcm/pallet-xcm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -777,13 +777,12 @@ pub mod pallet {
let value = (origin_location, assets.drain());
ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
let (origin_location, assets) = value;
let inv_dest = T::LocationInverter::invert_location(&dest)
.map_err(|()| Error::<T>::DestinationNotInvertible)?;
let ancestry = T::LocationInverter::ancestry();
let fees = assets
.get(fee_asset_item as usize)
.ok_or(Error::<T>::Empty)?
.clone()
.reanchored(&inv_dest)
.reanchored(&dest, &ancestry)
.map_err(|_| Error::<T>::CannotReanchor)?;
let max_assets = assets.len() as u32;
let assets: MultiAssets = assets.into();
Expand Down Expand Up @@ -835,13 +834,12 @@ pub mod pallet {
let value = (origin_location, assets.drain());
ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
let (origin_location, assets) = value;
let inv_dest = T::LocationInverter::invert_location(&dest)
.map_err(|()| Error::<T>::DestinationNotInvertible)?;
let ancestry = T::LocationInverter::ancestry();
let fees = assets
.get(fee_asset_item as usize)
.ok_or(Error::<T>::Empty)?
.clone()
.reanchored(&inv_dest)
.reanchored(&dest, &ancestry)
.map_err(|_| Error::<T>::CannotReanchor)?;
let max_assets = assets.len() as u32;
let assets: MultiAssets = assets.into();
Expand Down
51 changes: 38 additions & 13 deletions xcm/src/v1/multiasset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,22 @@ impl From<Vec<u8>> for AssetId {

impl AssetId {
/// Prepend a `MultiLocation` to a concrete asset, giving it a new root location.
pub fn reanchor(&mut self, prepend: &MultiLocation) -> Result<(), ()> {
pub fn prepend_with(&mut self, prepend: &MultiLocation) -> Result<(), ()> {
if let AssetId::Concrete(ref mut l) = self {
l.prepend_with(prepend.clone()).map_err(|_| ())?;
}
Ok(())
}

/// Mutate the asset to represent the same value from the perspective of a new `target`
/// location. The local chain's location is provided in `ancestry`.
pub fn reanchor(&mut self, target: &MultiLocation, ancestry: &MultiLocation) -> Result<(), ()> {
if let AssetId::Concrete(ref mut l) = self {
l.reanchor(target, ancestry)?;
}
Ok(())
}

/// Use the value of `self` along with a `fun` fungibility specifier to create the corresponding `MultiAsset` value.
pub fn into_multiasset(self, fun: Fungibility) -> MultiAsset {
MultiAsset { fun, id: self }
Expand Down Expand Up @@ -203,13 +212,24 @@ impl MultiAsset {
}

/// Prepend a `MultiLocation` to a concrete asset, giving it a new root location.
pub fn reanchor(&mut self, prepend: &MultiLocation) -> Result<(), ()> {
self.id.reanchor(prepend)
pub fn prepend_with(&mut self, prepend: &MultiLocation) -> Result<(), ()> {
self.id.prepend_with(prepend)
}

/// Prepend a `MultiLocation` to a concrete asset, giving it a new root location.
pub fn reanchored(mut self, prepend: &MultiLocation) -> Result<Self, ()> {
self.reanchor(prepend)?;
/// Mutate the location of the asset identifier if concrete, giving it the same location
/// relative to a `target` context. The local context is provided as `ancestry`.
pub fn reanchor(&mut self, target: &MultiLocation, ancestry: &MultiLocation) -> Result<(), ()> {
self.id.reanchor(target, ancestry)
}

/// Mutate the location of the asset identifier if concrete, giving it the same location
/// relative to a `target` context. The local context is provided as `ancestry`.
pub fn reanchored(
mut self,
target: &MultiLocation,
ancestry: &MultiLocation,
) -> Result<Self, ()> {
self.id.reanchor(target, ancestry)?;
Ok(self)
}

Expand Down Expand Up @@ -413,8 +433,13 @@ impl MultiAssets {
}

/// Prepend a `MultiLocation` to any concrete asset items, giving it a new root location.
pub fn reanchor(&mut self, prepend: &MultiLocation) -> Result<(), ()> {
self.0.iter_mut().try_for_each(|i| i.reanchor(prepend))
pub fn prepend_with(&mut self, prefix: &MultiLocation) -> Result<(), ()> {
self.0.iter_mut().try_for_each(|i| i.prepend_with(prefix))
}

/// Prepend a `MultiLocation` to any concrete asset items, giving it a new root location.
pub fn reanchor(&mut self, target: &MultiLocation, ancestry: &MultiLocation) -> Result<(), ()> {
self.0.iter_mut().try_for_each(|i| i.reanchor(target, ancestry))
}

/// Return a reference to an item at a specific index or `None` if it doesn't exist.
Expand Down Expand Up @@ -483,10 +508,10 @@ impl WildMultiAsset {
}

/// Prepend a `MultiLocation` to any concrete asset components, giving it a new root location.
pub fn reanchor(&mut self, prepend: &MultiLocation) -> Result<(), ()> {
pub fn reanchor(&mut self, target: &MultiLocation, ancestry: &MultiLocation) -> Result<(), ()> {
use WildMultiAsset::*;
match self {
AllOf { ref mut id, .. } => id.reanchor(prepend).map_err(|_| ()),
AllOf { ref mut id, .. } => id.reanchor(target, ancestry).map_err(|_| ()),
All => Ok(()),
}
}
Expand Down Expand Up @@ -545,10 +570,10 @@ impl MultiAssetFilter {
}

/// Prepend a `MultiLocation` to any concrete asset components, giving it a new root location.
pub fn reanchor(&mut self, prepend: &MultiLocation) -> Result<(), ()> {
pub fn reanchor(&mut self, target: &MultiLocation, ancestry: &MultiLocation) -> Result<(), ()> {
match self {
MultiAssetFilter::Definite(ref mut assets) => assets.reanchor(prepend),
MultiAssetFilter::Wild(ref mut wild) => wild.reanchor(prepend),
MultiAssetFilter::Definite(ref mut assets) => assets.reanchor(target, ancestry),
MultiAssetFilter::Wild(ref mut wild) => wild.reanchor(target, ancestry),
}
}
}
Expand Down
129 changes: 128 additions & 1 deletion xcm/src/v1/multilocation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,60 @@ impl MultiLocation {
}
Ok(())
}

/// Mutate `self` so that it represents the same location from the point of view of `target`.
/// The context of `self` is provided as `ancestry`.
///
/// Does not modify `self` in case of overflow.
pub fn reanchor(&mut self, target: &MultiLocation, ancestry: &MultiLocation) -> Result<(), ()> {
// TODO: https://github.com/paritytech/polkadot/issues/4489 Optimize this.

// 1. Use our `ancestry` to figure out how the `target` would address us.
let inverted_target = ancestry.inverted(target)?;

// 2. Prepend `inverted_target` to `self` to get self's location from the perspective of
// `target`.
self.prepend_with(inverted_target).map_err(|_| ())?;

// 3. Given that we know some of `target` ancestry, ensure that any parents in `self` are
// strictly needed.
self.simplify(target.interior());

Ok(())
}

/// Treating `self` as a context, determine how it would be referenced by a `target` location.
pub fn inverted(&self, target: &MultiLocation) -> Result<MultiLocation, ()> {
gavofyork marked this conversation as resolved.
Show resolved Hide resolved
use Junction::OnlyChild;
let mut ancestry = self.clone();
let mut junctions = Junctions::Here;
for _ in 0..target.parent_count() {
junctions = junctions
.pushed_front_with(ancestry.interior.take_last().unwrap_or(OnlyChild))
gavofyork marked this conversation as resolved.
Show resolved Hide resolved
.map_err(|_| ())?;
}
let parents = target.interior().len() as u8;
Ok(MultiLocation::new(parents, junctions))
}

/// Remove any unneeded parents/junctions in `self` based on the given context it will be
/// interpreted in.
pub fn simplify(&mut self, context: &Junctions) {
if context.len() < self.parents as usize {
// Not enough context
return
}
while self.parents > 0 {
let maybe = context.at(context.len() - (self.parents as usize));
match (self.interior.first(), maybe) {
(Some(i), Some(j)) if i == j => {
self.interior.take_first();
self.parents -= 1;
},
_ => break,
}
}
}
}

/// A unit struct which can be converted into a `MultiLocation` of `parents` value 1.
Expand Down Expand Up @@ -773,9 +827,82 @@ impl TryFrom<MultiLocation> for Junctions {
#[cfg(test)]
mod tests {
use super::{Ancestor, AncestorThen, Junctions::*, MultiLocation, Parent, ParentThen};
use crate::opaque::v1::{Junction::*, NetworkId::Any};
use crate::opaque::v1::{Junction::*, NetworkId::*};
use parity_scale_codec::{Decode, Encode};

#[test]
fn inverted_works() {
let ancestry: MultiLocation = (Parachain(1000), PalletInstance(42)).into();
let target = (Parent, PalletInstance(69)).into();
let expected = (Parent, PalletInstance(42)).into();
let inverted = ancestry.inverted(&target).unwrap();
assert_eq!(inverted, expected);
gavofyork marked this conversation as resolved.
Show resolved Hide resolved

let ancestry: MultiLocation = (Parachain(1000), PalletInstance(42), GeneralIndex(1)).into();
let target = (Parent, Parent, PalletInstance(69), GeneralIndex(2)).into();
let expected = (Parent, Parent, PalletInstance(42), GeneralIndex(1)).into();
let inverted = ancestry.inverted(&target).unwrap();
assert_eq!(inverted, expected);
}

#[test]
fn simplify_basic_works() {
let mut location: MultiLocation =
(Parent, Parent, Parachain(1000), PalletInstance(42), GeneralIndex(69)).into();
let context = X2(Parachain(1000), PalletInstance(42));
let expected = GeneralIndex(69).into();
location.simplify(&context);
assert_eq!(location, expected);

let mut location: MultiLocation = (Parent, PalletInstance(42), GeneralIndex(69)).into();
let context = X1(PalletInstance(42));
let expected = GeneralIndex(69).into();
location.simplify(&context);
assert_eq!(location, expected);

let mut location: MultiLocation = (Parent, PalletInstance(42), GeneralIndex(69)).into();
let context = X2(Parachain(1000), PalletInstance(42));
let expected = GeneralIndex(69).into();
location.simplify(&context);
assert_eq!(location, expected);

let mut location: MultiLocation =
(Parent, Parent, Parachain(1000), PalletInstance(42), GeneralIndex(69)).into();
let context = X3(OnlyChild, Parachain(1000), PalletInstance(42));
let expected = GeneralIndex(69).into();
location.simplify(&context);
assert_eq!(location, expected);
Copy link
Member

@shawntabrizi shawntabrizi Dec 13, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this pass? Because it does.

		let mut location: MultiLocation = (Parent, PalletInstance(42), GeneralIndex(69)).into();
		let context = X2(PalletInstance(30), PalletInstance(42)); // <-- change here
		let expected = GeneralIndex(69).into();
		location.simplify(&context);
		assert_eq!(location, expected);

I am having a really hard time understanding this function's behavior.

Copy link
Member Author

@gavofyork gavofyork Dec 13, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. All locations within a consensus system must be taken to be within some local context (aka the consensus system's ancestry). The ancestry is essentially just the consensus system's location from the point of view of the most distant ancestor consensus system under which the local consensus sits. For the Polkadot Relay, this is conventionally empty (as it is its own universe) but for a parachain which knows that it exists within a broader consensus system it would be the Parachain junction by which the parent Relay-chain refers to it.

Local locations which begin with Parent should be "escaping" outside of the local consensus system (otherwise there is no need to begin with Parent), however if after the Parent node(s), the rest of the location is such that it descends back into the local consensus then this becomes a non-canonical location. Basically, the Parent items and their corresponding junctions are superfluous because they're already implied by the ancestry.

In order to normalise it, we need to detect and remove this case.

In this case the context is (PalletInstance(30), PalletInstance(42)
and the location is Parent, PalletInstance(42), GeneralIndex(69). The location is therefore non-canonical, because the Parent is being used to descend outside of the local consensus and beyond PalletInstance(42) (the last item of context) and then re-descend immediately back into the very same PalletInstance(42). Therefore this location is exactly equivalent to the simpler (and shorter) GeneralIndex(69).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I understand now. Basically, we use context and the front of the multilocation and cancel out all "redundant" data, and normalize it.

The ancestry is essentially just the consensus system's location from the point of view of the most distant ancestor consensus system under which the local consensus sits.

This was very key. Thanks.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 just some note here
image

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where I can read about stuff happening here? except code:) I read all papers on xcm I found and ported acala tests. so found reanchor stuff. do not get what it is. actually security of xcm is very unclear.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dzmitry-lahoda Substrate/Polkadot doesn't have a concept of CurrencyId, and it looks to me to be an Acala/Karura concept. Would you kindly raise this issue in their repo instead?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:) Substrate/Polkadot has the location and general key concept. why would somebody trust these are genuine? Asset with location FooBar. Parachain X xcmed FooBar from X to Z. Why would we trust that FooBar was not just minted?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are not understanding that every consensus system (or blockchain in layman terms) is responsible for interpreting incoming XCM messages correctly. As such, how a chain decides to interpret GeneralKey or GeneralIndex is not up to us to decide.

Thus, would you please raise this issue to Acala/Karura?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still not clear. Kusama can have KSM. Kusama can send (if we call its XCM pallet) to send KSM to paracahain. Can someone send KSM back to Kusama? Does Kusama tracks all places it send KSM to be equal? I mean DOT. Polkadot is relay chain. Does DOT sent via XCM has any security/alignment after it was send from Polkadot to Parachain?

So right now I speak about Relay and some abstract Parachain. And some 2 abstract parachains having DOT/KSM. How these could interpret correctly message with asset (1, Here) origin?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MultiLocations are relative paths, as explained in Gav's blogpost. A parachain of Kusama/Polkadot will identify KSM/DOT as (1, Here). When they send (1, Here) to the relay chain, their local XCM executor will reanchor the location to be (0, Here).

As I mentioned in my first message, reanchoring happens automatically behind the scenes and doesn't require any user input. If you are wondering about how assets are transferred between parachains, I have an answer on Stack Exchange that describes in detail how it is done, and how the trust issues are resolved.

}

#[test]
fn simplify_incompatible_location_fails() {
let mut location: MultiLocation =
(Parent, Parent, Parachain(1000), PalletInstance(42), GeneralIndex(69)).into();
let context = X3(Parachain(1000), PalletInstance(42), GeneralIndex(42));
let expected =
(Parent, Parent, Parachain(1000), PalletInstance(42), GeneralIndex(69)).into();
location.simplify(&context);
assert_eq!(location, expected);

let mut location: MultiLocation =
(Parent, Parent, Parachain(1000), PalletInstance(42), GeneralIndex(69)).into();
let context = X1(Parachain(1000));
let expected =
(Parent, Parent, Parachain(1000), PalletInstance(42), GeneralIndex(69)).into();
location.simplify(&context);
assert_eq!(location, expected);
}

#[test]
fn reanchor_works() {
let mut id: MultiLocation = (Parent, Parachain(1000), GeneralIndex(42)).into();
let ancestry = Parachain(2000).into();
let target = (Parent, Parachain(1000)).into();
let expected = GeneralIndex(42).into();
id.reanchor(&target, &ancestry).unwrap();
assert_eq!(id, expected);
}

#[test]
fn encode_and_decode_works() {
let m = MultiLocation {
Expand Down
3 changes: 3 additions & 0 deletions xcm/xcm-builder/src/location_conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@ impl<Network: Get<NetworkId>, AccountId: From<[u8; 20]> + Into<[u8; 20]> + Clone
/// ```
pub struct LocationInverter<Ancestry>(PhantomData<Ancestry>);
impl<Ancestry: Get<MultiLocation>> InvertLocation for LocationInverter<Ancestry> {
fn ancestry() -> MultiLocation {
Ancestry::get()
}
fn invert_location(location: &MultiLocation) -> Result<MultiLocation, ()> {
let mut ancestry = Ancestry::get();
let mut junctions = Here;
Expand Down
40 changes: 38 additions & 2 deletions xcm/xcm-executor/src/assets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ impl Assets {
self.fungible = fungible
.into_iter()
.map(|(mut id, amount)| {
let _ = id.reanchor(prepend);
let _ = id.prepend_with(prepend);
(id, amount)
})
.collect();
Expand All @@ -203,12 +203,48 @@ impl Assets {
self.non_fungible = non_fungible
.into_iter()
.map(|(mut class, inst)| {
let _ = class.reanchor(prepend);
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 `ancestry` is provided.
///
/// Any assets which were unable to be reanchored are introduced into `failed_bin`.
pub fn reanchor(
&mut self,
target: &MultiLocation,
ancestry: &MultiLocation,
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, ancestry) {
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, ancestry) {
Ok(()) => Some((class, inst)),
Err(()) => {
maybe_failed_bin.as_mut().map(|f| f.non_fungible.insert((class, inst)));
None
},
})
.collect();
}

/// 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: &MultiAssets) -> Result<(), TakeError> {
Expand Down
Loading