Skip to content
Merged
5 changes: 5 additions & 0 deletions precompiles/pallet-xcm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ xcm-primitives = { workspace = true }
# Substrate
frame-support = { workspace = true }
frame-system = { workspace = true }
scale-info = { workspace = true }
sp-core = { workspace = true }
sp-runtime = { workspace = true }
sp-std = { workspace = true }
sp-weights = { workspace = true }
parity-scale-codec = { workspace = true, features = [ "derive" ] }

# Frontier
evm = { workspace = true, features = [ "with-codec" ] }
Expand All @@ -28,6 +30,7 @@ pallet-evm = { workspace = true, features = [ "forbid-evm-reentrancy" ] }

# Polkadot
xcm = { workspace = true }
xcm-executor = { workspace = true }
pallet-xcm = { workspace = true }

# Cumulus
Expand Down Expand Up @@ -61,7 +64,9 @@ std = [
"frame-system/std",
"pallet-evm/std",
"pallet-xcm/std",
"parity-scale-codec/std",
"precompile-utils/std",
"scale-info/std",
"sp-core/std",
"sp-std/std",
"xcm/std",
Expand Down
32 changes: 32 additions & 0 deletions precompiles/pallet-xcm/XcmInterface.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ interface XCM {
uint256 amount;
}

// The values start at `0` and are represented as `uint8`
enum TransferType {
Teleport,
LocalReserve,
DestinationReserve,
RemoteReserve
}

/// @dev Function to send assets via XCM using transfer_assets() pallet-xcm extrinsic.
/// @custom:selector 59df8416
/// @param dest The destination chain.
Expand Down Expand Up @@ -93,4 +101,28 @@ interface XCM {
uint32 feeAssetItem,
Weight memory weight
) external;

function transferAssetsUsingTypeAndThenLocation(
Location memory dest,
AssetLocationInfo[] memory assets,
TransferType assetsTransferType,
Location memory maybeAssetsRemoteReserve,
uint8 remoteFeesIdIndex,
TransferType feesTransferType,
Location memory maybeFeesRemoteReserve,
bytes memory customXcmOnDest,
Weight memory weight
) external;

function transferAssetsUsingTypeAndThenAddress(
Location memory dest,
AssetAddressInfo[] memory assets,
TransferType assetsTransferType,
Location memory maybeAssetsRemoteReserve,
uint8 remoteFeesIdIndex,
TransferType feesTransferType,
Location memory maybeFeesRemoteReserve,
bytes memory customXcmOnDest,
Weight memory weight
) external;
}
207 changes: 200 additions & 7 deletions precompiles/pallet-xcm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,20 @@ use frame_support::{
traits::ConstU32,
};
use pallet_evm::AddressMapping;
use parity_scale_codec::{Decode, DecodeLimit, Encode, MaxEncodedLen};
use precompile_utils::prelude::*;

use sp_core::{MaxEncodedLen, H256, U256};
use scale_info::TypeInfo;
use sp_core::{H256, U256};
use sp_runtime::traits::Dispatchable;
use sp_std::{boxed::Box, marker::PhantomData, vec, vec::Vec};
use sp_weights::Weight;
use xcm::{
latest::{Asset, AssetId, Assets, Fungibility, Location},
prelude::WeightLimit::*,
VersionedAssets, VersionedLocation,
VersionedAssetId, VersionedAssets, VersionedLocation, VersionedXcm, MAX_XCM_DECODE_DEPTH,
};
use xcm_executor::traits::TransferType;
use xcm_primitives::{
generators::{
XcmLocalBeneficiary20Generator, XcmLocalBeneficiary32Generator,
Expand All @@ -49,6 +52,19 @@ mod tests;
pub const MAX_ASSETS_ARRAY_LIMIT: u32 = 2;
type GetArrayLimit = ConstU32<MAX_ASSETS_ARRAY_LIMIT>;

pub const XCM_SIZE_LIMIT: u32 = 2u32.pow(16);
type GetXcmSizeLimit = ConstU32<XCM_SIZE_LIMIT>;

#[derive(
Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, MaxEncodedLen, TypeInfo,
)]
pub enum TransferTypeHelper {
Teleport = 0,
LocalReserve = 1,
DestinationReserve = 2,
RemoteReserve = 3,
}

