referrerpolicy=no-referrer-when-downgrade

snowbridge_inbound_queue_primitives/v2/
message.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
3//! Converts messages from Ethereum to XCM messages
4
5use crate::{v2::IGatewayV2::Payload, Log};
6use alloy_core::{
7	primitives::B256,
8	sol,
9	sol_types::{SolEvent, SolType},
10};
11use codec::{Decode, Encode};
12use scale_info::TypeInfo;
13use sp_core::{RuntimeDebug, H160, H256};
14use sp_std::prelude::*;
15
16sol! {
17	interface IGatewayV2 {
18		struct AsNativeTokenERC20 {
19			address token_id;
20			uint128 value;
21		}
22		struct AsForeignTokenERC20 {
23			bytes32 token_id;
24			uint128 value;
25		}
26		struct EthereumAsset {
27			uint8 kind;
28			bytes data;
29		}
30		struct Xcm {
31			uint8 kind;
32			bytes data;
33		}
34		struct XcmCreateAsset {
35			address token;
36			uint8 network;
37		}
38		struct Payload {
39			address origin;
40			EthereumAsset[] assets;
41			Xcm xcm;
42			bytes claimer;
43			uint128 value;
44			uint128 executionFee;
45			uint128 relayerFee;
46		}
47		event OutboundMessageAccepted(uint64 nonce, Payload payload);
48	}
49}
50
51impl core::fmt::Debug for IGatewayV2::Payload {
52	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
53		f.debug_struct("Payload")
54			.field("origin", &self.origin)
55			.field("assets", &self.assets)
56			.field("xcm", &self.xcm)
57			.field("claimer", &self.claimer)
58			.field("value", &self.value)
59			.field("executionFee", &self.executionFee)
60			.field("relayerFee", &self.relayerFee)
61			.finish()
62	}
63}
64
65impl core::fmt::Debug for IGatewayV2::EthereumAsset {
66	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
67		f.debug_struct("EthereumAsset")
68			.field("kind", &self.kind)
69			.field("data", &self.data)
70			.finish()
71	}
72}
73
74impl core::fmt::Debug for IGatewayV2::Xcm {
75	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
76		f.debug_struct("Xcm")
77			.field("kind", &self.kind)
78			.field("data", &self.data)
79			.finish()
80	}
81}
82
83#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
84pub enum XcmPayload {
85	/// Represents raw XCM bytes
86	Raw(Vec<u8>),
87	/// A token registration template
88	CreateAsset { token: H160, network: Network },
89}
90
91/// Network enum for cross-chain message destination
92#[derive(Clone, Copy, Debug, Eq, PartialEq, Encode, Decode, TypeInfo)]
93pub enum Network {
94	/// Polkadot network
95	Polkadot,
96}
97
98/// The ethereum side sends messages which are transcoded into XCM on BH. These messages are
99/// self-contained, in that they can be transcoded using only information in the message.
100#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
101pub struct Message {
102	/// The address of the outbound queue on Ethereum that emitted this message as an event log
103	pub gateway: H160,
104	/// A nonce for enforcing replay protection and ordering.
105	pub nonce: u64,
106	/// The address on Ethereum that initiated the message.
107	pub origin: H160,
108	/// The assets sent from Ethereum (ERC-20s).
109	pub assets: Vec<EthereumAsset>,
110	/// The command originating from the Gateway contract.
111	pub xcm: XcmPayload,
112	/// The claimer in the case that funds get trapped. Expected to be an XCM::v5::Location.
113	pub claimer: Option<Vec<u8>>,
114	/// Native ether bridged over from Ethereum
115	pub value: u128,
116	/// Fee in eth to cover the xcm execution on AH.
117	pub execution_fee: u128,
118	/// Relayer reward in eth. Needs to cover all costs of sending a message.
119	pub relayer_fee: u128,
120}
121
122/// An asset that will be transacted on AH. The asset will be reserved/withdrawn and placed into
123/// the holding register. The user needs to provide additional xcm to deposit the asset
124/// in a beneficiary account.
125#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
126pub enum EthereumAsset {
127	NativeTokenERC20 {
128		/// The native token ID
129		token_id: H160,
130		/// The monetary value of the asset
131		value: u128,
132	},
133	ForeignTokenERC20 {
134		/// The foreign token ID
135		token_id: H256,
136		/// The monetary value of the asset
137		value: u128,
138	},
139}
140
141#[derive(Copy, Clone, RuntimeDebug)]
142pub struct MessageDecodeError;
143
144impl TryFrom<&Log> for Message {
145	type Error = MessageDecodeError;
146
147	fn try_from(log: &Log) -> Result<Self, Self::Error> {
148		// Convert to B256 for Alloy decoding
149		let topics: Vec<B256> = log.topics.iter().map(|x| B256::from_slice(x.as_ref())).collect();
150
151		// Decode the Solidity event from raw logs
152		let event = IGatewayV2::OutboundMessageAccepted::decode_raw_log_validate(topics, &log.data)
153			.map_err(|_| MessageDecodeError)?;
154
155		let payload = event.payload;
156
157		let substrate_assets = Self::extract_assets(&payload)?;
158
159		let xcm = XcmPayload::try_from(&payload)?;
160
161		let mut claimer = None;
162		if payload.claimer.len() > 0 {
163			claimer = Some(payload.claimer.to_vec());
164		}
165
166		let message = Message {
167			gateway: log.address,
168			nonce: event.nonce,
169			origin: H160::from(payload.origin.as_ref()),
170			assets: substrate_assets,
171			xcm,
172			claimer,
173			value: payload.value,
174			execution_fee: payload.executionFee,
175			relayer_fee: payload.relayerFee,
176		};
177
178		Ok(message)
179	}
180}
181
182impl Message {
183	fn extract_assets(
184		payload: &IGatewayV2::Payload,
185	) -> Result<Vec<EthereumAsset>, MessageDecodeError> {
186		let mut substrate_assets = vec![];
187		for asset in &payload.assets {
188			substrate_assets.push(EthereumAsset::try_from(asset)?);
189		}
190		Ok(substrate_assets)
191	}
192}
193
194impl TryFrom<&IGatewayV2::Payload> for XcmPayload {
195	type Error = MessageDecodeError;
196
197	fn try_from(payload: &Payload) -> Result<Self, Self::Error> {
198		let xcm = match payload.xcm.kind {
199			0 => XcmPayload::Raw(payload.xcm.data.to_vec()),
200			1 => {
201				let create_asset =
202					IGatewayV2::XcmCreateAsset::abi_decode_validate(&payload.xcm.data)
203						.map_err(|_| MessageDecodeError)?;
204				// Convert u8 network to Network enum
205				let network = match create_asset.network {
206					0 => Network::Polkadot,
207					_ => return Err(MessageDecodeError),
208				};
209				XcmPayload::CreateAsset { token: H160::from(create_asset.token.as_ref()), network }
210			},
211			_ => return Err(MessageDecodeError),
212		};
213		Ok(xcm)
214	}
215}
216
217impl TryFrom<&IGatewayV2::EthereumAsset> for EthereumAsset {
218	type Error = MessageDecodeError;
219
220	fn try_from(asset: &IGatewayV2::EthereumAsset) -> Result<EthereumAsset, Self::Error> {
221		let asset = match asset.kind {
222			0 => {
223				let native_data = IGatewayV2::AsNativeTokenERC20::abi_decode_validate(&asset.data)
224					.map_err(|_| MessageDecodeError)?;
225				EthereumAsset::NativeTokenERC20 {
226					token_id: H160::from(native_data.token_id.as_ref()),
227					value: native_data.value,
228				}
229			},
230			1 => {
231				let foreign_data =
232					IGatewayV2::AsForeignTokenERC20::abi_decode_validate(&asset.data)
233						.map_err(|_| MessageDecodeError)?;
234				EthereumAsset::ForeignTokenERC20 {
235					token_id: H256::from(foreign_data.token_id.as_ref()),
236					value: foreign_data.value,
237				}
238			},
239			_ => return Err(MessageDecodeError),
240		};
241		Ok(asset)
242	}
243}
244
245#[cfg(test)]
246mod tests {
247	use super::*;
248	use frame_support::assert_ok;
249	use hex_literal::hex;
250	use sp_core::H160;
251
252	#[test]
253	fn test_decode() {
254		let log = Log{
255			address: hex!("b1185ede04202fe62d38f5db72f71e38ff3e8305").into(),
256			topics: vec![hex!("550e2067494b1736ea5573f2d19cdc0ac95b410fff161bf16f11c6229655ec9c").into()],
257			data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000b1185ede04202fe62d38f5db72f71e38ff3e830500000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000009184e72a0000000000000000000000000000000000000000000000000000000015d3ef798000000000000000000000000000000000000000000000000000000015d3ef798000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000b8ea8cb425d85536b158d661da1ef0895bb92f1d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").to_vec(),
258		};
259
260		let result = Message::try_from(&log);
261		assert_ok!(result.clone());
262		let message = result.unwrap();
263
264		assert_eq!(H160::from(hex!("b1185ede04202fe62d38f5db72f71e38ff3e8305")), message.gateway);
265		assert_eq!(H160::from(hex!("b1185ede04202fe62d38f5db72f71e38ff3e8305")), message.origin);
266	}
267}