snowbridge_outbound_queue_primitives/v2/converter/
convert.rs1use codec::DecodeAll;
6use core::slice::Iter;
7use frame_support::{ensure, BoundedVec};
8use snowbridge_core::{AgentIdOf, TokenId, TokenIdOf};
9
10use crate::v2::{
11 message::{Command, Message},
12 ContractCall,
13};
14
15use crate::v2::convert::XcmConverterError::{AssetResolutionFailed, FilterDoesNotConsumeAllAssets};
16use sp_core::H160;
17use sp_runtime::traits::MaybeConvert;
18use sp_std::{iter::Peekable, marker::PhantomData, prelude::*};
19use xcm::prelude::*;
20use xcm_executor::traits::ConvertLocation;
21use XcmConverterError::*;
22
23#[derive(PartialEq, Debug)]
25pub enum XcmConverterError {
26 UnexpectedEndOfXcm,
27 EndOfXcmMessageExpected,
28 WithdrawAssetExpected,
29 DepositAssetExpected,
30 NoReserveAssets,
31 FilterDoesNotConsumeAllAssets,
32 TooManyAssets,
33 ZeroAssetTransfer,
34 BeneficiaryResolutionFailed,
35 AssetResolutionFailed,
36 InvalidFeeAsset,
37 SetTopicExpected,
38 ReserveAssetDepositedExpected,
39 InvalidAsset,
40 UnexpectedInstruction,
41 TooManyCommands,
42 AliasOriginExpected,
43 InvalidOrigin,
44 TransactDecodeFailed,
45 TransactParamsDecodeFailed,
46 FeeAssetResolutionFailed,
47 CallContractValueInsufficient,
48 NoCommands,
49}
50
51macro_rules! match_expression {
52 ($expression:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )?, $value:expr $(,)?) => {
53 match $expression {
54 $( $pattern )|+ $( if $guard )? => Some($value),
55 _ => None,
56 }
57 };
58}
59
60pub struct XcmConverter<'a, ConvertAssetId, Call> {
61 iter: Peekable<Iter<'a, Instruction<Call>>>,
62 ethereum_network: NetworkId,
63 _marker: PhantomData<ConvertAssetId>,
64}
65impl<'a, ConvertAssetId, Call> XcmConverter<'a, ConvertAssetId, Call>
66where
67 ConvertAssetId: MaybeConvert<TokenId, Location>,
68{
69 pub fn new(message: &'a Xcm<Call>, ethereum_network: NetworkId) -> Self {
70 Self {
71 iter: message.inner().iter().peekable(),
72 ethereum_network,
73 _marker: Default::default(),
74 }
75 }
76
77 fn next(&mut self) -> Result<&'a Instruction<Call>, XcmConverterError> {
78 self.iter.next().ok_or(XcmConverterError::UnexpectedEndOfXcm)
79 }
80
81 fn peek(&mut self) -> Result<&&'a Instruction<Call>, XcmConverterError> {
82 self.iter.peek().ok_or(XcmConverterError::UnexpectedEndOfXcm)
83 }
84
85 fn network_matches(&self, network: &Option<NetworkId>) -> bool {
86 if let Some(network) = network {
87 *network == self.ethereum_network
88 } else {
89 true
90 }
91 }
92
93 fn extract_remote_fee(&mut self) -> Result<u128, XcmConverterError> {
95 use XcmConverterError::*;
96 let reserved_fee_assets = match_expression!(self.next()?, WithdrawAsset(fee), fee)
97 .ok_or(WithdrawAssetExpected)?;
98 ensure!(reserved_fee_assets.len() == 1, AssetResolutionFailed);
99 let reserved_fee_asset =
100 reserved_fee_assets.inner().first().cloned().ok_or(AssetResolutionFailed)?;
101 let (reserved_fee_asset_id, reserved_fee_amount) = match reserved_fee_asset {
102 Asset { id: asset_id, fun: Fungible(amount) } => Ok((asset_id, amount)),
103 _ => Err(AssetResolutionFailed),
104 }?;
105 let fee_asset =
106 match_expression!(self.next()?, PayFees { asset: fee }, fee).ok_or(InvalidFeeAsset)?;
107 let (fee_asset_id, fee_amount) = match fee_asset {
108 Asset { id: asset_id, fun: Fungible(amount) } => Ok((asset_id, *amount)),
109 _ => Err(AssetResolutionFailed),
110 }?;
111 ensure!(fee_asset_id.0 == Here.into(), InvalidFeeAsset);
113 ensure!(reserved_fee_asset_id.0 == Here.into(), InvalidFeeAsset);
114 ensure!(reserved_fee_amount >= fee_amount, InvalidFeeAsset);
115 Ok(fee_amount)
116 }
117
118 fn extract_ethereum_native_assets(
120 &mut self,
121 enas: &Assets,
122 deposit_assets: &AssetFilter,
123 recipient: H160,
124 ) -> Result<Vec<Command>, XcmConverterError> {
125 let mut commands: Vec<Command> = Vec::new();
126 for ena in enas.clone().into_inner().into_iter() {
127 if !deposit_assets.matches(&ena) {
129 return Err(FilterDoesNotConsumeAllAssets);
130 }
131
132 let (token, amount) = match ena {
134 Asset { id: AssetId(inner_location), fun: Fungible(amount) } =>
135 match inner_location.unpack() {
136 (0, [AccountKey20 { network, key }]) if self.network_matches(network) =>
137 Ok((H160(*key), amount)),
138 (0, []) => Ok((H160([0; 20]), amount)),
140 _ => Err(AssetResolutionFailed),
141 },
142 _ => Err(AssetResolutionFailed),
143 }?;
144
145 ensure!(amount > 0, ZeroAssetTransfer);
147
148 commands.push(Command::UnlockNativeToken { token, recipient, amount });
149 }
150 Ok(commands)
151 }
152
153 fn extract_polkadot_native_assets(
155 &mut self,
156 pnas: &Assets,
157 deposit_assets: &AssetFilter,
158 recipient: H160,
159 ) -> Result<Vec<Command>, XcmConverterError> {
160 let mut commands: Vec<Command> = Vec::new();
161 ensure!(pnas.len() > 0, NoReserveAssets);
162 for pna in pnas.clone().into_inner().into_iter() {
163 if !deposit_assets.matches(&pna) {
164 return Err(FilterDoesNotConsumeAllAssets);
165 }
166
167 let Asset { id: AssetId(asset_id), fun: Fungible(amount) } = pna else {
169 return Err(AssetResolutionFailed);
170 };
171
172 ensure!(amount > 0, ZeroAssetTransfer);
174
175 let token_id = TokenIdOf::convert_location(&asset_id).ok_or(InvalidAsset)?;
177 ConvertAssetId::maybe_convert(token_id).ok_or(InvalidAsset)?;
178
179 commands.push(Command::MintForeignToken { token_id, recipient, amount });
180 }
181 Ok(commands)
182 }
183
184 pub fn convert(&mut self) -> Result<Message, XcmConverterError> {
214 let fee_amount = self.extract_remote_fee()?;
216
217 let mut enas =
219 match_expression!(self.peek(), Ok(WithdrawAsset(reserve_assets)), reserve_assets);
220 if enas.is_some() {
221 let _ = self.next();
222 }
223
224 let pnas = match_expression!(
226 self.peek(),
227 Ok(ReserveAssetDeposited(reserve_assets)),
228 reserve_assets
229 );
230 if pnas.is_some() {
231 let _ = self.next();
232 }
233
234 if enas.is_none() {
236 enas =
237 match_expression!(self.peek(), Ok(WithdrawAsset(reserve_assets)), reserve_assets);
238 if enas.is_some() {
239 let _ = self.next();
240 }
241 }
242 let origin_location = match_expression!(self.next()?, AliasOrigin(origin), origin)
244 .ok_or(AliasOriginExpected)?;
245 let origin = AgentIdOf::convert_location(origin_location).ok_or(InvalidOrigin)?;
246
247 let (deposit_assets, beneficiary) = match_expression!(
248 self.next()?,
249 DepositAsset { assets, beneficiary },
250 (assets, beneficiary)
251 )
252 .ok_or(DepositAssetExpected)?;
253
254 let recipient = match_expression!(
256 beneficiary.unpack(),
257 (0, [AccountKey20 { network, key }])
258 if self.network_matches(network),
259 H160(*key)
260 )
261 .ok_or(BeneficiaryResolutionFailed)?;
262
263 let mut commands: Vec<Command> = Vec::new();
264
265 if let Some(enas) = enas {
267 commands.append(&mut self.extract_ethereum_native_assets(
268 enas,
269 deposit_assets,
270 recipient,
271 )?);
272 }
273
274 if let Some(pnas) = pnas {
276 commands.append(&mut self.extract_polkadot_native_assets(
277 pnas,
278 deposit_assets,
279 recipient,
280 )?);
281 }
282
283 let transact_call = match_expression!(self.peek(), Ok(Transact { call, .. }), call);
285 if let Some(transact_call) = transact_call {
286 let _ = self.next();
287 let transact =
288 ContractCall::decode_all(&mut transact_call.clone().into_encoded().as_slice())
289 .map_err(|_| TransactDecodeFailed)?;
290 match transact {
291 ContractCall::V1 { target, calldata, gas, value } => commands
292 .push(Command::CallContract { target: target.into(), calldata, gas, value }),
293 }
294 }
295
296 ensure!(commands.len() > 0, NoCommands);
297
298 let topic_id = match_expression!(self.next()?, SetTopic(id), id).ok_or(SetTopicExpected)?;
300
301 let message = Message {
302 id: (*topic_id).into(),
303 origin,
304 fee: fee_amount,
305 commands: BoundedVec::try_from(commands).map_err(|_| TooManyCommands)?,
306 };
307
308 if self.next().is_ok() {
310 return Err(EndOfXcmMessageExpected);
311 }
312
313 Ok(message)
314 }
315}