From 1a39bfe12d9570d29511da7614205f7db8808dcf Mon Sep 17 00:00:00 2001 From: Agusrodri Date: Tue, 26 Mar 2024 20:55:16 -0700 Subject: [PATCH 01/22] add pallet-xcm precompile --- Cargo.lock | 30 ++ precompiles/pallet-xcm/Cargo.toml | 74 ++++ precompiles/pallet-xcm/XcmInterface.sol | 36 ++ precompiles/pallet-xcm/src/lib.rs | 105 ++++++ precompiles/pallet-xcm/src/mock.rs | 471 ++++++++++++++++++++++++ precompiles/pallet-xcm/src/tests.rs | 197 ++++++++++ 6 files changed, 913 insertions(+) create mode 100644 precompiles/pallet-xcm/Cargo.toml create mode 100644 precompiles/pallet-xcm/XcmInterface.sol create mode 100644 precompiles/pallet-xcm/src/lib.rs create mode 100644 precompiles/pallet-xcm/src/mock.rs create mode 100644 precompiles/pallet-xcm/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 87e04059..4b4d1362 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6980,6 +6980,36 @@ dependencies = [ "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=release-polkadot-v1.7.0)", ] +[[package]] +name = "pallet-evm-precompile-xcm" +version = "0.1.0" +dependencies = [ + "cumulus-primitives-core", + "derive_more", + "evm", + "fp-evm", + "frame-support", + "frame-system", + "log", + "num_enum", + "pallet-balances", + "pallet-evm", + "pallet-timestamp", + "pallet-xcm", + "parity-scale-codec", + "precompile-utils", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=release-polkadot-v1.7.0)", + "sp-weights", + "staging-xcm", + "staging-xcm-builder", + "staging-xcm-executor", + "xcm-primitives", +] + [[package]] name = "pallet-evm-precompile-xcm-utils" version = "0.1.0" diff --git a/precompiles/pallet-xcm/Cargo.toml b/precompiles/pallet-xcm/Cargo.toml new file mode 100644 index 00000000..27af1186 --- /dev/null +++ b/precompiles/pallet-xcm/Cargo.toml @@ -0,0 +1,74 @@ +[package] +name = "pallet-evm-precompile-xcm" +authors = { workspace = true } +description = "A Precompile to make pallet-xcm accessible to pallet-evm" +edition = "2021" +version = "0.1.0" + +[dependencies] +log = { workspace = true } +num_enum = { workspace = true } + +# Moonbeam +precompile-utils = { workspace = true } +xcm-primitives = { workspace = true } + +# Substrate +frame-support = { workspace = true } +frame-system = { workspace = true } +sp-core = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } +sp-weights = { workspace = true } + +# Frontier +evm = { workspace = true, features = [ "with-codec" ] } +fp-evm = { workspace = true } +pallet-evm = { workspace = true, features = [ "forbid-evm-reentrancy" ] } + +# Polkadot +xcm = { workspace = true } +pallet-xcm = { workspace = true } + +# Cumulus +cumulus-primitives-core = { workspace = true } + +[dev-dependencies] +derive_more = { workspace = true } + +# Moonbeam +precompile-utils = { workspace = true, features = [ "testing", "codec-xcm" ] } +xcm-primitives = { workspace = true } + +# Substrate +pallet-balances = { workspace = true, features = [ "std", "insecure_zero_ed" ] } +pallet-timestamp = { workspace = true } +parity-scale-codec = { workspace = true, features = [ "max-encoded-len" ] } +scale-info = { workspace = true, features = [ "derive" ] } +sp-io = { workspace = true } + +# Polkadot +xcm-builder = { workspace = true } +xcm-executor = { workspace = true } + +[features] +default = [ "std" ] +std = [ + "cumulus-primitives-core/std", + "frame-support/std", + "frame-system/std", + "pallet-evm/std", + "pallet-xcm/std", + "precompile-utils/std", + "sp-core/std", + "sp-std/std", + "xcm/std", + "xcm-builder/std", + "xcm-executor/std", + "xcm-primitives/std", +] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", +] diff --git a/precompiles/pallet-xcm/XcmInterface.sol b/precompiles/pallet-xcm/XcmInterface.sol new file mode 100644 index 00000000..51cbcfb4 --- /dev/null +++ b/precompiles/pallet-xcm/XcmInterface.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity >=0.8.3; + +/// @author The Moonbeam Team +/// @title XCM precompile Interface +/// @dev The interface that Solidity contracts use to interact with the substrate pallet-xcm. +/// @custom:address 0x0000000000000000000000000000000000000820 +interface XCM { + // A location is defined by its number of parents and the encoded junctions (interior) + struct Location { + uint8 parents; + bytes[] interior; + } + + // Support for Weights V2 + struct Weight { + uint64 refTime; + uint64 proofSize; + } + + // A way to represent fungible assets in XCM + struct Asset { + Location location; + uint256 amount; + } + + /// @dev Function to make use of the transfer_assets() pallet-xcm extrinsic + /// @custom:selector 650ef8c7 + function transferAssets( + Location memory dest, + Location memory beneficiary, + Asset[] memory assets, + uint32 feeAssetItem, + Weight memory weight + ) external; +} \ No newline at end of file diff --git a/precompiles/pallet-xcm/src/lib.rs b/precompiles/pallet-xcm/src/lib.rs new file mode 100644 index 00000000..1ceba601 --- /dev/null +++ b/precompiles/pallet-xcm/src/lib.rs @@ -0,0 +1,105 @@ +// Copyright Moonsong Labs +// This file is part of Moonkit. + +// Moonkit is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonkit is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonkit. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] + +use fp_evm::PrecompileHandle; +use frame_support::{ + dispatch::{GetDispatchInfo, PostDispatchInfo}, + traits::ConstU32, +}; +use pallet_evm::AddressMapping; +use precompile_utils::prelude::*; + +use sp_core::U256; +use sp_runtime::traits::Dispatchable; +use sp_std::marker::PhantomData; +use sp_weights::Weight; +use xcm::{ + latest::{Asset, AssetId, Assets, Fungibility, Location}, + prelude::WeightLimit::*, + VersionedAssets, VersionedLocation, +}; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +pub const MAX_ASSETS_ARRAY_LIMIT: u32 = 2; + +type GetArrayLimit = ConstU32; + +pub struct PalletXcmPrecompile(PhantomData); + +#[precompile_utils::precompile] +impl PalletXcmPrecompile +where + Runtime: pallet_xcm::Config + pallet_evm::Config + frame_system::Config, + ::RuntimeCall: + Dispatchable + GetDispatchInfo, + <::RuntimeCall as Dispatchable>::RuntimeOrigin: + From>, + ::RuntimeCall: From>, +{ + #[precompile::public( + "transferAssets(\ + (uint8,bytes[]),\ + (uint8,bytes[]),\ + ((uint8,bytes[]),uint256)[],\ + uint32,\ + (uint64,uint64))" + )] + fn transfer_assets( + handle: &mut impl PrecompileHandle, + dest: Location, + beneficiary: Location, + assets: BoundedVec<(Location, Convert), GetArrayLimit>, + fee_asset_item: u32, + weight: Weight, + ) -> EvmResult { + // TODO: record proper cost + handle.record_cost(1000)?; + + let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); + let assets: Vec<_> = assets.into(); + + let assets_to_send: Assets = assets + .into_iter() + .map(|asset| Asset { + id: AssetId(asset.0), + fun: Fungibility::Fungible(asset.1.converted()), + }) + .collect::>() + .into(); + + let weight_limit = match weight.ref_time() { + u64::MAX => Unlimited, + _ => Limited(weight), + }; + + let call = pallet_xcm::Call::::transfer_assets { + dest: Box::new(VersionedLocation::V4(dest)), + beneficiary: Box::new(VersionedLocation::V4(beneficiary)), + assets: Box::new(VersionedAssets::V4(assets_to_send)), + fee_asset_item, + weight_limit, + }; + + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + Ok(()) + } +} diff --git a/precompiles/pallet-xcm/src/mock.rs b/precompiles/pallet-xcm/src/mock.rs new file mode 100644 index 00000000..238ba16b --- /dev/null +++ b/precompiles/pallet-xcm/src/mock.rs @@ -0,0 +1,471 @@ +// Copyright Moonsong Labs +// This file is part of Moonkit. + +// Moonkit is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonkit is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonkit. If not, see . + +//! Test utilities +use super::*; +use frame_support::{ + construct_runtime, parameter_types, + traits::{Everything, Nothing, OriginTrait, PalletInfo as PalletInfoTrait}, + weights::{RuntimeDbWeight, Weight}, +}; +use pallet_evm::{EnsureAddressNever, EnsureAddressRoot, GasWeightMapping}; +use precompile_utils::{ + mock_account, + precompile_set::*, + testing::{AddressInPrefixedSet, MockAccount}, +}; +use sp_core::{ConstU32, H160, H256, U256}; +use sp_runtime::traits::{BlakeTwo256, IdentityLookup, TryConvert}; +use sp_runtime::BuildStorage; +use xcm::latest::{prelude::*, Error as XcmError}; +use xcm_builder::{ + AllowUnpaidExecutionFrom, Case, FixedWeightBounds, IsConcrete, SovereignSignedViaLocation, +}; +use xcm_executor::{ + traits::{ConvertLocation, TransactAsset, WeightTrader}, + AssetsInHolding, +}; +use Junctions::Here; + +pub type AccountId = MockAccount; +pub type Balance = u128; + +type Block = frame_system::mocking::MockBlockU32; + +// Configure a mock runtime to test the pallet. +construct_runtime!( + pub enum Runtime { + System: frame_system, + Balances: pallet_balances, + Evm: pallet_evm, + Timestamp: pallet_timestamp, + PolkadotXcm: pallet_xcm + } +); + +pub struct AccountIdToLocation; +impl sp_runtime::traits::Convert for AccountIdToLocation { + fn convert(account: AccountId) -> Location { + let as_h160: H160 = account.into(); + Location::new( + 0, + [AccountKey20 { + network: None, + key: as_h160.as_fixed_bytes().clone(), + }], + ) + } +} + +parameter_types! { + pub ParachainId: cumulus_primitives_core::ParaId = 100.into(); + pub LocalNetworkId: Option = None; +} + +parameter_types! { + pub const BlockHashCount: u32 = 250; + pub const SS58Prefix: u8 = 42; + pub const MockDbWeight: RuntimeDbWeight = RuntimeDbWeight { + read: 1, + write: 5, + }; +} + +impl frame_system::Config for Runtime { + type BaseCallFilter = Everything; + type DbWeight = MockDbWeight; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeTask = RuntimeTask; + type Nonce = u64; + type Block = Block; + type RuntimeCall = RuntimeCall; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type BlockWeights = (); + type BlockLength = (); + type SS58Prefix = SS58Prefix; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} +parameter_types! { + pub const ExistentialDeposit: u128 = 0; +} +impl pallet_balances::Config for Runtime { + type MaxReserves = (); + type ReserveIdentifier = (); + type MaxLocks = (); + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type RuntimeHoldReason = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeFreezeReason = (); +} + +pub type Precompiles = + PrecompileSetBuilder, PalletXcmPrecompile>>; + +pub type PCall = PalletXcmPrecompileCall; + +mock_account!(ParentAccount, |_| MockAccount::from_u64(4)); +mock_account!(SelfReserveAddress, |_| MockAccount::from_u64(3)); +mock_account!(AssetAddress(u128), |value: AssetAddress| { + AddressInPrefixedSet(0xffffffff, value.0).into() +}); + +// use simple encoding for parachain accounts. +mock_account!( + SiblingParachainAccount(u32), + |v: SiblingParachainAccount| { AddressInPrefixedSet(0xffffffff, v.0 as u128).into() } +); + +const MAX_POV_SIZE: u64 = 5 * 1024 * 1024; +/// Block storage limit in bytes. Set to 40 KB. +const BLOCK_STORAGE_LIMIT: u64 = 40 * 1024; + +parameter_types! { + pub BlockGasLimit: U256 = U256::from(u64::MAX); + pub PrecompilesValue: Precompiles = Precompiles::new(); + pub const WeightPerGas: Weight = Weight::from_parts(1, 0); + pub GasLimitPovSizeRatio: u64 = { + let block_gas_limit = BlockGasLimit::get().min(u64::MAX.into()).low_u64(); + block_gas_limit.saturating_div(MAX_POV_SIZE) + }; + pub GasLimitStorageGrowthRatio: u64 = { + let block_gas_limit = BlockGasLimit::get().min(u64::MAX.into()).low_u64(); + block_gas_limit.saturating_div(BLOCK_STORAGE_LIMIT) + }; +} + +/// A mapping function that converts Ethereum gas to Substrate weight +/// We are mocking this 1-1 to test db read charges too +pub struct MockGasWeightMapping; +impl GasWeightMapping for MockGasWeightMapping { + fn gas_to_weight(gas: u64, _without_base_weight: bool) -> Weight { + Weight::from_parts(gas, 1) + } + fn weight_to_gas(weight: Weight) -> u64 { + weight.ref_time().into() + } +} + +impl pallet_evm::Config for Runtime { + type FeeCalculator = (); + type GasWeightMapping = MockGasWeightMapping; + type WeightPerGas = WeightPerGas; + type CallOrigin = EnsureAddressRoot; + type WithdrawOrigin = EnsureAddressNever; + type AddressMapping = AccountId; + type Currency = Balances; + type RuntimeEvent = RuntimeEvent; + type Runner = pallet_evm::runner::stack::Runner; + type PrecompilesValue = PrecompilesValue; + type PrecompilesType = Precompiles; + type ChainId = (); + type OnChargeTransaction = (); + type BlockGasLimit = BlockGasLimit; + type BlockHashMapping = pallet_evm::SubstrateBlockHashMapping; + type FindAuthor = (); + type OnCreate = (); + type GasLimitPovSizeRatio = GasLimitPovSizeRatio; + type SuicideQuickClearLimit = ConstU32<0>; + type Timestamp = Timestamp; + type WeightInfo = pallet_evm::weights::SubstrateWeight; +} + +parameter_types! { + pub const MinimumPeriod: u64 = 5; +} +impl pallet_timestamp::Config for Runtime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +use frame_system::RawOrigin as SystemRawOrigin; +use xcm::latest::Junction; +pub struct MockAccountToAccountKey20(PhantomData<(Origin, AccountId)>); + +impl> TryConvert + for MockAccountToAccountKey20 +where + Origin::PalletsOrigin: From> + + TryInto, Error = Origin::PalletsOrigin>, +{ + fn try_convert(o: Origin) -> Result { + o.try_with_caller(|caller| match caller.try_into() { + Ok(SystemRawOrigin::Signed(who)) => { + let account_h160: H160 = who.into(); + Ok(Junction::AccountKey20 { + network: None, + key: account_h160.into(), + } + .into()) + } + Ok(other) => Err(other.into()), + Err(other) => Err(other), + }) + } +} + +pub struct MockParentMultilocationToAccountConverter; +impl ConvertLocation for MockParentMultilocationToAccountConverter { + fn convert_location(location: &Location) -> Option { + match location { + Location { + parents: 1, + interior: Here, + } => Some(ParentAccount.into()), + _ => None, + } + } +} + +pub struct MockParachainMultilocationToAccountConverter; +impl ConvertLocation for MockParachainMultilocationToAccountConverter { + fn convert_location(location: &Location) -> Option { + match location.unpack() { + (1, [Parachain(id)]) => Some(SiblingParachainAccount(*id).into()), + _ => None, + } + } +} + +pub type LocationToAccountId = ( + MockParachainMultilocationToAccountConverter, + MockParentMultilocationToAccountConverter, + xcm_builder::AccountKey20Aliases, +); + +pub type Barrier = AllowUnpaidExecutionFrom; + +pub type LocalOriginToLocation = MockAccountToAccountKey20; + +parameter_types! { + pub MatcherLocation: Location = Location::here(); + + pub UniversalLocation: InteriorLocation = + [GlobalConsensus(RelayNetwork::get()), Parachain(ParachainId::get().into())].into(); + + pub const BaseXcmWeight: Weight = Weight::from_parts(1000u64, 1000u64); + pub const RelayNetwork: NetworkId = NetworkId::Polkadot; + + pub MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; +} + +impl pallet_xcm::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; + type XcmRouter = TestSendXcm; + type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; + type XcmExecuteFilter = frame_support::traits::Everything; + type XcmExecutor = xcm_executor::XcmExecutor; + type XcmTeleportFilter = Everything; + type XcmReserveTransferFilter = Everything; + type Weigher = FixedWeightBounds; + type UniversalLocation = UniversalLocation; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; + type AdvertisedXcmVersion = (); + type Currency = Balances; + type CurrencyMatcher = IsConcrete; + type TrustedLockers = (); + type SovereignAccountOf = (); + type MaxLockers = ConstU32<8>; + type WeightInfo = pallet_xcm::TestWeightInfo; + type MaxRemoteLockConsumers = ConstU32<0>; + type RemoteLockConsumerIdentifier = (); + type AdminOrigin = frame_system::EnsureRoot; +} + +use sp_std::cell::RefCell; +use xcm::latest::opaque; +// Simulates sending a XCM message +thread_local! { + pub static SENT_XCM: RefCell> = RefCell::new(Vec::new()); +} + +pub struct TestSendXcm; +impl SendXcm for TestSendXcm { + type Ticket = (); + + fn validate( + destination: &mut Option, + message: &mut Option, + ) -> SendResult { + SENT_XCM.with(|q| { + q.borrow_mut() + .push((destination.clone().unwrap(), message.clone().unwrap())) + }); + Ok(((), Assets::new())) + } + + fn deliver(_: Self::Ticket) -> Result { + Ok(XcmHash::default()) + } +} + +pub struct DoNothingRouter; +impl SendXcm for DoNothingRouter { + type Ticket = (); + + fn validate( + _destination: &mut Option, + _message: &mut Option>, + ) -> SendResult { + Ok(((), Assets::new())) + } + + fn deliver(_: Self::Ticket) -> Result { + Ok(XcmHash::default()) + } +} + +pub struct DummyAssetTransactor; +impl TransactAsset for DummyAssetTransactor { + fn deposit_asset(_what: &Asset, _who: &Location, _context: Option<&XcmContext>) -> XcmResult { + Ok(()) + } + + fn withdraw_asset( + _what: &Asset, + _who: &Location, + _maybe_context: Option<&XcmContext>, + ) -> Result { + Ok(AssetsInHolding::default()) + } +} + +pub struct DummyWeightTrader; +impl WeightTrader for DummyWeightTrader { + fn new() -> Self { + DummyWeightTrader + } + + fn buy_weight( + &mut self, + _weight: Weight, + _payment: AssetsInHolding, + _context: &XcmContext, + ) -> Result { + Ok(AssetsInHolding::default()) + } +} + +pub type XcmOriginToTransactDispatchOrigin = ( + // Sovereign account converter; this attempts to derive an `AccountId` from the origin location + // using `LocationToAccountId` and then turn that into the usual `Signed` origin. Useful for + // foreign chains who want to have a local sovereign account on this chain which they control. + SovereignSignedViaLocation, +); + +parameter_types! { + pub ForeignReserveLocation: Location = Location::new( + 1, + [Parachain(2)] + ); + + pub ForeignAsset: Asset = Asset { + fun: Fungible(10000000), + id: AssetId(Location::new( + 1, + [Parachain(2), PalletInstance(3)], + )), + }; + + pub LocalAsset: (AssetFilter, Location) = (All.into(), Location::here()); + pub TrustedForeignAsset: (AssetFilter, Location) = (ForeignAsset::get().into(), ForeignReserveLocation::get()); +} + +pub struct XcmConfig; +impl xcm_executor::Config for XcmConfig { + type RuntimeCall = RuntimeCall; + type XcmSender = TestSendXcm; + type AssetTransactor = DummyAssetTransactor; + type OriginConverter = XcmOriginToTransactDispatchOrigin; + type IsReserve = (Case, Case); + type IsTeleporter = (); + type UniversalLocation = UniversalLocation; + type Barrier = Barrier; + type Weigher = FixedWeightBounds; + type Trader = DummyWeightTrader; + type ResponseHandler = (); + type SubscriptionService = (); + type AssetTrap = (); + type AssetClaims = (); + type CallDispatcher = RuntimeCall; + type AssetLocker = (); + type AssetExchanger = (); + type PalletInstancesInfo = (); + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type FeeManager = (); + type MessageExporter = (); + type UniversalAliases = Nothing; + type SafeCallFilter = Everything; + type Aliasers = Nothing; + + type TransactionalProcessor = (); +} + +pub(crate) struct ExtBuilder { + // endowed accounts with balances + balances: Vec<(AccountId, Balance)>, +} + +impl Default for ExtBuilder { + fn default() -> ExtBuilder { + ExtBuilder { balances: vec![] } + } +} + +impl ExtBuilder { + pub(crate) fn with_balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self { + self.balances = balances; + self + } + pub(crate) fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .expect("Frame system builds valid default genesis config"); + + pallet_balances::GenesisConfig:: { + balances: self.balances, + } + .assimilate_storage(&mut t) + .expect("Pallet balances storage can be assimilated"); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext + } +} diff --git a/precompiles/pallet-xcm/src/tests.rs b/precompiles/pallet-xcm/src/tests.rs new file mode 100644 index 00000000..a2cab787 --- /dev/null +++ b/precompiles/pallet-xcm/src/tests.rs @@ -0,0 +1,197 @@ +// Copyright Moonsong Labs +// This file is part of Moonkit. + +// Moonkit is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonkit is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonkit. If not, see . + +use crate::{mock::*, Location}; +use precompile_utils::testing::*; +use sp_weights::Weight; +use xcm::latest::Junction::*; + +fn precompiles() -> Precompiles { + PrecompilesValue::get() +} + +#[test] +fn test_solidity_interface_has_all_function_selectors_documented_and_implemented() { + check_precompile_implements_solidity_interfaces(&["XcmInterface.sol"], PCall::supports_selector) +} + +#[test] +fn selectors() { + assert!(PCall::transfer_assets_selectors().contains(&0x650ef8c7)); +} + +#[test] +fn modifiers() { + ExtBuilder::default().build().execute_with(|| { + let mut tester = + PrecompilesModifierTester::new(PrecompilesValue::get(), Alice, Precompile1); + + tester.test_default_modifier(PCall::transfer_assets_selectors()); + }); +} + +#[test] +fn selector_less_than_four_bytes() { + ExtBuilder::default().build().execute_with(|| { + // This selector is only three bytes long when four are required. + precompiles() + .prepare_test(Alice, Precompile1, vec![1u8, 2u8, 3u8]) + .execute_reverts(|output| output == b"Tried to read selector out of bounds"); + }); +} + +#[test] +fn no_selector_exists_but_length_is_right() { + ExtBuilder::default().build().execute_with(|| { + precompiles() + .prepare_test(Alice, Precompile1, vec![1u8, 2u8, 3u8, 4u8]) + .execute_reverts(|output| output == b"Unknown selector"); + }); +} + +#[test] +fn test_transfer_assets_works() { + ExtBuilder::default() + .with_balances(vec![(Alice.into(), 1000)]) + .build() + .execute_with(|| { + let dest = Location::new(1, [Parachain(2)]); + + // Specify the beneficiary from the destination's point of view + let beneficiary = Location::new( + 0, + [AccountKey20 { + network: None, + key: [1; 20], + }], + ); + + let destination_asset_location = Location::new(1, [Parachain(2), PalletInstance(3)]); + let origin_asset_location = Location::new(0, [PalletInstance(1)]); + + precompiles() + .prepare_test( + Alice, + Precompile1, + PCall::transfer_assets { + dest, + beneficiary, + assets: vec![ + (origin_asset_location, 100u128.into()), + (destination_asset_location, 150u128.into()), + ] + .into(), + fee_asset_item: 0u32, + // As we are indicating u64::MAX in ref_time, an Unlimited variant + // will be applied at the end. + weight: Weight::from_parts(u64::MAX, 80000), + }, + ) + .expect_cost(100005001) + .expect_no_logs() + .execute_returns(()); + }); +} + +#[test] +fn test_transfer_assets_success_when_paying_fees_with_foreign_asset() { + ExtBuilder::default() + .with_balances(vec![(Alice.into(), 1000)]) + .build() + .execute_with(|| { + let dest = Location::new(1, [Parachain(2)]); + + // Specify the beneficiary from the destination's point of view + let beneficiary = Location::new( + 0, + [AccountKey20 { + network: None, + key: [1; 20], + }], + ); + + let destination_asset_location = Location::new(1, [Parachain(2), PalletInstance(3)]); + let origin_asset_location = Location::new(0, [PalletInstance(1)]); + + precompiles() + .prepare_test( + Alice, + Precompile1, + PCall::transfer_assets { + dest, + beneficiary, + assets: vec![ + (origin_asset_location, 100u128.into()), + (destination_asset_location, 150u128.into()), + ] + .into(), + // We also act as a reserve for the foreign asset thus when can pay local + // fees with it. + fee_asset_item: 1u32, + // As we are indicating u64::MAX in ref_time, an Unlimited variant + // will be applied at the end. + weight: Weight::from_parts(u64::MAX, 80000), + }, + ) + .expect_cost(100005001) + .expect_no_logs() + .execute_returns(()); + }); +} + +#[test] +fn test_transfer_assets_fails_fees_unknown_reserve() { + ExtBuilder::default() + .with_balances(vec![(Alice.into(), 1000)]) + .build() + .execute_with(|| { + let dest = Location::new(1, [Parachain(3)]); + + // Specify the beneficiary from the destination's point of view + let beneficiary = Location::new( + 0, + [AccountKey20 { + network: None, + key: [1; 20], + }], + ); + + let destination_asset_location = Location::new(1, [Parachain(3), PalletInstance(3)]); + let origin_asset_location = Location::new(0, [PalletInstance(1)]); + + precompiles() + .prepare_test( + Alice, + Precompile1, + PCall::transfer_assets { + dest, + beneficiary, + assets: vec![ + (origin_asset_location, 100u128.into()), + (destination_asset_location, 150u128.into()), + ] + .into(), + // No reserve will be found for this asset. + fee_asset_item: 1u32, + // As we are indicating u64::MAX in ref_time, an Unlimited variant + // will be applied at the end. + weight: Weight::from_parts(u64::MAX, 80000), + }, + ) + .expect_no_logs() + .execute_reverts(|output| output.ends_with(b"InvalidAssetUnknownReserve\") })")); + }); +} From 508db6a5d7aed39c4b34f9ba5033d808119b0e00 Mon Sep 17 00:00:00 2001 From: Agusrodri Date: Tue, 2 Apr 2024 09:05:48 -0700 Subject: [PATCH 02/22] update interface docs --- precompiles/pallet-xcm/XcmInterface.sol | 7 ++++++- precompiles/pallet-xcm/src/mock.rs | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/precompiles/pallet-xcm/XcmInterface.sol b/precompiles/pallet-xcm/XcmInterface.sol index 51cbcfb4..9718032a 100644 --- a/precompiles/pallet-xcm/XcmInterface.sol +++ b/precompiles/pallet-xcm/XcmInterface.sol @@ -24,8 +24,13 @@ interface XCM { uint256 amount; } - /// @dev Function to make use of the transfer_assets() pallet-xcm extrinsic + /// @dev Function to send assets via XCM using transfer_assets() pallet-xcm extrinsic. /// @custom:selector 650ef8c7 + /// @param dest The destination chain. + /// @param beneficiary The actual account that will receive the tokens in dest. + /// @param assets The combination (array) of assets to send. + /// @param feeAssetItem The index of the asset that will be used to pay for fees. + /// @param weight The weight to be used for the whole XCM operation. function transferAssets( Location memory dest, Location memory beneficiary, diff --git a/precompiles/pallet-xcm/src/mock.rs b/precompiles/pallet-xcm/src/mock.rs index 238ba16b..c0e1ead8 100644 --- a/precompiles/pallet-xcm/src/mock.rs +++ b/precompiles/pallet-xcm/src/mock.rs @@ -18,7 +18,7 @@ use super::*; use frame_support::{ construct_runtime, parameter_types, - traits::{Everything, Nothing, OriginTrait, PalletInfo as PalletInfoTrait}, + traits::{Everything, Nothing, OriginTrait}, weights::{RuntimeDbWeight, Weight}, }; use pallet_evm::{EnsureAddressNever, EnsureAddressRoot, GasWeightMapping}; From 7ace48cdfce1152b6e3b063a13367817d3b23cb5 Mon Sep 17 00:00:00 2001 From: Agusrodri Date: Tue, 2 Apr 2024 09:39:51 -0700 Subject: [PATCH 03/22] update comment for Unlimited weight --- precompiles/pallet-xcm/XcmInterface.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/precompiles/pallet-xcm/XcmInterface.sol b/precompiles/pallet-xcm/XcmInterface.sol index 9718032a..e7a78b52 100644 --- a/precompiles/pallet-xcm/XcmInterface.sol +++ b/precompiles/pallet-xcm/XcmInterface.sol @@ -31,6 +31,7 @@ interface XCM { /// @param assets The combination (array) of assets to send. /// @param feeAssetItem The index of the asset that will be used to pay for fees. /// @param weight The weight to be used for the whole XCM operation. + /// (uint64::MAX in refTime means Unlimited weight) function transferAssets( Location memory dest, Location memory beneficiary, From 2134b1b4779c82bb04d7c34901667514fce3688d Mon Sep 17 00:00:00 2001 From: Agusrodri Date: Tue, 2 Apr 2024 10:01:29 -0700 Subject: [PATCH 04/22] add comment in record_cost --- precompiles/pallet-xcm/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/precompiles/pallet-xcm/src/lib.rs b/precompiles/pallet-xcm/src/lib.rs index 1ceba601..aa1f4c51 100644 --- a/precompiles/pallet-xcm/src/lib.rs +++ b/precompiles/pallet-xcm/src/lib.rs @@ -71,7 +71,8 @@ where fee_asset_item: u32, weight: Weight, ) -> EvmResult { - // TODO: record proper cost + // No DB access before try_dispatch but some logical stuff. + // To prevent spam, we charge an arbitrary amount of gas. handle.record_cost(1000)?; let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); From 346030f23deb6aa97e5ced797f08dcf19b1b71b7 Mon Sep 17 00:00:00 2001 From: Agusrodri Date: Tue, 2 Apr 2024 14:49:53 -0700 Subject: [PATCH 05/22] mock cleanup --- precompiles/pallet-xcm/src/mock.rs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/precompiles/pallet-xcm/src/mock.rs b/precompiles/pallet-xcm/src/mock.rs index c0e1ead8..c011db6b 100644 --- a/precompiles/pallet-xcm/src/mock.rs +++ b/precompiles/pallet-xcm/src/mock.rs @@ -56,20 +56,6 @@ construct_runtime!( } ); -pub struct AccountIdToLocation; -impl sp_runtime::traits::Convert for AccountIdToLocation { - fn convert(account: AccountId) -> Location { - let as_h160: H160 = account.into(); - Location::new( - 0, - [AccountKey20 { - network: None, - key: as_h160.as_fixed_bytes().clone(), - }], - ) - } -} - parameter_types! { pub ParachainId: cumulus_primitives_core::ParaId = 100.into(); pub LocalNetworkId: Option = None; @@ -135,10 +121,6 @@ pub type Precompiles = pub type PCall = PalletXcmPrecompileCall; mock_account!(ParentAccount, |_| MockAccount::from_u64(4)); -mock_account!(SelfReserveAddress, |_| MockAccount::from_u64(3)); -mock_account!(AssetAddress(u128), |value: AssetAddress| { - AddressInPrefixedSet(0xffffffff, value.0).into() -}); // use simple encoding for parachain accounts. mock_account!( From ebeeeda65d2c2ed5183e4e6203b61c2e5b2aab96 Mon Sep 17 00:00:00 2001 From: Agusrodri Date: Thu, 4 Apr 2024 15:47:20 -0700 Subject: [PATCH 06/22] remove custom address from solidity interface --- precompiles/pallet-xcm/XcmInterface.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/precompiles/pallet-xcm/XcmInterface.sol b/precompiles/pallet-xcm/XcmInterface.sol index e7a78b52..7bc632db 100644 --- a/precompiles/pallet-xcm/XcmInterface.sol +++ b/precompiles/pallet-xcm/XcmInterface.sol @@ -4,7 +4,6 @@ pragma solidity >=0.8.3; /// @author The Moonbeam Team /// @title XCM precompile Interface /// @dev The interface that Solidity contracts use to interact with the substrate pallet-xcm. -/// @custom:address 0x0000000000000000000000000000000000000820 interface XCM { // A location is defined by its number of parents and the encoded junctions (interior) struct Location { From 497553879e65fcd076f5ba3adc0d9e7bee99000d Mon Sep 17 00:00:00 2001 From: Agusrodri Date: Sun, 7 Apr 2024 19:05:41 -0700 Subject: [PATCH 07/22] start converter implementation (PoC) --- Cargo.lock | 7 ++ Cargo.toml | 2 + precompiles/pallet-xcm/Cargo.toml | 2 + precompiles/pallet-xcm/src/lib.rs | 40 +++++- precompiles/pallet-xcm/src/mock.rs | 67 ++++++++-- precompiles/pallet-xcm/src/tests.rs | 2 +- primitives/xcm/Cargo.toml | 10 ++ primitives/xcm/src/lib.rs | 2 + primitives/xcm/src/location_converter.rs | 148 +++++++++++++++++++++++ 9 files changed, 265 insertions(+), 15 deletions(-) create mode 100644 primitives/xcm/src/location_converter.rs diff --git a/Cargo.lock b/Cargo.lock index 4b4d1362..71290bbf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6992,8 +6992,10 @@ dependencies = [ "frame-system", "log", "num_enum", + "pallet-assets", "pallet-balances", "pallet-evm", + "pallet-foreign-asset-creator", "pallet-timestamp", "pallet-xcm", "parity-scale-codec", @@ -15396,7 +15398,12 @@ dependencies = [ name = "xcm-primitives" version = "0.1.0" dependencies = [ + "frame-support", + "parity-scale-codec", + "sp-core", "sp-runtime", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=release-polkadot-v1.7.0)", + "staging-xcm", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 687a9ecb..d529fa8c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,8 @@ flume = "0.11.0" jsonrpsee = { version = "0.20.3" } hex-literal = "0.4.1" +# Moonkit +pallet-foreign-asset-creator = { path = "pallets/foreign-asset-creator", default-features = false } # Substrate (wasm) frame-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.7.0", default-features = false } diff --git a/precompiles/pallet-xcm/Cargo.toml b/precompiles/pallet-xcm/Cargo.toml index 27af1186..ea7c8d58 100644 --- a/precompiles/pallet-xcm/Cargo.toml +++ b/precompiles/pallet-xcm/Cargo.toml @@ -41,7 +41,9 @@ precompile-utils = { workspace = true, features = [ "testing", "codec-xcm" ] } xcm-primitives = { workspace = true } # Substrate +pallet-assets = { workspace = true, features = [ "std" ] } pallet-balances = { workspace = true, features = [ "std", "insecure_zero_ed" ] } +pallet-foreign-asset-creator = { workspace = true, features = [ "std" ] } pallet-timestamp = { workspace = true } parity-scale-codec = { workspace = true, features = [ "max-encoded-len" ] } scale-info = { workspace = true, features = [ "derive" ] } diff --git a/precompiles/pallet-xcm/src/lib.rs b/precompiles/pallet-xcm/src/lib.rs index aa1f4c51..1ff96c2a 100644 --- a/precompiles/pallet-xcm/src/lib.rs +++ b/precompiles/pallet-xcm/src/lib.rs @@ -24,8 +24,8 @@ use frame_support::{ use pallet_evm::AddressMapping; use precompile_utils::prelude::*; -use sp_core::U256; -use sp_runtime::traits::Dispatchable; +use sp_core::{H160, U256}; +use sp_runtime::traits::{Dispatchable, MaybeEquivalence}; use sp_std::marker::PhantomData; use sp_weights::Weight; use xcm::{ @@ -33,6 +33,7 @@ use xcm::{ prelude::WeightLimit::*, VersionedAssets, VersionedLocation, }; +use xcm_primitives::location_converter::{AccountIdToLocationConverter, GetAssetId}; #[cfg(test)] mod mock; @@ -40,13 +41,20 @@ mod mock; mod tests; pub const MAX_ASSETS_ARRAY_LIMIT: u32 = 2; - type GetArrayLimit = ConstU32; -pub struct PalletXcmPrecompile(PhantomData); +pub struct PalletXcmPrecompile( + PhantomData<( + Runtime, + AssetId, + AssetIdToLocationManager, + AssetIdInfoGetter, + )>, +); #[precompile_utils::precompile] -impl PalletXcmPrecompile +impl + PalletXcmPrecompile where Runtime: pallet_xcm::Config + pallet_evm::Config + frame_system::Config, ::RuntimeCall: @@ -54,6 +62,10 @@ where <::RuntimeCall as Dispatchable>::RuntimeOrigin: From>, ::RuntimeCall: From>, + AssetIdToLocationManager: MaybeEquivalence, + AssetIdInfoGetter: GetAssetId, + Runtime::AccountId: From + Into, + AssetId: From + TryFrom + TryFrom, { #[precompile::public( "transferAssets(\ @@ -103,4 +115,22 @@ where RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; Ok(()) } + + // TODO: finish + #[precompile::public("transferAssetsWithAddress()")] + fn transfer_assets_with_address(handle: &mut impl PrecompileHandle) -> EvmResult { + /* + // Just testing stuff + let asset_info = AssetIdInfoGetter::get_asset_id_info(); + let account: ::AccountId = + H160::from_low_u64_be(2050).into(); + + let result = AccountIdToLocationConverter::<_, AssetId, AssetIdToLocationManager>::convert( + account, asset_info, 3u8, 42u8, + ); + + println!("RESULT: {:#?}", result); */ + + Ok(()) + } } diff --git a/precompiles/pallet-xcm/src/mock.rs b/precompiles/pallet-xcm/src/mock.rs index c011db6b..77169942 100644 --- a/precompiles/pallet-xcm/src/mock.rs +++ b/precompiles/pallet-xcm/src/mock.rs @@ -21,6 +21,7 @@ use frame_support::{ traits::{Everything, Nothing, OriginTrait}, weights::{RuntimeDbWeight, Weight}, }; +use frame_system::EnsureRoot; use pallet_evm::{EnsureAddressNever, EnsureAddressRoot, GasWeightMapping}; use precompile_utils::{ mock_account, @@ -38,6 +39,7 @@ use xcm_executor::{ traits::{ConvertLocation, TransactAsset, WeightTrader}, AssetsInHolding, }; +pub use xcm_primitives::location_converter::AssetIdInfoGetter; use Junctions::Here; pub type AccountId = MockAccount; @@ -52,7 +54,9 @@ construct_runtime!( Balances: pallet_balances, Evm: pallet_evm, Timestamp: pallet_timestamp, - PolkadotXcm: pallet_xcm + PolkadotXcm: pallet_xcm, + Assets: pallet_assets, + ForeignAssetCreator: pallet_foreign_asset_creator, } ); @@ -114,11 +118,56 @@ impl pallet_balances::Config for Runtime { type MaxFreezes = (); type RuntimeFreezeReason = (); } +parameter_types! { + pub const AssetDeposit: u64 = 0; + pub const ApprovalDeposit: u64 = 0; + pub const StringLimit: u32 = 50; + pub const MetadataDepositBase: u64 = 0; + pub const MetadataDepositPerByte: u64 = 0; +} + +pub type AssetId = u16; + +impl pallet_assets::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type AssetId = AssetId; + type AssetIdParameter = parity_scale_codec::Compact; + type Currency = Balances; + type CreateOrigin = frame_support::traits::NeverEnsureOrigin; + type ForceOrigin = EnsureRoot; + type AssetDeposit = AssetDeposit; + type AssetAccountDeposit = AssetDeposit; + type MetadataDepositBase = MetadataDepositBase; + type MetadataDepositPerByte = MetadataDepositPerByte; + type ApprovalDeposit = ApprovalDeposit; + type StringLimit = StringLimit; + type Freezer = (); + type Extra = (); + type CallbackHandle = (); + type WeightInfo = (); + type RemoveItemsLimit = ConstU32<1000>; + pallet_assets::runtime_benchmarks_enabled! { + type BenchmarkHelper = (); + } +} + +impl pallet_foreign_asset_creator::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ForeignAsset = Location; + type ForeignAssetCreatorOrigin = EnsureRoot; + type ForeignAssetModifierOrigin = EnsureRoot; + type ForeignAssetDestroyerOrigin = EnsureRoot; + type Fungibles = Assets; + type WeightInfo = (); + type OnForeignAssetCreated = (); + type OnForeignAssetDestroyed = (); +} -pub type Precompiles = - PrecompileSetBuilder, PalletXcmPrecompile>>; +pub type Precompiles = + PrecompileSetBuilder, PalletXcmPrecompile>>; -pub type PCall = PalletXcmPrecompileCall; +pub type PCall = PalletXcmPrecompileCall; mock_account!(ParentAccount, |_| MockAccount::from_u64(4)); @@ -134,7 +183,7 @@ const BLOCK_STORAGE_LIMIT: u64 = 40 * 1024; parameter_types! { pub BlockGasLimit: U256 = U256::from(u64::MAX); - pub PrecompilesValue: Precompiles = Precompiles::new(); + pub PrecompilesValue: Precompiles = Precompiles::new(); pub const WeightPerGas: Weight = Weight::from_parts(1, 0); pub GasLimitPovSizeRatio: u64 = { let block_gas_limit = BlockGasLimit::get().min(u64::MAX.into()).low_u64(); @@ -169,7 +218,7 @@ impl pallet_evm::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Runner = pallet_evm::runner::stack::Runner; type PrecompilesValue = PrecompilesValue; - type PrecompilesType = Precompiles; + type PrecompilesType = Precompiles; type ChainId = (); type OnChargeTransaction = (); type BlockGasLimit = BlockGasLimit; @@ -291,7 +340,7 @@ impl pallet_xcm::Config for Runtime { } use sp_std::cell::RefCell; -use xcm::latest::opaque; +use xcm::latest::{opaque, Assets as XcmAssets}; // Simulates sending a XCM message thread_local! { pub static SENT_XCM: RefCell> = RefCell::new(Vec::new()); @@ -309,7 +358,7 @@ impl SendXcm for TestSendXcm { q.borrow_mut() .push((destination.clone().unwrap(), message.clone().unwrap())) }); - Ok(((), Assets::new())) + Ok(((), XcmAssets::new())) } fn deliver(_: Self::Ticket) -> Result { @@ -325,7 +374,7 @@ impl SendXcm for DoNothingRouter { _destination: &mut Option, _message: &mut Option>, ) -> SendResult { - Ok(((), Assets::new())) + Ok(((), XcmAssets::new())) } fn deliver(_: Self::Ticket) -> Result { diff --git a/precompiles/pallet-xcm/src/tests.rs b/precompiles/pallet-xcm/src/tests.rs index a2cab787..a2ba31fb 100644 --- a/precompiles/pallet-xcm/src/tests.rs +++ b/precompiles/pallet-xcm/src/tests.rs @@ -19,7 +19,7 @@ use precompile_utils::testing::*; use sp_weights::Weight; use xcm::latest::Junction::*; -fn precompiles() -> Precompiles { +fn precompiles() -> Precompiles { PrecompilesValue::get() } diff --git a/primitives/xcm/Cargo.toml b/primitives/xcm/Cargo.toml index d3e1df3c..7051dee8 100644 --- a/primitives/xcm/Cargo.toml +++ b/primitives/xcm/Cargo.toml @@ -6,11 +6,21 @@ edition = "2021" version = "0.1.0" [dependencies] +frame-support = { workspace = true } +sp-core = { workspace = true } sp-runtime = { workspace = true } +sp-std = { workspace = true } +parity-scale-codec = { workspace = true, features = [ "derive" ] } +xcm = { workspace = true } [features] default = [ "std" ] std = [ + "frame-support/std", + "sp-core/std", "sp-runtime/std", + "sp-std/std", + "parity-scale-codec/std", + "xcm/std" ] runtime-benchmarks = [] diff --git a/primitives/xcm/src/lib.rs b/primitives/xcm/src/lib.rs index 75a7171a..16cbeb89 100644 --- a/primitives/xcm/src/lib.rs +++ b/primitives/xcm/src/lib.rs @@ -20,6 +20,8 @@ use sp_runtime::DispatchResult; +pub mod location_converter; + /// Pause and resume execution of XCM pub trait PauseXcmExecution { fn suspend_xcm_execution() -> DispatchResult; diff --git a/primitives/xcm/src/location_converter.rs b/primitives/xcm/src/location_converter.rs new file mode 100644 index 00000000..de95efe9 --- /dev/null +++ b/primitives/xcm/src/location_converter.rs @@ -0,0 +1,148 @@ +// Copyright Moonsong Labs +// This file is part of Moonkit. + +// Moonkit is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonkit is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonkit. If not, see . + +use frame_support::Parameter; +use sp_core::H160; +use sp_runtime::traits::MaybeEquivalence; +use sp_std::marker::PhantomData; +use xcm::latest::{Junction::*, Location}; + +pub struct AccountIdToLocationConverter( + pub PhantomData<(AccountId, AssetId, AssetIdToLocationManager)>, +); + +impl + AccountIdToLocationConverter +where + AccountId: Parameter + From + Into, + AssetId: From + TryFrom + TryFrom, + AssetIdToLocationManager: MaybeEquivalence, +{ + // Function that converts an AccountId into a Location. + pub fn convert( + account: AccountId, + asset_id_info: AssetIdInfo, + balances_index: u8, + erc20_xcm_bridge_index: u8, + ) -> Option { + match account { + // First we check if the account matches the pallet-balances address. + // In that case we return the self-reserve location, which is the location + // of pallet-balances. + // TODO: how do we make it more generic? + a if a == H160::from_low_u64_be(2050).into() => { + Some(Location::new(0, [PalletInstance(balances_index)])) + } + // Then check if the asset is a foreign asset, we use the prefix for this. + _ => match Self::account_to_asset_id(account.clone(), asset_id_info) { + // If we encounter a prefix, it means the account is a foreign asset. + // In this case we call AssetIdToLocationManager::convert_back() to retrieve the + // desired location in a generic way. + Some((_prefix, asset_id)) => AssetIdToLocationManager::convert_back(&asset_id), + + // If the address is not a foreign asset, then it means it is a real ERC20. + // Here we append an AccountKey20 to the location of the Erc20XcmBridgePallet. + // + // TODO: this will retrieve an unreal location in case the pallet is not installed + // in the runtime, we should handle this case as well. + // TODO: how do we make it more generic? + None => { + let h160_account: H160 = account.into(); + Some(Location::new( + 0, + [ + PalletInstance(erc20_xcm_bridge_index), + AccountKey20 { + network: None, + key: h160_account.into(), + }, + ], + )) + } + }, + } + } + + // Helper function that retrieves the asset_id of a foreign asset account. + pub fn account_to_asset_id( + account: AccountId, + asset_id_info: AssetIdInfo, + ) -> Option<(Vec, AssetId)> { + let h160_account: H160 = account.into(); + let (prefix_part, id_part) = h160_account + .as_fixed_bytes() + .split_at(asset_id_info.split_at); + + if prefix_part == asset_id_info.foreign_asset_prefix { + let asset_id: AssetId = match asset_id_info.bit_type { + 16 => { + let mut data = [0u8; 2]; + data.copy_from_slice(id_part); + u16::from_be_bytes(data).try_into().unwrap_or(0u8.into()) + } + 128 => { + let mut data = [0u8; 16]; + data.copy_from_slice(id_part); + u128::from_be_bytes(data).try_into().unwrap_or(0u8.into()) + } + _ => return None, + }; + return Some((prefix_part.to_vec(), asset_id)); + } else { + return None; + } + } +} + +/// Information to retrieve for a specific AssetId type. +pub struct AssetIdInfo<'a> { + pub foreign_asset_prefix: &'a [u8], + pub split_at: usize, + pub bit_type: usize, +} + +// Define a trait to abstract over different types of AssetId. +// We retrieve an AssetIdInfo type containing all the information we need +// to handle the generic types over AssetId. +pub trait GetAssetId { + fn get_asset_id_info() -> AssetIdInfo<'static>; +} + +/// A getter that contains the info for each type +/// we admit as AssetId. +pub struct AssetIdInfoGetter; + +// Implement GetAssetId trait for u128. +impl GetAssetId for AssetIdInfoGetter { + fn get_asset_id_info() -> AssetIdInfo<'static> { + AssetIdInfo { + foreign_asset_prefix: &[255u8; 4], + split_at: 4, + bit_type: 128, + } + } +} + +// Implement GetAssetId trait for u16. +impl GetAssetId for AssetIdInfoGetter { + fn get_asset_id_info() -> AssetIdInfo<'static> { + AssetIdInfo { + foreign_asset_prefix: &[255u8; 18], + split_at: 18, + bit_type: 16, + } + } +} From 0035cc86163cfd0aa60984620e6daddafbfefa70 Mon Sep 17 00:00:00 2001 From: Agusrodri Date: Mon, 8 Apr 2024 13:32:08 -0700 Subject: [PATCH 08/22] use size_of in AssetIdInfo --- precompiles/pallet-xcm/src/lib.rs | 4 ++-- primitives/xcm/src/location_converter.rs | 20 +++++++++----------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/precompiles/pallet-xcm/src/lib.rs b/precompiles/pallet-xcm/src/lib.rs index 1ff96c2a..1eeb554c 100644 --- a/precompiles/pallet-xcm/src/lib.rs +++ b/precompiles/pallet-xcm/src/lib.rs @@ -33,7 +33,7 @@ use xcm::{ prelude::WeightLimit::*, VersionedAssets, VersionedLocation, }; -use xcm_primitives::location_converter::{AccountIdToLocationConverter, GetAssetId}; +use xcm_primitives::location_converter::GetAssetId; #[cfg(test)] mod mock; @@ -118,7 +118,7 @@ where // TODO: finish #[precompile::public("transferAssetsWithAddress()")] - fn transfer_assets_with_address(handle: &mut impl PrecompileHandle) -> EvmResult { + fn transfer_assets_with_address(_handle: &mut impl PrecompileHandle) -> EvmResult { /* // Just testing stuff let asset_info = AssetIdInfoGetter::get_asset_id_info(); diff --git a/primitives/xcm/src/location_converter.rs b/primitives/xcm/src/location_converter.rs index de95efe9..a3bf91d1 100644 --- a/primitives/xcm/src/location_converter.rs +++ b/primitives/xcm/src/location_converter.rs @@ -17,7 +17,7 @@ use frame_support::Parameter; use sp_core::H160; use sp_runtime::traits::MaybeEquivalence; -use sp_std::marker::PhantomData; +use sp_std::{marker::PhantomData, mem::size_of}; use xcm::latest::{Junction::*, Location}; pub struct AccountIdToLocationConverter( @@ -84,16 +84,16 @@ where let h160_account: H160 = account.into(); let (prefix_part, id_part) = h160_account .as_fixed_bytes() - .split_at(asset_id_info.split_at); + .split_at(asset_id_info.size_of + 2); if prefix_part == asset_id_info.foreign_asset_prefix { - let asset_id: AssetId = match asset_id_info.bit_type { - 16 => { + let asset_id: AssetId = match asset_id_info.size_of { + 2 => { let mut data = [0u8; 2]; data.copy_from_slice(id_part); u16::from_be_bytes(data).try_into().unwrap_or(0u8.into()) } - 128 => { + 16 => { let mut data = [0u8; 16]; data.copy_from_slice(id_part); u128::from_be_bytes(data).try_into().unwrap_or(0u8.into()) @@ -110,8 +110,7 @@ where /// Information to retrieve for a specific AssetId type. pub struct AssetIdInfo<'a> { pub foreign_asset_prefix: &'a [u8], - pub split_at: usize, - pub bit_type: usize, + pub size_of: usize, } // Define a trait to abstract over different types of AssetId. @@ -130,8 +129,7 @@ impl GetAssetId for AssetIdInfoGetter { fn get_asset_id_info() -> AssetIdInfo<'static> { AssetIdInfo { foreign_asset_prefix: &[255u8; 4], - split_at: 4, - bit_type: 128, + size_of: size_of::(), } } } @@ -141,8 +139,8 @@ impl GetAssetId for AssetIdInfoGetter { fn get_asset_id_info() -> AssetIdInfo<'static> { AssetIdInfo { foreign_asset_prefix: &[255u8; 18], - split_at: 18, - bit_type: 16, + size_of: size_of::(), } } } + From 835639abec695fcf12d2e8bcbdeb285e942be2d9 Mon Sep 17 00:00:00 2001 From: Agusrodri Date: Mon, 8 Apr 2024 13:34:53 -0700 Subject: [PATCH 09/22] fmt --- primitives/xcm/src/location_converter.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/primitives/xcm/src/location_converter.rs b/primitives/xcm/src/location_converter.rs index a3bf91d1..fcbfbb59 100644 --- a/primitives/xcm/src/location_converter.rs +++ b/primitives/xcm/src/location_converter.rs @@ -143,4 +143,3 @@ impl GetAssetId for AssetIdInfoGetter { } } } - From 00a9e223dfd6bcfef3781b4407bd281ca3ab3005 Mon Sep 17 00:00:00 2001 From: Agusrodri Date: Wed, 10 Apr 2024 18:04:20 -0700 Subject: [PATCH 10/22] refactor converter --- Cargo.lock | 2 + precompiles/pallet-xcm/src/lib.rs | 22 +-- primitives/xcm/Cargo.toml | 3 + primitives/xcm/src/location_converter.rs | 186 ++++++++++++----------- 4 files changed, 108 insertions(+), 105 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7b05e983..77d8aad8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15502,6 +15502,8 @@ name = "xcm-primitives" version = "0.1.0" dependencies = [ "frame-support", + "impl-trait-for-tuples", + "log", "parity-scale-codec", "sp-core", "sp-runtime", diff --git a/precompiles/pallet-xcm/src/lib.rs b/precompiles/pallet-xcm/src/lib.rs index 1eeb554c..b8376d15 100644 --- a/precompiles/pallet-xcm/src/lib.rs +++ b/precompiles/pallet-xcm/src/lib.rs @@ -33,7 +33,7 @@ use xcm::{ prelude::WeightLimit::*, VersionedAssets, VersionedLocation, }; -use xcm_primitives::location_converter::GetAssetId; +use xcm_primitives::location_converter::GetAssetIdInfo; #[cfg(test)] mod mock; @@ -63,7 +63,7 @@ where From>, ::RuntimeCall: From>, AssetIdToLocationManager: MaybeEquivalence, - AssetIdInfoGetter: GetAssetId, + AssetIdInfoGetter: GetAssetIdInfo, Runtime::AccountId: From + Into, AssetId: From + TryFrom + TryFrom, { @@ -115,22 +115,4 @@ where RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; Ok(()) } - - // TODO: finish - #[precompile::public("transferAssetsWithAddress()")] - fn transfer_assets_with_address(_handle: &mut impl PrecompileHandle) -> EvmResult { - /* - // Just testing stuff - let asset_info = AssetIdInfoGetter::get_asset_id_info(); - let account: ::AccountId = - H160::from_low_u64_be(2050).into(); - - let result = AccountIdToLocationConverter::<_, AssetId, AssetIdToLocationManager>::convert( - account, asset_info, 3u8, 42u8, - ); - - println!("RESULT: {:#?}", result); */ - - Ok(()) - } } diff --git a/primitives/xcm/Cargo.toml b/primitives/xcm/Cargo.toml index 7051dee8..4acf8b8b 100644 --- a/primitives/xcm/Cargo.toml +++ b/primitives/xcm/Cargo.toml @@ -6,6 +6,9 @@ edition = "2021" version = "0.1.0" [dependencies] +impl-trait-for-tuples = { workspace = true } +log = { workspace = true } + frame-support = { workspace = true } sp-core = { workspace = true } sp-runtime = { workspace = true } diff --git a/primitives/xcm/src/location_converter.rs b/primitives/xcm/src/location_converter.rs index fcbfbb59..ba9c8c5a 100644 --- a/primitives/xcm/src/location_converter.rs +++ b/primitives/xcm/src/location_converter.rs @@ -14,68 +14,102 @@ // You should have received a copy of the GNU General Public License // along with Moonkit. If not, see . -use frame_support::Parameter; +use frame_support::{traits::PalletInfoAccess, Parameter}; use sp_core::H160; use sp_runtime::traits::MaybeEquivalence; -use sp_std::{marker::PhantomData, mem::size_of}; +use sp_std::{fmt::Debug, marker::PhantomData, mem::size_of}; use xcm::latest::{Junction::*, Location}; -pub struct AccountIdToLocationConverter( - pub PhantomData<(AccountId, AssetId, AssetIdToLocationManager)>, +/// Information to retrieve for a specific AssetId type. +pub struct AssetIdInfo<'a> { + pub foreign_asset_prefix: &'a [u8], + pub size_of: usize, +} + +// Define a trait to abstract over different types of AssetId. +// We retrieve an AssetIdInfo type containing all the information we need +// to handle the generic types over AssetId. +pub trait GetAssetIdInfo { + fn get_asset_id_info() -> AssetIdInfo<'static>; +} + +/// A getter that contains the info for each type +/// we admit as AssetId. +pub struct AssetIdInfoGetter; + +// Implement GetAssetId trait for u128. +impl GetAssetIdInfo for AssetIdInfoGetter { + fn get_asset_id_info() -> AssetIdInfo<'static> { + AssetIdInfo { + foreign_asset_prefix: &[255u8; 4], + size_of: size_of::(), + } + } +} + +// Implement GetAssetId trait for u16. +impl GetAssetIdInfo for AssetIdInfoGetter { + fn get_asset_id_info() -> AssetIdInfo<'static> { + AssetIdInfo { + foreign_asset_prefix: &[255u8; 18], + size_of: size_of::(), + } + } +} + +pub trait AccountIdToLocationMatcher { + fn convert(account: AccountId) -> Option; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl AccountIdToLocationMatcher for Tuple { + fn convert(account: AccountId) -> Option { + for_tuples!( #( + match Tuple::convert(account.clone()) { o @ Some(_) => return o, _ => () } + )* ); + log::trace!(target: "xcm_primitives::convert", "did not match any location to the account: {:?}", &account); + None + } +} + +pub struct SingleAddressMatcher( + PhantomData<(AccountId, PalletInstance)>, ); -impl - AccountIdToLocationConverter +impl AccountIdToLocationMatcher + for SingleAddressMatcher where - AccountId: Parameter + From + Into, - AssetId: From + TryFrom + TryFrom, - AssetIdToLocationManager: MaybeEquivalence, + AccountId: Parameter + From, + PalletInstance: PalletInfoAccess, { - // Function that converts an AccountId into a Location. - pub fn convert( - account: AccountId, - asset_id_info: AssetIdInfo, - balances_index: u8, - erc20_xcm_bridge_index: u8, - ) -> Option { - match account { - // First we check if the account matches the pallet-balances address. - // In that case we return the self-reserve location, which is the location - // of pallet-balances. - // TODO: how do we make it more generic? - a if a == H160::from_low_u64_be(2050).into() => { - Some(Location::new(0, [PalletInstance(balances_index)])) - } - // Then check if the asset is a foreign asset, we use the prefix for this. - _ => match Self::account_to_asset_id(account.clone(), asset_id_info) { - // If we encounter a prefix, it means the account is a foreign asset. - // In this case we call AssetIdToLocationManager::convert_back() to retrieve the - // desired location in a generic way. - Some((_prefix, asset_id)) => AssetIdToLocationManager::convert_back(&asset_id), - - // If the address is not a foreign asset, then it means it is a real ERC20. - // Here we append an AccountKey20 to the location of the Erc20XcmBridgePallet. - // - // TODO: this will retrieve an unreal location in case the pallet is not installed - // in the runtime, we should handle this case as well. - // TODO: how do we make it more generic? - None => { - let h160_account: H160 = account.into(); - Some(Location::new( - 0, - [ - PalletInstance(erc20_xcm_bridge_index), - AccountKey20 { - network: None, - key: h160_account.into(), - }, - ], - )) - } - }, + fn convert(account: AccountId) -> Option { + if account == H160::from_low_u64_be(ADDRESS).into() { + return Some(Location::new( + 0, + [PalletInstance( + ::index() as u8, + )], + )); } + None } +} + +pub struct MatchThroughEquivalence( + PhantomData<( + AccountId, + AssetId, + AssetIdInfoGetter, + AssetIdToLocationManager, + )>, +); +impl + MatchThroughEquivalence +where + AccountId: Parameter + Into, + AssetId: From + TryFrom + TryFrom, +{ // Helper function that retrieves the asset_id of a foreign asset account. pub fn account_to_asset_id( account: AccountId, @@ -101,45 +135,27 @@ where _ => return None, }; return Some((prefix_part.to_vec(), asset_id)); - } else { - return None; } + None } } -/// Information to retrieve for a specific AssetId type. -pub struct AssetIdInfo<'a> { - pub foreign_asset_prefix: &'a [u8], - pub size_of: usize, -} - -// Define a trait to abstract over different types of AssetId. -// We retrieve an AssetIdInfo type containing all the information we need -// to handle the generic types over AssetId. -pub trait GetAssetId { - fn get_asset_id_info() -> AssetIdInfo<'static>; -} - -/// A getter that contains the info for each type -/// we admit as AssetId. -pub struct AssetIdInfoGetter; - -// Implement GetAssetId trait for u128. -impl GetAssetId for AssetIdInfoGetter { - fn get_asset_id_info() -> AssetIdInfo<'static> { - AssetIdInfo { - foreign_asset_prefix: &[255u8; 4], - size_of: size_of::(), +impl + AccountIdToLocationMatcher + for MatchThroughEquivalence +where + AccountId: Parameter + Into, + AssetId: From + TryFrom + TryFrom, + AssetIdInfoGetter: GetAssetIdInfo, + AssetIdToLocationManager: MaybeEquivalence, +{ + fn convert(account: AccountId) -> Option { + let asset_id_info = AssetIdInfoGetter::get_asset_id_info(); + if let Some((_prefix, asset_id)) = Self::account_to_asset_id(account, asset_id_info) { + return AssetIdToLocationManager::convert_back(&asset_id); } + None } } -// Implement GetAssetId trait for u16. -impl GetAssetId for AssetIdInfoGetter { - fn get_asset_id_info() -> AssetIdInfo<'static> { - AssetIdInfo { - foreign_asset_prefix: &[255u8; 18], - size_of: size_of::(), - } - } -} +// TODO: Erc20BridgeMatcher \ No newline at end of file From d6ebcfdf4026f56030771357a040d31c6ff9fb27 Mon Sep 17 00:00:00 2001 From: Agusrodri Date: Wed, 10 Apr 2024 18:07:00 -0700 Subject: [PATCH 11/22] fmt --- primitives/xcm/src/location_converter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/primitives/xcm/src/location_converter.rs b/primitives/xcm/src/location_converter.rs index ba9c8c5a..c0261a33 100644 --- a/primitives/xcm/src/location_converter.rs +++ b/primitives/xcm/src/location_converter.rs @@ -158,4 +158,4 @@ where } } -// TODO: Erc20BridgeMatcher \ No newline at end of file +// TODO: Erc20BridgeMatcher From 5183abe8915db9c0a4e89ce01a84cad09bb853e9 Mon Sep 17 00:00:00 2001 From: Agusrodri Date: Fri, 12 Apr 2024 14:32:50 -0700 Subject: [PATCH 12/22] add Erc20PalletMatcher and adapt mock --- precompiles/pallet-xcm/src/lib.rs | 23 ++++++-------------- precompiles/pallet-xcm/src/mock.rs | 26 +++++++++++++++++------ precompiles/pallet-xcm/src/tests.rs | 2 +- primitives/xcm/src/location_converter.rs | 27 +++++++++++++++++++++++- 4 files changed, 52 insertions(+), 26 deletions(-) diff --git a/precompiles/pallet-xcm/src/lib.rs b/precompiles/pallet-xcm/src/lib.rs index b8376d15..5a44ffab 100644 --- a/precompiles/pallet-xcm/src/lib.rs +++ b/precompiles/pallet-xcm/src/lib.rs @@ -24,8 +24,8 @@ use frame_support::{ use pallet_evm::AddressMapping; use precompile_utils::prelude::*; -use sp_core::{H160, U256}; -use sp_runtime::traits::{Dispatchable, MaybeEquivalence}; +use sp_core::U256; +use sp_runtime::traits::Dispatchable; use sp_std::marker::PhantomData; use sp_weights::Weight; use xcm::{ @@ -33,7 +33,7 @@ use xcm::{ prelude::WeightLimit::*, VersionedAssets, VersionedLocation, }; -use xcm_primitives::location_converter::GetAssetIdInfo; +use xcm_primitives::location_converter::AccountIdToLocationMatcher; #[cfg(test)] mod mock; @@ -43,18 +43,10 @@ mod tests; pub const MAX_ASSETS_ARRAY_LIMIT: u32 = 2; type GetArrayLimit = ConstU32; -pub struct PalletXcmPrecompile( - PhantomData<( - Runtime, - AssetId, - AssetIdToLocationManager, - AssetIdInfoGetter, - )>, -); +pub struct PalletXcmPrecompile(PhantomData<(Runtime, LocationMatcher)>); #[precompile_utils::precompile] -impl - PalletXcmPrecompile +impl PalletXcmPrecompile where Runtime: pallet_xcm::Config + pallet_evm::Config + frame_system::Config, ::RuntimeCall: @@ -62,10 +54,7 @@ where <::RuntimeCall as Dispatchable>::RuntimeOrigin: From>, ::RuntimeCall: From>, - AssetIdToLocationManager: MaybeEquivalence, - AssetIdInfoGetter: GetAssetIdInfo, - Runtime::AccountId: From + Into, - AssetId: From + TryFrom + TryFrom, + LocationMatcher: AccountIdToLocationMatcher, { #[precompile::public( "transferAssets(\ diff --git a/precompiles/pallet-xcm/src/mock.rs b/precompiles/pallet-xcm/src/mock.rs index 77169942..f4db10a7 100644 --- a/precompiles/pallet-xcm/src/mock.rs +++ b/precompiles/pallet-xcm/src/mock.rs @@ -28,7 +28,7 @@ use precompile_utils::{ precompile_set::*, testing::{AddressInPrefixedSet, MockAccount}, }; -use sp_core::{ConstU32, H160, H256, U256}; +use sp_core::{ConstU32, ConstU64, H160, H256, U256}; use sp_runtime::traits::{BlakeTwo256, IdentityLookup, TryConvert}; use sp_runtime::BuildStorage; use xcm::latest::{prelude::*, Error as XcmError}; @@ -39,7 +39,9 @@ use xcm_executor::{ traits::{ConvertLocation, TransactAsset, WeightTrader}, AssetsInHolding, }; -pub use xcm_primitives::location_converter::AssetIdInfoGetter; +pub use xcm_primitives::location_converter::{ + AssetIdInfoGetter, Erc20PalletMatcher, MatchThroughEquivalence, SingleAddressMatcher, +}; use Junctions::Here; pub type AccountId = MockAccount; @@ -164,10 +166,20 @@ impl pallet_foreign_asset_creator::Config for Runtime { type OnForeignAssetDestroyed = (); } -pub type Precompiles = - PrecompileSetBuilder, PalletXcmPrecompile>>; +pub type Precompiles = + PrecompileSetBuilder, PalletXcmPrecompile>>; + +pub type AccountIdAlias = ::AccountId; + +pub type SingleAddressMatch = SingleAddressMatcher; + +pub type EquivalenceMatch = + MatchThroughEquivalence; + +pub type Erc20Match = Erc20PalletMatcher; -pub type PCall = PalletXcmPrecompileCall; +pub type PCall = + PalletXcmPrecompileCall; mock_account!(ParentAccount, |_| MockAccount::from_u64(4)); @@ -183,7 +195,7 @@ const BLOCK_STORAGE_LIMIT: u64 = 40 * 1024; parameter_types! { pub BlockGasLimit: U256 = U256::from(u64::MAX); - pub PrecompilesValue: Precompiles = Precompiles::new(); + pub PrecompilesValue: Precompiles = Precompiles::new(); pub const WeightPerGas: Weight = Weight::from_parts(1, 0); pub GasLimitPovSizeRatio: u64 = { let block_gas_limit = BlockGasLimit::get().min(u64::MAX.into()).low_u64(); @@ -218,7 +230,7 @@ impl pallet_evm::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Runner = pallet_evm::runner::stack::Runner; type PrecompilesValue = PrecompilesValue; - type PrecompilesType = Precompiles; + type PrecompilesType = Precompiles; type ChainId = (); type OnChargeTransaction = (); type BlockGasLimit = BlockGasLimit; diff --git a/precompiles/pallet-xcm/src/tests.rs b/precompiles/pallet-xcm/src/tests.rs index a2ba31fb..0d77b9f0 100644 --- a/precompiles/pallet-xcm/src/tests.rs +++ b/precompiles/pallet-xcm/src/tests.rs @@ -19,7 +19,7 @@ use precompile_utils::testing::*; use sp_weights::Weight; use xcm::latest::Junction::*; -fn precompiles() -> Precompiles { +fn precompiles() -> Precompiles { PrecompilesValue::get() } diff --git a/primitives/xcm/src/location_converter.rs b/primitives/xcm/src/location_converter.rs index c0261a33..0418ae81 100644 --- a/primitives/xcm/src/location_converter.rs +++ b/primitives/xcm/src/location_converter.rs @@ -57,6 +57,7 @@ impl GetAssetIdInfo for AssetIdInfoGetter { } } +/// A converter from AccountId to a XCM Location. pub trait AccountIdToLocationMatcher { fn convert(account: AccountId) -> Option; } @@ -72,6 +73,8 @@ impl AccountIdToLocationMatcher for Tup } } +/// A matcher for any address that we would like to compare against a received account. +/// Tipically used to manage self-reserve currency through the pallet-balances address. pub struct SingleAddressMatcher( PhantomData<(AccountId, PalletInstance)>, ); @@ -95,6 +98,7 @@ where } } +/// Matcher to compare a received account against some possible foreign asset address. pub struct MatchThroughEquivalence( PhantomData<( AccountId, @@ -158,4 +162,25 @@ where } } -// TODO: Erc20BridgeMatcher +// Matcher for any pallet that handles ERC20s internally. +pub struct Erc20PalletMatcher(PhantomData); + +impl AccountIdToLocationMatcher + for Erc20PalletMatcher +where + AccountId: Parameter + Into, +{ + fn convert(account: AccountId) -> Option { + let h160_account = account.into(); + return Some(Location::new( + 0, + [ + PalletInstance(PALLET_INDEX), + AccountKey20 { + key: h160_account.0, + network: None, + }, + ], + )); + } +} From 7e287921d8d5c55c485c3163feccfe3b700186af Mon Sep 17 00:00:00 2001 From: Agusrodri Date: Fri, 12 Apr 2024 14:35:05 -0700 Subject: [PATCH 13/22] remove unused import --- precompiles/pallet-xcm/src/mock.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/precompiles/pallet-xcm/src/mock.rs b/precompiles/pallet-xcm/src/mock.rs index f4db10a7..d522a465 100644 --- a/precompiles/pallet-xcm/src/mock.rs +++ b/precompiles/pallet-xcm/src/mock.rs @@ -28,7 +28,7 @@ use precompile_utils::{ precompile_set::*, testing::{AddressInPrefixedSet, MockAccount}, }; -use sp_core::{ConstU32, ConstU64, H160, H256, U256}; +use sp_core::{ConstU32, H160, H256, U256}; use sp_runtime::traits::{BlakeTwo256, IdentityLookup, TryConvert}; use sp_runtime::BuildStorage; use xcm::latest::{prelude::*, Error as XcmError}; From be1291b85582deb3f0e1b5a0662277e403611879 Mon Sep 17 00:00:00 2001 From: Agusrodri Date: Fri, 12 Apr 2024 14:38:12 -0700 Subject: [PATCH 14/22] minor fixes --- precompiles/pallet-xcm/src/lib.rs | 2 +- primitives/xcm/src/location_converter.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/precompiles/pallet-xcm/src/lib.rs b/precompiles/pallet-xcm/src/lib.rs index 5a44ffab..0c9f1f13 100644 --- a/precompiles/pallet-xcm/src/lib.rs +++ b/precompiles/pallet-xcm/src/lib.rs @@ -54,7 +54,7 @@ where <::RuntimeCall as Dispatchable>::RuntimeOrigin: From>, ::RuntimeCall: From>, - LocationMatcher: AccountIdToLocationMatcher, + LocationMatcher: AccountIdToLocationMatcher<::AccountId>, { #[precompile::public( "transferAssets(\ diff --git a/primitives/xcm/src/location_converter.rs b/primitives/xcm/src/location_converter.rs index 0418ae81..5d1a0c3d 100644 --- a/primitives/xcm/src/location_converter.rs +++ b/primitives/xcm/src/location_converter.rs @@ -172,7 +172,7 @@ where { fn convert(account: AccountId) -> Option { let h160_account = account.into(); - return Some(Location::new( + Some(Location::new( 0, [ PalletInstance(PALLET_INDEX), @@ -181,6 +181,6 @@ where network: None, }, ], - )); + )) } } From c048e7c27432774430c98e79d20ef542813265b5 Mon Sep 17 00:00:00 2001 From: Agusrodri Date: Mon, 15 Apr 2024 17:12:16 -0700 Subject: [PATCH 15/22] add new functions --- precompiles/pallet-xcm/XcmInterface.sol | 42 ++++- precompiles/pallet-xcm/src/lib.rs | 165 +++++++++++++++++- precompiles/pallet-xcm/src/mock.rs | 2 +- precompiles/pallet-xcm/src/tests.rs | 13 +- primitives/xcm/src/generators.rs | 40 +++++ primitives/xcm/src/lib.rs | 3 +- ...ation_converter.rs => location_matcher.rs} | 0 7 files changed, 249 insertions(+), 16 deletions(-) create mode 100644 primitives/xcm/src/generators.rs rename primitives/xcm/src/{location_converter.rs => location_matcher.rs} (100%) diff --git a/precompiles/pallet-xcm/XcmInterface.sol b/precompiles/pallet-xcm/XcmInterface.sol index 7bc632db..7cd75ff6 100644 --- a/precompiles/pallet-xcm/XcmInterface.sol +++ b/precompiles/pallet-xcm/XcmInterface.sol @@ -18,23 +18,57 @@ interface XCM { } // A way to represent fungible assets in XCM - struct Asset { + struct AssetLocationInfo { Location location; uint256 amount; } + struct AssetAddressInfo { + address asset; + uint256 amount; + } + /// @dev Function to send assets via XCM using transfer_assets() pallet-xcm extrinsic. - /// @custom:selector 650ef8c7 + /// @custom:selector 59df8416 /// @param dest The destination chain. /// @param beneficiary The actual account that will receive the tokens in dest. /// @param assets The combination (array) of assets to send. /// @param feeAssetItem The index of the asset that will be used to pay for fees. /// @param weight The weight to be used for the whole XCM operation. /// (uint64::MAX in refTime means Unlimited weight) - function transferAssets( + function transferAssetsLocation( Location memory dest, Location memory beneficiary, - Asset[] memory assets, + AssetLocationInfo[] memory assets, + uint32 feeAssetItem, + Weight memory weight + ) external; + + /// TODO add docs + /// @custom:selector b489262e + function transferAssetsToPara20( + uint32 paraId, + address beneficiary, + AssetAddressInfo[] memory assets, + uint32 feeAssetItem, + Weight memory weight + ) external; + + /// TODO add docs + /// @custom:selector 4461e6f5 + function transferAssetsToPara32( + uint32 paraId, + bytes32 beneficiary, + AssetAddressInfo[] memory assets, + uint32 feeAssetItem, + Weight memory weight + ) external; + + /// TODO add docs + /// @custom:selector d7c89659 + function transferAssetsToRelay( + bytes32 beneficiary, + AssetAddressInfo[] memory assets, uint32 feeAssetItem, Weight memory weight ) external; diff --git a/precompiles/pallet-xcm/src/lib.rs b/precompiles/pallet-xcm/src/lib.rs index 0c9f1f13..a8131e5d 100644 --- a/precompiles/pallet-xcm/src/lib.rs +++ b/precompiles/pallet-xcm/src/lib.rs @@ -16,7 +16,7 @@ #![cfg_attr(not(feature = "std"), no_std)] -use fp_evm::PrecompileHandle; +use fp_evm::{PrecompileFailure, PrecompileHandle}; use frame_support::{ dispatch::{GetDispatchInfo, PostDispatchInfo}, traits::ConstU32, @@ -24,7 +24,7 @@ use frame_support::{ use pallet_evm::AddressMapping; use precompile_utils::prelude::*; -use sp_core::U256; +use sp_core::{H256, U256}; use sp_runtime::traits::Dispatchable; use sp_std::marker::PhantomData; use sp_weights::Weight; @@ -33,7 +33,13 @@ use xcm::{ prelude::WeightLimit::*, VersionedAssets, VersionedLocation, }; -use xcm_primitives::location_converter::AccountIdToLocationMatcher; +use xcm_primitives::{ + generators::{ + XcmLocalBeneficiary20Generator, XcmLocalBeneficiary32Generator, + XcmSiblingDestinationGenerator, + }, + location_matcher::AccountIdToLocationMatcher, +}; #[cfg(test)] mod mock; @@ -57,14 +63,14 @@ where LocationMatcher: AccountIdToLocationMatcher<::AccountId>, { #[precompile::public( - "transferAssets(\ + "transferAssetsLocation(\ (uint8,bytes[]),\ (uint8,bytes[]),\ ((uint8,bytes[]),uint256)[],\ uint32,\ (uint64,uint64))" )] - fn transfer_assets( + fn transfer_assets_location( handle: &mut impl PrecompileHandle, dest: Location, beneficiary: Location, @@ -104,4 +110,153 @@ where RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; Ok(()) } + + #[precompile::public( + "transferAssetsToPara20(\ + uint32,\ + address,\ + (address,uint256)[],\ + uint32,\ + (uint64,uint64))" + )] + fn transfer_assets_to_para_20( + handle: &mut impl PrecompileHandle, + para_id: u32, + beneficiary: Address, + assets: BoundedVec<(Address, Convert), GetArrayLimit>, + fee_asset_item: u32, + weight: Weight, + ) -> EvmResult { + // TODO: account for a possible storage read inside LocationMatcher::convert() + + let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); + let assets: Vec<_> = assets.into(); + + let assets_to_send: Vec = Self::check_and_prepare_assets(assets)?; + + let weight_limit = match weight.ref_time() { + u64::MAX => Unlimited, + _ => Limited(weight), + }; + + let dest = XcmSiblingDestinationGenerator::generate(para_id); + let beneficiary = XcmLocalBeneficiary20Generator::generate(beneficiary.0 .0); + + let call = pallet_xcm::Call::::transfer_assets { + dest: Box::new(VersionedLocation::V4(dest)), + beneficiary: Box::new(VersionedLocation::V4(beneficiary)), + assets: Box::new(VersionedAssets::V4(assets_to_send.into())), + fee_asset_item, + weight_limit, + }; + + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + Ok(()) + } + + #[precompile::public( + "transferAssetsToPara32(\ + uint32,\ + bytes32,\ + (address,uint256)[],\ + uint32,\ + (uint64,uint64))" + )] + fn transfer_assets_to_para_32( + handle: &mut impl PrecompileHandle, + para_id: u32, + beneficiary: H256, + assets: BoundedVec<(Address, Convert), GetArrayLimit>, + fee_asset_item: u32, + weight: Weight, + ) -> EvmResult { + // TODO: account for a possible storage read inside LocationMatcher::convert() + + let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); + let assets: Vec<_> = assets.into(); + + let assets_to_send: Vec = Self::check_and_prepare_assets(assets)?; + + let weight_limit = match weight.ref_time() { + u64::MAX => Unlimited, + _ => Limited(weight), + }; + + let dest = XcmSiblingDestinationGenerator::generate(para_id); + let beneficiary = XcmLocalBeneficiary32Generator::generate(beneficiary.0); + + let call = pallet_xcm::Call::::transfer_assets { + dest: Box::new(VersionedLocation::V4(dest)), + beneficiary: Box::new(VersionedLocation::V4(beneficiary)), + assets: Box::new(VersionedAssets::V4(assets_to_send.into())), + fee_asset_item, + weight_limit, + }; + + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + Ok(()) + } + + #[precompile::public( + "transferAssetsToRelay(\ + bytes32,\ + (address,uint256)[],\ + uint32,\ + (uint64,uint64))" + )] + fn transfer_assets_to_relay( + handle: &mut impl PrecompileHandle, + beneficiary: H256, + assets: BoundedVec<(Address, Convert), GetArrayLimit>, + fee_asset_item: u32, + weight: Weight, + ) -> EvmResult { + // TODO: account for a possible storage read inside LocationMatcher::convert() + + let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); + let assets: Vec<_> = assets.into(); + + let assets_to_send: Vec = Self::check_and_prepare_assets(assets)?; + + let weight_limit = match weight.ref_time() { + u64::MAX => Unlimited, + _ => Limited(weight), + }; + + let dest = Location::parent(); + let beneficiary = XcmLocalBeneficiary32Generator::generate(beneficiary.0); + + let call = pallet_xcm::Call::::transfer_assets { + dest: Box::new(VersionedLocation::V4(dest)), + beneficiary: Box::new(VersionedLocation::V4(beneficiary)), + assets: Box::new(VersionedAssets::V4(assets_to_send.into())), + fee_asset_item, + weight_limit, + }; + + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + Ok(()) + } + + // Helper function to convert and prepare each asset into a proper Location. + fn check_and_prepare_assets( + assets: Vec<(Address, Convert)>, + ) -> Result, PrecompileFailure> { + let mut assets_to_send: Vec = vec![]; + for asset in assets { + let asset_account = Runtime::AddressMapping::into_account_id(asset.0 .0); + let asset_location = LocationMatcher::convert(asset_account); + if asset_location == None { + return Err(revert("Asset not found")); + } + assets_to_send.push(Asset { + id: AssetId(asset_location.unwrap_or_default()), + fun: Fungibility::Fungible(asset.1.converted()), + }) + } + Ok(assets_to_send) + } } diff --git a/precompiles/pallet-xcm/src/mock.rs b/precompiles/pallet-xcm/src/mock.rs index d522a465..c0ae470e 100644 --- a/precompiles/pallet-xcm/src/mock.rs +++ b/precompiles/pallet-xcm/src/mock.rs @@ -39,7 +39,7 @@ use xcm_executor::{ traits::{ConvertLocation, TransactAsset, WeightTrader}, AssetsInHolding, }; -pub use xcm_primitives::location_converter::{ +pub use xcm_primitives::location_matcher::{ AssetIdInfoGetter, Erc20PalletMatcher, MatchThroughEquivalence, SingleAddressMatcher, }; use Junctions::Here; diff --git a/precompiles/pallet-xcm/src/tests.rs b/precompiles/pallet-xcm/src/tests.rs index 0d77b9f0..495c1adb 100644 --- a/precompiles/pallet-xcm/src/tests.rs +++ b/precompiles/pallet-xcm/src/tests.rs @@ -30,7 +30,10 @@ fn test_solidity_interface_has_all_function_selectors_documented_and_implemented #[test] fn selectors() { - assert!(PCall::transfer_assets_selectors().contains(&0x650ef8c7)); + assert!(PCall::transfer_assets_location_selectors().contains(&0x59df8416)); + assert!(PCall::transfer_assets_to_para_20_selectors().contains(&0xb489262e)); + assert!(PCall::transfer_assets_to_para_32_selectors().contains(&0x4461e6f5)); + assert!(PCall::transfer_assets_to_relay_selectors().contains(&0xd7c89659)); } #[test] @@ -39,7 +42,7 @@ fn modifiers() { let mut tester = PrecompilesModifierTester::new(PrecompilesValue::get(), Alice, Precompile1); - tester.test_default_modifier(PCall::transfer_assets_selectors()); + tester.test_default_modifier(PCall::transfer_assets_location_selectors()); }); } @@ -86,7 +89,7 @@ fn test_transfer_assets_works() { .prepare_test( Alice, Precompile1, - PCall::transfer_assets { + PCall::transfer_assets_location { dest, beneficiary, assets: vec![ @@ -130,7 +133,7 @@ fn test_transfer_assets_success_when_paying_fees_with_foreign_asset() { .prepare_test( Alice, Precompile1, - PCall::transfer_assets { + PCall::transfer_assets_location { dest, beneficiary, assets: vec![ @@ -176,7 +179,7 @@ fn test_transfer_assets_fails_fees_unknown_reserve() { .prepare_test( Alice, Precompile1, - PCall::transfer_assets { + PCall::transfer_assets_location { dest, beneficiary, assets: vec![ diff --git a/primitives/xcm/src/generators.rs b/primitives/xcm/src/generators.rs new file mode 100644 index 00000000..9ab46f53 --- /dev/null +++ b/primitives/xcm/src/generators.rs @@ -0,0 +1,40 @@ +// Copyright Moonsong Labs +// This file is part of Moonkit. + +// Moonkit is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonkit is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonkit. If not, see . + +use xcm::latest::{Junction::*, Location}; + +// TODO: Does it make sense to have this generators +// instead of retrieving the proper location on each case? +pub struct XcmSiblingDestinationGenerator; +impl XcmSiblingDestinationGenerator { + pub fn generate(para_id: u32) -> Location { + Location::new(1, Parachain(para_id)) + } +} + +pub struct XcmLocalBeneficiary20Generator; +impl XcmLocalBeneficiary20Generator { + pub fn generate(key: [u8; 20]) -> Location { + Location::new(0, AccountKey20 { network: None, key }) + } +} + +pub struct XcmLocalBeneficiary32Generator; +impl XcmLocalBeneficiary32Generator { + pub fn generate(id: [u8; 32]) -> Location { + Location::new(0, AccountId32 { network: None, id }) + } +} diff --git a/primitives/xcm/src/lib.rs b/primitives/xcm/src/lib.rs index 16cbeb89..0429d4ae 100644 --- a/primitives/xcm/src/lib.rs +++ b/primitives/xcm/src/lib.rs @@ -20,7 +20,8 @@ use sp_runtime::DispatchResult; -pub mod location_converter; +pub mod generators; +pub mod location_matcher; /// Pause and resume execution of XCM pub trait PauseXcmExecution { diff --git a/primitives/xcm/src/location_converter.rs b/primitives/xcm/src/location_matcher.rs similarity index 100% rename from primitives/xcm/src/location_converter.rs rename to primitives/xcm/src/location_matcher.rs From f050e1f6f5aeb8531e70df980ed6abe335eae29e Mon Sep 17 00:00:00 2001 From: Agusrodri Date: Tue, 16 Apr 2024 17:18:57 -0700 Subject: [PATCH 16/22] refactor ForeignAssetMatcher --- Cargo.lock | 1 + precompiles/assets-erc20/Cargo.toml | 4 ++ precompiles/assets-erc20/src/lib.rs | 11 +-- precompiles/assets-erc20/src/mock.rs | 1 + precompiles/pallet-xcm/src/mock.rs | 45 +++++++++--- precompiles/pallet-xcm/src/tests.rs | 2 +- primitives/xcm/src/lib.rs | 9 +++ primitives/xcm/src/location_matcher.rs | 96 ++++---------------------- 8 files changed, 67 insertions(+), 102 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 77d8aad8..22c38280 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7085,6 +7085,7 @@ dependencies = [ "sp-io", "sp-runtime", "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=release-polkadot-v1.7.0)", + "xcm-primitives", ] [[package]] diff --git a/precompiles/assets-erc20/Cargo.toml b/precompiles/assets-erc20/Cargo.toml index ccc668ea..50d36982 100644 --- a/precompiles/assets-erc20/Cargo.toml +++ b/precompiles/assets-erc20/Cargo.toml @@ -27,6 +27,9 @@ sp-std = { workspace = true } fp-evm = { workspace = true } pallet-evm = { workspace = true, features = [ "forbid-evm-reentrancy" ] } +# Moonkit +xcm-primitives = { workspace = true } + [dev-dependencies] hex-literal = { workspace = true } libsecp256k1 = { workspace = true } @@ -57,4 +60,5 @@ std = [ "sp-io/std", "sp-runtime/std", "sp-std/std", + "xcm-primitives/std", ] diff --git a/precompiles/assets-erc20/src/lib.rs b/precompiles/assets-erc20/src/lib.rs index cc8c54bd..77c2cf97 100644 --- a/precompiles/assets-erc20/src/lib.rs +++ b/precompiles/assets-erc20/src/lib.rs @@ -38,6 +38,7 @@ use sp_std::{ convert::{TryFrom, TryInto}, marker::PhantomData, }; +use xcm_primitives::AccountIdAssetIdConversion; mod eip2612; use eip2612::Eip2612; @@ -59,16 +60,6 @@ pub type BalanceOf = = >::AssetId; -/// This trait ensure we can convert AccountIds to AssetIds -/// We will require Runtime to have this trait implemented -pub trait AccountIdAssetIdConversion { - // Get assetId and prefix from account - fn account_to_asset_id(account: Account) -> Option<(Vec, AssetId)>; - - // Get AccountId from AssetId and prefix - fn asset_id_to_account(prefix: &[u8], asset_id: AssetId) -> Account; -} - /// The following distribution has been decided for the precompiles /// 0-1023: Ethereum Mainnet Precompiles /// 1024-2047 Precompiles that are not in Ethereum Mainnet but are neither Moonbeam specific diff --git a/precompiles/assets-erc20/src/mock.rs b/precompiles/assets-erc20/src/mock.rs index e645bdf9..93920fdc 100644 --- a/precompiles/assets-erc20/src/mock.rs +++ b/precompiles/assets-erc20/src/mock.rs @@ -36,6 +36,7 @@ use sp_runtime::{ traits::{BlakeTwo256, ConstU32, IdentityLookup}, BuildStorage, }; +use xcm_primitives::AccountIdAssetIdConversion; pub type AccountId = MockAccount; pub type AssetId = u128; diff --git a/precompiles/pallet-xcm/src/mock.rs b/precompiles/pallet-xcm/src/mock.rs index c0ae470e..203318a4 100644 --- a/precompiles/pallet-xcm/src/mock.rs +++ b/precompiles/pallet-xcm/src/mock.rs @@ -39,9 +39,9 @@ use xcm_executor::{ traits::{ConvertLocation, TransactAsset, WeightTrader}, AssetsInHolding, }; -pub use xcm_primitives::location_matcher::{ - AssetIdInfoGetter, Erc20PalletMatcher, MatchThroughEquivalence, SingleAddressMatcher, -}; +pub use xcm_primitives::{AccountIdAssetIdConversion, location_matcher::{ + Erc20PalletMatcher, ForeignAssetMatcher, SingleAddressMatcher, +}}; use Junctions::Here; pub type AccountId = MockAccount; @@ -128,6 +128,35 @@ parameter_types! { pub const MetadataDepositPerByte: u64 = 0; } +pub const FOREIGN_ASSET_ADDRESS_PREFIX: &[u8] = &[255u8; 18]; + +// Instruct how to go from an H160 to an AssetID +// We just take the lowest 2 bytes +impl AccountIdAssetIdConversion for Runtime { + /// The way to convert an account to assetId is by ensuring that the prefix is [0xFF, 18] + /// and by taking the lowest 2 bytes as the assetId + fn account_to_asset_id(account: AccountId) -> Option<(Vec, AssetId)> { + let h160_account: H160 = account.into(); + let mut data = [0u8; 2]; + let (prefix_part, id_part) = h160_account.as_fixed_bytes().split_at(18); + if prefix_part == FOREIGN_ASSET_ADDRESS_PREFIX { + data.copy_from_slice(id_part); + let asset_id: AssetId = u16::from_be_bytes(data); + Some((prefix_part.to_vec(), asset_id)) + } else { + None + } + } + + // The opposite conversion + fn asset_id_to_account(prefix: &[u8], asset_id: AssetId) -> AccountId { + let mut data = [0u8; 20]; + data[0..18].copy_from_slice(prefix); + data[18..20].copy_from_slice(&asset_id.to_be_bytes()); + AccountId::from(data) + } +} + pub type AssetId = u16; impl pallet_assets::Config for Runtime { @@ -173,13 +202,13 @@ pub type AccountIdAlias = ::AccountId; pub type SingleAddressMatch = SingleAddressMatcher; -pub type EquivalenceMatch = - MatchThroughEquivalence; +pub type ForeignAssetMatch = + ForeignAssetMatcher; pub type Erc20Match = Erc20PalletMatcher; pub type PCall = - PalletXcmPrecompileCall; + PalletXcmPrecompileCall; mock_account!(ParentAccount, |_| MockAccount::from_u64(4)); @@ -195,7 +224,7 @@ const BLOCK_STORAGE_LIMIT: u64 = 40 * 1024; parameter_types! { pub BlockGasLimit: U256 = U256::from(u64::MAX); - pub PrecompilesValue: Precompiles = Precompiles::new(); + pub PrecompilesValue: Precompiles = Precompiles::new(); pub const WeightPerGas: Weight = Weight::from_parts(1, 0); pub GasLimitPovSizeRatio: u64 = { let block_gas_limit = BlockGasLimit::get().min(u64::MAX.into()).low_u64(); @@ -230,7 +259,7 @@ impl pallet_evm::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Runner = pallet_evm::runner::stack::Runner; type PrecompilesValue = PrecompilesValue; - type PrecompilesType = Precompiles; + type PrecompilesType = Precompiles; type ChainId = (); type OnChargeTransaction = (); type BlockGasLimit = BlockGasLimit; diff --git a/precompiles/pallet-xcm/src/tests.rs b/precompiles/pallet-xcm/src/tests.rs index 495c1adb..32fe82bc 100644 --- a/precompiles/pallet-xcm/src/tests.rs +++ b/precompiles/pallet-xcm/src/tests.rs @@ -19,7 +19,7 @@ use precompile_utils::testing::*; use sp_weights::Weight; use xcm::latest::Junction::*; -fn precompiles() -> Precompiles { +fn precompiles() -> Precompiles { PrecompilesValue::get() } diff --git a/primitives/xcm/src/lib.rs b/primitives/xcm/src/lib.rs index 0429d4ae..64bcfc08 100644 --- a/primitives/xcm/src/lib.rs +++ b/primitives/xcm/src/lib.rs @@ -36,3 +36,12 @@ impl PauseXcmExecution for () { Ok(()) } } + +/// This trait ensure we can convert AccountIds to AssetIds. +pub trait AccountIdAssetIdConversion { + // Get assetId and prefix from account + fn account_to_asset_id(account: Account) -> Option<(Vec, AssetId)>; + + // Get AccountId from AssetId and prefix + fn asset_id_to_account(prefix: &[u8], asset_id: AssetId) -> Account; +} diff --git a/primitives/xcm/src/location_matcher.rs b/primitives/xcm/src/location_matcher.rs index 5d1a0c3d..48d0f4db 100644 --- a/primitives/xcm/src/location_matcher.rs +++ b/primitives/xcm/src/location_matcher.rs @@ -14,49 +14,13 @@ // You should have received a copy of the GNU General Public License // along with Moonkit. If not, see . +use crate::AccountIdAssetIdConversion; use frame_support::{traits::PalletInfoAccess, Parameter}; use sp_core::H160; use sp_runtime::traits::MaybeEquivalence; -use sp_std::{fmt::Debug, marker::PhantomData, mem::size_of}; +use sp_std::{fmt::Debug, marker::PhantomData}; use xcm::latest::{Junction::*, Location}; -/// Information to retrieve for a specific AssetId type. -pub struct AssetIdInfo<'a> { - pub foreign_asset_prefix: &'a [u8], - pub size_of: usize, -} - -// Define a trait to abstract over different types of AssetId. -// We retrieve an AssetIdInfo type containing all the information we need -// to handle the generic types over AssetId. -pub trait GetAssetIdInfo { - fn get_asset_id_info() -> AssetIdInfo<'static>; -} - -/// A getter that contains the info for each type -/// we admit as AssetId. -pub struct AssetIdInfoGetter; - -// Implement GetAssetId trait for u128. -impl GetAssetIdInfo for AssetIdInfoGetter { - fn get_asset_id_info() -> AssetIdInfo<'static> { - AssetIdInfo { - foreign_asset_prefix: &[255u8; 4], - size_of: size_of::(), - } - } -} - -// Implement GetAssetId trait for u16. -impl GetAssetIdInfo for AssetIdInfoGetter { - fn get_asset_id_info() -> AssetIdInfo<'static> { - AssetIdInfo { - foreign_asset_prefix: &[255u8; 18], - size_of: size_of::(), - } - } -} - /// A converter from AccountId to a XCM Location. pub trait AccountIdToLocationMatcher { fn convert(account: AccountId) -> Option; @@ -99,63 +63,29 @@ where } /// Matcher to compare a received account against some possible foreign asset address. -pub struct MatchThroughEquivalence( +pub struct ForeignAssetMatcher< + AccountId, + AssetId, + AccountIdAssetIdConverter, + AssetIdToLocationManager, +>( PhantomData<( AccountId, AssetId, - AssetIdInfoGetter, + AccountIdAssetIdConverter, AssetIdToLocationManager, )>, ); -impl - MatchThroughEquivalence -where - AccountId: Parameter + Into, - AssetId: From + TryFrom + TryFrom, -{ - // Helper function that retrieves the asset_id of a foreign asset account. - pub fn account_to_asset_id( - account: AccountId, - asset_id_info: AssetIdInfo, - ) -> Option<(Vec, AssetId)> { - let h160_account: H160 = account.into(); - let (prefix_part, id_part) = h160_account - .as_fixed_bytes() - .split_at(asset_id_info.size_of + 2); - - if prefix_part == asset_id_info.foreign_asset_prefix { - let asset_id: AssetId = match asset_id_info.size_of { - 2 => { - let mut data = [0u8; 2]; - data.copy_from_slice(id_part); - u16::from_be_bytes(data).try_into().unwrap_or(0u8.into()) - } - 16 => { - let mut data = [0u8; 16]; - data.copy_from_slice(id_part); - u128::from_be_bytes(data).try_into().unwrap_or(0u8.into()) - } - _ => return None, - }; - return Some((prefix_part.to_vec(), asset_id)); - } - None - } -} - -impl +impl AccountIdToLocationMatcher - for MatchThroughEquivalence + for ForeignAssetMatcher where - AccountId: Parameter + Into, - AssetId: From + TryFrom + TryFrom, - AssetIdInfoGetter: GetAssetIdInfo, + AccountIdAssetIdConverter: AccountIdAssetIdConversion, AssetIdToLocationManager: MaybeEquivalence, { fn convert(account: AccountId) -> Option { - let asset_id_info = AssetIdInfoGetter::get_asset_id_info(); - if let Some((_prefix, asset_id)) = Self::account_to_asset_id(account, asset_id_info) { + if let Some((_prefix, asset_id)) = AccountIdAssetIdConverter::account_to_asset_id(account) { return AssetIdToLocationManager::convert_back(&asset_id); } None From 205e402a10a2cfabf87e3b7aba95b7d6bd0c5784 Mon Sep 17 00:00:00 2001 From: Agusrodri Date: Tue, 16 Apr 2024 17:26:07 -0700 Subject: [PATCH 17/22] rust fmt --- precompiles/pallet-xcm/src/mock.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/precompiles/pallet-xcm/src/mock.rs b/precompiles/pallet-xcm/src/mock.rs index 203318a4..97d1e907 100644 --- a/precompiles/pallet-xcm/src/mock.rs +++ b/precompiles/pallet-xcm/src/mock.rs @@ -39,9 +39,10 @@ use xcm_executor::{ traits::{ConvertLocation, TransactAsset, WeightTrader}, AssetsInHolding, }; -pub use xcm_primitives::{AccountIdAssetIdConversion, location_matcher::{ - Erc20PalletMatcher, ForeignAssetMatcher, SingleAddressMatcher, -}}; +pub use xcm_primitives::{ + location_matcher::{Erc20PalletMatcher, ForeignAssetMatcher, SingleAddressMatcher}, + AccountIdAssetIdConversion, +}; use Junctions::Here; pub type AccountId = MockAccount; From 8ca301162e880e50d1f0008064f1e3cfa9cfbce5 Mon Sep 17 00:00:00 2001 From: Agusrodri Date: Mon, 22 Apr 2024 13:27:29 -0700 Subject: [PATCH 18/22] add more tests and handle db reads costs --- precompiles/pallet-xcm/src/lib.rs | 23 ++- precompiles/pallet-xcm/src/mock.rs | 70 ++++++++- precompiles/pallet-xcm/src/tests.rs | 213 +++++++++++++++++++++++++++- 3 files changed, 297 insertions(+), 9 deletions(-) diff --git a/precompiles/pallet-xcm/src/lib.rs b/precompiles/pallet-xcm/src/lib.rs index a8131e5d..3a745066 100644 --- a/precompiles/pallet-xcm/src/lib.rs +++ b/precompiles/pallet-xcm/src/lib.rs @@ -24,7 +24,7 @@ use frame_support::{ use pallet_evm::AddressMapping; use precompile_utils::prelude::*; -use sp_core::{H256, U256}; +use sp_core::{MaxEncodedLen, H256, U256}; use sp_runtime::traits::Dispatchable; use sp_std::marker::PhantomData; use sp_weights::Weight; @@ -127,7 +127,12 @@ where fee_asset_item: u32, weight: Weight, ) -> EvmResult { - // TODO: account for a possible storage read inside LocationMatcher::convert() + // Account for a possible storage read inside LocationMatcher::convert(). + // + // Storage items: AssetIdToForeignAsset (ForeignAssetCreator pallet) or AssetIdType (AssetManager pallet). + // + // Blake2_128(16) + AssetId(16) + Location + handle.record_db_read::(32 + Location::max_encoded_len())?; let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); let assets: Vec<_> = assets.into(); @@ -171,7 +176,12 @@ where fee_asset_item: u32, weight: Weight, ) -> EvmResult { - // TODO: account for a possible storage read inside LocationMatcher::convert() + // Account for a possible storage read inside LocationMatcher::convert(). + // + // Storage items: AssetIdToForeignAsset (ForeignAssetCreator pallet) or AssetIdType (AssetManager pallet). + // + // Blake2_128(16) + AssetId(16) + Location + handle.record_db_read::(32 + Location::max_encoded_len())?; let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); let assets: Vec<_> = assets.into(); @@ -213,7 +223,12 @@ where fee_asset_item: u32, weight: Weight, ) -> EvmResult { - // TODO: account for a possible storage read inside LocationMatcher::convert() + // Account for a possible storage read inside LocationMatcher::convert(). + // + // Storage items: AssetIdToForeignAsset (ForeignAssetCreator pallet) or AssetIdType (AssetManager pallet). + // + // Blake2_128(16) + AssetId(16) + Location + handle.record_db_read::(32 + Location::max_encoded_len())?; let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); let assets: Vec<_> = assets.into(); diff --git a/precompiles/pallet-xcm/src/mock.rs b/precompiles/pallet-xcm/src/mock.rs index 97d1e907..99a84244 100644 --- a/precompiles/pallet-xcm/src/mock.rs +++ b/precompiles/pallet-xcm/src/mock.rs @@ -26,7 +26,7 @@ use pallet_evm::{EnsureAddressNever, EnsureAddressRoot, GasWeightMapping}; use precompile_utils::{ mock_account, precompile_set::*, - testing::{AddressInPrefixedSet, MockAccount}, + testing::{AddressInPrefixedSet, Alice, MockAccount}, }; use sp_core::{ConstU32, H160, H256, U256}; use sp_runtime::traits::{BlakeTwo256, IdentityLookup, TryConvert}; @@ -476,8 +476,16 @@ parameter_types! { )), }; + pub RelayLocation: Location = Location::parent(); + + pub RelayAsset: Asset = Asset { + fun: Fungible(10000000), + id: AssetId(Location::parent()), + }; + pub LocalAsset: (AssetFilter, Location) = (All.into(), Location::here()); pub TrustedForeignAsset: (AssetFilter, Location) = (ForeignAsset::get().into(), ForeignReserveLocation::get()); + pub RelayForeignAsset: (AssetFilter, Location) = (RelayAsset::get().into(), RelayLocation::get()); } pub struct XcmConfig; @@ -486,7 +494,11 @@ impl xcm_executor::Config for XcmConfig { type XcmSender = TestSendXcm; type AssetTransactor = DummyAssetTransactor; type OriginConverter = XcmOriginToTransactDispatchOrigin; - type IsReserve = (Case, Case); + type IsReserve = ( + Case, + Case, + Case, + ); type IsTeleporter = (); type UniversalLocation = UniversalLocation; type Barrier = Barrier; @@ -510,14 +522,36 @@ impl xcm_executor::Config for XcmConfig { type TransactionalProcessor = (); } +pub fn root_origin() -> ::RuntimeOrigin { + ::RuntimeOrigin::root() +} + +pub fn origin_of(account_id: AccountId) -> ::RuntimeOrigin { + ::RuntimeOrigin::signed(account_id) +} + +#[derive(Clone)] +pub struct XcmAssetDetails { + pub location: Location, + pub admin: ::AccountId, + pub asset_id: ::AssetId, + pub is_sufficient: bool, + pub min_balance: u128, + pub balance_to_mint: u128, +} + pub(crate) struct ExtBuilder { // endowed accounts with balances balances: Vec<(AccountId, Balance)>, + xcm_assets: Vec, } impl Default for ExtBuilder { fn default() -> ExtBuilder { - ExtBuilder { balances: vec![] } + ExtBuilder { + balances: vec![], + xcm_assets: vec![], + } } } @@ -526,6 +560,11 @@ impl ExtBuilder { self.balances = balances; self } + pub(crate) fn with_xcm_assets(mut self, xcm_assets: Vec) -> Self { + self.xcm_assets = xcm_assets; + self + } + pub(crate) fn build(self) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default() .build_storage() @@ -537,8 +576,31 @@ impl ExtBuilder { .assimilate_storage(&mut t) .expect("Pallet balances storage can be assimilated"); + let xcm_assets = self.xcm_assets.clone(); + let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); + ext.execute_with(|| { + for xcm_asset in xcm_assets { + ForeignAssetCreator::create_foreign_asset( + root_origin(), + xcm_asset.location, + xcm_asset.asset_id, + xcm_asset.admin, + xcm_asset.is_sufficient, + xcm_asset.min_balance, + ) + .unwrap(); + + Assets::mint( + origin_of(Alice.into()), + xcm_asset.asset_id.into(), + xcm_asset.admin, + xcm_asset.balance_to_mint, + ) + .unwrap(); + } + System::set_block_number(1); + }); ext } } diff --git a/precompiles/pallet-xcm/src/tests.rs b/precompiles/pallet-xcm/src/tests.rs index 32fe82bc..00fbcc36 100644 --- a/precompiles/pallet-xcm/src/tests.rs +++ b/precompiles/pallet-xcm/src/tests.rs @@ -14,8 +14,11 @@ // You should have received a copy of the GNU General Public License // along with Moonkit. If not, see . +use core::str::FromStr; + use crate::{mock::*, Location}; -use precompile_utils::testing::*; +use precompile_utils::{prelude::*, testing::*}; +use sp_core::{H160, H256}; use sp_weights::Weight; use xcm::latest::Junction::*; @@ -198,3 +201,211 @@ fn test_transfer_assets_fails_fees_unknown_reserve() { .execute_reverts(|output| output.ends_with(b"InvalidAssetUnknownReserve\") })")); }); } + +#[test] +fn test_transfer_assets_to_para_20_native_asset() { + ExtBuilder::default() + .with_balances(vec![(Alice.into(), 1000)]) + .build() + .execute_with(|| { + // We send the native currency of the origin chain. + let pallet_balances_address = H160::from_low_u64_be(2050); + + precompiles() + .prepare_test( + Alice, + Precompile1, + PCall::transfer_assets_to_para_20 { + para_id: 2u32, + beneficiary: Address(Bob.into()), + assets: vec![(Address(pallet_balances_address), 500.into())].into(), + fee_asset_item: 0u32, + weight: Weight::from_parts(u64::MAX, 80000), + }, + ) + .expect_cost(100004001) + .expect_no_logs() + .execute_returns(()); + }); +} + +#[test] +fn test_transfer_assets_to_para_32_native_asset() { + ExtBuilder::default() + .with_balances(vec![(Alice.into(), 1000)]) + .build() + .execute_with(|| { + // We send the native currency of the origin chain. + let pallet_balances_address = H160::from_low_u64_be(2050); + + precompiles() + .prepare_test( + Alice, + Precompile1, + PCall::transfer_assets_to_para_32 { + para_id: 2u32, + beneficiary: H256([1u8; 32]), + assets: vec![(Address(pallet_balances_address), 500.into())].into(), + fee_asset_item: 0u32, + weight: Weight::from_parts(u64::MAX, 80000), + }, + ) + .expect_cost(100004001) + .expect_no_logs() + .execute_returns(()); + }); +} + +#[test] +fn test_transfer_assets_to_relay_native_asset() { + ExtBuilder::default() + .with_balances(vec![(Alice.into(), 1000)]) + .build() + .execute_with(|| { + // We send the native currency of the origin chain. + let pallet_balances_address = H160::from_low_u64_be(2050); + + precompiles() + .prepare_test( + Alice, + Precompile1, + PCall::transfer_assets_to_relay { + beneficiary: H256([1u8; 32]), + assets: vec![(Address(pallet_balances_address), 500.into())].into(), + fee_asset_item: 0u32, + weight: Weight::from_parts(u64::MAX, 80000), + }, + ) + .expect_cost(100004001) + .expect_no_logs() + .execute_returns(()); + }); +} + +#[test] +fn test_transfer_assets_to_para_20_foreign_asset() { + ExtBuilder::default() + .with_balances(vec![(Alice.into(), 1000)]) + .with_xcm_assets(vec![XcmAssetDetails { + location: Location::new(1, [Parachain(2), PalletInstance(3)]), + admin: Alice.into(), + asset_id: 5u16, + is_sufficient: true, + balance_to_mint: 10000u128, + min_balance: 1u128, + }]) + .build() + .execute_with(|| { + // Foreign asset with prefix [255; 18] and assetId of 5u16. + let asset_address = + H160::from_str("0xfFfFFFffFffFFFFffFFfFfffFfFFFFFfffFF0005").unwrap(); + + // We send the native currency of the origin chain and pay fees with it. + let pallet_balances_address = H160::from_low_u64_be(2050); + + precompiles() + .prepare_test( + Alice, + Precompile1, + PCall::transfer_assets_to_para_20 { + para_id: 2u32, + beneficiary: Address(Bob.into()), + assets: vec![ + (Address(pallet_balances_address), 500.into()), + (Address(asset_address), 500.into()), + ] + .into(), + fee_asset_item: 0u32, + weight: Weight::from_parts(u64::MAX, 80000), + }, + ) + .expect_cost(100004001) + .expect_no_logs() + .execute_returns(()); + }); +} + +#[test] +fn test_transfer_assets_to_para_32_foreign_asset() { + ExtBuilder::default() + .with_balances(vec![(Alice.into(), 1000)]) + .with_xcm_assets(vec![XcmAssetDetails { + location: Location::new(1, [Parachain(2), PalletInstance(3)]), + admin: Alice.into(), + asset_id: 5u16, + is_sufficient: true, + balance_to_mint: 10000u128, + min_balance: 1u128, + }]) + .build() + .execute_with(|| { + // Foreign asset with prefix [255; 18] and assetId of 5u16. + let asset_address = + H160::from_str("0xfFfFFFffFffFFFFffFFfFfffFfFFFFFfffFF0005").unwrap(); + + // We send the native currency of the origin chain and pay fees with it. + let pallet_balances_address = H160::from_low_u64_be(2050); + + precompiles() + .prepare_test( + Alice, + Precompile1, + PCall::transfer_assets_to_para_32 { + para_id: 2u32, + beneficiary: H256([0u8; 32]), + assets: vec![ + (Address(pallet_balances_address), 500.into()), + (Address(asset_address), 500.into()), + ] + .into(), + fee_asset_item: 0u32, + weight: Weight::from_parts(u64::MAX, 80000), + }, + ) + .expect_cost(100004001) + .expect_no_logs() + .execute_returns(()); + }); +} + +#[test] +fn test_transfer_assets_to_relay_foreign_asset() { + ExtBuilder::default() + .with_balances(vec![(Alice.into(), 1000)]) + .with_xcm_assets(vec![XcmAssetDetails { + location: Location::parent(), + admin: Alice.into(), + asset_id: 5u16, + is_sufficient: true, + balance_to_mint: 10000u128, + min_balance: 1u128, + }]) + .build() + .execute_with(|| { + // Foreign asset with prefix [255; 18] and assetId of 5u16. + let asset_address = + H160::from_str("0xfFfFFFffFffFFFFffFFfFfffFfFFFFFfffFF0005").unwrap(); + + // We send the native currency of the origin chain and pay fees with it. + let pallet_balances_address = H160::from_low_u64_be(2050); + + precompiles() + .prepare_test( + Alice, + Precompile1, + PCall::transfer_assets_to_relay { + beneficiary: H256([0u8; 32]), + assets: vec![ + (Address(pallet_balances_address), 500.into()), + (Address(asset_address), 500.into()), + ] + .into(), + fee_asset_item: 0u32, + weight: Weight::from_parts(u64::MAX, 80000), + }, + ) + .expect_cost(100004001) + .expect_no_logs() + .execute_returns(()); + }); +} From 4832f5852c54cce094b7e2a7e2c3ddeb66b0a0f4 Mon Sep 17 00:00:00 2001 From: Agusrodri Date: Mon, 22 Apr 2024 13:31:56 -0700 Subject: [PATCH 19/22] fix tests --- precompiles/pallet-xcm/src/lib.rs | 3 +++ precompiles/pallet-xcm/src/tests.rs | 12 ++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/precompiles/pallet-xcm/src/lib.rs b/precompiles/pallet-xcm/src/lib.rs index 3a745066..cc533470 100644 --- a/precompiles/pallet-xcm/src/lib.rs +++ b/precompiles/pallet-xcm/src/lib.rs @@ -133,6 +133,7 @@ where // // Blake2_128(16) + AssetId(16) + Location handle.record_db_read::(32 + Location::max_encoded_len())?; + handle.record_cost(1000)?; let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); let assets: Vec<_> = assets.into(); @@ -182,6 +183,7 @@ where // // Blake2_128(16) + AssetId(16) + Location handle.record_db_read::(32 + Location::max_encoded_len())?; + handle.record_cost(1000)?; let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); let assets: Vec<_> = assets.into(); @@ -229,6 +231,7 @@ where // // Blake2_128(16) + AssetId(16) + Location handle.record_db_read::(32 + Location::max_encoded_len())?; + handle.record_cost(1000)?; let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); let assets: Vec<_> = assets.into(); diff --git a/precompiles/pallet-xcm/src/tests.rs b/precompiles/pallet-xcm/src/tests.rs index 00fbcc36..c1e95920 100644 --- a/precompiles/pallet-xcm/src/tests.rs +++ b/precompiles/pallet-xcm/src/tests.rs @@ -223,7 +223,7 @@ fn test_transfer_assets_to_para_20_native_asset() { weight: Weight::from_parts(u64::MAX, 80000), }, ) - .expect_cost(100004001) + .expect_cost(100005002) .expect_no_logs() .execute_returns(()); }); @@ -250,7 +250,7 @@ fn test_transfer_assets_to_para_32_native_asset() { weight: Weight::from_parts(u64::MAX, 80000), }, ) - .expect_cost(100004001) + .expect_cost(100005002) .expect_no_logs() .execute_returns(()); }); @@ -276,7 +276,7 @@ fn test_transfer_assets_to_relay_native_asset() { weight: Weight::from_parts(u64::MAX, 80000), }, ) - .expect_cost(100004001) + .expect_cost(100005002) .expect_no_logs() .execute_returns(()); }); @@ -319,7 +319,7 @@ fn test_transfer_assets_to_para_20_foreign_asset() { weight: Weight::from_parts(u64::MAX, 80000), }, ) - .expect_cost(100004001) + .expect_cost(100005002) .expect_no_logs() .execute_returns(()); }); @@ -362,7 +362,7 @@ fn test_transfer_assets_to_para_32_foreign_asset() { weight: Weight::from_parts(u64::MAX, 80000), }, ) - .expect_cost(100004001) + .expect_cost(100005002) .expect_no_logs() .execute_returns(()); }); @@ -404,7 +404,7 @@ fn test_transfer_assets_to_relay_foreign_asset() { weight: Weight::from_parts(u64::MAX, 80000), }, ) - .expect_cost(100004001) + .expect_cost(100005002) .expect_no_logs() .execute_returns(()); }); From f5dca12db4e06ae37f44a2090db21ceee4187a3f Mon Sep 17 00:00:00 2001 From: Agusrodri Date: Mon, 22 Apr 2024 13:34:18 -0700 Subject: [PATCH 20/22] use proper origin in mock --- precompiles/pallet-xcm/src/mock.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/precompiles/pallet-xcm/src/mock.rs b/precompiles/pallet-xcm/src/mock.rs index 99a84244..399342a0 100644 --- a/precompiles/pallet-xcm/src/mock.rs +++ b/precompiles/pallet-xcm/src/mock.rs @@ -26,7 +26,7 @@ use pallet_evm::{EnsureAddressNever, EnsureAddressRoot, GasWeightMapping}; use precompile_utils::{ mock_account, precompile_set::*, - testing::{AddressInPrefixedSet, Alice, MockAccount}, + testing::{AddressInPrefixedSet, MockAccount}, }; use sp_core::{ConstU32, H160, H256, U256}; use sp_runtime::traits::{BlakeTwo256, IdentityLookup, TryConvert}; @@ -592,7 +592,7 @@ impl ExtBuilder { .unwrap(); Assets::mint( - origin_of(Alice.into()), + origin_of(xcm_asset.admin.into()), xcm_asset.asset_id.into(), xcm_asset.admin, xcm_asset.balance_to_mint, From b07151ae237eb0cdc914368c247a042cb3a53290 Mon Sep 17 00:00:00 2001 From: Agusrodri Date: Mon, 22 Apr 2024 13:58:23 -0700 Subject: [PATCH 21/22] update solidity interface docs --- precompiles/pallet-xcm/XcmInterface.sol | 31 +++++++++++++++++++++---- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/precompiles/pallet-xcm/XcmInterface.sol b/precompiles/pallet-xcm/XcmInterface.sol index 7cd75ff6..303bad53 100644 --- a/precompiles/pallet-xcm/XcmInterface.sol +++ b/precompiles/pallet-xcm/XcmInterface.sol @@ -17,12 +17,13 @@ interface XCM { uint64 proofSize; } - // A way to represent fungible assets in XCM + // A way to represent fungible assets in XCM using Location format struct AssetLocationInfo { Location location; uint256 amount; } + // A way to represent fungible assets in XCM using address format struct AssetAddressInfo { address asset; uint256 amount; @@ -31,7 +32,7 @@ interface XCM { /// @dev Function to send assets via XCM using transfer_assets() pallet-xcm extrinsic. /// @custom:selector 59df8416 /// @param dest The destination chain. - /// @param beneficiary The actual account that will receive the tokens in dest. + /// @param beneficiary The actual account that will receive the tokens on dest. /// @param assets The combination (array) of assets to send. /// @param feeAssetItem The index of the asset that will be used to pay for fees. /// @param weight The weight to be used for the whole XCM operation. @@ -44,8 +45,15 @@ interface XCM { Weight memory weight ) external; - /// TODO add docs + /// @dev Function to send assets via XCM to a 20 byte-like parachain + /// using transfer_assets() pallet-xcm extrinsic. /// @custom:selector b489262e + /// @param paraId The para-id of the destination chain. + /// @param beneficiary The actual account that will receive the tokens on paraId destination. + /// @param assets The combination (array) of assets to send. + /// @param feeAssetItem The index of the asset that will be used to pay for fees. + /// @param weight The weight to be used for the whole XCM operation. + /// (uint64::MAX in refTime means Unlimited weight) function transferAssetsToPara20( uint32 paraId, address beneficiary, @@ -54,8 +62,15 @@ interface XCM { Weight memory weight ) external; - /// TODO add docs + /// @dev Function to send assets via XCM to a 32 byte-like parachain + /// using transfer_assets() pallet-xcm extrinsic. /// @custom:selector 4461e6f5 + /// @param paraId The para-id of the destination chain. + /// @param beneficiary The actual account that will receive the tokens on paraId destination. + /// @param assets The combination (array) of assets to send. + /// @param feeAssetItem The index of the asset that will be used to pay for fees. + /// @param weight The weight to be used for the whole XCM operation. + /// (uint64::MAX in refTime means Unlimited weight) function transferAssetsToPara32( uint32 paraId, bytes32 beneficiary, @@ -64,8 +79,14 @@ interface XCM { Weight memory weight ) external; - /// TODO add docs + /// @dev Function to send assets via XCM to the relay chain + /// using transfer_assets() pallet-xcm extrinsic. /// @custom:selector d7c89659 + /// @param beneficiary The actual account that will receive the tokens on the relay chain. + /// @param assets The combination (array) of assets to send. + /// @param feeAssetItem The index of the asset that will be used to pay for fees. + /// @param weight The weight to be used for the whole XCM operation. + /// (uint64::MAX in refTime means Unlimited weight) function transferAssetsToRelay( bytes32 beneficiary, AssetAddressInfo[] memory assets, From 950b7a4c0f1091ab21e7cc6da173ab84c4cb1217 Mon Sep 17 00:00:00 2001 From: Agusrodri Date: Wed, 24 Apr 2024 05:49:49 -0700 Subject: [PATCH 22/22] remove TODO comment --- primitives/xcm/src/generators.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/primitives/xcm/src/generators.rs b/primitives/xcm/src/generators.rs index 9ab46f53..214dcf30 100644 --- a/primitives/xcm/src/generators.rs +++ b/primitives/xcm/src/generators.rs @@ -16,8 +16,6 @@ use xcm::latest::{Junction::*, Location}; -// TODO: Does it make sense to have this generators -// instead of retrieving the proper location on each case? pub struct XcmSiblingDestinationGenerator; impl XcmSiblingDestinationGenerator { pub fn generate(para_id: u32) -> Location {