From 904470b81b5d72b4ce50f8bc1ebea43c2a19d608 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Fri, 2 Jun 2023 10:03:42 +0530 Subject: [PATCH 01/33] add SetAppendix in remote_transact --- precompiles/xcm/XCM.sol | 5 +-- precompiles/xcm/src/lib.rs | 70 ++++++++++++++++++++++++------------ precompiles/xcm/src/tests.rs | 1 + 3 files changed, 52 insertions(+), 24 deletions(-) diff --git a/precompiles/xcm/XCM.sol b/precompiles/xcm/XCM.sol index 19af7ae9..77ce8905 100644 --- a/precompiles/xcm/XCM.sol +++ b/precompiles/xcm/XCM.sol @@ -61,12 +61,13 @@ interface XCM { * @return bool confirmation whether the XCM message sent. */ function remote_transact( - uint256 parachain_id, + uint256 dest_parachain_id, bool is_relay, address payment_asset_id, uint256 payment_amount, bytes calldata call, - uint64 transact_weight + uint64 transact_weight, + uint256 self_parachain_id ) external returns (bool); /** diff --git a/precompiles/xcm/src/lib.rs b/precompiles/xcm/src/lib.rs index 985efcdf..54e6481a 100644 --- a/precompiles/xcm/src/lib.rs +++ b/precompiles/xcm/src/lib.rs @@ -38,7 +38,6 @@ use precompile_utils::{ revert, succeed, Address, Bytes, EvmDataWriter, EvmResult, FunctionModifier, PrecompileHandleExt, RuntimeHelper, }; - #[cfg(test)] mod mock; #[cfg(test)] @@ -70,6 +69,7 @@ where + AddressToAssetId<::AssetId>, <::RuntimeCall as Dispatchable>::RuntimeOrigin: From>, + ::AccountId: Into<[u8; 32]>, ::RuntimeCall: From> + Dispatchable + GetDispatchInfo, C: Convert::AssetId>, @@ -106,17 +106,19 @@ enum BeneficiaryType { Account20, } -impl XcmPrecompile +impl XcmPrecompile where - R: pallet_evm::Config + Runtime: pallet_evm::Config + pallet_xcm::Config + pallet_assets::Config - + AddressToAssetId<::AssetId>, - <::RuntimeCall as Dispatchable>::RuntimeOrigin: - From>, - ::RuntimeCall: - From> + Dispatchable + GetDispatchInfo, - C: Convert::AssetId>, + + AddressToAssetId<::AssetId>, + <::RuntimeCall as Dispatchable>::RuntimeOrigin: + From>, + ::AccountId: Into<[u8; 32]>, + ::RuntimeCall: From> + + Dispatchable + + GetDispatchInfo, + C: Convert::AssetId>, { fn assets_withdraw( handle: &mut impl PrecompileHandle, @@ -131,7 +133,7 @@ where .iter() .cloned() .filter_map(|address| { - R::address_to_asset_id(address.into()).and_then(|x| C::reverse_ref(x).ok()) + Runtime::address_to_asset_id(address.into()).and_then(|x| C::reverse_ref(x).ok()) }) .collect(); let amounts_raw = input.read::>()?; @@ -189,8 +191,11 @@ where .into(); // Build call with origin. - let origin = Some(R::AddressMapping::into_account_id(handle.context().caller)).into(); - let call = pallet_xcm::Call::::reserve_withdraw_assets { + let origin = Some(Runtime::AddressMapping::into_account_id( + handle.context().caller, + )) + .into(); + let call = pallet_xcm::Call::::reserve_withdraw_assets { dest: Box::new(dest.into()), beneficiary: Box::new(beneficiary.into()), assets: Box::new(assets.into()), @@ -198,7 +203,7 @@ where }; // Dispatch a call. - RuntimeHelper::::try_dispatch(handle, origin, call)?; + RuntimeHelper::::try_dispatch(handle, origin, call)?; Ok(succeed(EvmDataWriter::new().write(true).build())) } @@ -216,6 +221,8 @@ where let remote_call: Vec = input.read::()?.into(); let transact_weight = input.read::()?; + let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); + let self_para_id = input.read::()?.low_u32(); log::trace!(target: "xcm-precompile:remote_transact", "Raw arguments: para_id: {}, is_relay: {}, fee_asset_addr: {:?}, \ fee_amount: {:?}, remote_call: {:?}, transact_weight: {}", @@ -235,7 +242,7 @@ where if address == NATIVE_ADDRESS { Here.into() } else { - let fee_asset_id = R::address_to_asset_id(address) + let fee_asset_id = Runtime::address_to_asset_id(address) .ok_or(revert("Failed to resolve fee asset id from address"))?; C::reverse_ref(fee_asset_id).map_err(|_| { revert("Failed to resolve fee asset multilocation from local id") @@ -248,7 +255,7 @@ where } let fee_amount = fee_amount.low_u128(); - let context = R::UniversalLocation::get(); + let context = Runtime::UniversalLocation::get(); let fee_multilocation = MultiAsset { id: Concrete(fee_asset), fun: Fungible(fee_amount), @@ -259,6 +266,19 @@ where // Prepare XCM let xcm = Xcm(vec![ + SetAppendix(Xcm(vec![DepositAsset { + assets: All.into(), + beneficiary: MultiLocation { + parents: 1, + interior: X2( + Parachain(self_para_id), + AccountId32 { + network: None, + id: origin.into(), + }, + ), + }, + }])), WithdrawAsset(fee_multilocation.clone().into()), BuyExecution { fees: fee_multilocation.clone().into(), @@ -274,14 +294,17 @@ where log::trace!(target: "xcm-precompile:remote_transact", "Processed arguments: dest: {:?}, fee asset: {:?}, XCM: {:?}", dest, fee_multilocation, xcm); // Build call with origin. - let origin = Some(R::AddressMapping::into_account_id(handle.context().caller)).into(); - let call = pallet_xcm::Call::::send { + let origin = Some(Runtime::AddressMapping::into_account_id( + handle.context().caller, + )) + .into(); + let call = pallet_xcm::Call::::send { dest: Box::new(dest.into()), message: Box::new(xcm::VersionedXcm::V3(xcm)), }; // Dispatch a call. - RuntimeHelper::::try_dispatch(handle, origin, call)?; + RuntimeHelper::::try_dispatch(handle, origin, call)?; Ok(succeed(EvmDataWriter::new().write(true).build())) } @@ -305,7 +328,7 @@ where if address == NATIVE_ADDRESS { Some(Here.into()) } else { - R::address_to_asset_id(address).and_then(|x| C::reverse_ref(x).ok()) + Runtime::address_to_asset_id(address).and_then(|x| C::reverse_ref(x).ok()) } }) .collect(); @@ -366,8 +389,11 @@ where .into(); // Build call with origin. - let origin = Some(R::AddressMapping::into_account_id(handle.context().caller)).into(); - let call = pallet_xcm::Call::::reserve_transfer_assets { + let origin = Some(Runtime::AddressMapping::into_account_id( + handle.context().caller, + )) + .into(); + let call = pallet_xcm::Call::::reserve_transfer_assets { dest: Box::new(dest.into()), beneficiary: Box::new(beneficiary.into()), assets: Box::new(assets.into()), @@ -375,7 +401,7 @@ where }; // Dispatch a call. - RuntimeHelper::::try_dispatch(handle, origin, call)?; + RuntimeHelper::::try_dispatch(handle, origin, call)?; Ok(succeed(EvmDataWriter::new().write(true).build())) } diff --git a/precompiles/xcm/src/tests.rs b/precompiles/xcm/src/tests.rs index 8604dc44..de0308b6 100644 --- a/precompiles/xcm/src/tests.rs +++ b/precompiles/xcm/src/tests.rs @@ -120,6 +120,7 @@ fn remote_transact_works() { .write(U256::from(367)) .write(vec![0xff_u8, 0xaa, 0x77, 0x00]) .write(U256::from(3_000_000_000u64)) + .write(U256::from(1_u64)) .build(), ) .expect_no_logs() From 401c1cec65ce70ed84a98ba7fd9cdbebc95af1c9 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Fri, 2 Jun 2023 13:19:57 +0530 Subject: [PATCH 02/33] formatting abstract params --- precompiles/xcm/src/lib.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/precompiles/xcm/src/lib.rs b/precompiles/xcm/src/lib.rs index 54e6481a..3ffd3400 100644 --- a/precompiles/xcm/src/lib.rs +++ b/precompiles/xcm/src/lib.rs @@ -61,18 +61,18 @@ const NATIVE_ADDRESS: H160 = H160::zero(); /// A precompile that expose XCM related functions. pub struct XcmPrecompile(PhantomData<(T, C)>); -impl Precompile for XcmPrecompile +impl Precompile for XcmPrecompile where - R: pallet_evm::Config + Runtime: pallet_evm::Config + pallet_xcm::Config + pallet_assets::Config - + AddressToAssetId<::AssetId>, - <::RuntimeCall as Dispatchable>::RuntimeOrigin: - From>, - ::AccountId: Into<[u8; 32]>, - ::RuntimeCall: - From> + Dispatchable + GetDispatchInfo, - C: Convert::AssetId>, + + AddressToAssetId<::AssetId>, + <::RuntimeCall as Dispatchable>::RuntimeOrigin: + From>, + ::AccountId: Into<[u8; 32]>, + ::RuntimeCall: + From> + Dispatchable + GetDispatchInfo, + C: Convert::AssetId>, { fn execute(handle: &mut impl PrecompileHandle) -> EvmResult { log::trace!(target: "xcm-precompile", "In XCM precompile"); From bcff3fd32c3b6ced4297d8eba2571451d414125e Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Fri, 2 Jun 2023 18:49:42 +0530 Subject: [PATCH 03/33] update remote_transact() --- precompiles/xcm/XCM.sol | 3 +-- precompiles/xcm/src/lib.rs | 6 ++++-- precompiles/xcm/src/mock.rs | 14 +++++++------- precompiles/xcm/src/tests.rs | 3 +-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/precompiles/xcm/XCM.sol b/precompiles/xcm/XCM.sol index 77ce8905..5c7e339a 100644 --- a/precompiles/xcm/XCM.sol +++ b/precompiles/xcm/XCM.sol @@ -61,13 +61,12 @@ interface XCM { * @return bool confirmation whether the XCM message sent. */ function remote_transact( - uint256 dest_parachain_id, + uint256 parachain_id, bool is_relay, address payment_asset_id, uint256 payment_amount, bytes calldata call, uint64 transact_weight, - uint256 self_parachain_id ) external returns (bool); /** diff --git a/precompiles/xcm/src/lib.rs b/precompiles/xcm/src/lib.rs index 3ffd3400..48f41531 100644 --- a/precompiles/xcm/src/lib.rs +++ b/precompiles/xcm/src/lib.rs @@ -222,7 +222,6 @@ where let remote_call: Vec = input.read::()?.into(); let transact_weight = input.read::()?; let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); - let self_para_id = input.read::()?.low_u32(); log::trace!(target: "xcm-precompile:remote_transact", "Raw arguments: para_id: {}, is_relay: {}, fee_asset_addr: {:?}, \ fee_amount: {:?}, remote_call: {:?}, transact_weight: {}", @@ -263,6 +262,7 @@ where let fee_multilocation = fee_multilocation .reanchored(&dest, context) .map_err(|_| revert("Failed to reanchor fee asset"))?; + println!("Context {:?}",context); // Prepare XCM let xcm = Xcm(vec![ @@ -271,7 +271,9 @@ where beneficiary: MultiLocation { parents: 1, interior: X2( - Parachain(self_para_id), + // last() returns the last Juction Enum in Junctions + // for Univeral Location it is the Parachain() variant + *context.last().unwrap(), AccountId32 { network: None, id: origin.into(), diff --git a/precompiles/xcm/src/mock.rs b/precompiles/xcm/src/mock.rs index 839484ab..c13eba4b 100644 --- a/precompiles/xcm/src/mock.rs +++ b/precompiles/xcm/src/mock.rs @@ -182,18 +182,18 @@ impl frame_system::Config for Runtime { #[derive(Debug, Clone, Copy)] pub struct TestPrecompileSet(PhantomData); -impl PrecompileSet for TestPrecompileSet +impl PrecompileSet for TestPrecompileSet where - R: pallet_evm::Config + Runtime: pallet_evm::Config + pallet_xcm::Config + pallet_assets::Config - + AddressToAssetId<::AssetId>, - XcmPrecompile>: Precompile, + + AddressToAssetId<::AssetId>, + XcmPrecompile>: Precompile, { fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { match handle.code_address() { a if a == PRECOMPILE_ADDRESS => Some( - XcmPrecompile::>::execute(handle), + XcmPrecompile::>::execute(handle), ), _ => None, } @@ -312,9 +312,9 @@ impl pallet_evm::Config for Runtime { } parameter_types! { - pub const RelayLocation: MultiLocation = Here.into_location(); + pub RelayNetwork: Option = Some(NetworkId::Polkadot); pub const AnyNetwork: Option = None; - pub UniversalLocation: InteriorMultiLocation = Here; + pub UniversalLocation: InteriorMultiLocation = X2(GlobalConsensus(RelayNetwork::get().unwrap()), Parachain(123)); pub Ancestry: MultiLocation = Here.into(); pub UnitWeightCost: u64 = 1_000; pub const MaxAssetsIntoHolding: u32 = 64; diff --git a/precompiles/xcm/src/tests.rs b/precompiles/xcm/src/tests.rs index de0308b6..7ecf2f04 100644 --- a/precompiles/xcm/src/tests.rs +++ b/precompiles/xcm/src/tests.rs @@ -120,7 +120,6 @@ fn remote_transact_works() { .write(U256::from(367)) .write(vec![0xff_u8, 0xaa, 0x77, 0x00]) .write(U256::from(3_000_000_000u64)) - .write(U256::from(1_u64)) .build(), ) .expect_no_logs() @@ -255,7 +254,7 @@ fn reserve_transfer_currency_works() { fun: Fungible(42000), id: xcm::v3::AssetId::from(MultiLocation { parents: 0, - interior: X1(OnlyChild), + interior: X1(Parachain(123)), }), }; From cc97ce4fb2a1355f9648843a0128d968a3943fe9 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Sun, 4 Jun 2023 13:40:36 +0530 Subject: [PATCH 04/33] remove println : --- precompiles/xcm/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/precompiles/xcm/src/lib.rs b/precompiles/xcm/src/lib.rs index 48f41531..d118ea56 100644 --- a/precompiles/xcm/src/lib.rs +++ b/precompiles/xcm/src/lib.rs @@ -262,7 +262,6 @@ where let fee_multilocation = fee_multilocation .reanchored(&dest, context) .map_err(|_| revert("Failed to reanchor fee asset"))?; - println!("Context {:?}",context); // Prepare XCM let xcm = Xcm(vec![ From af7fcefb1019979a27644854e5a4c7a3aea93228 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Thu, 8 Jun 2023 13:31:05 +0530 Subject: [PATCH 05/33] add send_xcm function to precompile --- precompiles/xcm/XCM.sol | 13 ++++++++++ precompiles/xcm/src/lib.rs | 49 +++++++++++++++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/precompiles/xcm/XCM.sol b/precompiles/xcm/XCM.sol index 5c7e339a..9288e68a 100644 --- a/precompiles/xcm/XCM.sol +++ b/precompiles/xcm/XCM.sol @@ -114,4 +114,17 @@ interface XCM { uint256 parachain_id, uint256 fee_index ) external returns (bool); + + /** + * @dev Reserve transfer using PalletXCM call. + * @param xcm_call - encoded xcm call you want to send to destination + * @param is_relay - set `true` for using relay chain as destination + * @param destination_parachain_id - set parachain id of destination parachain (when is_relay set to false) + * @return A boolean confirming whether the XCM message sent. + **/ + function send_xcm( + bytes32 xcm_call, + bool is_realy, + uint256[] destination_parachain_id + ) external returns (bool); } diff --git a/precompiles/xcm/src/lib.rs b/precompiles/xcm/src/lib.rs index d118ea56..98edd7f3 100644 --- a/precompiles/xcm/src/lib.rs +++ b/precompiles/xcm/src/lib.rs @@ -25,12 +25,13 @@ use frame_support::{ pallet_prelude::Weight, traits::Get, }; +use parity_scale_codec::{DecodeLimit, MaxEncodedLen}; use pallet_evm::{AddressMapping, Precompile}; use sp_core::{H160, H256, U256}; use sp_std::marker::PhantomData; use sp_std::prelude::*; -use xcm::latest::prelude::*; +use xcm::{latest::prelude::*,VersionedXcm,MAX_XCM_DECODE_DEPTH}; use xcm_executor::traits::Convert; use pallet_evm_precompile_assets_erc20::AddressToAssetId; @@ -53,6 +54,7 @@ pub enum Action { "assets_reserve_transfer(address[],uint256[],bytes32,bool,uint256,uint256)", AssetsReserveTransferEvm = "assets_reserve_transfer(address[],uint256[],address,bool,uint256,uint256)", + SendXCM = "send_xcm(bytes32,bool,uint256[])" } /// Dummy H160 address representing native currency (e.g. ASTR or SDN) @@ -94,6 +96,9 @@ where Action::AssetsReserveTransferEvm => { Self::assets_reserve_transfer(handle, BeneficiaryType::Account20) } + Action::SendXCM => { + Self::send_xcm(handle) + } } } } @@ -406,4 +411,46 @@ where Ok(succeed(EvmDataWriter::new().write(true).build())) } + + fn send_xcm( + handle: &mut impl PrecompileHandle + ) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(3)?; + + // Raw call arguments + let xcm_call : Vec = input.read::()?.into(); + let is_relay = input.read::()?; + let dest_para_id: u32 = input.read::()?.low_u32(); + + let xcm = xcm::VersionedXcm::<()>::decode_all_with_depth_limit( + xcm::MAX_XCM_DECODE_DEPTH, + &mut xcm_call.as_slice(), + ).map_err(|_| { + revert("Failed to decode xcm instructions") + })?; + + log::trace!(target: "xcm-precompile:send_xcm", "Raw arguments: xcm_call: {:?}, is_relay: {}, destination_parachain_id: {:?}", xcm_call, is_relay,dest_para_id); + + let dest = if is_relay { + MultiLocation::parent() + } else { + X1(Junction::Parachain(dest_para_id)).into_exterior(1) + }; + + // Build call with origin. + let origin = Some(Runtime::AddressMapping::into_account_id( + handle.context().caller, + )) + .into(); + let call = pallet_xcm::Call::::send { + dest: Box::new(dest.into()), + message: Box::new(xcm), + }; + log::trace!(target: "xcm-precompile:send_xcm", "Processed arguments: XCM call: {:?}", call); + // Dispatch a call. + RuntimeHelper::::try_dispatch(handle, origin, call)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } } From 527b64272f719d812512c9a1c5740c86746dc00a Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Thu, 8 Jun 2023 13:33:20 +0530 Subject: [PATCH 06/33] fix remote_transact call --- precompiles/xcm/src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/precompiles/xcm/src/lib.rs b/precompiles/xcm/src/lib.rs index 98edd7f3..0968e891 100644 --- a/precompiles/xcm/src/lib.rs +++ b/precompiles/xcm/src/lib.rs @@ -270,6 +270,11 @@ where // Prepare XCM let xcm = Xcm(vec![ + WithdrawAsset(fee_multilocation.clone().into()), + BuyExecution { + fees: fee_multilocation.clone().into(), + weight_limit: WeightLimit::Unlimited, + }, SetAppendix(Xcm(vec![DepositAsset { assets: All.into(), beneficiary: MultiLocation { @@ -285,11 +290,6 @@ where ), }, }])), - WithdrawAsset(fee_multilocation.clone().into()), - BuyExecution { - fees: fee_multilocation.clone().into(), - weight_limit: WeightLimit::Unlimited, - }, Transact { origin_kind: OriginKind::SovereignAccount, require_weight_at_most: Weight::from_ref_time(transact_weight), From 97b60aa83917b8e413976f7578fcbad2afe920f3 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Thu, 8 Jun 2023 14:30:05 +0530 Subject: [PATCH 07/33] small fixes xcm.sol --- precompiles/xcm/XCM.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/precompiles/xcm/XCM.sol b/precompiles/xcm/XCM.sol index 9288e68a..c3c8f383 100644 --- a/precompiles/xcm/XCM.sol +++ b/precompiles/xcm/XCM.sol @@ -66,7 +66,7 @@ interface XCM { address payment_asset_id, uint256 payment_amount, bytes calldata call, - uint64 transact_weight, + uint64 transact_weight ) external returns (bool); /** @@ -124,7 +124,7 @@ interface XCM { **/ function send_xcm( bytes32 xcm_call, - bool is_realy, - uint256[] destination_parachain_id + bool is_relay, + uint256[] calldata destination_parachain_id ) external returns (bool); } From 55886ec0d0180d2fa5fd327040e4fe1d85d675ba Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Thu, 8 Jun 2023 14:44:35 +0530 Subject: [PATCH 08/33] fix typos xcm.sol --- precompiles/xcm/XCM.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/precompiles/xcm/XCM.sol b/precompiles/xcm/XCM.sol index c3c8f383..328b3502 100644 --- a/precompiles/xcm/XCM.sol +++ b/precompiles/xcm/XCM.sol @@ -125,6 +125,6 @@ interface XCM { function send_xcm( bytes32 xcm_call, bool is_relay, - uint256[] calldata destination_parachain_id + uint256 destination_parachain_id ) external returns (bool); } From a9b7a5d1f493f7c7a998aba57d9f9468d60cd65a Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Thu, 8 Jun 2023 20:33:47 +0530 Subject: [PATCH 09/33] change bytes32 to bytes in send_xcm() --- precompiles/xcm/XCM.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/precompiles/xcm/XCM.sol b/precompiles/xcm/XCM.sol index 328b3502..b6b14251 100644 --- a/precompiles/xcm/XCM.sol +++ b/precompiles/xcm/XCM.sol @@ -123,7 +123,7 @@ interface XCM { * @return A boolean confirming whether the XCM message sent. **/ function send_xcm( - bytes32 xcm_call, + bytes calldata xcm_call, bool is_relay, uint256 destination_parachain_id ) external returns (bool); From 3df8b14eeda467b014420fa12f06c0c2b8a97f80 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Mon, 12 Jun 2023 10:43:58 +0530 Subject: [PATCH 10/33] add test for xcm_call --- precompiles/xcm/XCM.sol | 2 +- precompiles/xcm/src/lib.rs | 5 ++++- precompiles/xcm/src/tests.rs | 25 +++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/precompiles/xcm/XCM.sol b/precompiles/xcm/XCM.sol index b6b14251..7dd371a6 100644 --- a/precompiles/xcm/XCM.sol +++ b/precompiles/xcm/XCM.sol @@ -123,7 +123,7 @@ interface XCM { * @return A boolean confirming whether the XCM message sent. **/ function send_xcm( - bytes calldata xcm_call, + bytes memory xcm_call, bool is_relay, uint256 destination_parachain_id ) external returns (bool); diff --git a/precompiles/xcm/src/lib.rs b/precompiles/xcm/src/lib.rs index 0968e891..34ac8abe 100644 --- a/precompiles/xcm/src/lib.rs +++ b/precompiles/xcm/src/lib.rs @@ -422,6 +422,10 @@ where let xcm_call : Vec = input.read::()?.into(); let is_relay = input.read::()?; let dest_para_id: u32 = input.read::()?.low_u32(); + + log::trace!(target:"xcm-precompile:send_xcm", "Raw arguments: xcm_call: {:?}, is_relay: {}, destination_parachain_id: {:?}", xcm_call, is_relay,dest_para_id); + + let xcm_call: Vec<_> = xcm_call.to_vec(); let xcm = xcm::VersionedXcm::<()>::decode_all_with_depth_limit( xcm::MAX_XCM_DECODE_DEPTH, @@ -430,7 +434,6 @@ where revert("Failed to decode xcm instructions") })?; - log::trace!(target: "xcm-precompile:send_xcm", "Raw arguments: xcm_call: {:?}, is_relay: {}, destination_parachain_id: {:?}", xcm_call, is_relay,dest_para_id); let dest = if is_relay { MultiLocation::parent() diff --git a/precompiles/xcm/src/tests.rs b/precompiles/xcm/src/tests.rs index 7ecf2f04..32b693dc 100644 --- a/precompiles/xcm/src/tests.rs +++ b/precompiles/xcm/src/tests.rs @@ -24,6 +24,7 @@ use crate::*; use precompile_utils::testing::*; use precompile_utils::EvmDataWriter; use sp_core::H160; +use parity_scale_codec::{Encode,Decode}; fn precompiles() -> TestPrecompileSet { PrecompilesValue::get() @@ -280,3 +281,27 @@ fn reserve_transfer_currency_works() { ); } } + +#[test] +fn test_send_clear_origin() { + ExtBuilder::default().build().execute_with(|| { + let xcm_to_send = VersionedXcm::<()>::V3(Xcm(vec![ClearOrigin])).encode(); + precompiles() + .prepare_test(TestAccount::Alice, PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::SendXCM) + .write(xcm_to_send) + .write(false) + .write(U256::from(1_u64)) + .build(), + ) + // Fixed: TestWeightInfo + (BaseXcmWeight * MessageLen) + .expect_cost(100001000) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + let sent_messages = take_sent_xcm(); + let (_, sent_message) = sent_messages.first().unwrap(); + // Lets make sure the message is as expected + assert!(sent_message.0.contains(&ClearOrigin)); + }) +} \ No newline at end of file From a0646749391f6822f35f011261e26cdd8f512cd0 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Mon, 12 Jun 2023 12:00:15 +0530 Subject: [PATCH 11/33] fix xcm_send using Bytes --- precompiles/xcm/src/lib.rs | 4 ++-- precompiles/xcm/src/tests.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/precompiles/xcm/src/lib.rs b/precompiles/xcm/src/lib.rs index 34ac8abe..61a9f08d 100644 --- a/precompiles/xcm/src/lib.rs +++ b/precompiles/xcm/src/lib.rs @@ -423,7 +423,7 @@ where let is_relay = input.read::()?; let dest_para_id: u32 = input.read::()?.low_u32(); - log::trace!(target:"xcm-precompile:send_xcm", "Raw arguments: xcm_call: {:?}, is_relay: {}, destination_parachain_id: {:?}", xcm_call, is_relay,dest_para_id); + log::trace!(target:"xcm-send_xcm", "Raw arguments: xcm_call: {:?}, is_relay: {}, destination_parachain_id: {:?}", xcm_call, is_relay,dest_para_id); let xcm_call: Vec<_> = xcm_call.to_vec(); @@ -450,7 +450,7 @@ where dest: Box::new(dest.into()), message: Box::new(xcm), }; - log::trace!(target: "xcm-precompile:send_xcm", "Processed arguments: XCM call: {:?}", call); + log::trace!(target: "xcm-send_xcm", "Processed arguments: XCM call: {:?}", call); // Dispatch a call. RuntimeHelper::::try_dispatch(handle, origin, call)?; diff --git a/precompiles/xcm/src/tests.rs b/precompiles/xcm/src/tests.rs index 32b693dc..ec23c396 100644 --- a/precompiles/xcm/src/tests.rs +++ b/precompiles/xcm/src/tests.rs @@ -289,7 +289,7 @@ fn test_send_clear_origin() { precompiles() .prepare_test(TestAccount::Alice, PRECOMPILE_ADDRESS, EvmDataWriter::new_with_selector(Action::SendXCM) - .write(xcm_to_send) + .write(Bytes::from(xcm_to_send.as_slice())) .write(false) .write(U256::from(1_u64)) .build(), From 75a1bea648eb00a8a2c6693112bda0764c160023 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Tue, 13 Jun 2023 18:36:46 +0530 Subject: [PATCH 12/33] add multilocation for xcm-precompile --- precompiles/utils/src/bytes.rs | 218 ++++++++++++++++++++ precompiles/utils/src/data.rs | 7 +- precompiles/utils/src/lib.rs | 2 + precompiles/utils/src/xcm.rs | 362 +++++++++++++++++++++++++++++++++ precompiles/xcm/XCM.sol | 90 ++------ precompiles/xcm/src/lib.rs | 181 ++++++----------- precompiles/xcm/src/mock.rs | 6 +- precompiles/xcm/src/tests.rs | 184 ++++++++++++----- 8 files changed, 797 insertions(+), 253 deletions(-) create mode 100644 precompiles/utils/src/bytes.rs create mode 100644 precompiles/utils/src/xcm.rs diff --git a/precompiles/utils/src/bytes.rs b/precompiles/utils/src/bytes.rs new file mode 100644 index 00000000..cac5f368 --- /dev/null +++ b/precompiles/utils/src/bytes.rs @@ -0,0 +1,218 @@ +// Copyright 2019-2022 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam 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. + +// Moonbeam 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 Moonbeam. If not, see . + +use super::*; +use alloc::borrow::ToOwned; +use sp_core::{ConstU32, Get}; + +type ConstU32Max = ConstU32<{ u32::MAX }>; + +pub type UnboundedBytes = BoundedBytesString; +pub type BoundedBytes = BoundedBytesString; + +trait Kind { + fn signature() -> String; +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct BytesKind; + +impl Kind for BytesKind { + fn signature() -> String { + String::from("bytes") + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct StringKind; + +impl Kind for StringKind { + fn signature() -> String { + String::from("string") + } +} + +/// The `bytes/string` type of Solidity. +/// It is different from `Vec` which will be serialized with padding for each `u8` element +/// of the array, while `Bytes` is tightly packed. +#[derive(Debug)] +pub struct BoundedBytesString { + data: Vec, + _phantom: PhantomData<(K, S)>, +} + +impl> Clone for BoundedBytesString { + fn clone(&self) -> Self { + Self { + data: self.data.clone(), + _phantom: PhantomData, + } + } +} + +impl PartialEq> for BoundedBytesString { + fn eq(&self, other: &BoundedBytesString) -> bool { + self.data.eq(&other.data) + } +} + +impl Eq for BoundedBytesString {} + +impl> BoundedBytesString { + pub fn as_bytes(&self) -> &[u8] { + &self.data + } + + pub fn as_str(&self) -> Result<&str, sp_std::str::Utf8Error> { + sp_std::str::from_utf8(&self.data) + } +} + +impl> EvmData for BoundedBytesString { + fn read(reader: &mut EvmDataReader) -> EvmResult { + let mut inner_reader = reader.read_pointer()?; + + // Read bytes/string size. + let array_size: usize = inner_reader + .read::() + .map_err(|_| revert("length, out of bounds"))? + .try_into() + .map_err(|_| revert("length, value too large"))?; + + if array_size > S::get() as usize { + return Err(revert("length, value too large").into()); + } + + // Get valid range over the bytes data. + let range = inner_reader.move_cursor(array_size)?; + + let data = inner_reader + .input() + .get(range) + .ok_or_else(|| revert(K::signature()))?; + + let bytes = Self { + data: data.to_owned(), + _phantom: PhantomData, + }; + + Ok(bytes) + } + + fn write(writer: &mut EvmDataWriter, value: Self) { + let value: Vec<_> = value.into(); + let length = value.len(); + + // Pad the data. + // Leave it as is if a multiple of 32, otherwise pad to next + // multiple or 32. + let chunks = length / 32; + let padded_size = match length % 32 { + 0 => chunks * 32, + _ => (chunks + 1) * 32, + }; + + let mut value = value.to_vec(); + value.resize(padded_size, 0); + + writer.write_pointer( + EvmDataWriter::new() + .write(U256::from(length)) + .write_raw_bytes(&value) + .build(), + ); + } + + fn has_static_size() -> bool { + false + } + + // fn signature() -> String { + // K::signature() + // } +} + +// BytesString <=> Vec/&[u8] + +impl From> for Vec { + fn from(value: BoundedBytesString) -> Self { + value.data + } +} + +impl From> for BoundedBytesString { + fn from(value: Vec) -> Self { + Self { + data: value, + _phantom: PhantomData, + } + } +} + +impl From<&[u8]> for BoundedBytesString { + fn from(value: &[u8]) -> Self { + Self { + data: value.to_vec(), + _phantom: PhantomData, + } + } +} + +impl From<[u8; N]> for BoundedBytesString { + fn from(value: [u8; N]) -> Self { + Self { + data: value.to_vec(), + _phantom: PhantomData, + } + } +} + +impl From<&[u8; N]> for BoundedBytesString { + fn from(value: &[u8; N]) -> Self { + Self { + data: value.to_vec(), + _phantom: PhantomData, + } + } +} + +// BytesString <=> String/str + +impl TryFrom> for String { + type Error = alloc::string::FromUtf8Error; + + fn try_from(value: BoundedBytesString) -> Result { + alloc::string::String::from_utf8(value.data) + } +} + +impl From<&str> for BoundedBytesString { + fn from(value: &str) -> Self { + Self { + data: value.as_bytes().into(), + _phantom: PhantomData, + } + } +} + +impl From for BoundedBytesString { + fn from(value: String) -> Self { + Self { + data: value.as_bytes().into(), + _phantom: PhantomData, + } + } +} diff --git a/precompiles/utils/src/data.rs b/precompiles/utils/src/data.rs index 2359bcef..b32232d0 100644 --- a/precompiles/utils/src/data.rs +++ b/precompiles/utils/src/data.rs @@ -174,6 +174,9 @@ impl<'a> EvmDataReader<'a> { cursor: 0, }) } + pub fn input(&mut self) -> &[u8] { + self.input + } /// Read remaining bytes pub fn read_till_end(&mut self) -> EvmResult<&[u8]> { @@ -190,7 +193,7 @@ impl<'a> EvmDataReader<'a> { /// Move the reading cursor with provided length, and return a range from the previous cursor /// location to the new one. /// Checks cursor overflows. - fn move_cursor(&mut self, len: usize) -> EvmResult> { + pub fn move_cursor(&mut self, len: usize) -> EvmResult> { let start = self.cursor; let end = self .cursor @@ -285,7 +288,7 @@ impl EvmDataWriter { /// Write arbitrary bytes. /// Doesn't handle any alignement checks, prefer using `write` instead if possible. - fn write_raw_bytes(mut self, value: &[u8]) -> Self { + pub fn write_raw_bytes(mut self, value: &[u8]) -> Self { self.data.extend_from_slice(value); self } diff --git a/precompiles/utils/src/lib.rs b/precompiles/utils/src/lib.rs index f36412a2..6ad24788 100644 --- a/precompiles/utils/src/lib.rs +++ b/precompiles/utils/src/lib.rs @@ -37,7 +37,9 @@ use pallet_evm::{GasWeightMapping, Log}; use sp_core::{H160, H256, U256}; use sp_std::{marker::PhantomData, vec, vec::Vec}; +mod bytes; mod data; +mod xcm; pub use data::{Address, Bytes, EvmData, EvmDataReader, EvmDataWriter}; pub use precompile_utils_macro::{generate_function_selector, keccak256}; diff --git a/precompiles/utils/src/xcm.rs b/precompiles/utils/src/xcm.rs new file mode 100644 index 00000000..473a44dc --- /dev/null +++ b/precompiles/utils/src/xcm.rs @@ -0,0 +1,362 @@ +// Copyright 2019-2022 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam 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. + +// Moonbeam 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 Moonbeam. If not, see . + +//! Encoding of XCM types for solidity + +use { + crate::{bytes::*, revert, EvmData, EvmDataReader, EvmDataWriter, EvmResult}, + frame_support::{ensure, traits::ConstU32}, + sp_core::H256, + sp_std::vec::Vec, + xcm::latest::{Junction, Junctions, MultiLocation, NetworkId}, +}; + +pub const JUNCTION_SIZE_LIMIT: u32 = 2u32.pow(16); + +// Function to convert network id to bytes +// We don't implement solidity::Codec here as these bytes will be appended only +// to certain Junction variants +// Each NetworkId variant is represented as bytes +// The first byte represents the enum variant to be used. +// - Indexes 0,2,3 represent XCM V2 variants +// - Index 1 changes name in V3 (`ByGenesis`), but is compatible with V2 `Named` +// - Indexes 4~10 represent new XCM V3 variants +// The rest of the bytes (if any), represent the additional data that such enum variant requires +// In such a case, since NetworkIds will be appended at the end, we will read the buffer until the +// end to recover the name + +pub(crate) fn network_id_to_bytes(network_id: Option) -> Vec { + let mut encoded: Vec = Vec::new(); + match network_id.clone() { + None => { + encoded.push(0u8); + encoded + } + Some(NetworkId::ByGenesis(id)) => { + encoded.push(1u8); + encoded.append(&mut id.into()); + encoded + } + Some(NetworkId::Polkadot) => { + encoded.push(2u8); + encoded.push(2u8); + encoded + } + Some(NetworkId::Kusama) => { + encoded.push(3u8); + encoded.push(3u8); + encoded + } + Some(NetworkId::ByFork { + block_number, + block_hash, + }) => { + encoded.push(4u8); + encoded.push(1u8); + encoded.append(&mut block_number.to_be_bytes().into()); + encoded.append(&mut block_hash.into()); + encoded + } + Some(NetworkId::Westend) => { + encoded.push(5u8); + encoded.push(4u8); + encoded + } + Some(NetworkId::Rococo) => { + encoded.push(6u8); + encoded.push(5u8); + encoded + } + Some(NetworkId::Wococo) => { + encoded.push(7u8); + encoded.push(6u8); + encoded + } + Some(NetworkId::Ethereum { chain_id }) => { + encoded.push(8u8); + encoded.push(7u8); + encoded.append(&mut chain_id.to_be_bytes().into()); + encoded + } + Some(NetworkId::BitcoinCore) => { + encoded.push(9u8); + encoded.push(8u8); + encoded + } + Some(NetworkId::BitcoinCash) => { + encoded.push(10u8); + encoded.push(9u8); + encoded + } + } +} + +// Function to convert bytes to networkId +pub(crate) fn network_id_from_bytes(encoded_bytes: Vec) -> EvmResult> { + ensure!(encoded_bytes.len() > 0, revert("Junctions cannot be empty")); + let mut encoded_network_id = EvmDataReader::new(&encoded_bytes); + + let network_selector = encoded_network_id + .read_raw_bytes(1) + .map_err(|_| revert("network selector (1 byte)"))?; + + match network_selector[0] { + 0 => Ok(None), + 1 => Ok(Some(NetworkId::ByGenesis( + encoded_network_id + .read_till_end() + .map_err(|_| revert("can't read till end"))? + .to_vec() + .try_into() + .map_err(|_| revert("network by genesis"))?, + ))), + 2 => Ok(Some(NetworkId::Polkadot)), + 3 => Ok(Some(NetworkId::Kusama)), + 4 => { + let mut block_number: [u8; 8] = Default::default(); + block_number.copy_from_slice(&encoded_network_id.read_raw_bytes(8)?); + + let mut block_hash: [u8; 32] = Default::default(); + block_hash.copy_from_slice(&encoded_network_id.read_raw_bytes(32)?); + Ok(Some(NetworkId::ByFork { + block_number: u64::from_be_bytes(block_number), + block_hash, + })) + } + 5 => Ok(Some(NetworkId::Westend)), + 6 => Ok(Some(NetworkId::Rococo)), + 7 => Ok(Some(NetworkId::Wococo)), + 8 => { + let mut chain_id: [u8; 8] = Default::default(); + chain_id.copy_from_slice(&encoded_network_id.read_raw_bytes(8)?); + Ok(Some(NetworkId::Ethereum { + chain_id: u64::from_be_bytes(chain_id), + })) + } + 9 => Ok(Some(NetworkId::BitcoinCore)), + 10 => Ok(Some(NetworkId::BitcoinCash)), + _ => Err(revert("Non-valid Network Id").into()), + } +} + +impl EvmData for Junction { + fn read(reader: &mut EvmDataReader) -> EvmResult { + let junction = reader.read::>>()?; + let junction_bytes: Vec<_> = junction.into(); + + ensure!( + junction_bytes.len() > 0, + revert("Junctions cannot be empty") + ); + + // For simplicity we use an EvmReader here + let mut encoded_junction = EvmDataReader::new(&junction_bytes); + + // We take the first byte + let enum_selector = encoded_junction + .read_raw_bytes(1) + .map_err(|_| revert("junction variant"))?; + + // The firs byte selects the enum variant + match enum_selector[0] { + 0 => { + // In the case of Junction::Parachain, we need 4 additional bytes + let mut data: [u8; 4] = Default::default(); + data.copy_from_slice(&encoded_junction.read_raw_bytes(4)?); + let para_id = u32::from_be_bytes(data); + Ok(Junction::Parachain(para_id)) + } + 1 => { + // In the case of Junction::AccountId32, we need 32 additional bytes plus NetworkId + let mut account: [u8; 32] = Default::default(); + account.copy_from_slice(&encoded_junction.read_raw_bytes(32)?); + + let network = encoded_junction.read_till_end()?.to_vec(); + Ok(Junction::AccountId32 { + network: network_id_from_bytes(network)?, + id: account, + }) + } + 2 => { + // In the case of Junction::AccountIndex64, we need 8 additional bytes plus NetworkId + let mut index: [u8; 8] = Default::default(); + index.copy_from_slice(&encoded_junction.read_raw_bytes(8)?); + // Now we read the network + let network = encoded_junction.read_till_end()?.to_vec(); + Ok(Junction::AccountIndex64 { + network: network_id_from_bytes(network)?, + index: u64::from_be_bytes(index), + }) + } + 3 => { + // In the case of Junction::AccountKey20, we need 20 additional bytes plus NetworkId + let mut account: [u8; 20] = Default::default(); + account.copy_from_slice(&encoded_junction.read_raw_bytes(20)?); + + let network = encoded_junction.read_till_end()?.to_vec(); + Ok(Junction::AccountKey20 { + network: network_id_from_bytes(network)?, + key: account, + }) + } + 4 => Ok(Junction::PalletInstance( + encoded_junction.read_raw_bytes(1)?[0], + )), + 5 => { + // In the case of Junction::GeneralIndex, we need 16 additional bytes + let mut general_index: [u8; 16] = Default::default(); + general_index.copy_from_slice(&encoded_junction.read_raw_bytes(16)?); + Ok(Junction::GeneralIndex(u128::from_be_bytes(general_index))) + } + 6 => { + let length = encoded_junction + .read_raw_bytes(1) + .map_err(|_| revert("General Key length"))?[0]; + + let data = encoded_junction + .read::() + .map_err(|_| revert("can't read"))? + .into(); + + Ok(Junction::GeneralKey { length, data }) + } + 7 => Ok(Junction::OnlyChild), + 8 => Err(revert("Junction::Plurality not supported yet").into()), + 9 => { + let network = encoded_junction.read_till_end()?.to_vec(); + if let Some(network_id) = network_id_from_bytes(network)? { + Ok(Junction::GlobalConsensus(network_id)) + } else { + Err(revert("Unknown NetworkId").into()) + } + } + _ => Err(revert("Unknown Junction variant").into()), + } + } + + fn write(writer: &mut EvmDataWriter, value: Self) { + let mut encoded: Vec = Vec::new(); + let encoded_bytes: UnboundedBytes = match value { + Junction::Parachain(para_id) => { + encoded.push(0u8); + encoded.append(&mut para_id.to_be_bytes().to_vec()); + encoded.as_slice().into() + } + Junction::AccountId32 { network, id } => { + encoded.push(1u8); + encoded.append(&mut id.to_vec()); + encoded.append(&mut network_id_to_bytes(network)); + encoded.as_slice().into() + } + Junction::AccountIndex64 { network, index } => { + encoded.push(2u8); + encoded.append(&mut index.to_be_bytes().to_vec()); + encoded.append(&mut network_id_to_bytes(network)); + encoded.as_slice().into() + } + Junction::AccountKey20 { network, key } => { + encoded.push(3u8); + encoded.append(&mut key.to_vec()); + encoded.append(&mut network_id_to_bytes(network)); + encoded.as_slice().into() + } + Junction::PalletInstance(intance) => { + encoded.push(4u8); + encoded.append(&mut intance.to_be_bytes().to_vec()); + encoded.as_slice().into() + } + Junction::GeneralIndex(id) => { + encoded.push(5u8); + encoded.append(&mut id.to_be_bytes().to_vec()); + encoded.as_slice().into() + } + Junction::GeneralKey { length, data } => { + encoded.push(6u8); + encoded.push(length); + encoded.append(&mut data.into()); + encoded.as_slice().into() + } + Junction::OnlyChild => { + encoded.push(7u8); + encoded.as_slice().into() + } + Junction::GlobalConsensus(network_id) => { + encoded.push(9u8); + encoded.append(&mut network_id_to_bytes(Some(network_id))); + encoded.as_slice().into() + } + // TODO: The only missing item here is Junciton::Plurality. This is a complex encoded + // type that we need to evaluate how to support + _ => unreachable!("Junction::Plurality not supported yet"), + }; + EvmData::write(writer, encoded_bytes); + } + + fn has_static_size() -> bool { + false + } + + // fn signature() -> String { + // UnboundedBytes::signature() + // } +} + +impl EvmData for Junctions { + fn read(reader: &mut EvmDataReader) -> EvmResult { + let junctions_bytes: Vec = reader.read()?; + let mut junctions = Junctions::Here; + for item in junctions_bytes { + junctions + .push(item) + .map_err(|_| revert("overflow when reading junctions"))?; + } + + Ok(junctions) + } + + fn write(writer: &mut EvmDataWriter, value: Self) { + let encoded: Vec = value.iter().map(|junction| junction.clone()).collect(); + EvmData::write(writer, encoded); + } + + fn has_static_size() -> bool { + false + } + + // fn signature() -> String { + // Vec::::signature() + // } +} + +// Cannot used derive macro since it is a foreign struct. +impl EvmData for MultiLocation { + fn read(reader: &mut EvmDataReader) -> EvmResult { + let (parents, interior) = reader.read()?; + Ok(MultiLocation { parents, interior }) + } + + fn write(writer: &mut EvmDataWriter, value: Self) { + EvmData::write(writer, (value.parents, value.interior)); + } + + fn has_static_size() -> bool { + <(u8, Junctions)>::has_static_size() + } + + // fn signature() -> String { + // <(u8, Junctions)>::signature() + // } +} diff --git a/precompiles/xcm/XCM.sol b/precompiles/xcm/XCM.sol index 7dd371a6..16bc7a77 100644 --- a/precompiles/xcm/XCM.sol +++ b/precompiles/xcm/XCM.sol @@ -4,13 +4,18 @@ pragma solidity ^0.8.0; * @title XCM interface. */ interface XCM { + // A multilocation is defined by its number of parents and the encoded junctions (interior) + struct Multilocation { + uint8 parents; + bytes[] interior; + } + /** * @dev Withdraw assets using PalletXCM call. * @param asset_id - list of XC20 asset addresses * @param asset_amount - list of transfer amounts (must match with asset addresses above) - * @param recipient_account_id - SS58 public key of the destination account - * @param is_relay - set `true` for using relay chain as reserve - * @param parachain_id - set parachain id of reserve parachain (when is_relay set to false) + * @param beneficiary - Multilocation of beneficiary in respect to destination parachain + * @param destination - Multilocation of destination chain * @param fee_index - index of asset_id item that should be used as a XCM fee * @return bool confirmation whether the XCM message sent. * @@ -21,39 +26,14 @@ interface XCM { function assets_withdraw( address[] calldata asset_id, uint256[] calldata asset_amount, - bytes32 recipient_account_id, - bool is_relay, - uint256 parachain_id, - uint256 fee_index - ) external returns (bool); - - /** - * @dev Withdraw assets using PalletXCM call. - * @param asset_id - list of XC20 asset addresses - * @param asset_amount - list of transfer amounts (must match with asset addresses above) - * @param recipient_account_id - ETH address of the destination account - * @param is_relay - set `true` for using relay chain as reserve - * @param parachain_id - set parachain id of reserve parachain (when is_relay set to false) - * @param fee_index - index of asset_id item that should be used as a XCM fee - * @return bool confirmation whether the XCM message sent. - * - * How method check that assets list is valid: - * - all assets resolved to multi-location (on runtime level) - * - all assets has corresponded amount (lenght of assets list matched to amount list) - */ - function assets_withdraw( - address[] calldata asset_id, - uint256[] calldata asset_amount, - address recipient_account_id, - bool is_relay, - uint256 parachain_id, + Multilocation memory beneficiary, + Multilocation memory destination, uint256 fee_index ) external returns (bool); /** * @dev Execute a transaction on a remote chain. - * @param parachain_id - destination parachain Id (ignored if is_relay is true) - * @param is_relay - if true, destination is relay_chain, if false it is parachain (see previous argument) + * @param destination - Multilocation of destination chain * @param payment_asset_id - ETH address of the local asset derivate used to pay for execution in the destination chain * @param payment_amount - amount of payment asset to use for execution payment - should cover cost of XCM instructions + Transact call weight. * @param call - encoded call data (must be decodable by remote chain) @@ -61,8 +41,7 @@ interface XCM { * @return bool confirmation whether the XCM message sent. */ function remote_transact( - uint256 parachain_id, - bool is_relay, + Multilocation memory destination, address payment_asset_id, uint256 payment_amount, bytes calldata call, @@ -73,12 +52,10 @@ interface XCM { * @dev Reserve transfer assets using PalletXCM call. * @param asset_id - list of XC20 asset addresses * @param asset_amount - list of transfer amounts (must match with asset addresses above) - * @param recipient_account_id - SS58 public key of the destination account - * @param is_relay - set `true` for using relay chain as destination - * @param parachain_id - set parachain id of destination parachain (when is_relay set to false) + * @param beneficiary - Multilocation of beneficiary in respect to destination parachain + * @param destination - Multilocation of destination chain * @param fee_index - index of asset_id item that should be used as a XCM fee * @return A boolean confirming whether the XCM message sent. - * * How method check that assets list is valid: * - all assets resolved to multi-location (on runtime level) * - all assets has corresponded amount (lenght of assets list matched to amount list) @@ -86,45 +63,18 @@ interface XCM { function assets_reserve_transfer( address[] calldata asset_id, uint256[] calldata asset_amount, - bytes32 recipient_account_id, - bool is_relay, - uint256 parachain_id, + Multilocation memory beneficiary, + Multilocation memory destination, uint256 fee_index ) external returns (bool); /** - * @dev Reserve transfer using PalletXCM call. - * @param asset_id - list of XC20 asset addresses - * @param asset_amount - list of transfer amounts (must match with asset addresses above) - * @param recipient_account_id - ETH address of the destination account - * @param is_relay - set `true` for using relay chain as destination - * @param parachain_id - set parachain id of destination parachain (when is_relay set to false) - * @param fee_index - index of asset_id item that should be used as a XCM fee - * @return A boolean confirming whether the XCM message sent. - * - * How method check that assets list is valid: - * - all assets resolved to multi-location (on runtime level) - * - all assets has corresponded amount (lenght of assets list matched to amount list) - */ - function assets_reserve_transfer( - address[] calldata asset_id, - uint256[] calldata asset_amount, - address recipient_account_id, - bool is_relay, - uint256 parachain_id, - uint256 fee_index - ) external returns (bool); - - /** - * @dev Reserve transfer using PalletXCM call. + * @dev send xcm using PalletXCM call. + * @param destination - Multilocation of destination chain where to send this call * @param xcm_call - encoded xcm call you want to send to destination - * @param is_relay - set `true` for using relay chain as destination - * @param destination_parachain_id - set parachain id of destination parachain (when is_relay set to false) - * @return A boolean confirming whether the XCM message sent. **/ function send_xcm( - bytes memory xcm_call, - bool is_relay, - uint256 destination_parachain_id + Multilocation memory destination, + bytes memory xcm_call ) external returns (bool); } diff --git a/precompiles/xcm/src/lib.rs b/precompiles/xcm/src/lib.rs index 61a9f08d..b91e54fe 100644 --- a/precompiles/xcm/src/lib.rs +++ b/precompiles/xcm/src/lib.rs @@ -25,13 +25,13 @@ use frame_support::{ pallet_prelude::Weight, traits::Get, }; -use parity_scale_codec::{DecodeLimit, MaxEncodedLen}; use pallet_evm::{AddressMapping, Precompile}; -use sp_core::{H160, H256, U256}; +use parity_scale_codec::DecodeLimit; +use sp_core::{H160, U256}; use sp_std::marker::PhantomData; use sp_std::prelude::*; -use xcm::{latest::prelude::*,VersionedXcm,MAX_XCM_DECODE_DEPTH}; +use xcm::latest::prelude::*; use xcm_executor::traits::Convert; use pallet_evm_precompile_assets_erc20::AddressToAssetId; @@ -47,14 +47,11 @@ mod tests; #[precompile_utils::generate_function_selector] #[derive(Debug, PartialEq)] pub enum Action { - AssetsWithdrawNative = "assets_withdraw(address[],uint256[],bytes32,bool,uint256,uint256)", - AssetsWithdrawEvm = "assets_withdraw(address[],uint256[],address,bool,uint256,uint256)", - RemoteTransact = "remote_transact(uint256,bool,address,uint256,bytes,uint64)", - AssetsReserveTransferNative = - "assets_reserve_transfer(address[],uint256[],bytes32,bool,uint256,uint256)", - AssetsReserveTransferEvm = - "assets_reserve_transfer(address[],uint256[],address,bool,uint256,uint256)", - SendXCM = "send_xcm(bytes32,bool,uint256[])" + AssetsWithdraw = "assets_withdraw(address[],uint256[],(uint8,bytes[]),(uint8,bytes[]),uint256)", + RemoteTransact = "remote_transact((uint8,bytes[]),address,uint256,bytes,uint64)", + AssetsReserveTransfer = + "assets_reserve_transfer(address[],uint256[],(uint8,bytes[]),(uint8,bytes[]),uint256)", + SendXCM = "send_xcm((uint8,bytes[]),bytes)", } /// Dummy H160 address representing native currency (e.g. ASTR or SDN) @@ -72,8 +69,9 @@ where <::RuntimeCall as Dispatchable>::RuntimeOrigin: From>, ::AccountId: Into<[u8; 32]>, - ::RuntimeCall: - From> + Dispatchable + GetDispatchInfo, + ::RuntimeCall: From> + + Dispatchable + + GetDispatchInfo, C: Convert::AssetId>, { fn execute(handle: &mut impl PrecompileHandle) -> EvmResult { @@ -85,31 +83,21 @@ where // Dispatch the call match selector { - Action::AssetsWithdrawNative => { - Self::assets_withdraw(handle, BeneficiaryType::Account32) - } - Action::AssetsWithdrawEvm => Self::assets_withdraw(handle, BeneficiaryType::Account20), + Action::AssetsWithdraw => Self::assets_withdraw(handle), Action::RemoteTransact => Self::remote_transact(handle), - Action::AssetsReserveTransferNative => { - Self::assets_reserve_transfer(handle, BeneficiaryType::Account32) - } - Action::AssetsReserveTransferEvm => { - Self::assets_reserve_transfer(handle, BeneficiaryType::Account20) - } - Action::SendXCM => { - Self::send_xcm(handle) - } + Action::AssetsReserveTransfer => Self::assets_reserve_transfer(handle), + Action::SendXCM => Self::send_xcm(handle), } } } /// The supported beneficiary account types -enum BeneficiaryType { - /// 256 bit (32 byte) public key - Account32, - /// 160 bit (20 byte) address is expected - Account20, -} +// enum BeneficiaryType { +// /// 256 bit (32 byte) public key +// Account32, +// /// 160 bit (20 byte) address is expected +// Account20, +// } impl XcmPrecompile where @@ -125,10 +113,7 @@ where + GetDispatchInfo, C: Convert::AssetId>, { - fn assets_withdraw( - handle: &mut impl PrecompileHandle, - beneficiary_type: BeneficiaryType, - ) -> EvmResult { + fn assets_withdraw(handle: &mut impl PrecompileHandle) -> EvmResult { let mut input = handle.read_input()?; input.expect_arguments(6)?; @@ -154,39 +139,15 @@ where return Err(revert("Assets resolution failure.")); } - let beneficiary: MultiLocation = match beneficiary_type { - BeneficiaryType::Account32 => { - let recipient: [u8; 32] = input.read::()?.into(); - X1(Junction::AccountId32 { - network: None, - id: recipient, - }) - } - BeneficiaryType::Account20 => { - let recipient: H160 = input.read::
()?.into(); - X1(Junction::AccountKey20 { - network: None, - key: recipient.to_fixed_bytes(), - }) - } - } - .into(); + let beneficiary: MultiLocation = input.read::()?; + let dest: MultiLocation = input.read::()?; - let is_relay = input.read::()?; - let parachain_id: u32 = input.read::()?.low_u32(); let fee_asset_item: u32 = input.read::()?.low_u32(); if fee_asset_item as usize > assets.len() { return Err(revert("Bad fee index.")); } - // Prepare pallet-xcm call arguments - let dest = if is_relay { - MultiLocation::parent() - } else { - X1(Junction::Parachain(parachain_id)).into_exterior(1) - }; - let assets: MultiAssets = assets .iter() .cloned() @@ -218,9 +179,10 @@ where input.expect_arguments(6)?; // Raw call arguments - let para_id: u32 = input.read::()?.low_u32(); - let is_relay = input.read::()?; + // let para_id: u32 = input.read::()?.low_u32(); + // let is_relay = input.read::()?; + let dest: MultiLocation = input.read::()?; let fee_asset_addr = input.read::
()?; let fee_amount = input.read::()?; @@ -228,16 +190,16 @@ where let transact_weight = input.read::()?; let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); - log::trace!(target: "xcm-precompile:remote_transact", "Raw arguments: para_id: {}, is_relay: {}, fee_asset_addr: {:?}, \ - fee_amount: {:?}, remote_call: {:?}, transact_weight: {}", - para_id, is_relay, fee_asset_addr, fee_amount, remote_call, transact_weight); + // log::trace!(target: "xcm-precompile:remote_transact", "Raw arguments: para_id: {}, is_relay: {}, fee_asset_addr: {:?}, \ + // fee_amount: {:?}, remote_call: {:?}, transact_weight: {}", + // para_id, is_relay, fee_asset_addr, fee_amount, remote_call, transact_weight); // Process arguments - let dest = if is_relay { - MultiLocation::parent() - } else { - X1(Junction::Parachain(para_id)).into_exterior(1) - }; + // let dest = if is_relay { + // MultiLocation::parent() + // } else { + // X1(Junction::Parachain(para_id)).into_exterior(1) + // }; let fee_asset = { let address: H160 = fee_asset_addr.into(); @@ -315,10 +277,7 @@ where Ok(succeed(EvmDataWriter::new().write(true).build())) } - fn assets_reserve_transfer( - handle: &mut impl PrecompileHandle, - beneficiary_type: BeneficiaryType, - ) -> EvmResult { + fn assets_reserve_transfer(handle: &mut impl PrecompileHandle) -> EvmResult { let mut input = handle.read_input()?; input.expect_arguments(6)?; @@ -353,38 +312,21 @@ where return Err(revert("Assets resolution failure.")); } - let beneficiary: MultiLocation = match beneficiary_type { - BeneficiaryType::Account32 => { - let recipient: [u8; 32] = input.read::()?.into(); - X1(Junction::AccountId32 { - network: None, - id: recipient, - }) - } - BeneficiaryType::Account20 => { - let recipient: H160 = input.read::
()?.into(); - X1(Junction::AccountKey20 { - network: None, - key: recipient.to_fixed_bytes(), - }) - } - } - .into(); + let beneficiary: MultiLocation = input.read::()?; + let dest: MultiLocation = input.read::()?; - let is_relay = input.read::()?; - let parachain_id: u32 = input.read::()?.low_u32(); let fee_asset_item: u32 = input.read::()?.low_u32(); if fee_asset_item as usize > assets.len() { return Err(revert("Bad fee index.")); } - // Prepare pallet-xcm call arguments - let dest = if is_relay { - MultiLocation::parent() - } else { - X1(Junction::Parachain(parachain_id)).into_exterior(1) - }; + // // Prepare pallet-xcm call arguments + // let dest = if is_relay { + // MultiLocation::parent() + // } else { + // X1(Junction::Parachain(parachain_id)).into_exterior(1) + // }; let assets: MultiAssets = assets .iter() @@ -412,36 +354,29 @@ where Ok(succeed(EvmDataWriter::new().write(true).build())) } - fn send_xcm( - handle: &mut impl PrecompileHandle - ) -> EvmResult { + fn send_xcm(handle: &mut impl PrecompileHandle) -> EvmResult { let mut input = handle.read_input()?; input.expect_arguments(3)?; // Raw call arguments - let xcm_call : Vec = input.read::()?.into(); - let is_relay = input.read::()?; - let dest_para_id: u32 = input.read::()?.low_u32(); - - log::trace!(target:"xcm-send_xcm", "Raw arguments: xcm_call: {:?}, is_relay: {}, destination_parachain_id: {:?}", xcm_call, is_relay,dest_para_id); - - let xcm_call: Vec<_> = xcm_call.to_vec(); + let dest: MultiLocation = input.read::()?; + let xcm_call: Vec = input.read::()?.into(); - let xcm = xcm::VersionedXcm::<()>::decode_all_with_depth_limit( - xcm::MAX_XCM_DECODE_DEPTH, - &mut xcm_call.as_slice(), - ).map_err(|_| { - revert("Failed to decode xcm instructions") - })?; + // log::trace!(target:"xcm-precompile", "Raw arguments: xcm_call: {:?}, is_relay: {}, destination_parachain_id: {:?}", xcm_call, is_relay,dest_para_id); + let xcm = xcm::VersionedXcm::<()>::decode_all_with_depth_limit( + xcm::MAX_XCM_DECODE_DEPTH, + &mut xcm_call.as_slice(), + ) + .map_err(|_| revert("Failed to decode xcm instructions"))?; - let dest = if is_relay { - MultiLocation::parent() - } else { - X1(Junction::Parachain(dest_para_id)).into_exterior(1) - }; + // let dest = if is_relay { + // MultiLocation::parent() + // } else { + // X1(Junction::Parachain(dest_para_id)).into_exterior(1) + // }; - // Build call with origin. + // Build call with origin. let origin = Some(Runtime::AddressMapping::into_account_id( handle.context().caller, )) diff --git a/precompiles/xcm/src/mock.rs b/precompiles/xcm/src/mock.rs index c13eba4b..0983dfc5 100644 --- a/precompiles/xcm/src/mock.rs +++ b/precompiles/xcm/src/mock.rs @@ -192,9 +192,9 @@ where { fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { match handle.code_address() { - a if a == PRECOMPILE_ADDRESS => Some( - XcmPrecompile::>::execute(handle), - ), + a if a == PRECOMPILE_ADDRESS => { + Some(XcmPrecompile::>::execute(handle)) + } _ => None, } } diff --git a/precompiles/xcm/src/tests.rs b/precompiles/xcm/src/tests.rs index ec23c396..d4c88089 100644 --- a/precompiles/xcm/src/tests.rs +++ b/precompiles/xcm/src/tests.rs @@ -21,10 +21,11 @@ use core::assert_matches::assert_matches; use crate::mock::*; use crate::*; +use parity_scale_codec::Encode; use precompile_utils::testing::*; use precompile_utils::EvmDataWriter; -use sp_core::H160; -use parity_scale_codec::{Encode,Decode}; +use sp_core::{H160, H256}; +use xcm::VersionedXcm; fn precompiles() -> TestPrecompileSet { PrecompilesValue::get() @@ -33,16 +34,27 @@ fn precompiles() -> TestPrecompileSet { #[test] fn wrong_assets_len_or_fee_index_reverts() { ExtBuilder::default().build().execute_with(|| { + let dest: MultiLocation = MultiLocation { + parents: 1, + interior: Junctions::X1(Junction::Parachain(2000u32)), + }; + + let beneficiary: MultiLocation = MultiLocation { + parents: 0, + interior: Junctions::X1(AccountId32 { + network: None, + id: H256::repeat_byte(0xF1).into(), + }), + }; precompiles() .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsWithdrawNative) + EvmDataWriter::new_with_selector(Action::AssetsWithdraw) .write(vec![Address::from(H160::repeat_byte(0xF1))]) .write(Vec::::new()) - .write(H256::repeat_byte(0xF1)) - .write(true) - .write(U256::from(0_u64)) + .write(beneficiary) + .write(dest) .write(U256::from(0_u64)) .build(), ) @@ -53,12 +65,11 @@ fn wrong_assets_len_or_fee_index_reverts() { .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsWithdrawNative) + EvmDataWriter::new_with_selector(Action::AssetsWithdraw) .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) .write(vec![U256::from(42000u64)]) - .write(H256::repeat_byte(0xF1)) - .write(true) - .write(U256::from(0_u64)) + .write(beneficiary) + .write(dest) .write(U256::from(2_u64)) .build(), ) @@ -70,17 +81,29 @@ fn wrong_assets_len_or_fee_index_reverts() { #[test] fn assets_withdraw_works() { ExtBuilder::default().build().execute_with(|| { + let dest: MultiLocation = MultiLocation { + parents: 1, + interior: Junctions::Here, + }; + + let beneficiary: MultiLocation = MultiLocation { + parents: 0, + interior: Junctions::X1(AccountId32 { + network: None, + id: H256::repeat_byte(0xF1).into(), + }), + }; + // SS58 precompiles() .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsWithdrawNative) + EvmDataWriter::new_with_selector(Action::AssetsWithdraw) .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) .write(vec![U256::from(42000u64)]) - .write(H256::repeat_byte(0xF1)) - .write(true) - .write(U256::from(0_u64)) + .write(beneficiary) + .write(dest) .write(U256::from(0_u64)) .build(), ) @@ -88,16 +111,22 @@ fn assets_withdraw_works() { .execute_returns(EvmDataWriter::new().write(true).build()); // H160 + let beneficiary: MultiLocation = MultiLocation { + parents: 0, + interior: Junctions::X1(AccountKey20 { + network: None, + key: H160::repeat_byte(0xDE).into(), + }), + }; precompiles() .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsWithdrawEvm) + EvmDataWriter::new_with_selector(Action::AssetsWithdraw) .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) .write(vec![U256::from(42000u64)]) - .write(Address::from(H160::repeat_byte(0xDE))) - .write(true) - .write(U256::from(0_u64)) + .write(beneficiary) + .write(dest) .write(U256::from(0_u64)) .build(), ) @@ -109,14 +138,17 @@ fn assets_withdraw_works() { #[test] fn remote_transact_works() { ExtBuilder::default().build().execute_with(|| { + let multilocation = MultiLocation { + parents: 1, + interior: Junctions::X1(Junction::Parachain(2000u32)), + }; // SS58 precompiles() .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, EvmDataWriter::new_with_selector(Action::RemoteTransact) - .write(U256::from(0_u64)) - .write(true) + .write(multilocation) .write(Address::from(Runtime::asset_id_to_address(1_u128))) .write(U256::from(367)) .write(vec![0xff_u8, 0xaa, 0x77, 0x00]) @@ -131,17 +163,28 @@ fn remote_transact_works() { #[test] fn reserve_transfer_assets_works() { ExtBuilder::default().build().execute_with(|| { + let dest: MultiLocation = MultiLocation { + parents: 1, + interior: Junctions::Here, + }; + + let beneficiary: MultiLocation = MultiLocation { + parents: 0, + interior: Junctions::X1(AccountId32 { + network: None, + id: H256::repeat_byte(0xF1).into(), + }), + }; // SS58 precompiles() .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsReserveTransferNative) + EvmDataWriter::new_with_selector(Action::AssetsReserveTransfer) .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) .write(vec![U256::from(42000u64)]) - .write(H256::repeat_byte(0xF1)) - .write(true) - .write(U256::from(0_u64)) + .write(beneficiary) + .write(dest) .write(U256::from(0_u64)) .build(), ) @@ -149,16 +192,22 @@ fn reserve_transfer_assets_works() { .execute_returns(EvmDataWriter::new().write(true).build()); // H160 + let beneficiary: MultiLocation = MultiLocation { + parents: 0, + interior: Junctions::X1(AccountKey20 { + network: None, + key: H160::repeat_byte(0xDE).into(), + }), + }; precompiles() .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsReserveTransferEvm) + EvmDataWriter::new_with_selector(Action::AssetsReserveTransfer) .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) .write(vec![U256::from(42000u64)]) - .write(Address::from(H160::repeat_byte(0xDE))) - .write(true) - .write(U256::from(0_u64)) + .write(beneficiary) + .write(dest) .write(U256::from(0_u64)) .build(), ) @@ -209,32 +258,49 @@ fn reserve_transfer_assets_works() { #[test] fn reserve_transfer_currency_works() { ExtBuilder::default().build().execute_with(|| { + let dest: MultiLocation = MultiLocation { + parents: 1, + interior: Junctions::Here, + }; + + let beneficiary: MultiLocation = MultiLocation { + parents: 0, + interior: Junctions::X1(AccountId32 { + network: None, + id: H256::repeat_byte(0xF1).into(), + }), + }; precompiles() .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsReserveTransferNative) + EvmDataWriter::new_with_selector(Action::AssetsReserveTransfer) .write(vec![Address::from(H160::zero())]) // zero address by convention .write(vec![U256::from(42000u64)]) - .write(H256::repeat_byte(0xF1)) - .write(true) - .write(U256::from(0_u64)) + .write(beneficiary) + .write(dest) .write(U256::from(0_u64)) .build(), ) .expect_no_logs() .execute_returns(EvmDataWriter::new().write(true).build()); + let beneficiary: MultiLocation = MultiLocation { + parents: 0, + interior: Junctions::X1(AccountKey20 { + network: None, + key: H160::repeat_byte(0xDE).into(), + }), + }; precompiles() .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsReserveTransferEvm) + EvmDataWriter::new_with_selector(Action::AssetsReserveTransfer) .write(vec![Address::from(H160::zero())]) // zero address by convention .write(vec![U256::from(42000u64)]) - .write(Address::from(H160::repeat_byte(0xDE))) - .write(true) - .write(U256::from(0_u64)) + .write(beneficiary) + .write(dest) .write(U256::from(0_u64)) .build(), ) @@ -284,24 +350,32 @@ fn reserve_transfer_currency_works() { #[test] fn test_send_clear_origin() { - ExtBuilder::default().build().execute_with(|| { - let xcm_to_send = VersionedXcm::<()>::V3(Xcm(vec![ClearOrigin])).encode(); - precompiles() - .prepare_test(TestAccount::Alice, PRECOMPILE_ADDRESS, + ExtBuilder::default().build().execute_with(|| { + let dest: MultiLocation = MultiLocation { + parents: 1, + interior: Junctions::X1(Junction::AccountId32 { + network: None, + id: H256::repeat_byte(0xF1).into(), + }), + }; + let xcm_to_send = VersionedXcm::<()>::V3(Xcm(vec![ClearOrigin])).encode(); + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, EvmDataWriter::new_with_selector(Action::SendXCM) - .write(Bytes::from(xcm_to_send.as_slice())) - .write(false) - .write(U256::from(1_u64)) - .build(), + .write(dest) + .write(Bytes::from(xcm_to_send.as_slice())) + .build(), ) - // Fixed: TestWeightInfo + (BaseXcmWeight * MessageLen) - .expect_cost(100001000) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); - - let sent_messages = take_sent_xcm(); - let (_, sent_message) = sent_messages.first().unwrap(); - // Lets make sure the message is as expected - assert!(sent_message.0.contains(&ClearOrigin)); - }) -} \ No newline at end of file + // Fixed: TestWeightInfo + (BaseXcmWeight * MessageLen) + .expect_cost(100001000) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + let sent_messages = take_sent_xcm(); + let (_, sent_message) = sent_messages.first().unwrap(); + // Lets make sure the message is as expected + assert!(sent_message.0.contains(&ClearOrigin)); + }) +} From d1c0d56f45ab0b6ac7bffcb54ad2c14247be64bc Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Tue, 13 Jun 2023 18:39:37 +0530 Subject: [PATCH 13/33] update licenses --- precompiles/utils/src/bytes.rs | 16 +++++++++++----- precompiles/utils/src/xcm.rs | 18 +++++++++++------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/precompiles/utils/src/bytes.rs b/precompiles/utils/src/bytes.rs index cac5f368..df55a7d8 100644 --- a/precompiles/utils/src/bytes.rs +++ b/precompiles/utils/src/bytes.rs @@ -1,18 +1,24 @@ -// Copyright 2019-2022 PureStake Inc. -// This file is part of Moonbeam. +// This file is part of Astar. -// Moonbeam is free software: you can redistribute it and/or modify +// Copyright 2019-2022 PureStake Inc. +// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Utils package, originally developed by Purestake Inc. +// Utils package used in Astar Network in terms of GPLv3. +// +// Utils 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. -// Moonbeam is distributed in the hope that it will be useful, +// Utils 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 Moonbeam. If not, see . +// along with Utils. If not, see . use super::*; use alloc::borrow::ToOwned; diff --git a/precompiles/utils/src/xcm.rs b/precompiles/utils/src/xcm.rs index 473a44dc..46e3a84a 100644 --- a/precompiles/utils/src/xcm.rs +++ b/precompiles/utils/src/xcm.rs @@ -1,18 +1,24 @@ -// Copyright 2019-2022 PureStake Inc. -// This file is part of Moonbeam. +// This file is part of Astar. -// Moonbeam is free software: you can redistribute it and/or modify +// Copyright 2019-2022 PureStake Inc. +// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Utils package, originally developed by Purestake Inc. +// Utils package used in Astar Network in terms of GPLv3. +// +// Utils 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. -// Moonbeam is distributed in the hope that it will be useful, +// Utils 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 Moonbeam. If not, see . +// along with Utils. If not, see . //! Encoding of XCM types for solidity @@ -27,8 +33,6 @@ use { pub const JUNCTION_SIZE_LIMIT: u32 = 2u32.pow(16); // Function to convert network id to bytes -// We don't implement solidity::Codec here as these bytes will be appended only -// to certain Junction variants // Each NetworkId variant is represented as bytes // The first byte represents the enum variant to be used. // - Indexes 0,2,3 represent XCM V2 variants From b77fcc2ba1bf08895f0b8335c369c27489e0bba5 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Wed, 14 Jun 2023 15:50:17 +0530 Subject: [PATCH 14/33] fix scope issue for String --- precompiles/utils/src/bytes.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/precompiles/utils/src/bytes.rs b/precompiles/utils/src/bytes.rs index df55a7d8..50384a8a 100644 --- a/precompiles/utils/src/bytes.rs +++ b/precompiles/utils/src/bytes.rs @@ -23,6 +23,7 @@ use super::*; use alloc::borrow::ToOwned; use sp_core::{ConstU32, Get}; +pub use alloc::string::String; type ConstU32Max = ConstU32<{ u32::MAX }>; From edc738e2391050ac83db098747143958c57ca685 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Wed, 14 Jun 2023 15:56:44 +0530 Subject: [PATCH 15/33] fmt --- precompiles/utils/src/bytes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/precompiles/utils/src/bytes.rs b/precompiles/utils/src/bytes.rs index 50384a8a..13da1a00 100644 --- a/precompiles/utils/src/bytes.rs +++ b/precompiles/utils/src/bytes.rs @@ -22,8 +22,8 @@ use super::*; use alloc::borrow::ToOwned; -use sp_core::{ConstU32, Get}; pub use alloc::string::String; +use sp_core::{ConstU32, Get}; type ConstU32Max = ConstU32<{ u32::MAX }>; From 450c780ca6444b33d324c34f62f8c2a56f018ecb Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Tue, 20 Jun 2023 14:24:15 +0530 Subject: [PATCH 16/33] minor refactor, add log:trace --- precompiles/utils/src/lib.rs | 2 +- precompiles/utils/src/xcm.rs | 12 -------- precompiles/xcm/src/lib.rs | 55 ++++++++++-------------------------- 3 files changed, 16 insertions(+), 53 deletions(-) diff --git a/precompiles/utils/src/lib.rs b/precompiles/utils/src/lib.rs index 6ad24788..77f69c66 100644 --- a/precompiles/utils/src/lib.rs +++ b/precompiles/utils/src/lib.rs @@ -37,7 +37,7 @@ use pallet_evm::{GasWeightMapping, Log}; use sp_core::{H160, H256, U256}; use sp_std::{marker::PhantomData, vec, vec::Vec}; -mod bytes; +pub mod bytes; mod data; mod xcm; diff --git a/precompiles/utils/src/xcm.rs b/precompiles/utils/src/xcm.rs index 46e3a84a..22e5b789 100644 --- a/precompiles/utils/src/xcm.rs +++ b/precompiles/utils/src/xcm.rs @@ -312,10 +312,6 @@ impl EvmData for Junction { fn has_static_size() -> bool { false } - - // fn signature() -> String { - // UnboundedBytes::signature() - // } } impl EvmData for Junctions { @@ -339,10 +335,6 @@ impl EvmData for Junctions { fn has_static_size() -> bool { false } - - // fn signature() -> String { - // Vec::::signature() - // } } // Cannot used derive macro since it is a foreign struct. @@ -359,8 +351,4 @@ impl EvmData for MultiLocation { fn has_static_size() -> bool { <(u8, Junctions)>::has_static_size() } - - // fn signature() -> String { - // <(u8, Junctions)>::signature() - // } } diff --git a/precompiles/xcm/src/lib.rs b/precompiles/xcm/src/lib.rs index b91e54fe..37f35920 100644 --- a/precompiles/xcm/src/lib.rs +++ b/precompiles/xcm/src/lib.rs @@ -23,8 +23,11 @@ use fp_evm::{PrecompileHandle, PrecompileOutput}; use frame_support::{ dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, pallet_prelude::Weight, - traits::Get, + traits::{ConstU32, Get}, }; +pub const XCM_SIZE_LIMIT: u32 = 2u32.pow(16); +type GetXcmSizeLimit = ConstU32; + use pallet_evm::{AddressMapping, Precompile}; use parity_scale_codec::DecodeLimit; use sp_core::{H160, U256}; @@ -36,8 +39,8 @@ use xcm_executor::traits::Convert; use pallet_evm_precompile_assets_erc20::AddressToAssetId; use precompile_utils::{ - revert, succeed, Address, Bytes, EvmDataWriter, EvmResult, FunctionModifier, - PrecompileHandleExt, RuntimeHelper, + bytes::BoundedBytes, revert, succeed, Address, Bytes, EvmDataWriter, EvmResult, + FunctionModifier, PrecompileHandleExt, RuntimeHelper, }; #[cfg(test)] mod mock; @@ -91,14 +94,6 @@ where } } -/// The supported beneficiary account types -// enum BeneficiaryType { -// /// 256 bit (32 byte) public key -// Account32, -// /// 160 bit (20 byte) address is expected -// Account20, -// } - impl XcmPrecompile where Runtime: pallet_evm::Config @@ -144,6 +139,10 @@ where let fee_asset_item: u32 = input.read::()?.low_u32(); + log::trace!(target: "xcm-precompile::asset_withdraw", "Raw arguments: assets: {:?}, asset_amount: {:?} \ + beneficiart: {:?}, destination: {:?}, fee_index: {}", + assets, amounts_raw, beneficiary, dest, fee_asset_item); + if fee_asset_item as usize > assets.len() { return Err(revert("Bad fee index.")); } @@ -178,10 +177,6 @@ where let mut input = handle.read_input()?; input.expect_arguments(6)?; - // Raw call arguments - // let para_id: u32 = input.read::()?.low_u32(); - // let is_relay = input.read::()?; - let dest: MultiLocation = input.read::()?; let fee_asset_addr = input.read::
()?; let fee_amount = input.read::()?; @@ -190,16 +185,9 @@ where let transact_weight = input.read::()?; let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); - // log::trace!(target: "xcm-precompile:remote_transact", "Raw arguments: para_id: {}, is_relay: {}, fee_asset_addr: {:?}, \ - // fee_amount: {:?}, remote_call: {:?}, transact_weight: {}", - // para_id, is_relay, fee_asset_addr, fee_amount, remote_call, transact_weight); - - // Process arguments - // let dest = if is_relay { - // MultiLocation::parent() - // } else { - // X1(Junction::Parachain(para_id)).into_exterior(1) - // }; + log::trace!(target: "xcm-precompile::remote_transact", "Raw arguments: dest: {:?}, fee_asset_addr: {:?} \ + fee_amount: {:?}, remote_call: {:?}, transact_weight: {}", + dest, fee_asset_addr, fee_amount, remote_call, transact_weight); let fee_asset = { let address: H160 = fee_asset_addr.into(); @@ -321,13 +309,6 @@ where return Err(revert("Bad fee index.")); } - // // Prepare pallet-xcm call arguments - // let dest = if is_relay { - // MultiLocation::parent() - // } else { - // X1(Junction::Parachain(parachain_id)).into_exterior(1) - // }; - let assets: MultiAssets = assets .iter() .cloned() @@ -360,9 +341,9 @@ where // Raw call arguments let dest: MultiLocation = input.read::()?; - let xcm_call: Vec = input.read::()?.into(); + let xcm_call: Vec = input.read::>()?.into(); - // log::trace!(target:"xcm-precompile", "Raw arguments: xcm_call: {:?}, is_relay: {}, destination_parachain_id: {:?}", xcm_call, is_relay,dest_para_id); + log::trace!(target:"xcm-precompile::send_xcm", "Raw arguments: dest: {:?}, xcm_call: {:?}", dest, xcm_call); let xcm = xcm::VersionedXcm::<()>::decode_all_with_depth_limit( xcm::MAX_XCM_DECODE_DEPTH, @@ -370,12 +351,6 @@ where ) .map_err(|_| revert("Failed to decode xcm instructions"))?; - // let dest = if is_relay { - // MultiLocation::parent() - // } else { - // X1(Junction::Parachain(dest_para_id)).into_exterior(1) - // }; - // Build call with origin. let origin = Some(Runtime::AddressMapping::into_account_id( handle.context().caller, From a4cf0be7c37712c15bdb37a31579a8e8af3206c3 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Tue, 20 Jun 2023 15:04:31 +0530 Subject: [PATCH 17/33] Empty-Commit From 83ae4ad2063c9a4f0a8d10bd81f9adf726b3a0e8 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Fri, 23 Jun 2023 13:40:55 +0530 Subject: [PATCH 18/33] make input field in EvmDataReader pub(crate) --- precompiles/utils/src/bytes.rs | 5 +---- precompiles/utils/src/data.rs | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/precompiles/utils/src/bytes.rs b/precompiles/utils/src/bytes.rs index 13da1a00..aa33aab3 100644 --- a/precompiles/utils/src/bytes.rs +++ b/precompiles/utils/src/bytes.rs @@ -107,7 +107,7 @@ impl> EvmData for BoundedBytesString { let range = inner_reader.move_cursor(array_size)?; let data = inner_reader - .input() + .input .get(range) .ok_or_else(|| revert(K::signature()))?; @@ -147,9 +147,6 @@ impl> EvmData for BoundedBytesString { false } - // fn signature() -> String { - // K::signature() - // } } // BytesString <=> Vec/&[u8] diff --git a/precompiles/utils/src/data.rs b/precompiles/utils/src/data.rs index b32232d0..1b459ebb 100644 --- a/precompiles/utils/src/data.rs +++ b/precompiles/utils/src/data.rs @@ -87,7 +87,7 @@ impl From for Vec { /// Provide functions to parse common types. #[derive(Clone, Copy, Debug)] pub struct EvmDataReader<'a> { - input: &'a [u8], + pub(crate) input: &'a [u8], cursor: usize, } @@ -174,9 +174,6 @@ impl<'a> EvmDataReader<'a> { cursor: 0, }) } - pub fn input(&mut self) -> &[u8] { - self.input - } /// Read remaining bytes pub fn read_till_end(&mut self) -> EvmResult<&[u8]> { From 65a22efb11c08ef40f4f7a84ecdb90afdeae3098 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Mon, 26 Jun 2023 08:08:55 +0530 Subject: [PATCH 19/33] remove setAppendix from remote_transact() --- precompiles/xcm/src/lib.rs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/precompiles/xcm/src/lib.rs b/precompiles/xcm/src/lib.rs index 37f35920..989ea30c 100644 --- a/precompiles/xcm/src/lib.rs +++ b/precompiles/xcm/src/lib.rs @@ -225,21 +225,6 @@ where fees: fee_multilocation.clone().into(), weight_limit: WeightLimit::Unlimited, }, - SetAppendix(Xcm(vec![DepositAsset { - assets: All.into(), - beneficiary: MultiLocation { - parents: 1, - interior: X2( - // last() returns the last Juction Enum in Junctions - // for Univeral Location it is the Parachain() variant - *context.last().unwrap(), - AccountId32 { - network: None, - id: origin.into(), - }, - ), - }, - }])), Transact { origin_kind: OriginKind::SovereignAccount, require_weight_at_most: Weight::from_ref_time(transact_weight), From 0171f31ade1968726d12572e5fb9fe6643ae1a94 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Mon, 26 Jun 2023 08:51:21 +0530 Subject: [PATCH 20/33] old interface added for legacy support --- precompiles/xcm/XCM.sol | 88 +++- precompiles/xcm/XCM_v2.sol | 80 +++ precompiles/xcm/src/lib.rs | 306 +++++++++++- precompiles/xcm/src/tests.rs | 917 ++++++++++++++++++++++------------- 4 files changed, 1034 insertions(+), 357 deletions(-) create mode 100644 precompiles/xcm/XCM_v2.sol diff --git a/precompiles/xcm/XCM.sol b/precompiles/xcm/XCM.sol index 16bc7a77..c3b7e4a3 100644 --- a/precompiles/xcm/XCM.sol +++ b/precompiles/xcm/XCM.sol @@ -1,21 +1,44 @@ + /** + * DISCLAIMER: Please note that this file ise deprecated and users are advised to use the XCM_v2.sol file instead. + */ + pragma solidity ^0.8.0; /** * @title XCM interface. */ + interface XCM { - // A multilocation is defined by its number of parents and the encoded junctions (interior) - struct Multilocation { - uint8 parents; - bytes[] interior; - } - /** * @dev Withdraw assets using PalletXCM call. * @param asset_id - list of XC20 asset addresses * @param asset_amount - list of transfer amounts (must match with asset addresses above) - * @param beneficiary - Multilocation of beneficiary in respect to destination parachain - * @param destination - Multilocation of destination chain + * @param recipient_account_id - SS58 public key of the destination account + * @param is_relay - set `true` for using relay chain as reserve + * @param parachain_id - set parachain id of reserve parachain (when is_relay set to false) + * @param fee_index - index of asset_id item that should be used as a XCM fee + * @return bool confirmation whether the XCM message sent. + * + * How method check that assets list is valid: + * - all assets resolved to multi-location (on runtime level) + * - all assets has corresponded amount (lenght of assets list matched to amount list) + */ + function assets_withdraw( + address[] calldata asset_id, + uint256[] calldata asset_amount, + bytes32 recipient_account_id, + bool is_relay, + uint256 parachain_id, + uint256 fee_index + ) external returns (bool); + + /** + * @dev Withdraw assets using PalletXCM call. + * @param asset_id - list of XC20 asset addresses + * @param asset_amount - list of transfer amounts (must match with asset addresses above) + * @param recipient_account_id - ETH address of the destination account + * @param is_relay - set `true` for using relay chain as reserve + * @param parachain_id - set parachain id of reserve parachain (when is_relay set to false) * @param fee_index - index of asset_id item that should be used as a XCM fee * @return bool confirmation whether the XCM message sent. * @@ -26,14 +49,16 @@ interface XCM { function assets_withdraw( address[] calldata asset_id, uint256[] calldata asset_amount, - Multilocation memory beneficiary, - Multilocation memory destination, + address recipient_account_id, + bool is_relay, + uint256 parachain_id, uint256 fee_index ) external returns (bool); /** * @dev Execute a transaction on a remote chain. - * @param destination - Multilocation of destination chain + * @param parachain_id - destination parachain Id (ignored if is_relay is true) + * @param is_relay - if true, destination is relay_chain, if false it is parachain (see previous argument) * @param payment_asset_id - ETH address of the local asset derivate used to pay for execution in the destination chain * @param payment_amount - amount of payment asset to use for execution payment - should cover cost of XCM instructions + Transact call weight. * @param call - encoded call data (must be decodable by remote chain) @@ -41,7 +66,8 @@ interface XCM { * @return bool confirmation whether the XCM message sent. */ function remote_transact( - Multilocation memory destination, + uint256 parachain_id, + bool is_relay, address payment_asset_id, uint256 payment_amount, bytes calldata call, @@ -52,10 +78,12 @@ interface XCM { * @dev Reserve transfer assets using PalletXCM call. * @param asset_id - list of XC20 asset addresses * @param asset_amount - list of transfer amounts (must match with asset addresses above) - * @param beneficiary - Multilocation of beneficiary in respect to destination parachain - * @param destination - Multilocation of destination chain + * @param recipient_account_id - SS58 public key of the destination account + * @param is_relay - set `true` for using relay chain as destination + * @param parachain_id - set parachain id of destination parachain (when is_relay set to false) * @param fee_index - index of asset_id item that should be used as a XCM fee * @return A boolean confirming whether the XCM message sent. + * * How method check that assets list is valid: * - all assets resolved to multi-location (on runtime level) * - all assets has corresponded amount (lenght of assets list matched to amount list) @@ -63,18 +91,32 @@ interface XCM { function assets_reserve_transfer( address[] calldata asset_id, uint256[] calldata asset_amount, - Multilocation memory beneficiary, - Multilocation memory destination, + bytes32 recipient_account_id, + bool is_relay, + uint256 parachain_id, uint256 fee_index ) external returns (bool); /** - * @dev send xcm using PalletXCM call. - * @param destination - Multilocation of destination chain where to send this call - * @param xcm_call - encoded xcm call you want to send to destination - **/ - function send_xcm( - Multilocation memory destination, - bytes memory xcm_call + * @dev Reserve transfer using PalletXCM call. + * @param asset_id - list of XC20 asset addresses + * @param asset_amount - list of transfer amounts (must match with asset addresses above) + * @param recipient_account_id - ETH address of the destination account + * @param is_relay - set `true` for using relay chain as destination + * @param parachain_id - set parachain id of destination parachain (when is_relay set to false) + * @param fee_index - index of asset_id item that should be used as a XCM fee + * @return A boolean confirming whether the XCM message sent. + * + * How method check that assets list is valid: + * - all assets resolved to multi-location (on runtime level) + * - all assets has corresponded amount (lenght of assets list matched to amount list) + */ + function assets_reserve_transfer( + address[] calldata asset_id, + uint256[] calldata asset_amount, + address recipient_account_id, + bool is_relay, + uint256 parachain_id, + uint256 fee_index ) external returns (bool); } diff --git a/precompiles/xcm/XCM_v2.sol b/precompiles/xcm/XCM_v2.sol new file mode 100644 index 00000000..24233a75 --- /dev/null +++ b/precompiles/xcm/XCM_v2.sol @@ -0,0 +1,80 @@ +pragma solidity ^0.8.0; + +/** + * @title XCM interface. + */ +interface XCM { + // A multilocation is defined by its number of parents and the encoded junctions (interior) + struct Multilocation { + uint8 parents; + bytes[] interior; + } + + /** + * @dev Withdraw assets using PalletXCM call. + * @param asset_id - list of XC20 asset addresses + * @param asset_amount - list of transfer amounts (must match with asset addresses above) + * @param beneficiary - Multilocation of beneficiary in respect to destination parachain + * @param destination - Multilocation of destination chain + * @param fee_index - index of asset_id item that should be used as a XCM fee + * @return bool confirmation whether the XCM message sent. + * + * How method check that assets list is valid: + * - all assets resolved to multi-location (on runtime level) + * - all assets has corresponded amount (lenght of assets list matched to amount list) + */ + function assets_withdraw( + address[] calldata asset_id, + uint256[] calldata asset_amount, + Multilocation memory beneficiary, + Multilocation memory destination, + uint256 fee_index + ) external returns (bool); + + /** + * @dev Execute a transaction on a remote chain. + * @param destination - Multilocation of destination chain + * @param payment_asset_id - ETH address of the local asset derivate used to pay for execution in the destination chain + * @param payment_amount - amount of payment asset to use for execution payment - should cover cost of XCM instructions + Transact call weight. + * @param call - encoded call data (must be decodable by remote chain) + * @param transact_weight - max weight that the encoded call is allowed to consume in the destination chain + * @return bool confirmation whether the XCM message sent. + */ + function remote_transact( + Multilocation memory destination, + address payment_asset_id, + uint256 payment_amount, + bytes calldata call, + uint64 transact_weight + ) external returns (bool); + + /** + * @dev Reserve transfer assets using PalletXCM call. + * @param asset_id - list of XC20 asset addresses + * @param asset_amount - list of transfer amounts (must match with asset addresses above) + * @param beneficiary - Multilocation of beneficiary in respect to destination parachain + * @param destination - Multilocation of destination chain + * @param fee_index - index of asset_id item that should be used as a XCM fee + * @return A boolean confirming whether the XCM message sent. + * How method check that assets list is valid: + * - all assets resolved to multi-location (on runtime level) + * - all assets has corresponded amount (lenght of assets list matched to amount list) + */ + function assets_reserve_transfer( + address[] calldata asset_id, + uint256[] calldata asset_amount, + Multilocation memory beneficiary, + Multilocation memory destination, + uint256 fee_index + ) external returns (bool); + + /** + * @dev send xcm using PalletXCM call. + * @param destination - Multilocation of destination chain where to send this call + * @param xcm_call - encoded xcm call you want to send to destination + **/ + function send_xcm( + Multilocation memory destination, + bytes memory xcm_call + ) external returns (bool); +} \ No newline at end of file diff --git a/precompiles/xcm/src/lib.rs b/precompiles/xcm/src/lib.rs index 989ea30c..6e8ea126 100644 --- a/precompiles/xcm/src/lib.rs +++ b/precompiles/xcm/src/lib.rs @@ -30,7 +30,7 @@ type GetXcmSizeLimit = ConstU32; use pallet_evm::{AddressMapping, Precompile}; use parity_scale_codec::DecodeLimit; -use sp_core::{H160, U256}; +use sp_core::{H160, H256, U256}; use sp_std::marker::PhantomData; use sp_std::prelude::*; @@ -50,8 +50,15 @@ mod tests; #[precompile_utils::generate_function_selector] #[derive(Debug, PartialEq)] pub enum Action { + AssetsWithdrawNative = "assets_withdraw(address[],uint256[],bytes32,bool,uint256,uint256)", + AssetsWithdrawEvm = "assets_withdraw(address[],uint256[],address,bool,uint256,uint256)", + RemoteTransactOld = "remote_transact(uint256,bool,address,uint256,bytes,uint64)", + AssetsReserveTransferNative = + "assets_reserve_transfer(address[],uint256[],bytes32,bool,uint256,uint256)", + AssetsReserveTransferEvm = + "assets_reserve_transfer(address[],uint256[],address,bool,uint256,uint256)", AssetsWithdraw = "assets_withdraw(address[],uint256[],(uint8,bytes[]),(uint8,bytes[]),uint256)", - RemoteTransact = "remote_transact((uint8,bytes[]),address,uint256,bytes,uint64)", + RemoteTransactNew = "remote_transact((uint8,bytes[]),address,uint256,bytes,uint64)", AssetsReserveTransfer = "assets_reserve_transfer(address[],uint256[],(uint8,bytes[]),(uint8,bytes[]),uint256)", SendXCM = "send_xcm((uint8,bytes[]),bytes)", @@ -86,14 +93,35 @@ where // Dispatch the call match selector { + Action::AssetsWithdrawNative => { + Self::assets_withdraw_v1(handle, BeneficiaryType::Account32) + } + Action::AssetsWithdrawEvm => { + Self::assets_withdraw_v1(handle, BeneficiaryType::Account20) + } + Action::RemoteTransactOld => Self::remote_transact_v1(handle), + Action::AssetsReserveTransferNative => { + Self::assets_reserve_transfer_v1(handle, BeneficiaryType::Account32) + } + Action::AssetsReserveTransferEvm => { + Self::assets_reserve_transfer_v1(handle, BeneficiaryType::Account20) + } Action::AssetsWithdraw => Self::assets_withdraw(handle), - Action::RemoteTransact => Self::remote_transact(handle), + Action::RemoteTransactNew => Self::remote_transact(handle), Action::AssetsReserveTransfer => Self::assets_reserve_transfer(handle), Action::SendXCM => Self::send_xcm(handle), } } } +/// The supported beneficiary account types +enum BeneficiaryType { + /// 256 bit (32 byte) public key + Account32, + /// 160 bit (20 byte) address is expected + Account20, +} + impl XcmPrecompile where Runtime: pallet_evm::Config @@ -108,6 +136,277 @@ where + GetDispatchInfo, C: Convert::AssetId>, { + fn assets_withdraw_v1( + handle: &mut impl PrecompileHandle, + beneficiary_type: BeneficiaryType, + ) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(6)?; + + // Read arguments and check it + let assets: Vec = input + .read::>()? + .iter() + .cloned() + .filter_map(|address| { + Runtime::address_to_asset_id(address.into()).and_then(|x| C::reverse_ref(x).ok()) + }) + .collect(); + let amounts_raw = input.read::>()?; + if amounts_raw.iter().any(|x| *x > u128::MAX.into()) { + return Err(revert("Asset amount is too big")); + } + let amounts: Vec = amounts_raw.iter().map(|x| x.low_u128()).collect(); + + // Check that assets list is valid: + // * all assets resolved to multi-location + // * all assets has corresponded amount + if assets.len() != amounts.len() || assets.is_empty() { + return Err(revert("Assets resolution failure.")); + } + + let beneficiary: MultiLocation = match beneficiary_type { + BeneficiaryType::Account32 => { + let recipient: [u8; 32] = input.read::()?.into(); + X1(Junction::AccountId32 { + network: None, + id: recipient, + }) + } + BeneficiaryType::Account20 => { + let recipient: H160 = input.read::
()?.into(); + X1(Junction::AccountKey20 { + network: None, + key: recipient.to_fixed_bytes(), + }) + } + } + .into(); + + let is_relay = input.read::()?; + let parachain_id: u32 = input.read::()?.low_u32(); + let fee_asset_item: u32 = input.read::()?.low_u32(); + + if fee_asset_item as usize > assets.len() { + return Err(revert("Bad fee index.")); + } + + // Prepare pallet-xcm call arguments + let dest = if is_relay { + MultiLocation::parent() + } else { + X1(Junction::Parachain(parachain_id)).into_exterior(1) + }; + + let assets: MultiAssets = assets + .iter() + .cloned() + .zip(amounts.iter().cloned()) + .map(Into::into) + .collect::>() + .into(); + + // Build call with origin. + let origin = Some(Runtime::AddressMapping::into_account_id( + handle.context().caller, + )) + .into(); + let call = pallet_xcm::Call::::reserve_withdraw_assets { + dest: Box::new(dest.into()), + beneficiary: Box::new(beneficiary.into()), + assets: Box::new(assets.into()), + fee_asset_item, + }; + + // Dispatch a call. + RuntimeHelper::::try_dispatch(handle, origin, call)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + fn remote_transact_v1(handle: &mut impl PrecompileHandle) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(6)?; + + // Raw call arguments + let para_id: u32 = input.read::()?.low_u32(); + let is_relay = input.read::()?; + + let fee_asset_addr = input.read::
()?; + let fee_amount = input.read::()?; + + let remote_call: Vec = input.read::()?.into(); + let transact_weight = input.read::()?; + + log::trace!(target: "xcm-precompile:remote_transact", "Raw arguments: para_id: {}, is_relay: {}, fee_asset_addr: {:?}, \ + fee_amount: {:?}, remote_call: {:?}, transact_weight: {}", + para_id, is_relay, fee_asset_addr, fee_amount, remote_call, transact_weight); + + // Process arguments + let dest = if is_relay { + MultiLocation::parent() + } else { + X1(Junction::Parachain(para_id)).into_exterior(1) + }; + + let fee_asset = { + let address: H160 = fee_asset_addr.into(); + + // Special case where zero address maps to native token by convention. + if address == NATIVE_ADDRESS { + Here.into() + } else { + let fee_asset_id = Runtime::address_to_asset_id(address) + .ok_or(revert("Failed to resolve fee asset id from address"))?; + C::reverse_ref(fee_asset_id).map_err(|_| { + revert("Failed to resolve fee asset multilocation from local id") + })? + } + }; + + if fee_amount > u128::MAX.into() { + return Err(revert("Fee amount is too big")); + } + let fee_amount = fee_amount.low_u128(); + + let context = Runtime::UniversalLocation::get(); + let fee_multilocation = MultiAsset { + id: Concrete(fee_asset), + fun: Fungible(fee_amount), + }; + let fee_multilocation = fee_multilocation + .reanchored(&dest, context) + .map_err(|_| revert("Failed to reanchor fee asset"))?; + + // Prepare XCM + let xcm = Xcm(vec![ + WithdrawAsset(fee_multilocation.clone().into()), + BuyExecution { + fees: fee_multilocation.clone().into(), + weight_limit: WeightLimit::Unlimited, + }, + Transact { + origin_kind: OriginKind::SovereignAccount, + require_weight_at_most: Weight::from_ref_time(transact_weight), + call: remote_call.into(), + }, + ]); + + log::trace!(target: "xcm-precompile:remote_transact", "Processed arguments: dest: {:?}, fee asset: {:?}, XCM: {:?}", dest, fee_multilocation, xcm); + + // Build call with origin. + let origin = Some(Runtime::AddressMapping::into_account_id( + handle.context().caller, + )) + .into(); + let call = pallet_xcm::Call::::send { + dest: Box::new(dest.into()), + message: Box::new(xcm::VersionedXcm::V3(xcm)), + }; + + // Dispatch a call. + RuntimeHelper::::try_dispatch(handle, origin, call)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + fn assets_reserve_transfer_v1( + handle: &mut impl PrecompileHandle, + beneficiary_type: BeneficiaryType, + ) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(6)?; + + // Read arguments and check it + let assets: Vec = input + .read::>()? + .iter() + .cloned() + .filter_map(|address| { + let address: H160 = address.into(); + + // Special case where zero address maps to native token by convention. + if address == NATIVE_ADDRESS { + Some(Here.into()) + } else { + Runtime::address_to_asset_id(address).and_then(|x| C::reverse_ref(x).ok()) + } + }) + .collect(); + let amounts_raw = input.read::>()?; + if amounts_raw.iter().any(|x| *x > u128::MAX.into()) { + return Err(revert("Asset amount is too big")); + } + let amounts: Vec = amounts_raw.iter().map(|x| x.low_u128()).collect(); + + log::trace!(target: "xcm-precompile:assets_reserve_transfer", "Processed arguments: assets {:?}, amounts: {:?}", assets, amounts); + + // Check that assets list is valid: + // * all assets resolved to multi-location + // * all assets has corresponded amount + if assets.len() != amounts.len() || assets.is_empty() { + return Err(revert("Assets resolution failure.")); + } + + let beneficiary: MultiLocation = match beneficiary_type { + BeneficiaryType::Account32 => { + let recipient: [u8; 32] = input.read::()?.into(); + X1(Junction::AccountId32 { + network: None, + id: recipient, + }) + } + BeneficiaryType::Account20 => { + let recipient: H160 = input.read::
()?.into(); + X1(Junction::AccountKey20 { + network: None, + key: recipient.to_fixed_bytes(), + }) + } + } + .into(); + + let is_relay = input.read::()?; + let parachain_id: u32 = input.read::()?.low_u32(); + let fee_asset_item: u32 = input.read::()?.low_u32(); + + if fee_asset_item as usize > assets.len() { + return Err(revert("Bad fee index.")); + } + + // Prepare pallet-xcm call arguments + let dest = if is_relay { + MultiLocation::parent() + } else { + X1(Junction::Parachain(parachain_id)).into_exterior(1) + }; + + let assets: MultiAssets = assets + .iter() + .cloned() + .zip(amounts.iter().cloned()) + .map(Into::into) + .collect::>() + .into(); + + // Build call with origin. + let origin = Some(Runtime::AddressMapping::into_account_id( + handle.context().caller, + )) + .into(); + let call = pallet_xcm::Call::::reserve_transfer_assets { + dest: Box::new(dest.into()), + beneficiary: Box::new(beneficiary.into()), + assets: Box::new(assets.into()), + fee_asset_item, + }; + + // Dispatch a call. + RuntimeHelper::::try_dispatch(handle, origin, call)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + fn assets_withdraw(handle: &mut impl PrecompileHandle) -> EvmResult { let mut input = handle.read_input()?; input.expect_arguments(6)?; @@ -183,7 +482,6 @@ where let remote_call: Vec = input.read::()?.into(); let transact_weight = input.read::()?; - let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); log::trace!(target: "xcm-precompile::remote_transact", "Raw arguments: dest: {:?}, fee_asset_addr: {:?} \ fee_amount: {:?}, remote_call: {:?}, transact_weight: {}", diff --git a/precompiles/xcm/src/tests.rs b/precompiles/xcm/src/tests.rs index d4c88089..63019e9d 100644 --- a/precompiles/xcm/src/tests.rs +++ b/precompiles/xcm/src/tests.rs @@ -31,351 +31,608 @@ fn precompiles() -> TestPrecompileSet { PrecompilesValue::get() } -#[test] -fn wrong_assets_len_or_fee_index_reverts() { - ExtBuilder::default().build().execute_with(|| { - let dest: MultiLocation = MultiLocation { - parents: 1, - interior: Junctions::X1(Junction::Parachain(2000u32)), - }; - - let beneficiary: MultiLocation = MultiLocation { - parents: 0, - interior: Junctions::X1(AccountId32 { - network: None, - id: H256::repeat_byte(0xF1).into(), - }), - }; - precompiles() - .prepare_test( - TestAccount::Alice, - PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsWithdraw) - .write(vec![Address::from(H160::repeat_byte(0xF1))]) - .write(Vec::::new()) - .write(beneficiary) - .write(dest) - .write(U256::from(0_u64)) - .build(), - ) - .expect_no_logs() - .execute_reverts(|output| output == b"Assets resolution failure."); - - precompiles() - .prepare_test( - TestAccount::Alice, - PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsWithdraw) - .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) - .write(vec![U256::from(42000u64)]) - .write(beneficiary) - .write(dest) - .write(U256::from(2_u64)) - .build(), - ) - .expect_no_logs() - .execute_reverts(|output| output == b"Bad fee index."); - }); -} +mod xcm_old_interface_test { + use super::*; + #[test] + fn wrong_assets_len_or_fee_index_reverts() { + ExtBuilder::default().build().execute_with(|| { + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsWithdrawNative) + .write(vec![Address::from(H160::repeat_byte(0xF1))]) + .write(Vec::::new()) + .write(H256::repeat_byte(0xF1)) + .write(true) + .write(U256::from(0_u64)) + .write(U256::from(0_u64)) + .build(), + ) + .expect_no_logs() + .execute_reverts(|output| output == b"Assets resolution failure."); + + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsWithdrawNative) + .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) + .write(vec![U256::from(42000u64)]) + .write(H256::repeat_byte(0xF1)) + .write(true) + .write(U256::from(0_u64)) + .write(U256::from(2_u64)) + .build(), + ) + .expect_no_logs() + .execute_reverts(|output| output == b"Bad fee index."); + }); + } -#[test] -fn assets_withdraw_works() { - ExtBuilder::default().build().execute_with(|| { - let dest: MultiLocation = MultiLocation { - parents: 1, - interior: Junctions::Here, - }; - - let beneficiary: MultiLocation = MultiLocation { - parents: 0, - interior: Junctions::X1(AccountId32 { - network: None, - id: H256::repeat_byte(0xF1).into(), - }), - }; - - // SS58 - precompiles() - .prepare_test( - TestAccount::Alice, - PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsWithdraw) - .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) - .write(vec![U256::from(42000u64)]) - .write(beneficiary) - .write(dest) - .write(U256::from(0_u64)) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); - - // H160 - let beneficiary: MultiLocation = MultiLocation { - parents: 0, - interior: Junctions::X1(AccountKey20 { - network: None, - key: H160::repeat_byte(0xDE).into(), - }), - }; - precompiles() - .prepare_test( - TestAccount::Alice, - PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsWithdraw) - .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) - .write(vec![U256::from(42000u64)]) - .write(beneficiary) - .write(dest) - .write(U256::from(0_u64)) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); - }); -} + #[test] + fn assets_withdraw_works() { + ExtBuilder::default().build().execute_with(|| { + // SS58 + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsWithdrawNative) + .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) + .write(vec![U256::from(42000u64)]) + .write(H256::repeat_byte(0xF1)) + .write(true) + .write(U256::from(0_u64)) + .write(U256::from(0_u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + // H160 + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsWithdrawEvm) + .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) + .write(vec![U256::from(42000u64)]) + .write(Address::from(H160::repeat_byte(0xDE))) + .write(true) + .write(U256::from(0_u64)) + .write(U256::from(0_u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + }); + } + + #[test] + fn remote_transact_works() { + ExtBuilder::default().build().execute_with(|| { + // SS58 + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::RemoteTransactOld) + .write(U256::from(0_u64)) + .write(true) + .write(Address::from(Runtime::asset_id_to_address(1_u128))) + .write(U256::from(367)) + .write(vec![0xff_u8, 0xaa, 0x77, 0x00]) + .write(U256::from(3_000_000_000u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + }); + } + + #[test] + fn reserve_transfer_assets_works() { + ExtBuilder::default().build().execute_with(|| { + // SS58 + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsReserveTransferNative) + .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) + .write(vec![U256::from(42000u64)]) + .write(H256::repeat_byte(0xF1)) + .write(true) + .write(U256::from(0_u64)) + .write(U256::from(0_u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + // H160 + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsReserveTransferEvm) + .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) + .write(vec![U256::from(42000u64)]) + .write(Address::from(H160::repeat_byte(0xDE))) + .write(true) + .write(U256::from(0_u64)) + .write(U256::from(0_u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + }); + + for (location, Xcm(instructions)) in take_sent_xcm() { + assert_eq!( + location, + MultiLocation { + parents: 1, + interior: Here + } + ); + + let non_native_asset = MultiAsset { + fun: Fungible(42000), + id: xcm::v3::AssetId::from(MultiLocation { + parents: 0, + interior: Here, + }), + }; + + assert_matches!( + instructions.as_slice(), + [ + ReserveAssetDeposited(assets), + ClearOrigin, + BuyExecution { + fees, + .. + }, + DepositAsset { + beneficiary: MultiLocation { + parents: 0, + interior: X1(_), + }, + .. + } + ] + + if fees.contains(&non_native_asset) && assets.contains(&non_native_asset) + ); + } + } -#[test] -fn remote_transact_works() { - ExtBuilder::default().build().execute_with(|| { - let multilocation = MultiLocation { - parents: 1, - interior: Junctions::X1(Junction::Parachain(2000u32)), - }; - // SS58 - precompiles() - .prepare_test( - TestAccount::Alice, - PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::RemoteTransact) - .write(multilocation) - .write(Address::from(Runtime::asset_id_to_address(1_u128))) - .write(U256::from(367)) - .write(vec![0xff_u8, 0xaa, 0x77, 0x00]) - .write(U256::from(3_000_000_000u64)) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); - }); + #[test] + fn reserve_transfer_currency_works() { + ExtBuilder::default().build().execute_with(|| { + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsReserveTransferNative) + .write(vec![Address::from(H160::zero())]) // zero address by convention + .write(vec![U256::from(42000u64)]) + .write(H256::repeat_byte(0xF1)) + .write(true) + .write(U256::from(0_u64)) + .write(U256::from(0_u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsReserveTransferEvm) + .write(vec![Address::from(H160::zero())]) // zero address by convention + .write(vec![U256::from(42000u64)]) + .write(Address::from(H160::repeat_byte(0xDE))) + .write(true) + .write(U256::from(0_u64)) + .write(U256::from(0_u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + }); + + for (location, Xcm(instructions)) in take_sent_xcm() { + assert_eq!( + location, + MultiLocation { + parents: 1, + interior: Here + } + ); + + let native_asset = MultiAsset { + fun: Fungible(42000), + id: xcm::v3::AssetId::from(MultiLocation { + parents: 0, + interior: X1(Parachain(123)), + }), + }; + + assert_matches!( + instructions.as_slice(), + [ + ReserveAssetDeposited(assets), + ClearOrigin, + BuyExecution { + fees, + .. + }, + DepositAsset { + beneficiary: MultiLocation { + parents: 0, + interior: X1(_), + }, + .. + } + ] + + if fees.contains(&native_asset) && assets.contains(&native_asset) + ); + } + } } +mod xcm_new_interface_test { + use super::*; + #[test] + fn wrong_assets_len_or_fee_index_reverts() { + ExtBuilder::default().build().execute_with(|| { + let dest: MultiLocation = MultiLocation { + parents: 1, + interior: Junctions::X1(Junction::Parachain(2000u32)), + }; -#[test] -fn reserve_transfer_assets_works() { - ExtBuilder::default().build().execute_with(|| { - let dest: MultiLocation = MultiLocation { - parents: 1, - interior: Junctions::Here, - }; - - let beneficiary: MultiLocation = MultiLocation { - parents: 0, - interior: Junctions::X1(AccountId32 { - network: None, - id: H256::repeat_byte(0xF1).into(), - }), - }; - // SS58 - precompiles() - .prepare_test( - TestAccount::Alice, - PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsReserveTransfer) - .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) - .write(vec![U256::from(42000u64)]) - .write(beneficiary) - .write(dest) - .write(U256::from(0_u64)) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); - - // H160 - let beneficiary: MultiLocation = MultiLocation { - parents: 0, - interior: Junctions::X1(AccountKey20 { - network: None, - key: H160::repeat_byte(0xDE).into(), - }), - }; - precompiles() - .prepare_test( - TestAccount::Alice, - PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsReserveTransfer) - .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) - .write(vec![U256::from(42000u64)]) - .write(beneficiary) - .write(dest) - .write(U256::from(0_u64)) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); - }); - - for (location, Xcm(instructions)) in take_sent_xcm() { - assert_eq!( - location, - MultiLocation { + let beneficiary: MultiLocation = MultiLocation { + parents: 0, + interior: Junctions::X1(AccountId32 { + network: None, + id: H256::repeat_byte(0xF1).into(), + }), + }; + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsWithdraw) + .write(vec![Address::from(H160::repeat_byte(0xF1))]) + .write(Vec::::new()) + .write(beneficiary) + .write(dest) + .write(U256::from(0_u64)) + .build(), + ) + .expect_no_logs() + .execute_reverts(|output| output == b"Assets resolution failure."); + + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsWithdraw) + .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) + .write(vec![U256::from(42000u64)]) + .write(beneficiary) + .write(dest) + .write(U256::from(2_u64)) + .build(), + ) + .expect_no_logs() + .execute_reverts(|output| output == b"Bad fee index."); + }); + } + + #[test] + fn assets_withdraw_works() { + ExtBuilder::default().build().execute_with(|| { + let dest: MultiLocation = MultiLocation { parents: 1, - interior: Here - } - ); + interior: Junctions::Here, + }; - let non_native_asset = MultiAsset { - fun: Fungible(42000), - id: xcm::v3::AssetId::from(MultiLocation { + let beneficiary: MultiLocation = MultiLocation { parents: 0, - interior: Here, - }), - }; - - assert_matches!( - instructions.as_slice(), - [ - ReserveAssetDeposited(assets), - ClearOrigin, - BuyExecution { - fees, - .. - }, - DepositAsset { - beneficiary: MultiLocation { - parents: 0, - interior: X1(_), - }, - .. - } - ] + interior: Junctions::X1(AccountId32 { + network: None, + id: H256::repeat_byte(0xF1).into(), + }), + }; + + // SS58 + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsWithdraw) + .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) + .write(vec![U256::from(42000u64)]) + .write(beneficiary) + .write(dest) + .write(U256::from(0_u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + // H160 + let beneficiary: MultiLocation = MultiLocation { + parents: 0, + interior: Junctions::X1(AccountKey20 { + network: None, + key: H160::repeat_byte(0xDE).into(), + }), + }; + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsWithdraw) + .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) + .write(vec![U256::from(42000u64)]) + .write(beneficiary) + .write(dest) + .write(U256::from(0_u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + }); + } - if fees.contains(&non_native_asset) && assets.contains(&non_native_asset) - ); + #[test] + fn remote_transact_works() { + ExtBuilder::default().build().execute_with(|| { + let multilocation = MultiLocation { + parents: 1, + interior: Junctions::X1(Junction::Parachain(2000u32)), + }; + // SS58 + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::RemoteTransactNew) + .write(multilocation) + .write(Address::from(Runtime::asset_id_to_address(1_u128))) + .write(U256::from(367)) + .write(vec![0xff_u8, 0xaa, 0x77, 0x00]) + .write(U256::from(3_000_000_000u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + }); } -} -#[test] -fn reserve_transfer_currency_works() { - ExtBuilder::default().build().execute_with(|| { - let dest: MultiLocation = MultiLocation { - parents: 1, - interior: Junctions::Here, - }; - - let beneficiary: MultiLocation = MultiLocation { - parents: 0, - interior: Junctions::X1(AccountId32 { - network: None, - id: H256::repeat_byte(0xF1).into(), - }), - }; - precompiles() - .prepare_test( - TestAccount::Alice, - PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsReserveTransfer) - .write(vec![Address::from(H160::zero())]) // zero address by convention - .write(vec![U256::from(42000u64)]) - .write(beneficiary) - .write(dest) - .write(U256::from(0_u64)) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); - - let beneficiary: MultiLocation = MultiLocation { - parents: 0, - interior: Junctions::X1(AccountKey20 { - network: None, - key: H160::repeat_byte(0xDE).into(), - }), - }; - precompiles() - .prepare_test( - TestAccount::Alice, - PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsReserveTransfer) - .write(vec![Address::from(H160::zero())]) // zero address by convention - .write(vec![U256::from(42000u64)]) - .write(beneficiary) - .write(dest) - .write(U256::from(0_u64)) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); - }); - - for (location, Xcm(instructions)) in take_sent_xcm() { - assert_eq!( - location, - MultiLocation { + #[test] + fn reserve_transfer_assets_works() { + ExtBuilder::default().build().execute_with(|| { + let dest: MultiLocation = MultiLocation { parents: 1, - interior: Here - } - ); + interior: Junctions::Here, + }; - let native_asset = MultiAsset { - fun: Fungible(42000), - id: xcm::v3::AssetId::from(MultiLocation { + let beneficiary: MultiLocation = MultiLocation { parents: 0, - interior: X1(Parachain(123)), - }), - }; - - assert_matches!( - instructions.as_slice(), - [ - ReserveAssetDeposited(assets), - ClearOrigin, - BuyExecution { - fees, - .. - }, - DepositAsset { - beneficiary: MultiLocation { - parents: 0, - interior: X1(_), - }, - .. + interior: Junctions::X1(AccountId32 { + network: None, + id: H256::repeat_byte(0xF1).into(), + }), + }; + // SS58 + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsReserveTransfer) + .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) + .write(vec![U256::from(42000u64)]) + .write(beneficiary) + .write(dest) + .write(U256::from(0_u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + // H160 + let beneficiary: MultiLocation = MultiLocation { + parents: 0, + interior: Junctions::X1(AccountKey20 { + network: None, + key: H160::repeat_byte(0xDE).into(), + }), + }; + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsReserveTransfer) + .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) + .write(vec![U256::from(42000u64)]) + .write(beneficiary) + .write(dest) + .write(U256::from(0_u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + }); + + for (location, Xcm(instructions)) in take_sent_xcm() { + assert_eq!( + location, + MultiLocation { + parents: 1, + interior: Here } - ] + ); + + let non_native_asset = MultiAsset { + fun: Fungible(42000), + id: xcm::v3::AssetId::from(MultiLocation { + parents: 0, + interior: Here, + }), + }; + + assert_matches!( + instructions.as_slice(), + [ + ReserveAssetDeposited(assets), + ClearOrigin, + BuyExecution { + fees, + .. + }, + DepositAsset { + beneficiary: MultiLocation { + parents: 0, + interior: X1(_), + }, + .. + } + ] + + if fees.contains(&non_native_asset) && assets.contains(&non_native_asset) + ); + } + } - if fees.contains(&native_asset) && assets.contains(&native_asset) - ); + #[test] + fn reserve_transfer_currency_works() { + ExtBuilder::default().build().execute_with(|| { + let dest: MultiLocation = MultiLocation { + parents: 1, + interior: Junctions::Here, + }; + + let beneficiary: MultiLocation = MultiLocation { + parents: 0, + interior: Junctions::X1(AccountId32 { + network: None, + id: H256::repeat_byte(0xF1).into(), + }), + }; + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsReserveTransfer) + .write(vec![Address::from(H160::zero())]) // zero address by convention + .write(vec![U256::from(42000u64)]) + .write(beneficiary) + .write(dest) + .write(U256::from(0_u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + let beneficiary: MultiLocation = MultiLocation { + parents: 0, + interior: Junctions::X1(AccountKey20 { + network: None, + key: H160::repeat_byte(0xDE).into(), + }), + }; + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsReserveTransfer) + .write(vec![Address::from(H160::zero())]) // zero address by convention + .write(vec![U256::from(42000u64)]) + .write(beneficiary) + .write(dest) + .write(U256::from(0_u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + }); + + for (location, Xcm(instructions)) in take_sent_xcm() { + assert_eq!( + location, + MultiLocation { + parents: 1, + interior: Here + } + ); + + let native_asset = MultiAsset { + fun: Fungible(42000), + id: xcm::v3::AssetId::from(MultiLocation { + parents: 0, + interior: X1(Parachain(123)), + }), + }; + + assert_matches!( + instructions.as_slice(), + [ + ReserveAssetDeposited(assets), + ClearOrigin, + BuyExecution { + fees, + .. + }, + DepositAsset { + beneficiary: MultiLocation { + parents: 0, + interior: X1(_), + }, + .. + } + ] + + if fees.contains(&native_asset) && assets.contains(&native_asset) + ); + } } -} -#[test] -fn test_send_clear_origin() { - ExtBuilder::default().build().execute_with(|| { - let dest: MultiLocation = MultiLocation { - parents: 1, - interior: Junctions::X1(Junction::AccountId32 { - network: None, - id: H256::repeat_byte(0xF1).into(), - }), - }; - let xcm_to_send = VersionedXcm::<()>::V3(Xcm(vec![ClearOrigin])).encode(); - precompiles() - .prepare_test( - TestAccount::Alice, - PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::SendXCM) - .write(dest) - .write(Bytes::from(xcm_to_send.as_slice())) - .build(), - ) - // Fixed: TestWeightInfo + (BaseXcmWeight * MessageLen) - .expect_cost(100001000) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); - - let sent_messages = take_sent_xcm(); - let (_, sent_message) = sent_messages.first().unwrap(); - // Lets make sure the message is as expected - assert!(sent_message.0.contains(&ClearOrigin)); - }) + #[test] + fn test_send_clear_origin() { + ExtBuilder::default().build().execute_with(|| { + let dest: MultiLocation = MultiLocation { + parents: 1, + interior: Junctions::X1(Junction::AccountId32 { + network: None, + id: H256::repeat_byte(0xF1).into(), + }), + }; + let xcm_to_send = VersionedXcm::<()>::V3(Xcm(vec![ClearOrigin])).encode(); + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::SendXCM) + .write(dest) + .write(Bytes::from(xcm_to_send.as_slice())) + .build(), + ) + // Fixed: TestWeightInfo + (BaseXcmWeight * MessageLen) + .expect_cost(100001000) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + let sent_messages = take_sent_xcm(); + let (_, sent_message) = sent_messages.first().unwrap(); + // Lets make sure the message is as expected + assert!(sent_message.0.contains(&ClearOrigin)); + }) + } } From 6c5a6ea066b0a71724e5f98ca567855bad8d4b42 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Mon, 26 Jun 2023 08:57:12 +0530 Subject: [PATCH 21/33] fmt --- precompiles/utils/src/bytes.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/precompiles/utils/src/bytes.rs b/precompiles/utils/src/bytes.rs index aa33aab3..ba37b4e7 100644 --- a/precompiles/utils/src/bytes.rs +++ b/precompiles/utils/src/bytes.rs @@ -146,7 +146,6 @@ impl> EvmData for BoundedBytesString { fn has_static_size() -> bool { false } - } // BytesString <=> Vec/&[u8] From 6038c635d222d3ec89e4447e8103a8d262e40ed0 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Mon, 26 Jun 2023 10:05:56 +0530 Subject: [PATCH 22/33] add tests for multilocation and bytes --- precompiles/utils/src/tests.rs | 261 +++++++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) diff --git a/precompiles/utils/src/tests.rs b/precompiles/utils/src/tests.rs index 6756687b..fb76ce74 100644 --- a/precompiles/utils/src/tests.rs +++ b/precompiles/utils/src/tests.rs @@ -20,9 +20,15 @@ // You should have received a copy of the GNU General Public License // along with Utils. If not, see . +use crate::bytes::UnboundedBytes; + use super::*; use hex_literal::hex; use sp_core::{H256, U256}; +use { + crate::xcm::{network_id_from_bytes, network_id_to_bytes}, + ::xcm::latest::{Junction, Junctions, NetworkId}, +}; fn u256_repeat_byte(byte: u8) -> U256 { let value = H256::repeat_byte(byte); @@ -505,7 +511,19 @@ fn read_bytes() { assert_eq!(data, parsed.as_bytes()); } +#[test] +fn read_unbounded_bytes() { + let data = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod\ + tempor incididunt ut labore et dolore magna aliqua."; + let writer_output = EvmDataWriter::new() + .write(UnboundedBytes::from(&data[..])) + .build(); + let mut reader = EvmDataReader::new(&writer_output); + let parsed: UnboundedBytes = reader.read().expect("to correctly parse Bytes"); + + assert_eq!(data, parsed.as_bytes()); +} #[test] fn write_bytes() { let data = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod\ @@ -529,6 +547,31 @@ fn write_bytes() { assert_eq!(read("read part 3"), H256::from_slice(&padded[0x40..0x60])); assert_eq!(read("read part 4"), H256::from_slice(&padded[0x60..0x80])); } +#[test] +fn write_unbounded_bytes() { + let data = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod\ + tempor incididunt ut labore et dolore magna aliqua."; + + let writer_output = EvmDataWriter::new() + .write(UnboundedBytes::from(&data[..])) + .build(); + + // We can read this "manualy" using simpler functions. + let mut reader = EvmDataReader::new(&writer_output); + + // We pad data to a multiple of 32 bytes. + let mut padded = data.to_vec(); + assert!(data.len() < 0x80); + padded.resize(0x80, 0); + + assert_eq!(reader.read::().expect("read offset"), 32.into()); + assert_eq!(reader.read::().expect("read size"), data.len().into()); + let mut read = |e| reader.read::().expect(e); // shorthand + assert_eq!(read("read part 1"), H256::from_slice(&padded[0x00..0x20])); + assert_eq!(read("read part 2"), H256::from_slice(&padded[0x20..0x40])); + assert_eq!(read("read part 3"), H256::from_slice(&padded[0x40..0x60])); + assert_eq!(read("read part 4"), H256::from_slice(&padded[0x60..0x80])); +} #[test] fn read_string() { @@ -618,7 +661,61 @@ fn write_vec_bytes() { assert_eq!(read("read part 3"), H256::from_slice(&padded[0x40..0x60])); assert_eq!(read("read part 4"), H256::from_slice(&padded[0x60..0x80])); } +#[test] +fn write_vec_unbounded_bytes() { + let data = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod\ + tempor incididunt ut labore et dolore magna aliqua."; + + let writer_output = EvmDataWriter::new() + .write(vec![ + UnboundedBytes::from(&data[..]), + UnboundedBytes::from(&data[..]), + ]) + .build(); + writer_output + .chunks_exact(32) + .map(|chunk| H256::from_slice(chunk)) + .for_each(|hash| println!("{:?}", hash)); + + // We pad data to a multiple of 32 bytes. + let mut padded = data.to_vec(); + assert!(data.len() < 0x80); + padded.resize(0x80, 0); + + let mut reader = EvmDataReader::new(&writer_output); + + // Offset of vec + assert_eq!(reader.read::().expect("read offset"), 32.into()); + + // Length of vec + assert_eq!(reader.read::().expect("read offset"), 2.into()); + + // Relative offset of first bytgmes object + assert_eq!(reader.read::().expect("read offset"), 0x40.into()); + // Relative offset of second bytes object + assert_eq!(reader.read::().expect("read offset"), 0xe0.into()); + + // Length of first bytes object + assert_eq!(reader.read::().expect("read size"), data.len().into()); + + // First byte objects data + let mut read = |e| reader.read::().expect(e); // shorthand + assert_eq!(read("read part 1"), H256::from_slice(&padded[0x00..0x20])); + assert_eq!(read("read part 2"), H256::from_slice(&padded[0x20..0x40])); + assert_eq!(read("read part 3"), H256::from_slice(&padded[0x40..0x60])); + assert_eq!(read("read part 4"), H256::from_slice(&padded[0x60..0x80])); + + // Length of second bytes object + assert_eq!(reader.read::().expect("read size"), data.len().into()); + + // Second byte objects data + let mut read = |e| reader.read::().expect(e); // shorthand + assert_eq!(read("read part 1"), H256::from_slice(&padded[0x00..0x20])); + assert_eq!(read("read part 2"), H256::from_slice(&padded[0x20..0x40])); + assert_eq!(read("read part 3"), H256::from_slice(&padded[0x40..0x60])); + assert_eq!(read("read part 4"), H256::from_slice(&padded[0x60..0x80])); +} #[test] fn read_vec_of_bytes() { let data = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod\ @@ -744,3 +841,167 @@ fn read_complex_solidity_function() { // weight assert_eq!(reader.read::().unwrap(), 100u32.into()); } + +#[test] +fn read_dynamic_size_tuple() { + // (uint8, bytes[]) encoded by web3 + let data = hex!( + "0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000040 + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000001 + 0100000000000000000000000000000000000000000000000000000000000000" + ); + + let mut reader = EvmDataReader::new(&data); + + assert_eq!( + reader.read::<(u8, Vec)>().unwrap(), + (1, vec![UnboundedBytes::from(vec![0x01])]) + ); +} +#[test] +fn junctions_decoder_works() { + let writer_output = EvmDataWriter::new() + .write(Junctions::X1(Junction::OnlyChild)) + .build(); + + let mut reader = EvmDataReader::new(&writer_output); + let parsed: Junctions = reader + .read::() + .expect("to correctly parse Junctions"); + + assert_eq!(parsed, Junctions::X1(Junction::OnlyChild)); + + let writer_output = EvmDataWriter::new() + .write(Junctions::X2(Junction::OnlyChild, Junction::OnlyChild)) + .build(); + + let mut reader = EvmDataReader::new(&writer_output); + let parsed: Junctions = reader + .read::() + .expect("to correctly parse Junctions"); + + assert_eq!( + parsed, + Junctions::X2(Junction::OnlyChild, Junction::OnlyChild) + ); + + let writer_output = EvmDataWriter::new() + .write(Junctions::X3( + Junction::OnlyChild, + Junction::OnlyChild, + Junction::OnlyChild, + )) + .build(); + + let mut reader = EvmDataReader::new(&writer_output); + let parsed: Junctions = reader + .read::() + .expect("to correctly parse Junctions"); + + assert_eq!( + parsed, + Junctions::X3( + Junction::OnlyChild, + Junction::OnlyChild, + Junction::OnlyChild + ), + ); +} + +#[test] +fn junction_decoder_works() { + let writer_output = EvmDataWriter::new().write(Junction::Parachain(0)).build(); + + let mut reader = EvmDataReader::new(&writer_output); + let parsed: Junction = reader + .read::() + .expect("to correctly parse Junctions"); + + assert_eq!(parsed, Junction::Parachain(0)); + + let writer_output = EvmDataWriter::new() + .write(Junction::AccountId32 { + network: None, + id: [1u8; 32], + }) + .build(); + + let mut reader = EvmDataReader::new(&writer_output); + let parsed: Junction = reader + .read::() + .expect("to correctly parse Junctions"); + + assert_eq!( + parsed, + Junction::AccountId32 { + network: None, + id: [1u8; 32], + } + ); + + let writer_output = EvmDataWriter::new() + .write(Junction::AccountIndex64 { + network: None, + index: u64::from_be_bytes([1u8; 8]), + }) + .build(); + + let mut reader = EvmDataReader::new(&writer_output); + let parsed: Junction = reader + .read::() + .expect("to correctly parse Junctions"); + + assert_eq!( + parsed, + Junction::AccountIndex64 { + network: None, + index: u64::from_be_bytes([1u8; 8]), + } + ); + + let writer_output = EvmDataWriter::new() + .write(Junction::AccountKey20 { + network: None, + key: H160::repeat_byte(0xAA).as_bytes().try_into().unwrap(), + }) + .build(); + + let mut reader = EvmDataReader::new(&writer_output); + let parsed: Junction = reader + .read::() + .expect("to correctly parse Junctions"); + + assert_eq!( + parsed, + Junction::AccountKey20 { + network: None, + key: H160::repeat_byte(0xAA).as_bytes().try_into().unwrap(), + } + ); +} + +#[test] +fn network_id_decoder_works() { + assert_eq!(network_id_from_bytes(network_id_to_bytes(None)), Ok(None)); + + let mut name = [0u8; 32]; + name[0..6].copy_from_slice(b"myname"); + assert_eq!( + network_id_from_bytes(network_id_to_bytes(Some(NetworkId::ByGenesis(name)))), + Ok(Some(NetworkId::ByGenesis(name))) + ); + + assert_eq!( + network_id_from_bytes(network_id_to_bytes(Some(NetworkId::Kusama))), + Ok(Some(NetworkId::Kusama)) + ); + + assert_eq!( + network_id_from_bytes(network_id_to_bytes(Some(NetworkId::Polkadot))), + Ok(Some(NetworkId::Polkadot)) + ); +} From 60bcbb604d88ea86d6e8b7ce9f4e6e254cd92235 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Mon, 26 Jun 2023 13:01:41 +0530 Subject: [PATCH 23/33] data encapsulation fix --- precompiles/utils/src/bytes.rs | 3 +-- precompiles/utils/src/data.rs | 7 ++++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/precompiles/utils/src/bytes.rs b/precompiles/utils/src/bytes.rs index ba37b4e7..047b97bf 100644 --- a/precompiles/utils/src/bytes.rs +++ b/precompiles/utils/src/bytes.rs @@ -107,8 +107,7 @@ impl> EvmData for BoundedBytesString { let range = inner_reader.move_cursor(array_size)?; let data = inner_reader - .input - .get(range) + .get_input_from_range(range) .ok_or_else(|| revert(K::signature()))?; let bytes = Self { diff --git a/precompiles/utils/src/data.rs b/precompiles/utils/src/data.rs index 1b459ebb..aca895d7 100644 --- a/precompiles/utils/src/data.rs +++ b/precompiles/utils/src/data.rs @@ -87,7 +87,7 @@ impl From for Vec { /// Provide functions to parse common types. #[derive(Clone, Copy, Debug)] pub struct EvmDataReader<'a> { - pub(crate) input: &'a [u8], + input: &'a [u8], cursor: usize, } @@ -175,6 +175,11 @@ impl<'a> EvmDataReader<'a> { }) } + /// Return Option<&[u8]> from a given range for EvmDataReader + pub fn get_input_from_range(&self, range: Range) -> Option<&[u8]> { + self.input.get(range) + } + /// Read remaining bytes pub fn read_till_end(&mut self) -> EvmResult<&[u8]> { let range = self.move_cursor(self.input.len() - self.cursor)?; From 27cd351a719d2d892096aa0c9e87b705dfa20290 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Sun, 9 Jul 2023 16:25:50 +0530 Subject: [PATCH 24/33] newline in tests --- precompiles/utils/src/tests.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/precompiles/utils/src/tests.rs b/precompiles/utils/src/tests.rs index fb76ce74..57938fac 100644 --- a/precompiles/utils/src/tests.rs +++ b/precompiles/utils/src/tests.rs @@ -511,6 +511,7 @@ fn read_bytes() { assert_eq!(data, parsed.as_bytes()); } + #[test] fn read_unbounded_bytes() { let data = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod\ From 028e3dce3b2c931b4dc25b48ef657e3d6bb30bde Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Fri, 14 Jul 2023 14:30:49 +0530 Subject: [PATCH 25/33] add mock and test::transfer for xtokens --- Cargo.toml | 3 + precompiles/xcm/Cargo.toml | 6 ++ precompiles/xcm/Xtokens.sol | 116 +++++++++++++++++++++++++++++++++++ precompiles/xcm/src/lib.rs | 65 +++++++++++++++++++- precompiles/xcm/src/mock.rs | 95 ++++++++++++++++++++++++++++ precompiles/xcm/src/tests.rs | 61 ++++++++++++++++++ 6 files changed, 343 insertions(+), 3 deletions(-) create mode 100644 precompiles/xcm/Xtokens.sol diff --git a/Cargo.toml b/Cargo.toml index 3eb8697f..a469b907 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -127,6 +127,9 @@ xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.3 xcm-executor = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39", default-features = false } polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39", default-features = false } xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39", default-features = false } +orml-xtokens = { git = "https://github.com/open-web3-stack/open-runtime-module-library", branch = "polkadot-v0.9.39", default-features = false } +orml-xcm-support = { git = "https://github.com/open-web3-stack/open-runtime-module-library", branch = "polkadot-v0.9.39", default-features = false } +orml-traits = { git = "https://github.com/open-web3-stack/open-runtime-module-library", branch = "polkadot-v0.9.39", default-features = false } # (native) polkadot-runtime-parachains = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39" } diff --git a/precompiles/xcm/Cargo.toml b/precompiles/xcm/Cargo.toml index b1ee2739..366bc840 100644 --- a/precompiles/xcm/Cargo.toml +++ b/precompiles/xcm/Cargo.toml @@ -28,6 +28,9 @@ fp-evm = { workspace = true } pallet-evm = { workspace = true } # Polkadot +orml-traits = { workspace = true } +orml-xcm-support = { workspace = true } +orml-xtokens = { workspace = true } xcm = { workspace = true } xcm-executor = { workspace = true } @@ -61,5 +64,8 @@ std = [ "sp-io/std", "xcm/std", "xcm-executor/std", + "orml-xtokens/std", + "orml-xcm-support/std", + "orml-traits/std", ] runtime-benchmarks = [] diff --git a/precompiles/xcm/Xtokens.sol b/precompiles/xcm/Xtokens.sol new file mode 100644 index 00000000..54411522 --- /dev/null +++ b/precompiles/xcm/Xtokens.sol @@ -0,0 +1,116 @@ + +pragma solidity ^0.8.0; + + +/** + * @title Xtokens interface. + */ +interface Xtokens { + // A multilocation is defined by its number of parents and the encoded junctions (interior) + struct Multilocation { + uint8 parents; + bytes[] interior; + } + + // A MultiAsset is defined by a multilocation and an amount + struct MultiAsset { + Multilocation location; + uint256 amount; + } + + // A Currency is defined by address and the amount to be transferred + struct Currency { + address currencyAddress; + uint256 amount; + } + + /// Transfer a token through XCM based on its currencyId + /// + /// @dev The token transfer burns/transfers the corresponding amount before sending + /// @param currencyAddress The ERC20 address of the currency we want to transfer + /// @param amount The amount of tokens we want to transfer + /// @param destination The Multilocation to which we want to send the tokens + /// @param weight The weight we want to buy in the destination chain + function transfer( + address currencyAddress, + uint256 amount, + Multilocation memory destination, + uint64 weight + ) external returns (bool); + + /// Transfer a token through XCM based on its currencyId specifying fee + /// + /// @dev The token transfer burns/transfers the corresponding amount before sending + /// @param currencyAddress The ERC20 address of the currency we want to transfer + /// @param amount The amount of tokens we want to transfer + /// @param destination The Multilocation to which we want to send the tokens + /// @param weight The weight we want to buy in the destination chain + function transfer_with_fee( + address currencyAddress, + uint256 amount, + uint256 fee, + Multilocation memory destination, + uint64 weight + ) external returns (bool); + + /// Transfer a token through XCM based on its MultiLocation + /// + /// @dev The token transfer burns/transfers the corresponding amount before sending + /// @param asset The asset we want to transfer, defined by its multilocation. + /// Currently only Concrete Fungible assets + /// @param amount The amount of tokens we want to transfer + /// @param destination The Multilocation to which we want to send the tokens + /// @param weight The weight we want to buy in the destination chain + function transfer_multiasset( + Multilocation memory asset, + uint256 amount, + Multilocation memory destination, + uint64 weight + ) external returns (bool); + + /// Transfer a token through XCM based on its MultiLocation specifying fee + /// + /// @dev The token transfer burns/transfers the corresponding amount before sending + /// @param asset The asset we want to transfer, defined by its multilocation. + /// Currently only Concrete Fungible assets + /// @param amount The amount of tokens we want to transfer + /// @param destination The Multilocation to which we want to send the tokens + /// @param weight The weight we want to buy in the destination chain + function transfer_multiasset_with_fee( + Multilocation memory asset, + uint256 amount, + uint256 fee, + Multilocation memory destination, + uint64 weight + ) external returns (bool); + + /// Transfer several tokens at once through XCM based on its address specifying fee + /// + /// @dev The token transfer burns/transfers the corresponding amount before sending + /// @param currencies The currencies we want to transfer, defined by their address and amount. + /// @param feeItem Which of the currencies to be used as fee + /// @param destination The Multilocation to which we want to send the tokens + /// @param weight The weight we want to buy in the destination chain + /// (uint64::MAX means Unlimited weight) + /// @custom:selector ab946323 + function transfer_multi_currencies( + Currency[] memory currencies, + uint32 feeItem, + Multilocation memory destination, + uint64 weight + ) external returns (bool); + + /// Transfer several tokens at once through XCM based on its location specifying fee + /// + /// @dev The token transfer burns/transfers the corresponding amount before sending + /// @param assets The assets we want to transfer, defined by their location and amount. + /// @param feeItem Which of the currencies to be used as fee + /// @param destination The Multilocation to which we want to send the tokens + /// @param weight The weight we want to buy in the destination chain + function transfer_multi_assets( + MultiAsset[] memory assets, + uint32 feeItem, + Multilocation memory destination, + uint64 weight + ) external returns (bool); +} diff --git a/precompiles/xcm/src/lib.rs b/precompiles/xcm/src/lib.rs index 6e8ea126..007e55a3 100644 --- a/precompiles/xcm/src/lib.rs +++ b/precompiles/xcm/src/lib.rs @@ -34,7 +34,7 @@ use sp_core::{H160, H256, U256}; use sp_std::marker::PhantomData; use sp_std::prelude::*; -use xcm::latest::prelude::*; +use xcm::{latest::prelude::*, VersionedMultiLocation}; use xcm_executor::traits::Convert; use pallet_evm_precompile_assets_erc20::AddressToAssetId; @@ -62,11 +62,20 @@ pub enum Action { AssetsReserveTransfer = "assets_reserve_transfer(address[],uint256[],(uint8,bytes[]),(uint8,bytes[]),uint256)", SendXCM = "send_xcm((uint8,bytes[]),bytes)", + XtokensTransfer = "transfer(address,uint256,(uint8,bytes[]),uint64)", + // XtokensTransferWithFee = "transfer_with_fee(address,uint256,uint256,(uint8,bytes[]),uint64)", + // XtokensTransferMultiasset = "transfer_multiasset((uint8,bytes[]),uint256,(uint8,bytes[]),uint64)", + // XtokensTransferMultiassetWithFee = "transfer_multiasset_with_fee((uint8,bytes[]),uint256,uint256,(uint8,bytes[]),uint64)", + // XtokensTransferMulticurrencies = "transfer_multi_currencies((address,uint256)[],uint32,(uint8,bytes[]),uint64)", + // XtokensTransferMultiassets = "transfet_multi_assets(((uint8,bytes[]),uint256)[],uint32,(uint8,bytes[]),uint64)", } /// Dummy H160 address representing native currency (e.g. ASTR or SDN) const NATIVE_ADDRESS: H160 = H160::zero(); +/// Dummy default 64KB +const DEFAULT_PROOF_SIZE: u64 = 1024 * 64; +pub type XBalanceOf = ::Balance; /// A precompile that expose XCM related functions. pub struct XcmPrecompile(PhantomData<(T, C)>); @@ -75,13 +84,18 @@ where Runtime: pallet_evm::Config + pallet_xcm::Config + pallet_assets::Config + + orml_xtokens::Config + AddressToAssetId<::AssetId>, <::RuntimeCall as Dispatchable>::RuntimeOrigin: From>, ::AccountId: Into<[u8; 32]>, ::RuntimeCall: From> + + From> + Dispatchable + GetDispatchInfo, + XBalanceOf: TryFrom + Into, + ::CurrencyId: + From<::AssetId>, C: Convert::AssetId>, { fn execute(handle: &mut impl PrecompileHandle) -> EvmResult { @@ -110,6 +124,7 @@ where Action::RemoteTransactNew => Self::remote_transact(handle), Action::AssetsReserveTransfer => Self::assets_reserve_transfer(handle), Action::SendXCM => Self::send_xcm(handle), + Action::XtokensTransfer => Self::transfer(handle), } } } @@ -126,14 +141,19 @@ impl XcmPrecompile where Runtime: pallet_evm::Config + pallet_xcm::Config + + orml_xtokens::Config + pallet_assets::Config + AddressToAssetId<::AssetId>, <::RuntimeCall as Dispatchable>::RuntimeOrigin: From>, ::AccountId: Into<[u8; 32]>, ::RuntimeCall: From> + + From> + Dispatchable + GetDispatchInfo, + XBalanceOf: TryFrom + Into, + ::CurrencyId: + From<::AssetId>, C: Convert::AssetId>, { fn assets_withdraw_v1( @@ -269,7 +289,7 @@ where } let fee_amount = fee_amount.low_u128(); - let context = Runtime::UniversalLocation::get(); + let context = ::UniversalLocation::get(); let fee_multilocation = MultiAsset { id: Concrete(fee_asset), fun: Fungible(fee_amount), @@ -507,7 +527,7 @@ where } let fee_amount = fee_amount.low_u128(); - let context = Runtime::UniversalLocation::get(); + let context = ::UniversalLocation::get(); let fee_multilocation = MultiAsset { id: Concrete(fee_asset), fun: Fungible(fee_amount), @@ -649,4 +669,43 @@ where Ok(succeed(EvmDataWriter::new().write(true).build())) } + + fn transfer(handle: &mut impl PrecompileHandle) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(5)?; + + // Read call arguments + let currency_address = input.read::
()?; + let amount_of_tokens = input + .read::()? + .try_into() + .map_err(|_| revert("error converting amount_of_tokens, maybe value too large"))?; + let destination = input.read::()?; + let weight = input.read::()?; + + let asset_id = Runtime::address_to_asset_id(currency_address.into()) + .ok_or(revert("Failed to resolve fee asset id from address"))?; + let dest_weight_limit = if weight == u64::MAX { + WeightLimit::Unlimited + } else { + WeightLimit::Limited(Weight::from_parts(weight, DEFAULT_PROOF_SIZE)) + }; + + let call = orml_xtokens::Call::::transfer { + currency_id: asset_id.into(), + amount: amount_of_tokens, + dest: Box::new(VersionedMultiLocation::V3(destination)), + dest_weight_limit, + }; + + let origin = Some(Runtime::AddressMapping::into_account_id( + handle.context().caller, + )) + .into(); + + // Dispatch a call. + RuntimeHelper::::try_dispatch(handle, origin, call)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } } diff --git a/precompiles/xcm/src/mock.rs b/precompiles/xcm/src/mock.rs index 0983dfc5..5c50ca51 100644 --- a/precompiles/xcm/src/mock.rs +++ b/precompiles/xcm/src/mock.rs @@ -46,6 +46,9 @@ use xcm_builder::{ AllowTopLevelPaidExecutionFrom, FixedWeightBounds, SignedToAccountId32, TakeWeightCredit, }; use xcm_executor::XcmExecutor; +// orml imports +use orml_traits::location::{RelativeReserveProvider, Reserve}; +use orml_xcm_support::DisabledParachainFee; pub type AccountId = TestAccount; pub type AssetId = u128; @@ -53,6 +56,22 @@ pub type Balance = u128; pub type BlockNumber = u64; pub type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; pub type Block = frame_system::mocking::MockBlock; +pub type CurrencyId = u128; + +/// Multilocations for assetId +const PARENT: MultiLocation = MultiLocation::parent(); +const PARACHAIN: MultiLocation = MultiLocation { + parents: 1, + interior: Junctions::X1(Parachain(10)), +}; +const GENERAL_INDEX: MultiLocation = MultiLocation { + parents: 1, + interior: Junctions::X2(Parachain(10), GeneralIndex(20)), +}; +const LOCAL_ASSET: MultiLocation = MultiLocation { + parents: 0, + interior: Junctions::X1(GeneralIndex(20)), +}; pub const PRECOMPILE_ADDRESS: H160 = H160::repeat_byte(0x7B); pub const ASSET_PRECOMPILE_ADDRESS_PREFIX: &[u8] = &[255u8; 4]; @@ -147,6 +166,49 @@ impl AddressToAssetId for Runtime { } } +pub struct CurrencyIdToMultiLocation; + +impl sp_runtime::traits::Convert> for CurrencyIdToMultiLocation { + fn convert(currency: CurrencyId) -> Option { + match currency { + a if a == 1u128 => Some(PARENT), + a if a == 2u128 => Some(PARACHAIN), + a if a == 3u128 => Some(GENERAL_INDEX), + a if a == 4u128 => Some(LOCAL_ASSET), + _ => None, + } + } +} + +/// Convert `AccountId` to `MultiLocation`. +pub struct AccountIdToMultiLocation; +impl sp_runtime::traits::Convert for AccountIdToMultiLocation { + fn convert(account: AccountId) -> MultiLocation { + X1(AccountId32 { + network: None, + id: account.into(), + }) + .into() + } +} + +/// `MultiAsset` reserve location provider. It's based on `RelativeReserveProvider` and in +/// addition will convert self absolute location to relative location. +pub struct AbsoluteAndRelativeReserveProvider(PhantomData); +impl> Reserve + for AbsoluteAndRelativeReserveProvider +{ + fn reserve(asset: &MultiAsset) -> Option { + RelativeReserveProvider::reserve(asset).map(|reserve_location| { + if reserve_location == AbsoluteLocation::get() { + MultiLocation::here() + } else { + reserve_location + } + }) + } +} + parameter_types! { pub const BlockHashCount: u64 = 250; pub const SS58Prefix: u8 = 42; @@ -376,6 +438,14 @@ impl xcm_executor::Config for XcmConfig { parameter_types! { pub static AdvertisedXcmVersion: XcmVersion = 3; + pub const MaxAssetsForTransfer: usize = 2; + pub const SelfLocation: MultiLocation = Here.into_location(); + pub SelfLocationAbsolute: MultiLocation = MultiLocation { + parents: 1, + interior: X1( + Parachain(123) + ) + }; } pub type LocalOriginToLocation = SignedToAccountId32; @@ -448,6 +518,24 @@ impl pallet_xcm::Config for Runtime { type ReachableDest = ReachableDest; } +impl orml_xtokens::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type CurrencyId = AssetId; + type CurrencyIdConvert = CurrencyIdToMultiLocation; + type AccountIdToMultiLocation = AccountIdToMultiLocation; + type SelfLocation = SelfLocation; + type XcmExecutor = XcmExecutor; + type Weigher = FixedWeightBounds; + type BaseXcmWeight = UnitWeightCost; + type UniversalLocation = UniversalLocation; + type MaxAssetsForTransfer = MaxAssetsForTransfer; + // Default impl. Refer to `orml-xtokens` docs for more details. + type MinXcmFee = DisabledParachainFee; + type MultiLocationsFilter = Everything; + type ReserveProvider = AbsoluteAndRelativeReserveProvider; +} + // Configure a mock runtime to test the pallet. construct_runtime!( pub enum Runtime where @@ -461,6 +549,7 @@ construct_runtime!( Evm: pallet_evm, Timestamp: pallet_timestamp, XcmPallet: pallet_xcm, + Xtokens: orml_xtokens, } ); @@ -478,3 +567,9 @@ impl ExtBuilder { ext } } +pub(crate) fn events() -> Vec { + System::events() + .into_iter() + .map(|r| r.event) + .collect::>() +} diff --git a/precompiles/xcm/src/tests.rs b/precompiles/xcm/src/tests.rs index 63019e9d..4a34356a 100644 --- a/precompiles/xcm/src/tests.rs +++ b/precompiles/xcm/src/tests.rs @@ -636,3 +636,64 @@ mod xcm_new_interface_test { }) } } + +mod xtokens_interface_test { + use super::*; + #[test] + fn xtokens_transfer_works() { + ExtBuilder::default().build().execute_with(|| { + let parent_destination = MultiLocation { + parents: 1, + interior: Junctions::X1(Junction::AccountId32 { + network: None, + id: [1u8; 32], + }), + }; + + let sibling_parachain_location = MultiLocation { + parents: 1, + interior: Junctions::X2( + Junction::Parachain(10), + Junction::AccountId32 { + network: None, + id: [1u8; 32], + }, + ), + }; + + // sending relay token back to relay chain + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::XtokensTransfer) + .write(Address::from(Runtime::asset_id_to_address(1u128))) // zero address by convention + .write(U256::from(42000u64)) + .write(parent_destination) + .write(U256::from(3_000_000_000u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + println!("{:?}", events()); + + // sending parachain token back to parachain + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::XtokensTransfer) + .write(Address::from(Runtime::asset_id_to_address(2u128))) // zero address by convention + .write(U256::from(42000u64)) + .write(sibling_parachain_location) + .write(U256::from(3_000_000_000u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + println!("{:?}", events()); + }); + } +} From 37aa31ccd9daac37c1d450a5afaeb91eb57ee629 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Mon, 17 Jul 2023 07:30:23 +0530 Subject: [PATCH 26/33] test: add transfer_multiasset --- precompiles/xcm/src/lib.rs | 93 +++++++++++++++++++++++++++++++++-- precompiles/xcm/src/tests.rs | 94 ++++++++++++++++++++++++++++++++++++ 2 files changed, 184 insertions(+), 3 deletions(-) diff --git a/precompiles/xcm/src/lib.rs b/precompiles/xcm/src/lib.rs index 007e55a3..6e8537ab 100644 --- a/precompiles/xcm/src/lib.rs +++ b/precompiles/xcm/src/lib.rs @@ -34,7 +34,7 @@ use sp_core::{H160, H256, U256}; use sp_std::marker::PhantomData; use sp_std::prelude::*; -use xcm::{latest::prelude::*, VersionedMultiLocation}; +use xcm::{latest::prelude::*, VersionedMultiAsset, VersionedMultiLocation}; use xcm_executor::traits::Convert; use pallet_evm_precompile_assets_erc20::AddressToAssetId; @@ -63,8 +63,9 @@ pub enum Action { "assets_reserve_transfer(address[],uint256[],(uint8,bytes[]),(uint8,bytes[]),uint256)", SendXCM = "send_xcm((uint8,bytes[]),bytes)", XtokensTransfer = "transfer(address,uint256,(uint8,bytes[]),uint64)", - // XtokensTransferWithFee = "transfer_with_fee(address,uint256,uint256,(uint8,bytes[]),uint64)", - // XtokensTransferMultiasset = "transfer_multiasset((uint8,bytes[]),uint256,(uint8,bytes[]),uint64)", + XtokensTransferWithFee = "transfer_with_fee(address,uint256,uint256,(uint8,bytes[]),uint64)", + XtokensTransferMultiasset = + "transfer_multiasset((uint8,bytes[]),uint256,(uint8,bytes[]),uint64)", // XtokensTransferMultiassetWithFee = "transfer_multiasset_with_fee((uint8,bytes[]),uint256,uint256,(uint8,bytes[]),uint64)", // XtokensTransferMulticurrencies = "transfer_multi_currencies((address,uint256)[],uint32,(uint8,bytes[]),uint64)", // XtokensTransferMultiassets = "transfet_multi_assets(((uint8,bytes[]),uint256)[],uint32,(uint8,bytes[]),uint64)", @@ -125,6 +126,8 @@ where Action::AssetsReserveTransfer => Self::assets_reserve_transfer(handle), Action::SendXCM => Self::send_xcm(handle), Action::XtokensTransfer => Self::transfer(handle), + Action::XtokensTransferWithFee => Self::transfer_with_fee(handle), + Action::XtokensTransferMultiasset => Self::transfer_multiasset(handle), } } } @@ -708,4 +711,88 @@ where Ok(succeed(EvmDataWriter::new().write(true).build())) } + + fn transfer_with_fee(handle: &mut impl PrecompileHandle) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(6)?; + + // Read call arguments + let currency_address = input.read::
()?; + let amount_of_tokens = input + .read::()? + .try_into() + .map_err(|_| revert("error converting amount_of_tokens, maybe value too large"))?; + let fee = input + .read::()? + .try_into() + .map_err(|_| revert("can't convert fee"))?; + + let destination = input.read::()?; + let weight = input.read::()?; + + let asset_id = Runtime::address_to_asset_id(currency_address.into()) + .ok_or(revert("Failed to resolve fee asset id from address"))?; + let dest_weight_limit = if weight == u64::MAX { + WeightLimit::Unlimited + } else { + WeightLimit::Limited(Weight::from_parts(weight, DEFAULT_PROOF_SIZE)) + }; + + let call = orml_xtokens::Call::::transfer_with_fee { + currency_id: asset_id.into(), + amount: amount_of_tokens, + fee, + dest: Box::new(VersionedMultiLocation::V3(destination)), + dest_weight_limit, + }; + + let origin = Some(Runtime::AddressMapping::into_account_id( + handle.context().caller, + )) + .into(); + + // Dispatch a call. + RuntimeHelper::::try_dispatch(handle, origin, call)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + fn transfer_multiasset(handle: &mut impl PrecompileHandle) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(5)?; + + // Read call arguments + let asset_location = input.read::()?; + let amount_of_tokens = input + .read::()? + .try_into() + .map_err(|_| revert("error converting amount_of_tokens, maybe value too large"))?; + let destination = input.read::()?; + let weight = input.read::()?; + + let dest_weight_limit = if weight == u64::MAX { + WeightLimit::Unlimited + } else { + WeightLimit::Limited(Weight::from_parts(weight, DEFAULT_PROOF_SIZE)) + }; + + let call = orml_xtokens::Call::::transfer_multiasset { + asset: Box::new(VersionedMultiAsset::V3(MultiAsset { + id: AssetId::Concrete(asset_location), + fun: Fungibility::Fungible(amount_of_tokens), + })), + dest: Box::new(VersionedMultiLocation::V3(destination)), + dest_weight_limit, + }; + + let origin = Some(Runtime::AddressMapping::into_account_id( + handle.context().caller, + )) + .into(); + + // Dispatch a call. + RuntimeHelper::::try_dispatch(handle, origin, call)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } } diff --git a/precompiles/xcm/src/tests.rs b/precompiles/xcm/src/tests.rs index 4a34356a..9d110ef9 100644 --- a/precompiles/xcm/src/tests.rs +++ b/precompiles/xcm/src/tests.rs @@ -20,7 +20,11 @@ use core::assert_matches::assert_matches; use crate::mock::*; use crate::*; +use xcm::latest::{ + AssetId, Fungibility, Junction, Junctions, MultiAsset, MultiAssets, MultiLocation, +}; +use orml_xtokens::Event as XtokensEvent; use parity_scale_codec::Encode; use precompile_utils::testing::*; use precompile_utils::EvmDataWriter; @@ -696,4 +700,94 @@ mod xtokens_interface_test { println!("{:?}", events()); }); } + + #[test] + fn transfer_multiasset_works() { + ExtBuilder::default().build().execute_with(|| { + let relay_token_location = MultiLocation { + parents: 1, + interior: Junctions::Here, + }; + let relay_destination = MultiLocation { + parents: 1, + interior: Junctions::X1(Junction::AccountId32 { + network: None, + id: [1u8; 32], + }), + }; + let para_destination = MultiLocation { + parents: 1, + interior: Junctions::X2( + Junction::Parachain(10), + Junction::AccountId32 { + network: None, + id: [1u8; 32], + }, + ), + }; + + let amount = 4200u64; + // relay token to relay + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::XtokensTransferMultiasset) + .write(relay_token_location) // zero address by convention + .write(U256::from(amount)) + .write(relay_destination) + .write(U256::from(3_000_000_000u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + let expected_asset: MultiAsset = MultiAsset { + id: AssetId::Concrete(relay_token_location), + fun: Fungibility::Fungible(amount.into()), + }; + let expected: crate::mock::RuntimeEvent = + mock::RuntimeEvent::Xtokens(XtokensEvent::TransferredMultiAssets { + sender: TestAccount::Alice.into(), + assets: vec![expected_asset.clone()].into(), + fee: expected_asset, + dest: relay_destination, + }) + .into(); + + // Assert that the events vector contains the one expected + assert!(events().contains(&expected)); + + // relay to para + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::XtokensTransferMultiasset) + .write(relay_token_location) // zero address by convention + .write(U256::from(amount)) + .write(para_destination) + .write(U256::from(3_000_000_000u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + let expected_asset: MultiAsset = MultiAsset { + id: AssetId::Concrete(relay_token_location), + fun: Fungibility::Fungible(amount.into()), + }; + let expected: crate::mock::RuntimeEvent = + mock::RuntimeEvent::Xtokens(XtokensEvent::TransferredMultiAssets { + sender: TestAccount::Alice.into(), + assets: vec![expected_asset.clone()].into(), + fee: expected_asset, + dest: para_destination, + }) + .into(); + + // Assert that the events vector contains the one expected + assert!(events().contains(&expected)); + }); + } } From 8117882d517425e44b2b2cb67004985d898b7be1 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Mon, 17 Jul 2023 15:38:40 +0530 Subject: [PATCH 27/33] add new functions for xtokens --- precompiles/utils/src/data.rs | 105 ++++++++++++++++++- precompiles/utils/src/lib.rs | 4 +- precompiles/utils/src/xcm.rs | 76 +++++++++++++- precompiles/xcm/src/lib.rs | 189 ++++++++++++++++++++++++++++++++-- 4 files changed, 363 insertions(+), 11 deletions(-) diff --git a/precompiles/utils/src/data.rs b/precompiles/utils/src/data.rs index aca895d7..28aaa5a5 100644 --- a/precompiles/utils/src/data.rs +++ b/precompiles/utils/src/data.rs @@ -23,9 +23,9 @@ use crate::{revert, EvmResult}; use alloc::borrow::ToOwned; -use core::{any::type_name, ops::Range}; +use core::{any::type_name, marker::PhantomData, ops::Range}; use impl_trait_for_tuples::impl_for_tuples; -use sp_core::{H160, H256, U256}; +use sp_core::{Get, H160, H256, U256}; use sp_std::{convert::TryInto, vec, vec::Vec}; /// The `address` type of Solidity. @@ -609,3 +609,104 @@ impl EvmData for Bytes { false } } +/// Wrapper around a Vec that provides a max length bound on read. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct BoundedVec { + inner: Vec, + _phantom: PhantomData, +} + +impl> EvmData for BoundedVec { + fn read(reader: &mut EvmDataReader) -> EvmResult { + let mut inner_reader = reader.read_pointer()?; + + let array_size: usize = inner_reader + .read::() + .map_err(|_| revert("out of bounds: length of array"))? + .try_into() + .map_err(|_| revert("value too large : length"))?; + + if array_size > S::get() as usize { + return Err(revert("value too large : length").into()); + } + + let mut array = vec![]; + + let mut item_reader = EvmDataReader { + input: inner_reader + .input + .get(32..) + .ok_or_else(|| revert("read out of bounds: array content"))?, + cursor: 0, + }; + + for _ in 0..array_size { + array.push(item_reader.read()?); + } + + Ok(BoundedVec { + inner: array, + _phantom: PhantomData, + }) + } + + fn write(writer: &mut EvmDataWriter, value: Self) { + let value: Vec<_> = value.into(); + let mut inner_writer = EvmDataWriter::new().write(U256::from(value.len())); + + for inner in value { + // Any offset in items are relative to the start of the item instead of the + // start of the array. However if there is offseted data it must but appended after + // all items (offsets) are written. We thus need to rely on `compute_offsets` to do + // that, and must store a "shift" to correct the offsets. + let shift = inner_writer.data.len(); + let item_writer = EvmDataWriter::new().write(inner); + + inner_writer = inner_writer.write_raw_bytes(&item_writer.data); + for mut offset_datum in item_writer.offset_data { + offset_datum.offset_shift += 32; + offset_datum.offset_position += shift; + inner_writer.offset_data.push(offset_datum); + } + } + + writer.write_pointer(inner_writer.build()); + } + + fn has_static_size() -> bool { + false + } +} + +impl From> for BoundedVec { + fn from(value: Vec) -> Self { + BoundedVec { + inner: value, + _phantom: PhantomData, + } + } +} + +impl From<&[T]> for BoundedVec { + fn from(value: &[T]) -> Self { + BoundedVec { + inner: value.to_vec(), + _phantom: PhantomData, + } + } +} + +impl From<[T; N]> for BoundedVec { + fn from(value: [T; N]) -> Self { + BoundedVec { + inner: value.to_vec(), + _phantom: PhantomData, + } + } +} + +impl From> for Vec { + fn from(value: BoundedVec) -> Self { + value.inner + } +} diff --git a/precompiles/utils/src/lib.rs b/precompiles/utils/src/lib.rs index 77f69c66..ab27e200 100644 --- a/precompiles/utils/src/lib.rs +++ b/precompiles/utils/src/lib.rs @@ -38,8 +38,8 @@ use sp_core::{H160, H256, U256}; use sp_std::{marker::PhantomData, vec, vec::Vec}; pub mod bytes; -mod data; -mod xcm; +pub mod data; +pub mod xcm; pub use data::{Address, Bytes, EvmData, EvmDataReader, EvmDataWriter}; pub use precompile_utils_macro::{generate_function_selector, keccak256}; diff --git a/precompiles/utils/src/xcm.rs b/precompiles/utils/src/xcm.rs index 22e5b789..96942a4b 100644 --- a/precompiles/utils/src/xcm.rs +++ b/precompiles/utils/src/xcm.rs @@ -22,6 +22,8 @@ //! Encoding of XCM types for solidity +use crate::Address; +use sp_core::U256; use { crate::{bytes::*, revert, EvmData, EvmDataReader, EvmDataWriter, EvmResult}, frame_support::{ensure, traits::ConstU32}, @@ -29,7 +31,6 @@ use { sp_std::vec::Vec, xcm::latest::{Junction, Junctions, MultiLocation, NetworkId}, }; - pub const JUNCTION_SIZE_LIMIT: u32 = 2u32.pow(16); // Function to convert network id to bytes @@ -352,3 +353,76 @@ impl EvmData for MultiLocation { <(u8, Junctions)>::has_static_size() } } + +pub struct EvmMultiAsset { + location: MultiLocation, + amount: U256, +} + +impl EvmMultiAsset { + pub fn get_location(&self) -> MultiLocation { + self.location + } + pub fn get_amount(&self) -> U256 { + self.amount + } +} +impl From<(MultiLocation, U256)> for EvmMultiAsset { + fn from(tuple: (MultiLocation, U256)) -> Self { + EvmMultiAsset { + location: tuple.0, + amount: tuple.1, + } + } +} +impl EvmData for EvmMultiAsset { + fn read(reader: &mut EvmDataReader) -> EvmResult { + let (location, amount) = reader.read()?; + Ok(EvmMultiAsset { location, amount }) + } + + fn write(writer: &mut EvmDataWriter, value: Self) { + EvmData::write(writer, (value.location, value.amount)); + } + + fn has_static_size() -> bool { + <(MultiLocation, U256)>::has_static_size() + } +} + +pub struct Currency { + address: Address, + amount: U256, +} + +impl Currency { + pub fn get_address(&self) -> Address { + self.address + } + pub fn get_amount(&self) -> U256 { + self.amount + } +} +impl From<(Address, U256)> for Currency { + fn from(tuple: (Address, U256)) -> Self { + Currency { + address: tuple.0, + amount: tuple.1, + } + } +} + +impl EvmData for Currency { + fn read(reader: &mut EvmDataReader) -> EvmResult { + let (address, amount) = reader.read()?; + Ok(Currency { address, amount }) + } + + fn write(writer: &mut EvmDataWriter, value: Self) { + EvmData::write(writer, (value.address, value.amount)); + } + + fn has_static_size() -> bool { + <(Address, U256)>::has_static_size() + } +} diff --git a/precompiles/xcm/src/lib.rs b/precompiles/xcm/src/lib.rs index 6e8537ab..016fa56c 100644 --- a/precompiles/xcm/src/lib.rs +++ b/precompiles/xcm/src/lib.rs @@ -31,16 +31,20 @@ type GetXcmSizeLimit = ConstU32; use pallet_evm::{AddressMapping, Precompile}; use parity_scale_codec::DecodeLimit; use sp_core::{H160, H256, U256}; + use sp_std::marker::PhantomData; use sp_std::prelude::*; -use xcm::{latest::prelude::*, VersionedMultiAsset, VersionedMultiLocation}; +use xcm::{latest::prelude::*, VersionedMultiAsset, VersionedMultiAssets, VersionedMultiLocation}; use xcm_executor::traits::Convert; use pallet_evm_precompile_assets_erc20::AddressToAssetId; use precompile_utils::{ - bytes::BoundedBytes, revert, succeed, Address, Bytes, EvmDataWriter, EvmResult, - FunctionModifier, PrecompileHandleExt, RuntimeHelper, + bytes::BoundedBytes, + data::BoundedVec, + revert, succeed, + xcm::{Currency, EvmMultiAsset}, + Address, Bytes, EvmDataWriter, EvmResult, FunctionModifier, PrecompileHandleExt, RuntimeHelper, }; #[cfg(test)] mod mock; @@ -66,9 +70,12 @@ pub enum Action { XtokensTransferWithFee = "transfer_with_fee(address,uint256,uint256,(uint8,bytes[]),uint64)", XtokensTransferMultiasset = "transfer_multiasset((uint8,bytes[]),uint256,(uint8,bytes[]),uint64)", - // XtokensTransferMultiassetWithFee = "transfer_multiasset_with_fee((uint8,bytes[]),uint256,uint256,(uint8,bytes[]),uint64)", - // XtokensTransferMulticurrencies = "transfer_multi_currencies((address,uint256)[],uint32,(uint8,bytes[]),uint64)", - // XtokensTransferMultiassets = "transfet_multi_assets(((uint8,bytes[]),uint256)[],uint32,(uint8,bytes[]),uint64)", + XtokensTransferMultiassetWithFee = + "transfer_multiasset_with_fee((uint8,bytes[]),uint256,uint256,(uint8,bytes[]),uint64)", + XtokensTransferMulticurrencies = + "transfer_multi_currencies((address,uint256)[],uint32,(uint8,bytes[]),uint64)", + XtokensTransferMultiassets = + "transfet_multi_assets(((uint8,bytes[]),uint256)[],uint32,(uint8,bytes[]),uint64)", } /// Dummy H160 address representing native currency (e.g. ASTR or SDN) @@ -77,6 +84,17 @@ const NATIVE_ADDRESS: H160 = H160::zero(); const DEFAULT_PROOF_SIZE: u64 = 1024 * 64; pub type XBalanceOf = ::Balance; + +pub struct GetMaxAssets(PhantomData); + +impl Get for GetMaxAssets +where + R: orml_xtokens::Config, +{ + fn get() -> u32 { + ::MaxAssetsForTransfer::get() as u32 + } +} /// A precompile that expose XCM related functions. pub struct XcmPrecompile(PhantomData<(T, C)>); @@ -128,6 +146,9 @@ where Action::XtokensTransfer => Self::transfer(handle), Action::XtokensTransferWithFee => Self::transfer_with_fee(handle), Action::XtokensTransferMultiasset => Self::transfer_multiasset(handle), + Action::XtokensTransferMultiassetWithFee => Self::transfer_multiasset_with_fee(handle), + Action::XtokensTransferMulticurrencies => Self::transfer_multi_currencies(handle), + Action::XtokensTransferMultiassets => Self::transfer_multi_assets(handle), } } } @@ -795,4 +816,160 @@ where Ok(succeed(EvmDataWriter::new().write(true).build())) } + + fn transfer_multiasset_with_fee( + handle: &mut impl PrecompileHandle, + ) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(6)?; + + // Read call arguments + let asset_location = input.read::()?; + let amount_of_tokens = input + .read::()? + .try_into() + .map_err(|_| revert("error converting amount_of_tokens, maybe value too large"))?; + let fee = input + .read::()? + .try_into() + .map_err(|_| revert("can't convert fee"))?; + let destination = input.read::()?; + let weight = input.read::()?; + + let dest_weight_limit = if weight == u64::MAX { + WeightLimit::Unlimited + } else { + WeightLimit::Limited(Weight::from_parts(weight, DEFAULT_PROOF_SIZE)) + }; + + let call = orml_xtokens::Call::::transfer_multiasset_with_fee { + asset: Box::new(VersionedMultiAsset::V3(MultiAsset { + id: AssetId::Concrete(asset_location), + fun: Fungibility::Fungible(amount_of_tokens), + })), + fee: Box::new(VersionedMultiAsset::V3(MultiAsset { + id: AssetId::Concrete(asset_location), + fun: Fungibility::Fungible(fee), + })), + dest: Box::new(VersionedMultiLocation::V3(destination)), + dest_weight_limit, + }; + + let origin = Some(Runtime::AddressMapping::into_account_id( + handle.context().caller, + )) + .into(); + + // Dispatch a call. + RuntimeHelper::::try_dispatch(handle, origin, call)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + fn transfer_multi_currencies( + handle: &mut impl PrecompileHandle, + ) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(5)?; + + let currencies: Vec<_> = input + .read::>>()? + .into(); + let fee_item = input.read::()?; + let destination = input.read::()?; + let weight = input.read::()?; + + let currencies = currencies + .into_iter() + .map(|currency| { + let currency_address: H160 = currency.get_address().into(); + let amount = currency + .get_amount() + .try_into() + .map_err(|_| revert("value too large: in currency"))?; + + Ok(( + Runtime::address_to_asset_id(currency_address.into()) + .ok_or(revert("can't convert into currency id"))? + .into(), + amount, + )) + }) + .collect::>()?; + let dest_weight_limit = if weight == u64::MAX { + WeightLimit::Unlimited + } else { + WeightLimit::Limited(Weight::from_parts(weight, DEFAULT_PROOF_SIZE)) + }; + + let call = orml_xtokens::Call::::transfer_multicurrencies { + currencies, + fee_item, + dest: Box::new(VersionedMultiLocation::V3(destination)), + dest_weight_limit, + }; + + let origin = Some(Runtime::AddressMapping::into_account_id( + handle.context().caller, + )) + .into(); + + // Dispatch a call. + RuntimeHelper::::try_dispatch(handle, origin, call)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + fn transfer_multi_assets(handle: &mut impl PrecompileHandle) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(5)?; + + let assets: Vec<_> = input + .read::>>()? + .into(); + let fee_item = input.read::()?; + let destination = input.read::()?; + let weight = input.read::()?; + + let dest_weight_limit = if weight == u64::MAX { + WeightLimit::Unlimited + } else { + WeightLimit::Limited(Weight::from_parts(weight, DEFAULT_PROOF_SIZE)) + }; + + let multiasset_vec: EvmResult> = assets + .into_iter() + .map(|evm_multiasset| { + let to_balance: u128 = evm_multiasset + .get_amount() + .try_into() + .map_err(|_| revert("value too large in assets"))?; + Ok((evm_multiasset.get_location(), to_balance).into()) + }) + .collect(); + + // Since multiassets sorts them, we need to check whether the index is still correct, + // and error otherwise as there is not much we can do other than that + let multiassets = + MultiAssets::from_sorted_and_deduplicated(multiasset_vec?).map_err(|_| { + revert("In field Assets, Provided assets either not sorted nor deduplicated") + })?; + + let call = orml_xtokens::Call::::transfer_multiassets { + assets: Box::new(VersionedMultiAssets::V3(multiassets)), + fee_item, + dest: Box::new(VersionedMultiLocation::V3(destination)), + dest_weight_limit, + }; + + let origin = Some(Runtime::AddressMapping::into_account_id( + handle.context().caller, + )) + .into(); + + // Dispatch a call. + RuntimeHelper::::try_dispatch(handle, origin, call)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } } From cd3360efee6b2384558ebc2a961811192710bba1 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Tue, 18 Jul 2023 08:35:32 +0530 Subject: [PATCH 28/33] test: transfer_multi_currencies --- precompiles/xcm/src/tests.rs | 61 ++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/precompiles/xcm/src/tests.rs b/precompiles/xcm/src/tests.rs index 9d110ef9..8ff95902 100644 --- a/precompiles/xcm/src/tests.rs +++ b/precompiles/xcm/src/tests.rs @@ -29,6 +29,7 @@ use parity_scale_codec::Encode; use precompile_utils::testing::*; use precompile_utils::EvmDataWriter; use sp_core::{H160, H256}; +use sp_runtime::traits::Convert; use xcm::VersionedXcm; fn precompiles() -> TestPrecompileSet { @@ -790,4 +791,64 @@ mod xtokens_interface_test { assert!(events().contains(&expected)); }); } + + #[test] + fn transfer_multi_currencies_works() { + let destination = MultiLocation::new( + 1, + Junctions::X1(Junction::AccountId32 { + network: None, + id: [1u8; 32], + }), + ); + // NOTE: Currently only support `ToReserve` with relay-chain asset as fee. other case + // like `NonReserve` or `SelfReserve` with relay-chain fee is not support. + let currencies: Vec = vec![ + ( + Address::from(Runtime::asset_id_to_address(2u128)), + U256::from(500), + ) + .into(), + ( + Address::from(Runtime::asset_id_to_address(3u128)), + U256::from(500), + ) + .into(), + ]; + + ExtBuilder::default().build().execute_with(|| { + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::XtokensTransferMulticurrencies) + .write(currencies) // zero address by convention + .write(U256::from(0)) + .write(destination) + .write(U256::from(3_000_000_000u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + let expected_asset_1: MultiAsset = MultiAsset { + id: AssetId::Concrete(CurrencyIdToMultiLocation::convert(2u128).unwrap()), + fun: Fungibility::Fungible(500), + }; + let expected_asset_2: MultiAsset = MultiAsset { + id: AssetId::Concrete(CurrencyIdToMultiLocation::convert(3u128).unwrap()), + fun: Fungibility::Fungible(500), + }; + + let expected: crate::mock::RuntimeEvent = + mock::RuntimeEvent::Xtokens(XtokensEvent::TransferredMultiAssets { + sender: TestAccount::Alice.into(), + assets: vec![expected_asset_1.clone(), expected_asset_2].into(), + fee: expected_asset_1, + dest: destination, + }) + .into(); + assert!(events().contains(&expected)); + }); + } } From fba5ee3f50e1bf493b57f0fc75d25df6b375de45 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Wed, 19 Jul 2023 07:49:31 +0530 Subject: [PATCH 29/33] test: transfer_multi_assets --- precompiles/xcm/Xtokens.sol | 2 -- precompiles/xcm/src/tests.rs | 60 ++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/precompiles/xcm/Xtokens.sol b/precompiles/xcm/Xtokens.sol index 54411522..2d082890 100644 --- a/precompiles/xcm/Xtokens.sol +++ b/precompiles/xcm/Xtokens.sol @@ -91,8 +91,6 @@ interface Xtokens { /// @param feeItem Which of the currencies to be used as fee /// @param destination The Multilocation to which we want to send the tokens /// @param weight The weight we want to buy in the destination chain - /// (uint64::MAX means Unlimited weight) - /// @custom:selector ab946323 function transfer_multi_currencies( Currency[] memory currencies, uint32 feeItem, diff --git a/precompiles/xcm/src/tests.rs b/precompiles/xcm/src/tests.rs index 8ff95902..85cb33a5 100644 --- a/precompiles/xcm/src/tests.rs +++ b/precompiles/xcm/src/tests.rs @@ -851,4 +851,64 @@ mod xtokens_interface_test { assert!(events().contains(&expected)); }); } + + #[test] + fn transfer_multiassets_works() { + let destination = MultiLocation::new( + 1, + Junctions::X2( + Junction::Parachain(2), + Junction::AccountId32 { + network: None, + id: [1u8; 32], + }, + ), + ); + + let asset_1_location = MultiLocation::new( + 1, + Junctions::X2(Junction::Parachain(2), Junction::GeneralIndex(0u128)), + ); + let asset_2_location = MultiLocation::new( + 1, + Junctions::X2(Junction::Parachain(2), Junction::GeneralIndex(1u128)), + ); + + let assets: Vec = vec![ + (asset_1_location.clone(), U256::from(500)).into(), + (asset_2_location.clone(), U256::from(500)).into(), + ]; + + let multiassets = MultiAssets::from_sorted_and_deduplicated(vec![ + (asset_1_location.clone(), 500).into(), + (asset_2_location, 500).into(), + ]) + .unwrap(); + + ExtBuilder::default().build().execute_with(|| { + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::XtokensTransferMultiassets) + .write(assets) // zero address by convention + .write(U256::from(0)) + .write(destination) + .write(U256::from(3_000_000_000u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + let expected: crate::mock::RuntimeEvent = + mock::RuntimeEvent::Xtokens(XtokensEvent::TransferredMultiAssets { + sender: TestAccount::Alice.into(), + assets: multiassets, + fee: (asset_1_location, 500).into(), + dest: destination, + }) + .into(); + assert!(events().contains(&expected)); + }); + } } From c61f3914147103c2cb894bb777ffddefb8fa081b Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Wed, 19 Jul 2023 08:09:12 +0530 Subject: [PATCH 30/33] test: transfer_multiassets_cannot_insert_more_than_max --- precompiles/utils/src/data.rs | 4 +-- precompiles/xcm/src/tests.rs | 54 +++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/precompiles/utils/src/data.rs b/precompiles/utils/src/data.rs index 28aaa5a5..1e378967 100644 --- a/precompiles/utils/src/data.rs +++ b/precompiles/utils/src/data.rs @@ -624,10 +624,10 @@ impl> EvmData for BoundedVec { .read::() .map_err(|_| revert("out of bounds: length of array"))? .try_into() - .map_err(|_| revert("value too large : length"))?; + .map_err(|_| revert("value too large : length of array for than max allowed"))?; if array_size > S::get() as usize { - return Err(revert("value too large : length").into()); + return Err(revert("value too large : length of array for than max allowed").into()); } let mut array = vec![]; diff --git a/precompiles/xcm/src/tests.rs b/precompiles/xcm/src/tests.rs index 85cb33a5..a0668edb 100644 --- a/precompiles/xcm/src/tests.rs +++ b/precompiles/xcm/src/tests.rs @@ -911,4 +911,58 @@ mod xtokens_interface_test { assert!(events().contains(&expected)); }); } + + #[test] + fn transfer_multiassets_cannot_insert_more_than_max() { + // We have definaed MaxAssetsForTransfer = 2, + // so any number greater than MaxAssetsForTransfer will result in error + let destination = MultiLocation::new( + 1, + Junctions::X2( + Junction::Parachain(2), + Junction::AccountId32 { + network: None, + id: [1u8; 32], + }, + ), + ); + + let asset_1_location = MultiLocation::new( + 1, + Junctions::X2(Junction::Parachain(2), Junction::GeneralIndex(0u128)), + ); + let asset_2_location = MultiLocation::new( + 1, + Junctions::X2(Junction::Parachain(2), Junction::GeneralIndex(1u128)), + ); + let asset_3_location = MultiLocation::new( + 1, + Junctions::X2(Junction::Parachain(2), Junction::GeneralIndex(3u128)), + ); + + + let assets: Vec = vec![ + (asset_1_location.clone(), U256::from(500)).into(), + (asset_2_location.clone(), U256::from(500)).into(), + (asset_3_location.clone(), U256::from(500)).into(), + ]; + + + ExtBuilder::default().build().execute_with(|| { + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::XtokensTransferMultiassets) + .write(assets) // zero address by convention + .write(U256::from(0)) + .write(destination) + .write(U256::from(3_000_000_000u64)) + .build(), + ) + .expect_no_logs() + .execute_reverts(|output| output == b"value too large : length of array for than max allowed"); + }); + } + } From 7dafadb52d814c72255dd48db24342273164f3a9 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Wed, 19 Jul 2023 08:14:56 +0530 Subject: [PATCH 31/33] test: transfer_multi_currencies_cannot_insert_more_than_max --- precompiles/xcm/src/tests.rs | 46 ++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/precompiles/xcm/src/tests.rs b/precompiles/xcm/src/tests.rs index a0668edb..78319819 100644 --- a/precompiles/xcm/src/tests.rs +++ b/precompiles/xcm/src/tests.rs @@ -852,6 +852,52 @@ mod xtokens_interface_test { }); } + + #[test] + fn transfer_multi_currencies_cannot_insert_more_than_max() { + let destination = MultiLocation::new( + 1, + Junctions::X1(Junction::AccountId32 { + network: None, + id: [1u8; 32], + }), + ); + // we only allow upto 2 currencies to be transfered + let currencies: Vec = vec![ + ( + Address::from(Runtime::asset_id_to_address(2u128)), + U256::from(500), + ) + .into(), + ( + Address::from(Runtime::asset_id_to_address(3u128)), + U256::from(500), + ) + .into(), + ( + Address::from(Runtime::asset_id_to_address(4u128)), + U256::from(500), + ) + .into(), + ]; + + ExtBuilder::default().build().execute_with(|| { + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::XtokensTransferMulticurrencies) + .write(currencies) // zero address by convention + .write(U256::from(0)) + .write(destination) + .write(U256::from(3_000_000_000u64)) + .build(), + ) + .expect_no_logs() + .execute_reverts(|output| output == b"value too large : length of array for than max allowed"); + }); + } + #[test] fn transfer_multiassets_works() { let destination = MultiLocation::new( From 910eee01bcfcbcf4716450a18bc3f205a4da58fd Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Wed, 19 Jul 2023 09:19:51 +0530 Subject: [PATCH 32/33] more tests and fixes --- precompiles/utils/src/data.rs | 4 +- precompiles/xcm/Cargo.toml | 4 +- precompiles/xcm/src/tests.rs | 90 +++++++++++++++++++++++++++++++---- 3 files changed, 87 insertions(+), 11 deletions(-) diff --git a/precompiles/utils/src/data.rs b/precompiles/utils/src/data.rs index 1e378967..75f32c91 100644 --- a/precompiles/utils/src/data.rs +++ b/precompiles/utils/src/data.rs @@ -624,10 +624,10 @@ impl> EvmData for BoundedVec { .read::() .map_err(|_| revert("out of bounds: length of array"))? .try_into() - .map_err(|_| revert("value too large : length of array for than max allowed"))?; + .map_err(|_| revert("value too large : Array has more than max items allowed"))?; if array_size > S::get() as usize { - return Err(revert("value too large : length of array for than max allowed").into()); + return Err(revert("value too large : Array has more than max items allowed").into()); } let mut array = vec![]; diff --git a/precompiles/xcm/Cargo.toml b/precompiles/xcm/Cargo.toml index 366bc840..684ae0e4 100644 --- a/precompiles/xcm/Cargo.toml +++ b/precompiles/xcm/Cargo.toml @@ -68,4 +68,6 @@ std = [ "orml-xcm-support/std", "orml-traits/std", ] -runtime-benchmarks = [] +runtime-benchmarks = [ + "orml-xtokens/runtime-benchmarks", +] diff --git a/precompiles/xcm/src/tests.rs b/precompiles/xcm/src/tests.rs index 78319819..75ca935a 100644 --- a/precompiles/xcm/src/tests.rs +++ b/precompiles/xcm/src/tests.rs @@ -681,7 +681,20 @@ mod xtokens_interface_test { .expect_no_logs() .execute_returns(EvmDataWriter::new().write(true).build()); - println!("{:?}", events()); + let expected_asset: MultiAsset = MultiAsset { + id: AssetId::Concrete(CurrencyIdToMultiLocation::convert(1).unwrap()), + fun: Fungibility::Fungible(42000), + }; + + let expected: crate::mock::RuntimeEvent = + mock::RuntimeEvent::Xtokens(XtokensEvent::TransferredMultiAssets { + sender: TestAccount::Alice.into(), + assets: vec![expected_asset.clone()].into(), + fee: expected_asset, + dest: parent_destination, + }) + .into(); + assert!(events().contains(&expected)); // sending parachain token back to parachain precompiles() @@ -698,7 +711,68 @@ mod xtokens_interface_test { .expect_no_logs() .execute_returns(EvmDataWriter::new().write(true).build()); - println!("{:?}", events()); + let expected_asset: MultiAsset = MultiAsset { + id: AssetId::Concrete(CurrencyIdToMultiLocation::convert(2).unwrap()), + fun: Fungibility::Fungible(42000), + }; + + let expected: crate::mock::RuntimeEvent = + mock::RuntimeEvent::Xtokens(XtokensEvent::TransferredMultiAssets { + sender: TestAccount::Alice.into(), + assets: vec![expected_asset.clone()].into(), + fee: expected_asset, + dest: sibling_parachain_location, + }) + .into(); + assert!(events().contains(&expected)); + }); + } + + #[test] + fn xtokens_transfer_with_fee_works() { + ExtBuilder::default().build().execute_with(|| { + let parent_destination = MultiLocation { + parents: 1, + interior: Junctions::X1(Junction::AccountId32 { + network: None, + id: [1u8; 32], + }), + }; + + // sending relay token back to relay chain + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::XtokensTransferWithFee) + .write(Address::from(Runtime::asset_id_to_address(1u128))) // zero address by convention + .write(U256::from(42000u64)) + .write(U256::from(50)) + .write(parent_destination) + .write(U256::from(3_000_000_000u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + let expected_asset: MultiAsset = MultiAsset { + id: AssetId::Concrete(CurrencyIdToMultiLocation::convert(1).unwrap()), + fun: Fungibility::Fungible(42000), + }; + let expected_fee: MultiAsset = MultiAsset { + id: AssetId::Concrete(CurrencyIdToMultiLocation::convert(1).unwrap()), + fun: Fungibility::Fungible(50), + }; + + let expected: crate::mock::RuntimeEvent = + mock::RuntimeEvent::Xtokens(XtokensEvent::TransferredMultiAssets { + sender: TestAccount::Alice.into(), + assets: vec![expected_asset.clone(), expected_fee.clone()].into(), + fee: expected_fee, + dest: parent_destination, + }) + .into(); + assert!(events().contains(&expected)); }); } @@ -852,7 +926,6 @@ mod xtokens_interface_test { }); } - #[test] fn transfer_multi_currencies_cannot_insert_more_than_max() { let destination = MultiLocation::new( @@ -894,7 +967,9 @@ mod xtokens_interface_test { .build(), ) .expect_no_logs() - .execute_reverts(|output| output == b"value too large : length of array for than max allowed"); + .execute_reverts(|output| { + output == b"value too large : Array has more than max items allowed" + }); }); } @@ -985,7 +1060,6 @@ mod xtokens_interface_test { 1, Junctions::X2(Junction::Parachain(2), Junction::GeneralIndex(3u128)), ); - let assets: Vec = vec![ (asset_1_location.clone(), U256::from(500)).into(), @@ -993,7 +1067,6 @@ mod xtokens_interface_test { (asset_3_location.clone(), U256::from(500)).into(), ]; - ExtBuilder::default().build().execute_with(|| { precompiles() .prepare_test( @@ -1007,8 +1080,9 @@ mod xtokens_interface_test { .build(), ) .expect_no_logs() - .execute_reverts(|output| output == b"value too large : length of array for than max allowed"); + .execute_reverts(|output| { + output == b"value too large : Array has more than max items allowed" + }); }); } - } From 6a6b941ffe10005e15aa52ac6661728197b207fa Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda <43631678+gitofdeepanshu@users.noreply.github.com> Date: Wed, 19 Jul 2023 13:14:48 +0530 Subject: [PATCH 33/33] typo fix Co-authored-by: PierreOssun <35110271+PierreOssun@users.noreply.github.com> --- precompiles/xcm/XCM.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/precompiles/xcm/XCM.sol b/precompiles/xcm/XCM.sol index c3b7e4a3..cc25f751 100644 --- a/precompiles/xcm/XCM.sol +++ b/precompiles/xcm/XCM.sol @@ -1,5 +1,5 @@ /** - * DISCLAIMER: Please note that this file ise deprecated and users are advised to use the XCM_v2.sol file instead. + * DISCLAIMER: Please note that this file is deprecated and users are advised to use the XCM_v2.sol file instead. */ pragma solidity ^0.8.0;