referrerpolicy=no-referrer-when-downgrade

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 { data, .. }] => Some((b"GeneralKey", *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			[PalletInstance(instance), GeneralKey { data, .. }] =>
89				Some((b"PalletInstance", *instance, b"GeneralKey", *data).encode()),
90
91			[PalletInstance(instance), AccountKey20 { key, .. }] =>
92				Some((b"PalletInstance", *instance, b"AccountKey20", *key).encode()),
93			[PalletInstance(instance), AccountId32 { id, .. }] =>
94				Some((b"PalletInstance", *instance, b"AccountId32", *id).encode()),
95
96			// Reject all other locations
97			_ => None,
98		}
99	}
100}
101
102#[cfg(test)]
103mod tests {
104	use crate::TokenIdOf;
105	use xcm::{
106		latest::WESTEND_GENESIS_HASH,
107		prelude::{
108			GeneralIndex, GeneralKey, GlobalConsensus, Junction::*, Location, NetworkId::ByGenesis,
109			PalletInstance, Parachain,
110		},
111	};
112	use xcm_executor::traits::ConvertLocation;
113
114	#[test]
115	fn test_token_of_id() {
116		let token_locations = [
117			// Relay Chain cases
118			// Relay Chain relative to Ethereum
119			Location::new(1, [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH))]),
120			// Parachain cases
121			// Parachain relative to Ethereum
122			Location::new(1, [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), Parachain(2000)]),
123			// Parachain general index
124			Location::new(
125				1,
126				[
127					GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),
128					Parachain(2000),
129					GeneralIndex(1),
130				],
131			),
132			// Parachain general key
133			Location::new(
134				1,
135				[
136					GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),
137					Parachain(2000),
138					GeneralKey { length: 32, data: [0; 32] },
139				],
140			),
141			// Parachain account key 20
142			Location::new(
143				1,
144				[
145					GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),
146					Parachain(2000),
147					AccountKey20 { network: None, key: [0; 20] },
148				],
149			),
150			// Parachain account id 32
151			Location::new(
152				1,
153				[
154					GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),
155					Parachain(2000),
156					AccountId32 { network: None, id: [0; 32] },
157				],
158			),
159			// Parchain Pallet instance cases
160			// Parachain pallet instance
161			Location::new(
162				1,
163				[
164					GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),
165					Parachain(2000),
166					PalletInstance(8),
167				],
168			),
169			// Parachain Pallet general index
170			Location::new(
171				1,
172				[
173					GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),
174					Parachain(2000),
175					PalletInstance(8),
176					GeneralIndex(1),
177				],
178			),
179			// Parachain Pallet general key
180			Location::new(
181				1,
182				[
183					GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),
184					Parachain(2000),
185					PalletInstance(8),
186					GeneralKey { length: 32, data: [0; 32] },
187				],
188			),
189			// Parachain Pallet account key 20
190			Location::new(
191				1,
192				[
193					GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),
194					Parachain(2000),
195					PalletInstance(8),
196					AccountKey20 { network: None, key: [0; 20] },
197				],
198			),
199			// Parachain Pallet account id 32
200			Location::new(
201				1,
202				[
203					GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),
204					Parachain(2000),
205					PalletInstance(8),
206					AccountId32 { network: None, id: [0; 32] },
207				],
208			),
209		];
210
211		for token in token_locations {
212			assert!(
213				TokenIdOf::convert_location(&token).is_some(),
214				"Valid token = {token:?} yields no TokenId."
215			);
216		}
217
218		let non_token_locations = [
219			// Relative location for a token should fail.
220			Location::new(1, []),
221			// Relative location for a token should fail.
222			Location::new(1, [Parachain(1000)]),
223		];
224
225		for token in non_token_locations {
226			assert!(
227				TokenIdOf::convert_location(&token).is_none(),
228				"Invalid token = {token:?} yields a TokenId."
229			);
230		}
231	}
232}