pub struct PalletXcmPrecompile<Runtime, LocationMatcher>(PhantomData<(Runtime, LocationMatcher)>);

#[precompile_utils::precompile]
Expand Down Expand Up @@ -259,6 +275,160 @@ where
Ok(())
}

#[precompile::public(
"transferAssetsUsingTypeAndThenLocation(\
(uint8,bytes[]),\
((uint8,bytes[]),uint256)[],\
uint8,\
(uint8,bytes[]),\
uint8,\
uint8,\
(uint8,bytes[]),\
bytes,\
(uint64,uint64))"
)]
fn transfer_assets_using_type_and_then_location(
handle: &mut impl PrecompileHandle,
dest: Location,
assets: BoundedVec<(Location, Convert<U256, u128>), GetArrayLimit>,
assets_transfer_type: u8,
maybe_assets_remote_reserve: Location,
remote_fees_id_index: u8,
fees_transfer_type: u8,
maybe_fees_remote_reserve: Location,
custom_xcm_on_dest: BoundedBytes<GetXcmSizeLimit>,
weight: Weight,
) -> EvmResult {
// No DB access before try_dispatch but some logical stuff.
// To prevent spam, we charge an arbitrary amount of gas.
handle.record_cost(1000)?;

let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
let assets: Vec<_> = assets.into();
let custom_xcm_on_dest: Vec<u8> = custom_xcm_on_dest.into();

let remote_fees_id: AssetId = {
let asset = assets
.get(remote_fees_id_index as usize)
.ok_or_else(|| RevertReason::custom("remote_fees_id not found"))?;
AssetId(asset.0.clone())
};

let assets_to_send: Assets = assets
.into_iter()
.map(|asset| Asset {
id: AssetId(asset.0),
fun: Fungibility::Fungible(asset.1.converted()),
})
.collect::<Vec<Asset>>()
.into();

let weight_limit = match weight.ref_time() {
u64::MAX => Unlimited,
_ => Limited(weight),
};

let assets_transfer_type =
Self::check_transfer_type(assets_transfer_type, maybe_assets_remote_reserve)?;
let fees_transfer_type =
Self::check_transfer_type(fees_transfer_type, maybe_fees_remote_reserve)?;

let custom_xcm_on_dest = VersionedXcm::<()>::decode_all_with_depth_limit(
MAX_XCM_DECODE_DEPTH,
&mut custom_xcm_on_dest.as_slice(),
)
.map_err(|_| RevertReason::custom("Failed decoding custom XCM message"))?;

let call = pallet_xcm::Call::<Runtime>::transfer_assets_using_type_and_then {
dest: Box::new(VersionedLocation::V4(dest)),
assets: Box::new(VersionedAssets::V4(assets_to_send)),
assets_transfer_type: Box::new(assets_transfer_type),
remote_fees_id: Box::new(VersionedAssetId::V4(remote_fees_id)),
fees_transfer_type: Box::new(fees_transfer_type),
custom_xcm_on_dest: Box::new(custom_xcm_on_dest),
weight_limit,
};

RuntimeHelper::<Runtime>::try_dispatch(handle, Some(origin).into(), call)?;

Ok(())
}

