Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for transfer_assets_using_type_and_then extrinsic in pallet-xcm precompile #54

Merged
merged 12 commits into from
Oct 8, 2024
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
Agusrodri marked this conversation as resolved.
Show resolved Hide resolved
}

/// @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
Agusrodri marked this conversation as resolved.
Show resolved Hide resolved
) 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,
Agusrodri marked this conversation as resolved.
Show resolved Hide resolved
}

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,
Agusrodri marked this conversation as resolved.
Show resolved Hide resolved
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 => {
Agusrodri marked this conversation as resolved.
Show resolved Hide resolved
return Ok(TransferType::RemoteReserve(VersionedLocation::V4(
remote_reserve,
)))
}
}
}
}
Loading