referrerpolicy=no-referrer-when-downgrade

snowbridge_outbound_queue_primitives/v2/converter/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
3//! Converts XCM messages into simpler commands that can be processed by the Gateway contract
4
5#[cfg(test)]
6mod tests;
7
8pub mod convert;
9pub use convert::XcmConverter;
10
11use super::message::SendMessage;
12use codec::{Decode, Encode};
13use frame_support::{
14	ensure,
15	traits::{Contains, Get, ProcessMessageError},
16};
17use snowbridge_core::{ParaId, TokenId};
18use sp_runtime::traits::MaybeConvert;
19use sp_std::{marker::PhantomData, ops::ControlFlow, prelude::*};
20use xcm::prelude::*;
21use xcm_builder::{CreateMatcher, ExporterFor, MatchXcm};
22use xcm_executor::traits::ExportXcm;
23
24pub const TARGET: &'static str = "xcm::ethereum_blob_exporter::v2";
25
26/// Used to process ExportMessages where the destination is Ethereum. It takes an ExportMessage
27/// and converts it into a simpler message that the Ethereum gateway contract can understand.
28pub struct EthereumBlobExporter<
29	UniversalLocation,
30	EthereumNetwork,
31	OutboundQueue,
32	ConvertAssetId,
33	AssetHubParaId,
34>(
35	PhantomData<(
36		UniversalLocation,
37		EthereumNetwork,
38		OutboundQueue,
39		ConvertAssetId,
40		AssetHubParaId,
41	)>,
42);
43
44impl<UniversalLocation, EthereumNetwork, OutboundQueue, ConvertAssetId, AssetHubParaId> ExportXcm
45	for EthereumBlobExporter<
46		UniversalLocation,
47		EthereumNetwork,
48		OutboundQueue,
49		ConvertAssetId,
50		AssetHubParaId,
51	>
52where
53	UniversalLocation: Get<InteriorLocation>,
54	EthereumNetwork: Get<NetworkId>,
55	OutboundQueue: SendMessage,
56	ConvertAssetId: MaybeConvert<TokenId, Location>,
57	AssetHubParaId: Get<ParaId>,
58{
59	type Ticket = (Vec<u8>, XcmHash);
60
61	fn validate(
62		network: NetworkId,
63		_channel: u32,
64		universal_source: &mut Option<InteriorLocation>,
65		destination: &mut Option<InteriorLocation>,
66		message: &mut Option<Xcm<()>>,
67	) -> SendResult<Self::Ticket> {
68		log::debug!(target: TARGET, "message route through bridge {message:?}.");
69
70		let expected_network = EthereumNetwork::get();
71		let universal_location = UniversalLocation::get();
72
73		if network != expected_network {
74			log::trace!(target: TARGET, "skipped due to unmatched bridge network {network:?}.");
75			return Err(SendError::NotApplicable)
76		}
77
78		// Cloning destination to avoid modifying the value so subsequent exporters can use it.
79		let dest = destination.clone().ok_or(SendError::MissingArgument)?;
80		if dest != Here {
81			log::trace!(target: TARGET, "skipped due to unmatched remote destination {dest:?}.");
82			return Err(SendError::NotApplicable)
83		}
84
85		// Cloning universal_source to avoid modifying the value so subsequent exporters can use it.
86		let (local_net, local_sub) = universal_source.clone()
87            .ok_or_else(|| {
88                log::error!(target: TARGET, "universal source not provided.");
89                SendError::MissingArgument
90            })?
91            .split_global()
92            .map_err(|()| {
93                log::error!(target: TARGET, "could not get global consensus from universal source '{universal_source:?}'.");
94                SendError::NotApplicable
95            })?;
96
97		if Ok(local_net) != universal_location.global_consensus() {
98			log::trace!(target: TARGET, "skipped due to unmatched relay network {local_net:?}.");
99			return Err(SendError::NotApplicable)
100		}
101
102		let para_id = match local_sub.as_slice() {
103			[Parachain(para_id)] => *para_id,
104			_ => {
105				log::error!(target: TARGET, "could not get parachain id from universal source '{local_sub:?}'.");
106				return Err(SendError::NotApplicable)
107			},
108		};
109
110		if ParaId::from(para_id) != AssetHubParaId::get() {
111			log::error!(target: TARGET, "is not from asset hub '{para_id:?}'.");
112			return Err(SendError::NotApplicable)
113		}
114
115		let message = message.clone().ok_or_else(|| {
116			log::error!(target: TARGET, "xcm message not provided.");
117			SendError::MissingArgument
118		})?;
119
120		// Inspect `AliasOrigin` as V2 message. This exporter should only process Snowbridge V2
121		// messages. We use the presence of an `AliasOrigin` instruction to distinguish between
122		// Snowbridge V2 and Snowbridge V1 messages, since XCM V5 came after Snowbridge V1 and
123		// so it's not supported in Snowbridge V1. Snowbridge V1 messages are processed by the
124		// snowbridge-outbound-queue-primitives v1 exporter.
125		let mut instructions = message.clone().0;
126		let result = instructions.matcher().match_next_inst_while(
127			|_| true,
128			|inst| {
129				return match inst {
130					AliasOrigin(..) => Err(ProcessMessageError::Yield),
131					_ => Ok(ControlFlow::Continue(())),
132				}
133			},
134		);
135		ensure!(result.is_err(), SendError::NotApplicable);
136
137		let mut converter = XcmConverter::<ConvertAssetId, ()>::new(&message, expected_network);
138		let message = converter.convert().map_err(|err| {
139			log::error!(target: TARGET, "unroutable due to pattern matching error '{err:?}'.");
140			SendError::Unroutable
141		})?;
142
143		// validate the message
144		let ticket = OutboundQueue::validate(&message).map_err(|err| {
145			log::error!(target: TARGET, "OutboundQueue validation of message failed. {err:?}");
146			SendError::Unroutable
147		})?;
148
149		Ok(((ticket.encode(), XcmHash::from(message.id)), Assets::default()))
150	}
151
152	fn deliver(blob: (Vec<u8>, XcmHash)) -> Result<XcmHash, SendError> {
153		let ticket: OutboundQueue::Ticket = OutboundQueue::Ticket::decode(&mut blob.0.as_ref())
154			.map_err(|_| {
155				log::trace!(target: TARGET, "undeliverable due to decoding error");
156				SendError::NotApplicable
157			})?;
158
159		let message_id = OutboundQueue::deliver(ticket).map_err(|_| {
160			log::error!(target: TARGET, "OutboundQueue submit of message failed");
161			SendError::Transport("other transport error")
162		})?;
163
164		log::info!(target: TARGET, "message delivered {message_id:#?}.");
165		Ok(message_id.into())
166	}
167}
168
169/// An adapter for the implementation of `ExporterFor`, which attempts to find the
170/// `(bridge_location, payment)` for the requested `network` and `remote_location` and `xcm`
171/// in the provided `T` table containing various exporters.
172pub struct XcmFilterExporter<T, M>(core::marker::PhantomData<(T, M)>);
173impl<T: ExporterFor, M: Contains<Xcm<()>>> ExporterFor for XcmFilterExporter<T, M> {
174	fn exporter_for(
175		network: &NetworkId,
176		remote_location: &InteriorLocation,
177		xcm: &Xcm<()>,
178	) -> Option<(Location, Option<Asset>)> {
179		// check the XCM
180		if !M::contains(xcm) {
181			return None
182		}
183		// check `network` and `remote_location`
184		T::exporter_for(network, remote_location, xcm)
185	}
186}
187
188/// Xcm for SnowbridgeV2 which requires XCMV5
189pub struct XcmForSnowbridgeV2;
190impl Contains<Xcm<()>> for XcmForSnowbridgeV2 {
191	fn contains(xcm: &Xcm<()>) -> bool {
192		let mut instructions = xcm.clone().0;
193		let result = instructions.matcher().match_next_inst_while(
194			|_| true,
195			|inst| {
196				return match inst {
197					AliasOrigin(..) => Err(ProcessMessageError::Yield),
198					_ => Ok(ControlFlow::Continue(())),
199				}
200			},
201		);
202		result.is_err()
203	}
204}