location_conversion.rs 9.2 KB
Newer Older
Shawn Tabrizi's avatar
Shawn Tabrizi committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.

// Polkadot 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.

// Polkadot 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 Polkadot.  If not, see <http://www.gnu.org/licenses/>.

use frame_support::traits::Get;
18
use parity_scale_codec::Encode;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
19
20
21
use sp_io::hashing::blake2_256;
use sp_runtime::traits::AccountIdConversion;
use sp_std::{borrow::Borrow, marker::PhantomData};
22
use xcm::latest::{Junction::*, Junctions::*, MultiLocation, NetworkId, Parent};
Shawn Tabrizi's avatar
Shawn Tabrizi committed
23
use xcm_executor::traits::{Convert, InvertLocation};
Shawn Tabrizi's avatar
Shawn Tabrizi committed
24
25

pub struct Account32Hash<Network, AccountId>(PhantomData<(Network, AccountId)>);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
26
27
28
impl<Network: Get<NetworkId>, AccountId: From<[u8; 32]> + Into<[u8; 32]> + Clone>
	Convert<MultiLocation, AccountId> for Account32Hash<Network, AccountId>
{
Gavin Wood's avatar
Gavin Wood committed
29
30
	fn convert_ref(location: impl Borrow<MultiLocation>) -> Result<AccountId, ()> {
		Ok(("multiloc", location.borrow()).using_encoded(blake2_256).into())
Shawn Tabrizi's avatar
Shawn Tabrizi committed
31
32
	}

Gavin Wood's avatar
Gavin Wood committed
33
34
	fn reverse_ref(_: impl Borrow<AccountId>) -> Result<MultiLocation, ()> {
		Err(())
Shawn Tabrizi's avatar
Shawn Tabrizi committed
35
36
37
	}
}

Alexander Popiak's avatar
Alexander Popiak committed
38
39
/// A [`MultiLocation`] consisting of a single `Parent` [`Junction`] will be converted to the
/// default value of `AccountId` (e.g. all zeros for `AccountId32`).
Shawn Tabrizi's avatar
Shawn Tabrizi committed
40
pub struct ParentIsDefault<AccountId>(PhantomData<AccountId>);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
41
42
43
impl<AccountId: Default + Eq + Clone> Convert<MultiLocation, AccountId>
	for ParentIsDefault<AccountId>
{
Gavin Wood's avatar
Gavin Wood committed
44
	fn convert_ref(location: impl Borrow<MultiLocation>) -> Result<AccountId, ()> {
45
		if location.borrow().contains_parents_only(1) {
Gavin Wood's avatar
Gavin Wood committed
46
			Ok(AccountId::default())
Shawn Tabrizi's avatar
Shawn Tabrizi committed
47
		} else {
Gavin Wood's avatar
Gavin Wood committed
48
			Err(())
Shawn Tabrizi's avatar
Shawn Tabrizi committed
49
50
51
		}
	}

Gavin Wood's avatar
Gavin Wood committed
52
53
	fn reverse_ref(who: impl Borrow<AccountId>) -> Result<MultiLocation, ()> {
		if who.borrow() == &AccountId::default() {
54
			Ok(Parent.into())
Shawn Tabrizi's avatar
Shawn Tabrizi committed
55
		} else {
Gavin Wood's avatar
Gavin Wood committed
56
			Err(())
Shawn Tabrizi's avatar
Shawn Tabrizi committed
57
58
59
60
61
		}
	}
}

pub struct ChildParachainConvertsVia<ParaId, AccountId>(PhantomData<(ParaId, AccountId)>);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
62
63
64
impl<ParaId: From<u32> + Into<u32> + AccountIdConversion<AccountId>, AccountId: Clone>
	Convert<MultiLocation, AccountId> for ChildParachainConvertsVia<ParaId, AccountId>
{
Gavin Wood's avatar
Gavin Wood committed
65
	fn convert_ref(location: impl Borrow<MultiLocation>) -> Result<AccountId, ()> {
66
67
68
69
		match location.borrow() {
			MultiLocation { parents: 0, interior: X1(Parachain(id)) } =>
				Ok(ParaId::from(*id).into_account()),
			_ => Err(()),
Shawn Tabrizi's avatar
Shawn Tabrizi committed
70
71
72
		}
	}

Gavin Wood's avatar
Gavin Wood committed
73
74
	fn reverse_ref(who: impl Borrow<AccountId>) -> Result<MultiLocation, ()> {
		if let Some(id) = ParaId::try_from_account(who.borrow()) {
75
			Ok(Parachain(id.into()).into())
Shawn Tabrizi's avatar
Shawn Tabrizi committed
76
		} else {
Gavin Wood's avatar
Gavin Wood committed
77
			Err(())
Shawn Tabrizi's avatar
Shawn Tabrizi committed
78
79
80
81
82
		}
	}
}

