Skip to main content

snowbridge_core/
location.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
3//! # Location
4//!
5//! Location helpers for dealing with Tokens and Agents
6
7pub use polkadot_parachain_primitives::primitives::{
8	Id as ParaId, IsSystem, Sibling as SiblingParaId,
9};
10pub use sp_core::U256;
11
12use codec::Encode;
13use sp_core::H256;
14use sp_std::prelude::*;
15use xcm::prelude::{
16	AccountId32, AccountKey20, GeneralIndex, GeneralKey, GlobalConsensus, Location, PalletInstance,
17};
18use xcm_builder::{
19	DescribeAllTerminal, DescribeFamily, DescribeLocation, DescribeTerminus, HashedDescription,
20};
21
22pub type AgentId = H256;
23
24/// Creates an AgentId from a Location. An AgentId is a unique mapping to an Agent contract on
25/// Ethereum which acts as the sovereign account for the Location.
26/// Resolves Polkadot locations (as seen by Ethereum) to unique `AgentId` identifiers.
27pub type AgentIdOf = HashedDescription<
28	AgentId,
29	(
30		DescribeHere,
31		DescribeFamily<DescribeAllTerminal>,
32		DescribeGlobalPrefix<(DescribeTerminus, DescribeFamily<DescribeTokenTerminal>)>,
33	),
34>;
35
36pub type TokenId = H256;
37
38/// Convert a token location (relative to Ethereum) to a stable ID that can be used on the Ethereum
39/// side
40pub type TokenIdOf = HashedDescription<
41	TokenId,
42	DescribeGlobalPrefix<(DescribeTerminus, DescribeFamily<DescribeTokenTerminal>)>,
43>;
44
45/// This looks like DescribeTerminus that was added to xcm-builder. However this does an extra
46/// `encode` to the Vector producing a different output to DescribeTerminus. `DescribeHere`
47/// should NOT be used for new code. This is left here for backwards compatibility of channels and
48/// agents.
49pub struct DescribeHere;
50#[allow(deprecated)]
51impl DescribeLocation for DescribeHere {
52	fn describe_location(l: &Location) -> Option<Vec<u8>> {
53		match l.unpack() {
54			(0, []) => Some(Vec::<u8>::new().encode()),
55			_ => None,
56		}
57	}
58}
59pub struct DescribeGlobalPrefix<DescribeInterior>(sp_std::marker::PhantomData<DescribeInterior>);
60impl<Suffix: DescribeLocation> DescribeLocation for DescribeGlobalPrefix<Suffix> {
61	fn describe_location(l: &Location) -> Option<Vec<u8>> {
62		match (l.parent_count(), l.first_interior()) {
63			(1, Some(GlobalConsensus(network))) => {
64				let mut tail = l.clone().split_first_interior().0;
65				tail.dec_parent();
66				let interior = Suffix::describe_location(&tail)?;
67				Some((b"GlobalConsensus", network, interior).encode())
68			},
69			_ => None,
70		}
71	}
72}
73
74pub struct DescribeTokenTerminal;
75impl DescribeLocation for DescribeTokenTerminal {
76	fn describe_location(l: &Location) -> Option<Vec<u8>> {
77		match l.unpack().1 {
78			[] => Some(Vec::<u8>::new().encode()),
79			[GeneralIndex(index)] => Some((b"GeneralIndex", *index).encode()),
80			[GeneralKey { length, data }] => Some((b"GeneralKey", *length, *data).encode()),
81			[AccountKey20 { key, .. }] => Some((b"AccountKey20", *key).encode()),
82			[AccountId32 { id, .. }] => Some((b"AccountId32", *id).encode()),
83
84			// Pallet
85			[PalletInstance(instance)] => Some((b"PalletInstance", *instance).encode()),
86			[PalletInstance(instance), GeneralIndex(index)] => {
87				Some((b"PalletInstance", *instance, b"GeneralIndex", *index).encode())
88			},
89			[PalletInstance(instance), GeneralKey { length, data }] => {
90				Some((b"PalletInstance", *instance, b"GeneralKey", *length, *data).encode())
91			},
92
93			[PalletInstance(instance), AccountKey20 { key, .. }] => {
94				Some((b"PalletInstance", *instance, b"AccountKey20", *key).encode())
95			},
96			[PalletInstance(instance), AccountId32 { id, .. }] => {
97				Some((b"PalletInstance", *instance, b"AccountId32", *id).encode())
98			},
99
100			// Reject all other locations
101			_ => None,
102		}
103	}
104}
105
106#[cfg(test)]
107mod tests {
108	use crate::TokenIdOf;
109	use xcm::{
110		latest::WESTEND_GENESIS_HASH,
111		prelude::{
112			GeneralIndex, GeneralKey, GlobalConsensus, Junction::*, Location, NetworkId::ByGenesis,
113			PalletInstance, Parachain,
114		},
115	};
116	use xcm_executor::traits::ConvertLocation;
117
118	#[test]
119	fn test_token_of_id() {
120		let token_locations = [
121			// Relay Chain cases
122			// Relay Chain relative to Ethereum
123			Location::new(1, [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH))]),
124			// Parachain cases
125			// Parachain relative to Ethereum
126			Location::new(1, [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), Parachain(2000)]),
127			// Parachain general index
128			Location::new(
129				1,
130				[
131					GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),
132					Parachain(2000),
133					GeneralIndex(1),
134				],
135			),
136			// Parachain general key
137			Location::new(
138				1,
139				[
140					GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),
141					Parachain(2000),
142					GeneralKey { length: 32, data: [0; 32] },
143				],
144			),
145			// Parachain account key 20
146			Location::new(
147				1,
148				[
149					GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),
150					Parachain(2000),
151					AccountKey20 { network: None, key: [0; 20] },
152				],
153			),
154			// Parachain account id 32
155			Location::new(
156				1,
157				[
158					GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),
159					Parachain(2000),
160					AccountId32 { network: None, id: [0; 32] },
161				],
162			),
163			// Parchain Pallet instance cases
164			// Parachain pallet instance
165			Location::new(
166				1,
167				[
168					GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),
169					Parachain(2000),
170					PalletInstance(8),
171				],
172			),
173			// Parachain Pallet general index
174			Location::new(
175				1,
176				[
177					GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),
178					Parachain(2000),
179					PalletInstance(8),
180					GeneralIndex(1),
181				],
182			),
183			// Parachain Pallet general key
184			Location::new(
185				1,
186				[
187					GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),
188					Parachain(2000),
189					PalletInstance(8),
190					GeneralKey { length: 32, data: [0; 32] },
191				],
192			),
193			// Parachain Pallet account key 20
194			Location::new(
195				1,
196				[
197					GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),
198					Parachain(2000),
199					PalletInstance(8),
200					AccountKey20 { network: None, key: [0; 20] },
201				],
202			),
203			// Parachain Pallet account id 32
204			Location::new(
205				1,
206				[
207					GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),
208					Parachain(2000),
209					PalletInstance(8),
210					AccountId32 { network: None, id: [0; 32] },
211				],
212			),
213		];
214
215		for token in token_locations {
216			assert!(
217				TokenIdOf::convert_location(&token).is_some(),
218				"Valid token = {token:?} yields no TokenId."
219			);
220		}
221
222		let non_token_locations = [
223			// Relative location for a token should fail.
224			Location::new(1, []),
225			// Relative location for a token should fail.
226			Location::new(1, [Parachain(1000)]),
227		];
228
229		for token in non_token_locations {
230			assert!(
231				TokenIdOf::convert_location(&token).is_none(),
232				"Invalid token = {token:?} yields a TokenId."
233			);
234		}
235	}
236}