1
// Copyright 2019-2025 PureStake Inc.
2
// This file is part of Moonbeam.
3

            
4
// Moonbeam is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8

            
9
// Moonbeam is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13

            
14
// You should have received a copy of the GNU General Public License
15
// along with Moonbeam.  If not, see <http://www.gnu.org/licenses/>.
16

            
17
//! Precompile to receive GMP callbacks and forward to XCM
18

            
19
use parity_scale_codec::{Decode, Encode};
20
use precompile_utils::prelude::*;
21
use sp_core::{H256, U256};
22
use sp_std::vec::Vec;
23

            
24
// The Polkadot-sdk removed support for XCM Location V1, but the GMP precompile still needs to support it,
25
// so we have to wrap VersionedLocation to re-add support for XCM Location V1.
26
#[derive(Encode, Decode, Debug)]
27
pub enum VersionedLocation {
28
	#[codec(index = 1)] // v2 is same as v1 and therefore re-using the v1 index
29
	V2(deprecated_xcm_v2::MultiLocationV2),
30
	#[codec(index = 3)]
31
	V3(xcm::v3::MultiLocation),
32
	#[codec(index = 4)]
33
	V4(xcm::v4::Location),
34
	#[codec(index = 5)]
35
	V5(xcm::v5::Location),
36
}
37

            
38
impl TryFrom<VersionedLocation> for xcm::latest::Location {
39
	type Error = ();
40

            
41
	fn try_from(value: VersionedLocation) -> Result<Self, Self::Error> {
42
		match value {
43
			VersionedLocation::V2(location) => {
44
				xcm::VersionedLocation::V3(location.try_into()?).try_into()
45
			}
46
			VersionedLocation::V3(location) => xcm::VersionedLocation::V3(location).try_into(),
47
			VersionedLocation::V4(location) => xcm::VersionedLocation::V4(location).try_into(),
48
			VersionedLocation::V5(location) => xcm::VersionedLocation::V5(location).try_into(),
49
		}
50
	}
51
}
52

            
53
// A user action which will attempt to route the transferred assets to the account/chain specified
54
// by the given Location. Recall that a Location can contain both a chain and an account
55
// on that chain, as this one should.
56
#[derive(Encode, Decode, Debug)]
57
pub struct XcmRoutingUserAction {
58
	pub destination: VersionedLocation,
59
}
60

            
61
// A user action which is the same as XcmRoutingUserAction but also allows a fee to be paid. The
62
// fee is paid in the same asset being transferred, and must be <= the amount being sent.
63
#[derive(Encode, Decode, Debug)]
64
pub struct XcmRoutingUserActionWithFee {
65
	pub destination: VersionedLocation,
66
	pub fee: U256,
67
}
68

            
69
// A simple versioning wrapper around the initial XcmRoutingUserAction use-case. This should make
70
// future breaking changes easy to add in a backwards-compatible way.
71
#[derive(Encode, Decode, Debug)]
72
#[non_exhaustive]
73
pub enum VersionedUserAction {
74
	V1(XcmRoutingUserAction),
75
	V2(XcmRoutingUserActionWithFee),
76
}
77

            
78
// Struct representing a Wormhole VM
79
// The main purpose of this struct is to decode the ABI encoded struct returned from certain calls
80
// in the Wormhole Ethereum contracts.
81
//
82
// https://github.com/wormhole-foundation/wormhole/blob/main/ethereum/contracts/Structs.sol
83
#[derive(Debug, solidity::Codec)]
84
pub struct WormholeVM {
85
	pub version: u8,
86
	pub timestamp: u32,
87
	pub nonce: u32,
88
	pub emitter_chain_id: u16,
89
	pub emitter_address: H256,
90
	pub sequence: u64,
91
	pub consistency_level: u8,
92
	pub payload: BoundedBytes<crate::GetCallDataLimit>,
93

            
94
	pub guardian_set_index: u32,
95
	pub signatures: Vec<WormholeSignature>, // TODO: review: can this allow unbounded allocations?
96
	pub hash: H256,
97
}
98

            
99
// Struct representing a Wormhole Signature struct
100
#[derive(Debug, solidity::Codec)]
101
pub struct WormholeSignature {
102
	pub r: U256,
103
	pub s: U256,
104
	pub v: u8,
105
	pub guardian_index: u8,
106
}
107

            
108
// Struct representing a wormhole "BridgeStructs.TransferWithPayload" struct
109
// As with WormholeVM, the main purpose of this struct is to decode the ABI encoded struct when it
110
// returned from calls to Wormhole Ethereum contracts.
111
#[derive(Debug, solidity::Codec)]
112
pub struct WormholeTransferWithPayloadData {
113
	pub payload_id: u8,
114
	pub amount: U256,
115
	pub token_address: H256,
116
	pub token_chain: u16,
117
	pub to: H256,
118
	pub to_chain: u16,
119
	pub from_address: H256,
120
	pub payload: BoundedBytes<crate::GetCallDataLimit>,
121
}
122

            
123
/// Reimplement the deprecated xcm v2 Location types to allow for backwards compatibility
124
mod deprecated_xcm_v2 {
125
	use super::*;
126

            
127
	#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug)]
128
	pub struct MultiLocationV2 {
129
		pub parents: u8,
130
		pub interior: JunctionsV2,
131
	}
132

            
133
	impl TryFrom<MultiLocationV2> for xcm::v3::MultiLocation {
134
		type Error = ();
135

            
136
		fn try_from(value: MultiLocationV2) -> Result<Self, Self::Error> {
137
			Ok(xcm::v3::MultiLocation::new(
138
				value.parents,
139
				xcm::v3::Junctions::try_from(value.interior)?,
140
			))
141
		}
142
	}
143

            
144
	#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug)]
145
	pub enum JunctionsV2 {
146
		/// The interpreting consensus system.
147
		Here,
148
		/// A relative path comprising 1 junction.
149
		X1(JunctionV2),
150
		/// A relative path comprising 2 junctions.
151
		X2(JunctionV2, JunctionV2),
152
		/// A relative path comprising 3 junctions.
153
		X3(JunctionV2, JunctionV2, JunctionV2),
154
		/// A relative path comprising 4 junctions.
155
		X4(JunctionV2, JunctionV2, JunctionV2, JunctionV2),
156
		/// A relative path comprising 5 junctions.
157
		X5(JunctionV2, JunctionV2, JunctionV2, JunctionV2, JunctionV2),
158
		/// A relative path comprising 6 junctions.
159
		X6(
160
			JunctionV2,
161
			JunctionV2,
162
			JunctionV2,
163
			JunctionV2,
164
			JunctionV2,
165
			JunctionV2,
166
		),
167
		/// A relative path comprising 7 junctions.
168
		X7(
169
			JunctionV2,
170
			JunctionV2,
171
			JunctionV2,
172
			JunctionV2,
173
			JunctionV2,
174
			JunctionV2,
175
			JunctionV2,
176
		),
177
		/// A relative path comprising 8 junctions.
178
		X8(
179
			JunctionV2,
180
			JunctionV2,
181
			JunctionV2,
182
			JunctionV2,
183
			JunctionV2,
184
			JunctionV2,
185
			JunctionV2,
186
			JunctionV2,
187
		),
188
	}
189

            
190
	impl TryFrom<JunctionsV2> for xcm::v3::Junctions {
191
		type Error = ();
192

            
193
		fn try_from(value: JunctionsV2) -> Result<Self, Self::Error> {
194
			use JunctionsV2::*;
195
			Ok(match value {
196
				Here => Self::Here,
197
				X1(j1) => Self::X1(j1.try_into()?),
198
				X2(j1, j2) => Self::X2(j1.try_into()?, j2.try_into()?),
199
				X3(j1, j2, j3) => Self::X3(j1.try_into()?, j2.try_into()?, j3.try_into()?),
200
				X4(j1, j2, j3, j4) => Self::X4(
201
					j1.try_into()?,
202
					j2.try_into()?,
203
					j3.try_into()?,
204
					j4.try_into()?,
205
				),
206
				X5(j1, j2, j3, j4, j5) => Self::X5(
207
					j1.try_into()?,
208
					j2.try_into()?,
209
					j3.try_into()?,
210
					j4.try_into()?,
211
					j5.try_into()?,
212
				),
213
				X6(j1, j2, j3, j4, j5, j6) => Self::X6(
214
					j1.try_into()?,
215
					j2.try_into()?,
216
					j3.try_into()?,
217
					j4.try_into()?,
218
					j5.try_into()?,
219
					j6.try_into()?,
220
				),
221
				X7(j1, j2, j3, j4, j5, j6, j7) => Self::X7(
222
					j1.try_into()?,
223
					j2.try_into()?,
224
					j3.try_into()?,
225
					j4.try_into()?,
226
					j5.try_into()?,
227
					j6.try_into()?,
228
					j7.try_into()?,
229
				),
230
				X8(j1, j2, j3, j4, j5, j6, j7, j8) => Self::X8(
231
					j1.try_into()?,
232
					j2.try_into()?,
233
					j3.try_into()?,
234
					j4.try_into()?,
235
					j5.try_into()?,
236
					j6.try_into()?,
237
					j7.try_into()?,
238
					j8.try_into()?,
239
				),
240
			})
241
		}
242
	}
243

            
244
	#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug)]
245
	pub enum JunctionV2 {
246
		/// An indexed parachain belonging to and operated by the context.
247
		///
248
		/// Generally used when the context is a Polkadot Relay-chain.
249
		Parachain(#[codec(compact)] u32),
250
		/// A 32-byte identifier for an account of a specific network that is respected as a sovereign
251
		/// endpoint within the context.
252
		///
253
		/// Generally used when the context is a Substrate-based chain.
254
		AccountId32 { network: NetworkIdV2, id: [u8; 32] },
255
		/// An 8-byte index for an account of a specific network that is respected as a sovereign
256
		/// endpoint within the context.
257
		///
258
		/// May be used when the context is a Frame-based chain and includes e.g. an indices pallet.
259
		AccountIndex64 {
260
			network: NetworkIdV2,
261
			#[codec(compact)]
262
			index: u64,
263
		},
264
		/// A 20-byte identifier for an account of a specific network that is respected as a sovereign
265
		/// endpoint within the context.
266
		///
267
		/// May be used when the context is an Ethereum or Bitcoin chain or smart-contract.
268
		AccountKey20 { network: NetworkIdV2, key: [u8; 20] },
269
		/// An instanced, indexed pallet that forms a constituent part of the context.
270
		///
271
		/// Generally used when the context is a Frame-based chain.
272
		PalletInstance(u8),
273
		/// A non-descript index within the context location.
274
		///
275
		/// Usage will vary widely owing to its generality.
276
		///
277
		/// NOTE: Try to avoid using this and instead use a more specific item.
278
		GeneralIndex(#[codec(compact)] u128),
279
		/// A nondescript datum acting as a key within the context location.
280
		///
281
		/// Usage will vary widely owing to its generality.
282
		///
283
		/// NOTE: Try to avoid using this and instead use a more specific item.
284
		GeneralKey(sp_runtime::WeakBoundedVec<u8, sp_core::ConstU32<32>>),
285
		/// The unambiguous child.
286
		///
287
		/// Not currently used except as a fallback when deriving ancestry.
288
		OnlyChild,
289
		// The GMP precompile doesn't need to support Plurality Junction
290
		//Plurality { id: BodyId, part: BodyPart },
291
	}
292

            
293
	impl TryFrom<JunctionV2> for xcm::v3::Junction {
294
		type Error = ();
295

            
296
		fn try_from(value: JunctionV2) -> Result<Self, ()> {
297
			use JunctionV2::*;
298
			Ok(match value {
299
				Parachain(id) => Self::Parachain(id),
300
				AccountId32 { network, id } => Self::AccountId32 {
301
					network: network.into(),
302
					id,
303
				},
304
				AccountIndex64 { network, index } => Self::AccountIndex64 {
305
					network: network.into(),
306
					index,
307
				},
308
				AccountKey20 { network, key } => Self::AccountKey20 {
309
					network: network.into(),
310
					key,
311
				},
312
				PalletInstance(index) => Self::PalletInstance(index),
313
				GeneralIndex(id) => Self::GeneralIndex(id),
314
				GeneralKey(key) => match key.len() {
315
					len @ 0..=32 => Self::GeneralKey {
316
						length: len as u8,
317
						data: {
318
							let mut data = [0u8; 32];
319
							data[..len].copy_from_slice(&key[..]);
320
							data
321
						},
322
					},
323
					_ => return Err(()),
324
				},
325
				OnlyChild => Self::OnlyChild,
326
			})
327
		}
328
	}
329

            
330
	#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug)]
331
	pub enum NetworkIdV2 {
332
1
		/// Unidentified/any.
333
		Any,
334
		/// Some named network.
335
		Named(sp_runtime::WeakBoundedVec<u8, sp_core::ConstU32<32>>),
336
		/// The Polkadot Relay chain
337
		Polkadot,
338
		/// Kusama.
339
		Kusama,
340
	}
341

            
342
	impl From<NetworkIdV2> for Option<xcm::v3::NetworkId> {
343
		fn from(old: NetworkIdV2) -> Option<xcm::v3::NetworkId> {
344
			use NetworkIdV2::*;
345
			match old {
346
				Any => None,
347
				Named(_) => None,
348
				Polkadot => Some(xcm::v3::NetworkId::Polkadot),
349
				Kusama => Some(xcm::v3::NetworkId::Kusama),
350
			}
351
		}
352
	}
353
}
354

            
355
#[cfg(test)]
356
mod tests {
357
	use super::*;
358

            
359
	#[test]
360
1
	fn test_versioned_user_action_decode() {
361
1
		// Encoded payload from this wormhole transfer:
362
1
		// https://wormholescan.io/#/tx/0x7a6985578742291842d25d80091cb8661f9ebf9301b266d6d4cd324758310569?view=advanced
363
1
		let encoded_payload = hex::decode(
364
1
			"0001010200c91f0100c862582c20ec0a5429c6d2239da9908f4b6c93ab4e2589784f8a5452f65f0e45",
365
1
		)
366
1
		.unwrap();
367
1

            
368
1
		// Ensure we can decode the VersionedUserAction
369
1
		let _versioned_user_action = VersionedUserAction::decode(&mut &encoded_payload[..])
370
1
			.expect("Failed to decode VersionedUserAction");
371
1
	}
372
}