pub struct SiblingParachainConvertsVia<ParaId, AccountId>(PhantomData<(ParaId, AccountId)>);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
83
84
85
impl<ParaId: From<u32> + Into<u32> + AccountIdConversion<AccountId>, AccountId: Clone>
	Convert<MultiLocation, AccountId> for SiblingParachainConvertsVia<ParaId, AccountId>
{
Gavin Wood's avatar
Gavin Wood committed
86
	fn convert_ref(location: impl Borrow<MultiLocation>) -> Result<AccountId, ()> {
87
88
89
90
		match location.borrow() {
			MultiLocation { parents: 1, interior: X1(Parachain(id)) } =>
				Ok(ParaId::from(*id).into_account()),
			_ => Err(()),
Shawn Tabrizi's avatar
Shawn Tabrizi committed
91
92
93
		}
	}

Gavin Wood's avatar
Gavin Wood committed
94
95
	fn reverse_ref(who: impl Borrow<AccountId>) -> Result<MultiLocation, ()> {
		if let Some(id) = ParaId::try_from_account(who.borrow()) {
96
			Ok(MultiLocation::new(1, X1(Parachain(id.into()))))
Shawn Tabrizi's avatar
Shawn Tabrizi committed
97
		} else {
Gavin Wood's avatar
Gavin Wood committed
98
			Err(())
Shawn Tabrizi's avatar
Shawn Tabrizi committed
99
100
101
102
		}
	}
}

Alexander Popiak's avatar
Alexander Popiak committed
103
/// Extracts the `AccountId32` from the passed `location` if the network matches.
Shawn Tabrizi's avatar
Shawn Tabrizi committed
104
pub struct AccountId32Aliases<Network, AccountId>(PhantomData<(Network, AccountId)>);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
105
106
107
impl<Network: Get<NetworkId>, AccountId: From<[u8; 32]> + Into<[u8; 32]> + Clone>
	Convert<MultiLocation, AccountId> for AccountId32Aliases<Network, AccountId>
{
Gavin Wood's avatar
Gavin Wood committed
108
109
	fn convert(location: MultiLocation) -> Result<AccountId, MultiLocation> {
		let id = match location {
110
111
112
113
114
115
			MultiLocation {
				parents: 0,
				interior: X1(AccountId32 { id, network: NetworkId::Any }),
			} => id,
			MultiLocation { parents: 0, interior: X1(AccountId32 { id, network }) }
				if network == Network::get() =>
Shawn Tabrizi's avatar
Shawn Tabrizi committed
116
				id,
117
			_ => return Err(location),
Gavin Wood's avatar
Gavin Wood committed
118
119
		};
		Ok(id.into())
Shawn Tabrizi's avatar
Shawn Tabrizi committed
120
121
	}

Gavin Wood's avatar
Gavin Wood committed
122
	fn reverse(who: AccountId) -> Result<MultiLocation, AccountId> {
123
		Ok(AccountId32 { id: who.into(), network: Network::get() }.into())
Shawn Tabrizi's avatar
Shawn Tabrizi committed
124
125
	}
}
126
127

pub struct AccountKey20Aliases<Network, AccountId>(PhantomData<(Network, AccountId)>);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
128
129
130
impl<Network: Get<NetworkId>, AccountId: From<[u8; 20]> + Into<[u8; 20]> + Clone>
	Convert<MultiLocation, AccountId> for AccountKey20Aliases<Network, AccountId>
{
Gavin Wood's avatar
Gavin Wood committed
131
132
	fn convert(location: MultiLocation) -> Result<AccountId, MultiLocation> {
		let key = match location {
133
134
135
136
137
138
			MultiLocation {
				parents: 0,
				interior: X1(AccountKey20 { key, network: NetworkId::Any }),
			} => key,
			MultiLocation { parents: 0, interior: X1(AccountKey20 { key, network }) }
				if network == Network::get() =>
Shawn Tabrizi's avatar
Shawn Tabrizi committed
139
				key,
140
			_ => return Err(location),
Gavin Wood's avatar
Gavin Wood committed
141
142
143
144
145
		};
		Ok(key.into())
	}

	fn reverse(who: AccountId) -> Result<MultiLocation, AccountId> {
146
		let j = AccountKey20 { key: who.into(), network: Network::get() };
Gavin Wood's avatar
Gavin Wood committed
147
		Ok(j.into())
148
	}
Gavin Wood's avatar
Gavin Wood committed
149
}
150

