snowbridge_outbound_queue_primitives/v2/converter/
convert.rsuse codec::DecodeAll;
use core::slice::Iter;
use frame_support::{ensure, BoundedVec};
use snowbridge_core::{AgentIdOf, TokenId, TokenIdOf};
use crate::v2::{
message::{Command, Message},
ContractCall,
};
use crate::v2::convert::XcmConverterError::{AssetResolutionFailed, FilterDoesNotConsumeAllAssets};
use sp_core::H160;
use sp_runtime::traits::MaybeEquivalence;
use sp_std::{iter::Peekable, marker::PhantomData, prelude::*};
use xcm::prelude::*;
use xcm_executor::traits::ConvertLocation;
use XcmConverterError::*;
#[derive(PartialEq, Debug)]
pub enum XcmConverterError {
UnexpectedEndOfXcm,
EndOfXcmMessageExpected,
WithdrawAssetExpected,
DepositAssetExpected,
NoReserveAssets,
FilterDoesNotConsumeAllAssets,
TooManyAssets,
ZeroAssetTransfer,
BeneficiaryResolutionFailed,
AssetResolutionFailed,
InvalidFeeAsset,
SetTopicExpected,
ReserveAssetDepositedExpected,
InvalidAsset,
UnexpectedInstruction,
TooManyCommands,
AliasOriginExpected,
InvalidOrigin,
TransactDecodeFailed,
TransactParamsDecodeFailed,
FeeAssetResolutionFailed,
CallContractValueInsufficient,
}
macro_rules! match_expression {
($expression:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )?, $value:expr $(,)?) => {
match $expression {
$( $pattern )|+ $( if $guard )? => Some($value),
_ => None,
}
};
}
pub struct XcmConverter<'a, ConvertAssetId, Call> {
iter: Peekable<Iter<'a, Instruction<Call>>>,
ethereum_network: NetworkId,
_marker: PhantomData<ConvertAssetId>,
}
impl<'a, ConvertAssetId, Call> XcmConverter<'a, ConvertAssetId, Call>
where
ConvertAssetId: MaybeEquivalence<TokenId, Location>,
{
pub fn new(message: &'a Xcm<Call>, ethereum_network: NetworkId) -> Self {
Self {
iter: message.inner().iter().peekable(),
ethereum_network,
_marker: Default::default(),
}
}
fn next(&mut self) -> Result<&'a Instruction<Call>, XcmConverterError> {
self.iter.next().ok_or(XcmConverterError::UnexpectedEndOfXcm)
}
fn peek(&mut self) -> Result<&&'a Instruction<Call>, XcmConverterError> {
self.iter.peek().ok_or(XcmConverterError::UnexpectedEndOfXcm)
}
fn network_matches(&self, network: &Option<NetworkId>) -> bool {
if let Some(network) = network {
*network == self.ethereum_network
} else {
true
}
}
fn extract_remote_fee(&mut self) -> Result<u128, XcmConverterError> {
use XcmConverterError::*;
let reserved_fee_assets = match_expression!(self.next()?, WithdrawAsset(fee), fee)
.ok_or(WithdrawAssetExpected)?;
ensure!(reserved_fee_assets.len() == 1, AssetResolutionFailed);
let reserved_fee_asset =
reserved_fee_assets.inner().first().cloned().ok_or(AssetResolutionFailed)?;
let (reserved_fee_asset_id, reserved_fee_amount) = match reserved_fee_asset {
Asset { id: asset_id, fun: Fungible(amount) } => Ok((asset_id, amount)),
_ => Err(AssetResolutionFailed),
}?;
let fee_asset =
match_expression!(self.next()?, PayFees { asset: fee }, fee).ok_or(InvalidFeeAsset)?;
let (fee_asset_id, fee_amount) = match fee_asset {
Asset { id: asset_id, fun: Fungible(amount) } => Ok((asset_id, *amount)),
_ => Err(AssetResolutionFailed),
}?;
ensure!(fee_asset_id.0 == Here.into(), InvalidFeeAsset);
ensure!(reserved_fee_asset_id.0 == Here.into(), InvalidFeeAsset);
ensure!(reserved_fee_amount >= fee_amount, InvalidFeeAsset);
Ok(fee_amount)
}
fn extract_ethereum_native_assets(
&mut self,
enas: &Assets,
deposit_assets: &AssetFilter,
recipient: H160,
) -> Result<Vec<Command>, XcmConverterError> {
let mut commands: Vec<Command> = Vec::new();
for ena in enas.clone().into_inner().into_iter() {
if !deposit_assets.matches(&ena) {
return Err(FilterDoesNotConsumeAllAssets);
}
let (token, amount) = match ena {
Asset { id: AssetId(inner_location), fun: Fungible(amount) } =>
match inner_location.unpack() {
(0, [AccountKey20 { network, key }]) if self.network_matches(network) =>
Ok((H160(*key), amount)),
(0, []) => Ok((H160([0; 20]), amount)),
_ => Err(AssetResolutionFailed),
},
_ => Err(AssetResolutionFailed),
}?;
ensure!(amount > 0, ZeroAssetTransfer);
commands.push(Command::UnlockNativeToken { token, recipient, amount });
}
Ok(commands)
}
fn extract_polkadot_native_assets(
&mut self,
pnas: &Assets,
deposit_assets: &AssetFilter,
recipient: H160,
) -> Result<Vec<Command>, XcmConverterError> {
let mut commands: Vec<Command> = Vec::new();
ensure!(pnas.len() > 0, NoReserveAssets);
for pna in pnas.clone().into_inner().into_iter() {
if !deposit_assets.matches(&pna) {
return Err(FilterDoesNotConsumeAllAssets);
}
let Asset { id: AssetId(asset_id), fun: Fungible(amount) } = pna else {
return Err(AssetResolutionFailed);
};
ensure!(amount > 0, ZeroAssetTransfer);
let token_id = TokenIdOf::convert_location(&asset_id).ok_or(InvalidAsset)?;
let expected_asset_id = ConvertAssetId::convert(&token_id).ok_or(InvalidAsset)?;
ensure!(asset_id == expected_asset_id, InvalidAsset);
commands.push(Command::MintForeignToken { token_id, recipient, amount });
}
Ok(commands)
}
pub fn convert(&mut self) -> Result<Message, XcmConverterError> {
let fee_amount = self.extract_remote_fee()?;
let mut enas =
match_expression!(self.peek(), Ok(WithdrawAsset(reserve_assets)), reserve_assets);
if enas.is_some() {
let _ = self.next();
}
let pnas = match_expression!(
self.peek(),
Ok(ReserveAssetDeposited(reserve_assets)),
reserve_assets
);
if pnas.is_some() {
let _ = self.next();
}
if enas.is_none() {
enas =
match_expression!(self.peek(), Ok(WithdrawAsset(reserve_assets)), reserve_assets);
if enas.is_some() {
let _ = self.next();
}
}
let origin_location = match_expression!(self.next()?, AliasOrigin(origin), origin)
.ok_or(AliasOriginExpected)?;
let origin = AgentIdOf::convert_location(origin_location).ok_or(InvalidOrigin)?;
let (deposit_assets, beneficiary) = match_expression!(
self.next()?,
DepositAsset { assets, beneficiary },
(assets, beneficiary)
)
.ok_or(DepositAssetExpected)?;
let recipient = match_expression!(
beneficiary.unpack(),
(0, [AccountKey20 { network, key }])
if self.network_matches(network),
H160(*key)
)
.ok_or(BeneficiaryResolutionFailed)?;
if enas.is_none() && pnas.is_none() {
return Err(NoReserveAssets);
}
let mut commands: Vec<Command> = Vec::new();
if let Some(enas) = enas {
commands.append(&mut self.extract_ethereum_native_assets(
enas,
deposit_assets,
recipient,
)?);
}
if let Some(pnas) = pnas {
commands.append(&mut self.extract_polkadot_native_assets(
pnas,
deposit_assets,
recipient,
)?);
}
let transact_call = match_expression!(self.peek(), Ok(Transact { call, .. }), call);
if let Some(transact_call) = transact_call {
let _ = self.next();
let transact =
ContractCall::decode_all(&mut transact_call.clone().into_encoded().as_slice())
.map_err(|_| TransactDecodeFailed)?;
match transact {
ContractCall::V1 { target, calldata, gas, value } => commands
.push(Command::CallContract { target: target.into(), calldata, gas, value }),
}
}
let topic_id = match_expression!(self.next()?, SetTopic(id), id).ok_or(SetTopicExpected)?;
let message = Message {
id: (*topic_id).into(),
origin,
fee: fee_amount,
commands: BoundedVec::try_from(commands).map_err(|_| TooManyCommands)?,
};
if self.next().is_ok() {
return Err(EndOfXcmMessageExpected);
}
Ok(message)
}
}