diff --git a/precompiles/pallet-xcm/XcmInterface.sol b/precompiles/pallet-xcm/XcmInterface.sol index 7bc632db..7cd75ff6 100644 --- a/precompiles/pallet-xcm/XcmInterface.sol +++ b/precompiles/pallet-xcm/XcmInterface.sol @@ -18,23 +18,57 @@ interface XCM { } // A way to represent fungible assets in XCM - struct Asset { + struct AssetLocationInfo { Location location; uint256 amount; } + struct AssetAddressInfo { + address asset; + uint256 amount; + } + /// @dev Function to send assets via XCM using transfer_assets() pallet-xcm extrinsic. - /// @custom:selector 650ef8c7 + /// @custom:selector 59df8416 /// @param dest The destination chain. /// @param beneficiary The actual account that will receive the tokens in dest. /// @param assets The combination (array) of assets to send. /// @param feeAssetItem The index of the asset that will be used to pay for fees. /// @param weight The weight to be used for the whole XCM operation. /// (uint64::MAX in refTime means Unlimited weight) - function transferAssets( + function transferAssetsLocation( Location memory dest, Location memory beneficiary, - Asset[] memory assets, + AssetLocationInfo[] memory assets, + uint32 feeAssetItem, + Weight memory weight + ) external; + + /// TODO add docs + /// @custom:selector b489262e + function transferAssetsToPara20( + uint32 paraId, + address beneficiary, + AssetAddressInfo[] memory assets, + uint32 feeAssetItem, + Weight memory weight + ) external; + + /// TODO add docs + /// @custom:selector 4461e6f5 + function transferAssetsToPara32( + uint32 paraId, + bytes32 beneficiary, + AssetAddressInfo[] memory assets, + uint32 feeAssetItem, + Weight memory weight + ) external; + + /// TODO add docs + /// @custom:selector d7c89659 + function transferAssetsToRelay( + bytes32 beneficiary, + AssetAddressInfo[] memory assets, uint32 feeAssetItem, Weight memory weight ) external; diff --git a/precompiles/pallet-xcm/src/lib.rs b/precompiles/pallet-xcm/src/lib.rs index 0c9f1f13..a8131e5d 100644 --- a/precompiles/pallet-xcm/src/lib.rs +++ b/precompiles/pallet-xcm/src/lib.rs @@ -16,7 +16,7 @@ #![cfg_attr(not(feature = "std"), no_std)] -use fp_evm::PrecompileHandle; +use fp_evm::{PrecompileFailure, PrecompileHandle}; use frame_support::{ dispatch::{GetDispatchInfo, PostDispatchInfo}, traits::ConstU32, @@ -24,7 +24,7 @@ use frame_support::{ use pallet_evm::AddressMapping; use precompile_utils::prelude::*; -use sp_core::U256; +use sp_core::{H256, U256}; use sp_runtime::traits::Dispatchable; use sp_std::marker::PhantomData; use sp_weights::Weight; @@ -33,7 +33,13 @@ use xcm::{ prelude::WeightLimit::*, VersionedAssets, VersionedLocation, }; -use xcm_primitives::location_converter::AccountIdToLocationMatcher; +use xcm_primitives::{ + generators::{ + XcmLocalBeneficiary20Generator, XcmLocalBeneficiary32Generator, + XcmSiblingDestinationGenerator, + }, + location_matcher::AccountIdToLocationMatcher, +}; #[cfg(test)] mod mock; @@ -57,14 +63,14 @@ where LocationMatcher: AccountIdToLocationMatcher<::AccountId>, { #[precompile::public( - "transferAssets(\ + "transferAssetsLocation(\ (uint8,bytes[]),\ (uint8,bytes[]),\ ((uint8,bytes[]),uint256)[],\ uint32,\ (uint64,uint64))" )] - fn transfer_assets( + fn transfer_assets_location( handle: &mut impl PrecompileHandle, dest: Location, beneficiary: Location, @@ -104,4 +110,153 @@ where RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; Ok(()) } + + #[precompile::public( + "transferAssetsToPara20(\ + uint32,\ + address,\ + (address,uint256)[],\ + uint32,\ + (uint64,uint64))" + )] + fn transfer_assets_to_para_20( + handle: &mut impl PrecompileHandle, + para_id: u32, + beneficiary: Address, + assets: BoundedVec<(Address, Convert), GetArrayLimit>, + fee_asset_item: u32, + weight: Weight, + ) -> EvmResult { + // TODO: account for a possible storage read inside LocationMatcher::convert() + + let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); + let assets: Vec<_> = assets.into(); + + let assets_to_send: Vec = Self::check_and_prepare_assets(assets)?; + + let weight_limit = match weight.ref_time() { + u64::MAX => Unlimited, + _ => Limited(weight), + }; + + let dest = XcmSiblingDestinationGenerator::generate(para_id); + let beneficiary = XcmLocalBeneficiary20Generator::generate(beneficiary.0 .0); + + let call = pallet_xcm::Call::::transfer_assets { + dest: Box::new(VersionedLocation::V4(dest)), + beneficiary: Box::new(VersionedLocation::V4(beneficiary)), + assets: Box::new(VersionedAssets::V4(assets_to_send.into())), + fee_asset_item, + weight_limit, + }; + + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + Ok(()) + } + + #[precompile::public( + "transferAssetsToPara32(\ + uint32,\ + bytes32,\ + (address,uint256)[],\ + uint32,\ + (uint64,uint64))" + )] + fn transfer_assets_to_para_32( + handle: &mut impl PrecompileHandle, + para_id: u32, + beneficiary: H256, + assets: BoundedVec<(Address, Convert), GetArrayLimit>, + fee_asset_item: u32, + weight: Weight, + ) -> EvmResult { + // TODO: account for a possible storage read inside LocationMatcher::convert() + + let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); + let assets: Vec<_> = assets.into(); + + let assets_to_send: Vec = Self::check_and_prepare_assets(assets)?; + + let weight_limit = match weight.ref_time() { + u64::MAX => Unlimited, + _ => Limited(weight), + }; + + let dest = XcmSiblingDestinationGenerator::generate(para_id); + let beneficiary = XcmLocalBeneficiary32Generator::generate(beneficiary.0); + + let call = pallet_xcm::Call::::transfer_assets { + dest: Box::new(VersionedLocation::V4(dest)), + beneficiary: Box::new(VersionedLocation::V4(beneficiary)), + assets: Box::new(VersionedAssets::V4(assets_to_send.into())), + fee_asset_item, + weight_limit, + }; + + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + Ok(()) + } + + #[precompile::public( + "transferAssetsToRelay(\ + bytes32,\ + (address,uint256)[],\ + uint32,\ + (uint64,uint64))" + )] + fn transfer_assets_to_relay( + handle: &mut impl PrecompileHandle, + beneficiary: H256, + assets: BoundedVec<(Address, Convert), GetArrayLimit>, + fee_asset_item: u32, + weight: Weight, + ) -> EvmResult { + // TODO: account for a possible storage read inside LocationMatcher::convert() + + let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); + let assets: Vec<_> = assets.into(); + + let assets_to_send: Vec = Self::check_and_prepare_assets(assets)?; + + let weight_limit = match weight.ref_time() { + u64::MAX => Unlimited, + _ => Limited(weight), + }; + + let dest = Location::parent(); + let beneficiary = XcmLocalBeneficiary32Generator::generate(beneficiary.0); + + let call = pallet_xcm::Call::::transfer_assets { + dest: Box::new(VersionedLocation::V4(dest)), + beneficiary: Box::new(VersionedLocation::V4(beneficiary)), + assets: Box::new(VersionedAssets::V4(assets_to_send.into())), + fee_asset_item, + weight_limit, + }; + + RuntimeHelper::::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)>, + ) -> Result, PrecompileFailure> { + let mut assets_to_send: Vec = vec![]; + 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 { + 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) + } } diff --git a/precompiles/pallet-xcm/src/mock.rs b/precompiles/pallet-xcm/src/mock.rs index d522a465..c0ae470e 100644 --- a/precompiles/pallet-xcm/src/mock.rs +++ b/precompiles/pallet-xcm/src/mock.rs @@ -39,7 +39,7 @@ use xcm_executor::{ traits::{ConvertLocation, TransactAsset, WeightTrader}, AssetsInHolding, }; -pub use xcm_primitives::location_converter::{ +pub use xcm_primitives::location_matcher::{ AssetIdInfoGetter, Erc20PalletMatcher, MatchThroughEquivalence, SingleAddressMatcher, }; use Junctions::Here; diff --git a/precompiles/pallet-xcm/src/tests.rs b/precompiles/pallet-xcm/src/tests.rs index 0d77b9f0..495c1adb 100644 --- a/precompiles/pallet-xcm/src/tests.rs +++ b/precompiles/pallet-xcm/src/tests.rs @@ -30,7 +30,10 @@ fn test_solidity_interface_has_all_function_selectors_documented_and_implemented #[test] fn selectors() { - assert!(PCall::transfer_assets_selectors().contains(&0x650ef8c7)); + assert!(PCall::transfer_assets_location_selectors().contains(&0x59df8416)); + assert!(PCall::transfer_assets_to_para_20_selectors().contains(&0xb489262e)); + assert!(PCall::transfer_assets_to_para_32_selectors().contains(&0x4461e6f5)); + assert!(PCall::transfer_assets_to_relay_selectors().contains(&0xd7c89659)); } #[test] @@ -39,7 +42,7 @@ fn modifiers() { let mut tester = PrecompilesModifierTester::new(PrecompilesValue::get(), Alice, Precompile1); - tester.test_default_modifier(PCall::transfer_assets_selectors()); + tester.test_default_modifier(PCall::transfer_assets_location_selectors()); }); } @@ -86,7 +89,7 @@ fn test_transfer_assets_works() { .prepare_test( Alice, Precompile1, - PCall::transfer_assets { + PCall::transfer_assets_location { dest, beneficiary, assets: vec![ @@ -130,7 +133,7 @@ fn test_transfer_assets_success_when_paying_fees_with_foreign_asset() { .prepare_test( Alice, Precompile1, - PCall::transfer_assets { + PCall::transfer_assets_location { dest, beneficiary, assets: vec![ @@ -176,7 +179,7 @@ fn test_transfer_assets_fails_fees_unknown_reserve() { .prepare_test( Alice, Precompile1, - PCall::transfer_assets { + PCall::transfer_assets_location { dest, beneficiary, assets: vec![ diff --git a/primitives/xcm/src/generators.rs b/primitives/xcm/src/generators.rs new file mode 100644 index 00000000..9ab46f53 --- /dev/null +++ b/primitives/xcm/src/generators.rs @@ -0,0 +1,40 @@ +// Copyright Moonsong Labs +// This file is part of Moonkit. + +// Moonkit is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonkit is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonkit. If not, see . + +use xcm::latest::{Junction::*, Location}; + +// TODO: Does it make sense to have this generators +// instead of retrieving the proper location on each case? +pub struct XcmSiblingDestinationGenerator; +impl XcmSiblingDestinationGenerator { + pub fn generate(para_id: u32) -> Location { + Location::new(1, Parachain(para_id)) + } +} + +pub struct XcmLocalBeneficiary20Generator; +impl XcmLocalBeneficiary20Generator { + pub fn generate(key: [u8; 20]) -> Location { + Location::new(0, AccountKey20 { network: None, key }) + } +} + +pub struct XcmLocalBeneficiary32Generator; +impl XcmLocalBeneficiary32Generator { + pub fn generate(id: [u8; 32]) -> Location { + Location::new(0, AccountId32 { network: None, id }) + } +} diff --git a/primitives/xcm/src/lib.rs b/primitives/xcm/src/lib.rs index 16cbeb89..0429d4ae 100644 --- a/primitives/xcm/src/lib.rs +++ b/primitives/xcm/src/lib.rs @@ -20,7 +20,8 @@ use sp_runtime::DispatchResult; -pub mod location_converter; +pub mod generators; +pub mod location_matcher; /// Pause and resume execution of XCM pub trait PauseXcmExecution { diff --git a/primitives/xcm/src/location_converter.rs b/primitives/xcm/src/location_matcher.rs similarity index 100% rename from primitives/xcm/src/location_converter.rs rename to primitives/xcm/src/location_matcher.rs