Alexander Popiak's avatar
Alexander Popiak committed
151
152
153
154
155
156
157
158
159
160
161
162
163
/// Simple location inverter; give it this location's ancestry and it'll figure out the inverted
/// location.
///
/// # Example
/// ## Network Topology
/// ```txt
///                    v Source
/// Relay -> Para 1 -> Account20
///       -> Para 2 -> Account32
///                    ^ Target
/// ```
/// ```rust
/// # use frame_support::parameter_types;
164
/// # use xcm::latest::{MultiLocation, Junction::*, Junctions::{self, *}, NetworkId::Any};
Alexander Popiak's avatar
Alexander Popiak committed
165
166
167
168
169
170
171
/// # use xcm_builder::LocationInverter;
/// # use xcm_executor::traits::InvertLocation;
/// # fn main() {
/// parameter_types!{
///     pub Ancestry: MultiLocation = X2(
///         Parachain(1),
///         AccountKey20 { network: Any, key: Default::default() },
172
///     ).into();
Alexander Popiak's avatar
Alexander Popiak committed
173
174
/// }
///
175
/// let input = MultiLocation::new(2, X2(Parachain(2), AccountId32 { network: Any, id: Default::default() }));
Alexander Popiak's avatar
Alexander Popiak committed
176
/// let inverted = LocationInverter::<Ancestry>::invert_location(&input);
177
/// assert_eq!(inverted, Ok(MultiLocation::new(
178
179
///     2,
///     X2(Parachain(1), AccountKey20 { network: Any, key: Default::default() }),
180
/// )));
Alexander Popiak's avatar
Alexander Popiak committed
181
182
/// # }
/// ```
Gavin Wood's avatar
Gavin Wood committed
183
184
pub struct LocationInverter<Ancestry>(PhantomData<Ancestry>);
impl<Ancestry: Get<MultiLocation>> InvertLocation for LocationInverter<Ancestry> {
185
	fn invert_location(location: &MultiLocation) -> Result<MultiLocation, ()> {
Gavin Wood's avatar
Gavin Wood committed
186
		let mut ancestry = Ancestry::get();
187
188
189
190
		let mut junctions = Here;
		for _ in 0..location.parent_count() {
			junctions = junctions
				.pushed_with(ancestry.take_first_interior().unwrap_or(OnlyChild))
191
				.map_err(|_| ())?;
192
		}
193
		let parents = location.interior().len() as u8;
194
		Ok(MultiLocation::new(parents, junctions))
195
196
	}
}
Alexander Popiak's avatar
Alexander Popiak committed
197
198
199
200
201
202

#[cfg(test)]
mod tests {
	use super::*;

	use frame_support::parameter_types;
203
	use xcm::latest::{Junction, NetworkId::Any};
Alexander Popiak's avatar
Alexander Popiak committed
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226

	fn account20() -> Junction {
		AccountKey20 { network: Any, key: Default::default() }
	}

	fn account32() -> Junction {
		AccountId32 { network: Any, id: Default::default() }
	}

	// Network Topology
	//                                     v Source
	// Relay -> Para 1 -> SmartContract -> Account
	//       -> Para 2 -> Account
	//                    ^ Target
	//
	// Inputs and outputs written as file paths:
	//
	// input location (source to target): ../../../para_2/account32_default
	// ancestry (root to source): para_1/account20_default/account20_default
	// =>
	// output (target to source): ../../para_1/account20_default/account20_default
	#[test]
	fn inverter_works_in_tree() {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
227
		parameter_types! {
228
			pub Ancestry: MultiLocation = X3(Parachain(1), account20(), account20()).into();
Alexander Popiak's avatar
Alexander Popiak committed
229
230
		}

231
		let input = MultiLocation::new(3, X2(Parachain(2), account32()));
232
		let inverted = LocationInverter::<Ancestry>::invert_location(&input).unwrap();
233
		assert_eq!(inverted, MultiLocation::new(2, X3(Parachain(1), account20(), account20())));
Alexander Popiak's avatar
Alexander Popiak committed
234
235
236
237
238
239
240
241
	}

	// Network Topology
	//                                     v Source
	// Relay -> Para 1 -> SmartContract -> Account
	//          ^ Target
	#[test]
	fn inverter_uses_ancestry_as_inverted_location() {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
242
		parameter_types! {
243
			pub Ancestry: MultiLocation = X2(account20(), account20()).into();
Alexander Popiak's avatar
Alexander Popiak committed
244
245
		}

246
		let input = MultiLocation::grandparent();
247
		let inverted = LocationInverter::<Ancestry>::invert_location(&input).unwrap();
248
		assert_eq!(inverted, X2(account20(), account20()).into());
Alexander Popiak's avatar
Alexander Popiak committed
249
250
251
252
253
254
255
256
	}

	// Network Topology
	//                                        v Source
	// Relay -> Para 1 -> CollectivePallet -> Plurality
	//          ^ Target
	#[test]
	fn inverter_uses_only_child_on_missing_ancestry() {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
257
		parameter_types! {
258
			pub Ancestry: MultiLocation = X1(PalletInstance(5)).into();
Alexander Popiak's avatar
Alexander Popiak committed
259
260
		}

261
		let input = MultiLocation::grandparent();
262
		let inverted = LocationInverter::<Ancestry>::invert_location(&input).unwrap();
263
		assert_eq!(inverted, X2(PalletInstance(5), OnlyChild).into());
Alexander Popiak's avatar
Alexander Popiak committed
264
	}
265
266
267
268
269
270
271
272
273
274
275

	#[test]
	fn inverter_errors_when_location_is_too_large() {
		parameter_types! {
			pub Ancestry: MultiLocation = Here.into();
		}

		let input = MultiLocation { parents: 99, interior: X1(Parachain(88)) };
		let inverted = LocationInverter::<Ancestry>::invert_location(&input);
		assert_eq!(inverted, Err(()));
	}
Alexander Popiak's avatar
Alexander Popiak committed
276
}