#[precompile::public(
"transferAssetsUsingTypeAndThenAddress(\
(uint8,bytes[]),\
(address,uint256)[],\
uint8,\
(uint8,bytes[]),\
uint8,\
uint8,\
(uint8,bytes[]),\
bytes,\
(uint64,uint64))"
)]
fn transfer_assets_using_type_and_then_address(
handle: &mut impl PrecompileHandle,
dest: Location,
assets: BoundedVec<(Address, Convert<U256, u128>), GetArrayLimit>,
assets_transfer_type: u8,
maybe_assets_remote_reserve: Location,
remote_fees_id_index: u8,
fees_transfer_type: u8,
maybe_fees_remote_reserve: Location,
custom_xcm_on_dest: BoundedBytes<GetXcmSizeLimit>,
weight: Weight,
) -> EvmResult {
// Account for a possible storage read inside LocationMatcher::convert().
//
// Storage items: AssetIdToForeignAsset (ForeignAssetCreator pallet) or AssetIdType (AssetManager pallet).
//
// Blake2_128(16) + AssetId(16) + Location
handle.record_db_read::<Runtime>(32 + Location::max_encoded_len())?;
handle.record_cost(1000)?;

let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
let assets: Vec<_> = assets.into();
let custom_xcm_on_dest: Vec<u8> = custom_xcm_on_dest.into();

let assets_to_send: Vec<Asset> = Self::check_and_prepare_assets(assets)?;
let remote_fees_id: AssetId = {
let asset = assets_to_send
.get(remote_fees_id_index as usize)
.ok_or_else(|| RevertReason::custom("remote_fees_id not found"))?;
AssetId(asset.id.0.clone())
};

let weight_limit = match weight.ref_time() {
u64::MAX => Unlimited,
_ => Limited(weight),
};

let assets_transfer_type =
Self::check_transfer_type(assets_transfer_type, maybe_assets_remote_reserve)?;
let fees_transfer_type =
Self::check_transfer_type(fees_transfer_type, maybe_fees_remote_reserve)?;

let custom_xcm_on_dest = VersionedXcm::<()>::decode_all_with_depth_limit(
MAX_XCM_DECODE_DEPTH,
&mut custom_xcm_on_dest.as_slice(),
)
.map_err(|_| RevertReason::custom("Failed decoding custom XCM message"))?;

let call = pallet_xcm::Call::<Runtime>::transfer_assets_using_type_and_then {
dest: Box::new(VersionedLocation::V4(dest)),
assets: Box::new(VersionedAssets::V4(assets_to_send.into())),
assets_transfer_type: Box::new(assets_transfer_type),
remote_fees_id: Box::new(VersionedAssetId::V4(remote_fees_id)),
fees_transfer_type: Box::new(fees_transfer_type),
custom_xcm_on_dest: Box::new(custom_xcm_on_dest),
weight_limit,
};

RuntimeHelper::<Runtime>::try_dispatch(handle, Some(origin).into(), call)?;

Ok(())
}

// Helper function to convert and prepare each asset into a proper Location.
fn check_and_prepare_assets(
assets: Vec<(Address, Convert<U256, u128>)>,
Expand All @@ -267,14 +437,37 @@ where
for asset in assets {
let asset_account = Runtime::AddressMapping::into_account_id(asset.0 .0);
let asset_location = LocationMatcher::convert(asset_account);
if asset_location == None {

if let Some(asset_loc) = asset_location {
assets_to_send.push(Asset {
id: AssetId(asset_loc),
fun: Fungibility::Fungible(asset.1.converted()),
})
} else {
return Err(revert("Asset not found"));
}
assets_to_send.push(Asset {
id: AssetId(asset_location.unwrap_or_default()),
fun: Fungibility::Fungible(asset.1.converted()),
})
}
Ok(assets_to_send)
}

fn check_transfer_type(
transfer_type: u8,
remote_reserve: Location,
) -> Result<TransferType, PrecompileFailure> {
let transfer_type_helper: TransferTypeHelper = TransferTypeHelper::decode(
&mut transfer_type.to_le_bytes().as_slice(),
)
.map_err(|_| RevertReason::custom("Failed decoding value for TransferTypeHelper"))?;

match transfer_type_helper {
TransferTypeHelper::Teleport => return Ok(TransferType::Teleport),
TransferTypeHelper::LocalReserve => return Ok(TransferType::LocalReserve),
TransferTypeHelper::DestinationReserve => return Ok(TransferType::DestinationReserve),
TransferTypeHelper::RemoteReserve => {
return Ok(TransferType::RemoteReserve(VersionedLocation::V4(
remote_reserve,
)))
}
}
}
}