pallet_evm_precompile_gmp/
types.rs

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
19use parity_scale_codec::{Decode, Encode};
20use precompile_utils::prelude::*;
21use sp_core::{H256, U256};
22use 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)]
27pub 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
38impl 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)]
57pub 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)]
64pub 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]
73pub 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)]
84pub 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)]
101pub 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)]
112pub 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
124mod 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		/// 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)]
356mod tests {
357	use super::*;
358
359	#[test]
360	fn test_versioned_user_action_decode() {
361		// Encoded payload from this wormhole transfer:
362		// https://wormholescan.io/#/tx/0x7a6985578742291842d25d80091cb8661f9ebf9301b266d6d4cd324758310569?view=advanced
363		let encoded_payload = hex::decode(
364			"0001010200c91f0100c862582c20ec0a5429c6d2239da9908f4b6c93ab4e2589784f8a5452f65f0e45",
365		)
366		.unwrap();
367
368		// Ensure we can decode the VersionedUserAction
369		let _versioned_user_action = VersionedUserAction::decode(&mut &encoded_payload[..])
370			.expect("Failed to decode VersionedUserAction");
371	}
372}