From a4ade27b9af38214971111465d3cad8dd2d9e017 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Mon, 7 Aug 2023 15:53:10 +0530 Subject: [PATCH 01/19] add xtokens, update multilocation --- Cargo.lock | 3 + precompiles/utils/src/bytes.rs | 223 ++++++ precompiles/utils/src/data.rs | 115 ++- precompiles/utils/src/lib.rs | 4 +- precompiles/utils/src/xcm.rs | 428 +++++++++++ precompiles/xcm/Cargo.toml | 6 + precompiles/xcm/XCM_v2.sol | 80 +++ precompiles/xcm/Xtokens.sol | 114 +++ precompiles/xcm/src/lib.rs | 681 ++++++++++++++++-- precompiles/xcm/src/mock.rs | 126 +++- precompiles/xcm/src/tests.rs | 1237 ++++++++++++++++++++++++++------ 11 files changed, 2735 insertions(+), 282 deletions(-) create mode 100644 precompiles/utils/src/bytes.rs create mode 100644 precompiles/utils/src/xcm.rs create mode 100644 precompiles/xcm/XCM_v2.sol create mode 100644 precompiles/xcm/Xtokens.sol diff --git a/Cargo.lock b/Cargo.lock index 155d1231eb..a9b4cc8ff0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7439,6 +7439,9 @@ dependencies = [ "hex-literal", "log", "num_enum 0.5.11", + "orml-traits", + "orml-xcm-support", + "orml-xtokens", "pallet-assets", "pallet-balances", "pallet-evm", diff --git a/precompiles/utils/src/bytes.rs b/precompiles/utils/src/bytes.rs new file mode 100644 index 0000000000..cfeb020a99 --- /dev/null +++ b/precompiles/utils/src/bytes.rs @@ -0,0 +1,223 @@ +// This file is part of Astar. + +// Copyright 2019-2022 PureStake Inc. +// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Utils package, originally developed by Purestake Inc. +// Utils package used in Astar Network in terms of GPLv3. +// +// Utils is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Utils is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Utils. If not, see . + +use super::*; +use alloc::borrow::ToOwned; +pub use alloc::string::String; +use sp_core::{ConstU32, Get}; + +type ConstU32Max = ConstU32<{ u32::MAX }>; + +pub type UnboundedBytes = BoundedBytesString; +pub type BoundedBytes = BoundedBytesString; + +pub type UnboundedString = BoundedBytesString; +pub type BoundedString = BoundedBytesString; + +trait Kind { + fn signature() -> String; +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct BytesKind; + +impl Kind for BytesKind { + fn signature() -> String { + String::from("bytes") + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct StringKind; + +impl Kind for StringKind { + fn signature() -> String { + String::from("string") + } +} + +/// The `bytes/string` type of Solidity. +/// It is different from `Vec` which will be serialized with padding for each `u8` element +/// of the array, while `Bytes` is tightly packed. +#[derive(Debug)] +pub struct BoundedBytesString { + data: Vec, + _phantom: PhantomData<(K, S)>, +} + +impl> Clone for BoundedBytesString { + fn clone(&self) -> Self { + Self { + data: self.data.clone(), + _phantom: PhantomData, + } + } +} + +impl PartialEq> for BoundedBytesString { + fn eq(&self, other: &BoundedBytesString) -> bool { + self.data.eq(&other.data) + } +} + +impl Eq for BoundedBytesString {} + +impl> BoundedBytesString { + pub fn as_bytes(&self) -> &[u8] { + &self.data + } + + pub fn as_str(&self) -> Result<&str, sp_std::str::Utf8Error> { + sp_std::str::from_utf8(&self.data) + } +} + +impl> EvmData for BoundedBytesString { + fn read(reader: &mut EvmDataReader) -> EvmResult { + let mut inner_reader = reader.read_pointer()?; + + // Read bytes/string size. + let array_size: usize = inner_reader + .read::() + .map_err(|_| revert("length, out of bounds"))? + .try_into() + .map_err(|_| revert("length, value too large"))?; + + if array_size > S::get() as usize { + return Err(revert("length, value too large").into()); + } + + // Get valid range over the bytes data. + let range = inner_reader.move_cursor(array_size)?; + + let data = inner_reader + .get_input_from_range(range) + .ok_or_else(|| revert(K::signature()))?; + + let bytes = Self { + data: data.to_owned(), + _phantom: PhantomData, + }; + + Ok(bytes) + } + + fn write(writer: &mut EvmDataWriter, value: Self) { + let value: Vec<_> = value.into(); + let length = value.len(); + + // Pad the data. + // Leave it as is if a multiple of 32, otherwise pad to next + // multiple or 32. + let chunks = length / 32; + let padded_size = match length % 32 { + 0 => chunks * 32, + _ => (chunks + 1) * 32, + }; + + let mut value = value.to_vec(); + value.resize(padded_size, 0); + + writer.write_pointer( + EvmDataWriter::new() + .write(U256::from(length)) + .write_raw_bytes(&value) + .build(), + ); + } + + fn has_static_size() -> bool { + false + } +} + +// BytesString <=> Vec/&[u8] + +impl From> for Vec { + fn from(value: BoundedBytesString) -> Self { + value.data + } +} + +impl From> for BoundedBytesString { + fn from(value: Vec) -> Self { + Self { + data: value, + _phantom: PhantomData, + } + } +} + +impl From<&[u8]> for BoundedBytesString { + fn from(value: &[u8]) -> Self { + Self { + data: value.to_vec(), + _phantom: PhantomData, + } + } +} + +impl From<[u8; N]> for BoundedBytesString { + fn from(value: [u8; N]) -> Self { + Self { + data: value.to_vec(), + _phantom: PhantomData, + } + } +} + +impl From<&[u8; N]> for BoundedBytesString { + fn from(value: &[u8; N]) -> Self { + Self { + data: value.to_vec(), + _phantom: PhantomData, + } + } +} + +// BytesString <=> String/str + +impl TryFrom> for String { + type Error = alloc::string::FromUtf8Error; + + fn try_from(value: BoundedBytesString) -> Result { + alloc::string::String::from_utf8(value.data) + } +} + +impl From<&str> for BoundedBytesString { + fn from(value: &str) -> Self { + Self { + data: value.as_bytes().into(), + _phantom: PhantomData, + } + } +} + +impl From for BoundedBytesString { + fn from(value: String) -> Self { + Self { + data: value.as_bytes().into(), + _phantom: PhantomData, + } + } +} diff --git a/precompiles/utils/src/data.rs b/precompiles/utils/src/data.rs index 2359bcefd4..45cd105c99 100644 --- a/precompiles/utils/src/data.rs +++ b/precompiles/utils/src/data.rs @@ -23,9 +23,9 @@ use crate::{revert, EvmResult}; use alloc::borrow::ToOwned; -use core::{any::type_name, ops::Range}; +use core::{any::type_name, marker::PhantomData, ops::Range}; use impl_trait_for_tuples::impl_for_tuples; -use sp_core::{H160, H256, U256}; +use sp_core::{Get, H160, H256, U256}; use sp_std::{convert::TryInto, vec, vec::Vec}; /// The `address` type of Solidity. @@ -175,6 +175,11 @@ impl<'a> EvmDataReader<'a> { }) } + /// Return Option<&[u8]> from a given range for EvmDataReader + pub fn get_input_from_range(&self, range: Range) -> Option<&[u8]> { + self.input.get(range) + } + /// Read remaining bytes pub fn read_till_end(&mut self) -> EvmResult<&[u8]> { let range = self.move_cursor(self.input.len() - self.cursor)?; @@ -190,7 +195,7 @@ impl<'a> EvmDataReader<'a> { /// Move the reading cursor with provided length, and return a range from the previous cursor /// location to the new one. /// Checks cursor overflows. - fn move_cursor(&mut self, len: usize) -> EvmResult> { + pub fn move_cursor(&mut self, len: usize) -> EvmResult> { let start = self.cursor; let end = self .cursor @@ -285,7 +290,7 @@ impl EvmDataWriter { /// Write arbitrary bytes. /// Doesn't handle any alignement checks, prefer using `write` instead if possible. - fn write_raw_bytes(mut self, value: &[u8]) -> Self { + pub fn write_raw_bytes(mut self, value: &[u8]) -> Self { self.data.extend_from_slice(value); self } @@ -604,3 +609,105 @@ impl EvmData for Bytes { false } } + +/// Wrapper around a Vec that provides a max length bound on read. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct BoundedVec { + inner: Vec, + _phantom: PhantomData, +} + +impl> EvmData for BoundedVec { + fn read(reader: &mut EvmDataReader) -> EvmResult { + let mut inner_reader = reader.read_pointer()?; + + let array_size: usize = inner_reader + .read::() + .map_err(|_| revert("out of bounds: length of array"))? + .try_into() + .map_err(|_| revert("value too large : Array has more than max items allowed"))?; + + if array_size > S::get() as usize { + return Err(revert("value too large : Array has more than max items allowed").into()); + } + + let mut array = vec![]; + + let mut item_reader = EvmDataReader { + input: inner_reader + .input + .get(32..) + .ok_or_else(|| revert("read out of bounds: array content"))?, + cursor: 0, + }; + + for _ in 0..array_size { + array.push(item_reader.read()?); + } + + Ok(BoundedVec { + inner: array, + _phantom: PhantomData, + }) + } + + fn write(writer: &mut EvmDataWriter, value: Self) { + let value: Vec<_> = value.into(); + let mut inner_writer = EvmDataWriter::new().write(U256::from(value.len())); + + for inner in value { + // Any offset in items are relative to the start of the item instead of the + // start of the array. However if there is offseted data it must but appended after + // all items (offsets) are written. We thus need to rely on `compute_offsets` to do + // that, and must store a "shift" to correct the offsets. + let shift = inner_writer.data.len(); + let item_writer = EvmDataWriter::new().write(inner); + + inner_writer = inner_writer.write_raw_bytes(&item_writer.data); + for mut offset_datum in item_writer.offset_data { + offset_datum.offset_shift += 32; + offset_datum.offset_position += shift; + inner_writer.offset_data.push(offset_datum); + } + } + + writer.write_pointer(inner_writer.build()); + } + + fn has_static_size() -> bool { + false + } +} + +impl From> for BoundedVec { + fn from(value: Vec) -> Self { + BoundedVec { + inner: value, + _phantom: PhantomData, + } + } +} + +impl From<&[T]> for BoundedVec { + fn from(value: &[T]) -> Self { + BoundedVec { + inner: value.to_vec(), + _phantom: PhantomData, + } + } +} + +impl From<[T; N]> for BoundedVec { + fn from(value: [T; N]) -> Self { + BoundedVec { + inner: value.to_vec(), + _phantom: PhantomData, + } + } +} + +impl From> for Vec { + fn from(value: BoundedVec) -> Self { + value.inner + } +} diff --git a/precompiles/utils/src/lib.rs b/precompiles/utils/src/lib.rs index 1b67c7ea24..641ce7b9cb 100644 --- a/precompiles/utils/src/lib.rs +++ b/precompiles/utils/src/lib.rs @@ -38,7 +38,9 @@ use pallet_evm::{GasWeightMapping, Log}; use sp_core::{H160, H256, U256}; use sp_std::{marker::PhantomData, vec, vec::Vec}; -mod data; +pub mod bytes; +pub mod data; +pub mod xcm; pub use data::{Address, Bytes, EvmData, EvmDataReader, EvmDataWriter}; pub use precompile_utils_macro::{generate_function_selector, keccak256}; diff --git a/precompiles/utils/src/xcm.rs b/precompiles/utils/src/xcm.rs new file mode 100644 index 0000000000..96942a4b64 --- /dev/null +++ b/precompiles/utils/src/xcm.rs @@ -0,0 +1,428 @@ +// This file is part of Astar. + +// Copyright 2019-2022 PureStake Inc. +// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Utils package, originally developed by Purestake Inc. +// Utils package used in Astar Network in terms of GPLv3. +// +// Utils is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Utils is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Utils. If not, see . + +//! Encoding of XCM types for solidity + +use crate::Address; +use sp_core::U256; +use { + crate::{bytes::*, revert, EvmData, EvmDataReader, EvmDataWriter, EvmResult}, + frame_support::{ensure, traits::ConstU32}, + sp_core::H256, + sp_std::vec::Vec, + xcm::latest::{Junction, Junctions, MultiLocation, NetworkId}, +}; +pub const JUNCTION_SIZE_LIMIT: u32 = 2u32.pow(16); + +// Function to convert network id to bytes +// Each NetworkId variant is represented as bytes +// The first byte represents the enum variant to be used. +// - Indexes 0,2,3 represent XCM V2 variants +// - Index 1 changes name in V3 (`ByGenesis`), but is compatible with V2 `Named` +// - Indexes 4~10 represent new XCM V3 variants +// The rest of the bytes (if any), represent the additional data that such enum variant requires +// In such a case, since NetworkIds will be appended at the end, we will read the buffer until the +// end to recover the name + +pub(crate) fn network_id_to_bytes(network_id: Option) -> Vec { + let mut encoded: Vec = Vec::new(); + match network_id.clone() { + None => { + encoded.push(0u8); + encoded + } + Some(NetworkId::ByGenesis(id)) => { + encoded.push(1u8); + encoded.append(&mut id.into()); + encoded + } + Some(NetworkId::Polkadot) => { + encoded.push(2u8); + encoded.push(2u8); + encoded + } + Some(NetworkId::Kusama) => { + encoded.push(3u8); + encoded.push(3u8); + encoded + } + Some(NetworkId::ByFork { + block_number, + block_hash, + }) => { + encoded.push(4u8); + encoded.push(1u8); + encoded.append(&mut block_number.to_be_bytes().into()); + encoded.append(&mut block_hash.into()); + encoded + } + Some(NetworkId::Westend) => { + encoded.push(5u8); + encoded.push(4u8); + encoded + } + Some(NetworkId::Rococo) => { + encoded.push(6u8); + encoded.push(5u8); + encoded + } + Some(NetworkId::Wococo) => { + encoded.push(7u8); + encoded.push(6u8); + encoded + } + Some(NetworkId::Ethereum { chain_id }) => { + encoded.push(8u8); + encoded.push(7u8); + encoded.append(&mut chain_id.to_be_bytes().into()); + encoded + } + Some(NetworkId::BitcoinCore) => { + encoded.push(9u8); + encoded.push(8u8); + encoded + } + Some(NetworkId::BitcoinCash) => { + encoded.push(10u8); + encoded.push(9u8); + encoded + } + } +} + +// Function to convert bytes to networkId +pub(crate) fn network_id_from_bytes(encoded_bytes: Vec) -> EvmResult> { + ensure!(encoded_bytes.len() > 0, revert("Junctions cannot be empty")); + let mut encoded_network_id = EvmDataReader::new(&encoded_bytes); + + let network_selector = encoded_network_id + .read_raw_bytes(1) + .map_err(|_| revert("network selector (1 byte)"))?; + + match network_selector[0] { + 0 => Ok(None), + 1 => Ok(Some(NetworkId::ByGenesis( + encoded_network_id + .read_till_end() + .map_err(|_| revert("can't read till end"))? + .to_vec() + .try_into() + .map_err(|_| revert("network by genesis"))?, + ))), + 2 => Ok(Some(NetworkId::Polkadot)), + 3 => Ok(Some(NetworkId::Kusama)), + 4 => { + let mut block_number: [u8; 8] = Default::default(); + block_number.copy_from_slice(&encoded_network_id.read_raw_bytes(8)?); + + let mut block_hash: [u8; 32] = Default::default(); + block_hash.copy_from_slice(&encoded_network_id.read_raw_bytes(32)?); + Ok(Some(NetworkId::ByFork { + block_number: u64::from_be_bytes(block_number), + block_hash, + })) + } + 5 => Ok(Some(NetworkId::Westend)), + 6 => Ok(Some(NetworkId::Rococo)), + 7 => Ok(Some(NetworkId::Wococo)), + 8 => { + let mut chain_id: [u8; 8] = Default::default(); + chain_id.copy_from_slice(&encoded_network_id.read_raw_bytes(8)?); + Ok(Some(NetworkId::Ethereum { + chain_id: u64::from_be_bytes(chain_id), + })) + } + 9 => Ok(Some(NetworkId::BitcoinCore)), + 10 => Ok(Some(NetworkId::BitcoinCash)), + _ => Err(revert("Non-valid Network Id").into()), + } +} + +impl EvmData for Junction { + fn read(reader: &mut EvmDataReader) -> EvmResult { + let junction = reader.read::>>()?; + let junction_bytes: Vec<_> = junction.into(); + + ensure!( + junction_bytes.len() > 0, + revert("Junctions cannot be empty") + ); + + // For simplicity we use an EvmReader here + let mut encoded_junction = EvmDataReader::new(&junction_bytes); + + // We take the first byte + let enum_selector = encoded_junction + .read_raw_bytes(1) + .map_err(|_| revert("junction variant"))?; + + // The firs byte selects the enum variant + match enum_selector[0] { + 0 => { + // In the case of Junction::Parachain, we need 4 additional bytes + let mut data: [u8; 4] = Default::default(); + data.copy_from_slice(&encoded_junction.read_raw_bytes(4)?); + let para_id = u32::from_be_bytes(data); + Ok(Junction::Parachain(para_id)) + } + 1 => { + // In the case of Junction::AccountId32, we need 32 additional bytes plus NetworkId + let mut account: [u8; 32] = Default::default(); + account.copy_from_slice(&encoded_junction.read_raw_bytes(32)?); + + let network = encoded_junction.read_till_end()?.to_vec(); + Ok(Junction::AccountId32 { + network: network_id_from_bytes(network)?, + id: account, + }) + } + 2 => { + // In the case of Junction::AccountIndex64, we need 8 additional bytes plus NetworkId + let mut index: [u8; 8] = Default::default(); + index.copy_from_slice(&encoded_junction.read_raw_bytes(8)?); + // Now we read the network + let network = encoded_junction.read_till_end()?.to_vec(); + Ok(Junction::AccountIndex64 { + network: network_id_from_bytes(network)?, + index: u64::from_be_bytes(index), + }) + } + 3 => { + // In the case of Junction::AccountKey20, we need 20 additional bytes plus NetworkId + let mut account: [u8; 20] = Default::default(); + account.copy_from_slice(&encoded_junction.read_raw_bytes(20)?); + + let network = encoded_junction.read_till_end()?.to_vec(); + Ok(Junction::AccountKey20 { + network: network_id_from_bytes(network)?, + key: account, + }) + } + 4 => Ok(Junction::PalletInstance( + encoded_junction.read_raw_bytes(1)?[0], + )), + 5 => { + // In the case of Junction::GeneralIndex, we need 16 additional bytes + let mut general_index: [u8; 16] = Default::default(); + general_index.copy_from_slice(&encoded_junction.read_raw_bytes(16)?); + Ok(Junction::GeneralIndex(u128::from_be_bytes(general_index))) + } + 6 => { + let length = encoded_junction + .read_raw_bytes(1) + .map_err(|_| revert("General Key length"))?[0]; + + let data = encoded_junction + .read::() + .map_err(|_| revert("can't read"))? + .into(); + + Ok(Junction::GeneralKey { length, data }) + } + 7 => Ok(Junction::OnlyChild), + 8 => Err(revert("Junction::Plurality not supported yet").into()), + 9 => { + let network = encoded_junction.read_till_end()?.to_vec(); + if let Some(network_id) = network_id_from_bytes(network)? { + Ok(Junction::GlobalConsensus(network_id)) + } else { + Err(revert("Unknown NetworkId").into()) + } + } + _ => Err(revert("Unknown Junction variant").into()), + } + } + + fn write(writer: &mut EvmDataWriter, value: Self) { + let mut encoded: Vec = Vec::new(); + let encoded_bytes: UnboundedBytes = match value { + Junction::Parachain(para_id) => { + encoded.push(0u8); + encoded.append(&mut para_id.to_be_bytes().to_vec()); + encoded.as_slice().into() + } + Junction::AccountId32 { network, id } => { + encoded.push(1u8); + encoded.append(&mut id.to_vec()); + encoded.append(&mut network_id_to_bytes(network)); + encoded.as_slice().into() + } + Junction::AccountIndex64 { network, index } => { + encoded.push(2u8); + encoded.append(&mut index.to_be_bytes().to_vec()); + encoded.append(&mut network_id_to_bytes(network)); + encoded.as_slice().into() + } + Junction::AccountKey20 { network, key } => { + encoded.push(3u8); + encoded.append(&mut key.to_vec()); + encoded.append(&mut network_id_to_bytes(network)); + encoded.as_slice().into() + } + Junction::PalletInstance(intance) => { + encoded.push(4u8); + encoded.append(&mut intance.to_be_bytes().to_vec()); + encoded.as_slice().into() + } + Junction::GeneralIndex(id) => { + encoded.push(5u8); + encoded.append(&mut id.to_be_bytes().to_vec()); + encoded.as_slice().into() + } + Junction::GeneralKey { length, data } => { + encoded.push(6u8); + encoded.push(length); + encoded.append(&mut data.into()); + encoded.as_slice().into() + } + Junction::OnlyChild => { + encoded.push(7u8); + encoded.as_slice().into() + } + Junction::GlobalConsensus(network_id) => { + encoded.push(9u8); + encoded.append(&mut network_id_to_bytes(Some(network_id))); + encoded.as_slice().into() + } + // TODO: The only missing item here is Junciton::Plurality. This is a complex encoded + // type that we need to evaluate how to support + _ => unreachable!("Junction::Plurality not supported yet"), + }; + EvmData::write(writer, encoded_bytes); + } + + fn has_static_size() -> bool { + false + } +} + +impl EvmData for Junctions { + fn read(reader: &mut EvmDataReader) -> EvmResult { + let junctions_bytes: Vec = reader.read()?; + let mut junctions = Junctions::Here; + for item in junctions_bytes { + junctions + .push(item) + .map_err(|_| revert("overflow when reading junctions"))?; + } + + Ok(junctions) + } + + fn write(writer: &mut EvmDataWriter, value: Self) { + let encoded: Vec = value.iter().map(|junction| junction.clone()).collect(); + EvmData::write(writer, encoded); + } + + fn has_static_size() -> bool { + false + } +} + +// Cannot used derive macro since it is a foreign struct. +impl EvmData for MultiLocation { + fn read(reader: &mut EvmDataReader) -> EvmResult { + let (parents, interior) = reader.read()?; + Ok(MultiLocation { parents, interior }) + } + + fn write(writer: &mut EvmDataWriter, value: Self) { + EvmData::write(writer, (value.parents, value.interior)); + } + + fn has_static_size() -> bool { + <(u8, Junctions)>::has_static_size() + } +} + +pub struct EvmMultiAsset { + location: MultiLocation, + amount: U256, +} + +impl EvmMultiAsset { + pub fn get_location(&self) -> MultiLocation { + self.location + } + pub fn get_amount(&self) -> U256 { + self.amount + } +} +impl From<(MultiLocation, U256)> for EvmMultiAsset { + fn from(tuple: (MultiLocation, U256)) -> Self { + EvmMultiAsset { + location: tuple.0, + amount: tuple.1, + } + } +} +impl EvmData for EvmMultiAsset { + fn read(reader: &mut EvmDataReader) -> EvmResult { + let (location, amount) = reader.read()?; + Ok(EvmMultiAsset { location, amount }) + } + + fn write(writer: &mut EvmDataWriter, value: Self) { + EvmData::write(writer, (value.location, value.amount)); + } + + fn has_static_size() -> bool { + <(MultiLocation, U256)>::has_static_size() + } +} + +pub struct Currency { + address: Address, + amount: U256, +} + +impl Currency { + pub fn get_address(&self) -> Address { + self.address + } + pub fn get_amount(&self) -> U256 { + self.amount + } +} +impl From<(Address, U256)> for Currency { + fn from(tuple: (Address, U256)) -> Self { + Currency { + address: tuple.0, + amount: tuple.1, + } + } +} + +impl EvmData for Currency { + fn read(reader: &mut EvmDataReader) -> EvmResult { + let (address, amount) = reader.read()?; + Ok(Currency { address, amount }) + } + + fn write(writer: &mut EvmDataWriter, value: Self) { + EvmData::write(writer, (value.address, value.amount)); + } + + fn has_static_size() -> bool { + <(Address, U256)>::has_static_size() + } +} diff --git a/precompiles/xcm/Cargo.toml b/precompiles/xcm/Cargo.toml index 8065e4e718..66540f926e 100644 --- a/precompiles/xcm/Cargo.toml +++ b/precompiles/xcm/Cargo.toml @@ -28,6 +28,9 @@ fp-evm = { workspace = true } pallet-evm = { workspace = true } # Polkadot +orml-traits = { workspace = true } +orml-xcm-support = { workspace = true } +orml-xtokens = { workspace = true } xcm = { workspace = true } xcm-executor = { workspace = true } @@ -62,5 +65,8 @@ std = [ "sp-io/std", "xcm/std", "xcm-executor/std", + "orml-xtokens/std", + "orml-xcm-support/std", + "orml-traits/std", ] runtime-benchmarks = [] diff --git a/precompiles/xcm/XCM_v2.sol b/precompiles/xcm/XCM_v2.sol new file mode 100644 index 0000000000..24233a7526 --- /dev/null +++ b/precompiles/xcm/XCM_v2.sol @@ -0,0 +1,80 @@ +pragma solidity ^0.8.0; + +/** + * @title XCM interface. + */ +interface XCM { + // A multilocation is defined by its number of parents and the encoded junctions (interior) + struct Multilocation { + uint8 parents; + bytes[] interior; + } + + /** + * @dev Withdraw assets using PalletXCM call. + * @param asset_id - list of XC20 asset addresses + * @param asset_amount - list of transfer amounts (must match with asset addresses above) + * @param beneficiary - Multilocation of beneficiary in respect to destination parachain + * @param destination - Multilocation of destination chain + * @param fee_index - index of asset_id item that should be used as a XCM fee + * @return bool confirmation whether the XCM message sent. + * + * How method check that assets list is valid: + * - all assets resolved to multi-location (on runtime level) + * - all assets has corresponded amount (lenght of assets list matched to amount list) + */ + function assets_withdraw( + address[] calldata asset_id, + uint256[] calldata asset_amount, + Multilocation memory beneficiary, + Multilocation memory destination, + uint256 fee_index + ) external returns (bool); + + /** + * @dev Execute a transaction on a remote chain. + * @param destination - Multilocation of destination chain + * @param payment_asset_id - ETH address of the local asset derivate used to pay for execution in the destination chain + * @param payment_amount - amount of payment asset to use for execution payment - should cover cost of XCM instructions + Transact call weight. + * @param call - encoded call data (must be decodable by remote chain) + * @param transact_weight - max weight that the encoded call is allowed to consume in the destination chain + * @return bool confirmation whether the XCM message sent. + */ + function remote_transact( + Multilocation memory destination, + address payment_asset_id, + uint256 payment_amount, + bytes calldata call, + uint64 transact_weight + ) external returns (bool); + + /** + * @dev Reserve transfer assets using PalletXCM call. + * @param asset_id - list of XC20 asset addresses + * @param asset_amount - list of transfer amounts (must match with asset addresses above) + * @param beneficiary - Multilocation of beneficiary in respect to destination parachain + * @param destination - Multilocation of destination chain + * @param fee_index - index of asset_id item that should be used as a XCM fee + * @return A boolean confirming whether the XCM message sent. + * How method check that assets list is valid: + * - all assets resolved to multi-location (on runtime level) + * - all assets has corresponded amount (lenght of assets list matched to amount list) + */ + function assets_reserve_transfer( + address[] calldata asset_id, + uint256[] calldata asset_amount, + Multilocation memory beneficiary, + Multilocation memory destination, + uint256 fee_index + ) external returns (bool); + + /** + * @dev send xcm using PalletXCM call. + * @param destination - Multilocation of destination chain where to send this call + * @param xcm_call - encoded xcm call you want to send to destination + **/ + function send_xcm( + Multilocation memory destination, + bytes memory xcm_call + ) external returns (bool); +} \ No newline at end of file diff --git a/precompiles/xcm/Xtokens.sol b/precompiles/xcm/Xtokens.sol new file mode 100644 index 0000000000..2d082890b5 --- /dev/null +++ b/precompiles/xcm/Xtokens.sol @@ -0,0 +1,114 @@ + +pragma solidity ^0.8.0; + + +/** + * @title Xtokens interface. + */ +interface Xtokens { + // A multilocation is defined by its number of parents and the encoded junctions (interior) + struct Multilocation { + uint8 parents; + bytes[] interior; + } + + // A MultiAsset is defined by a multilocation and an amount + struct MultiAsset { + Multilocation location; + uint256 amount; + } + + // A Currency is defined by address and the amount to be transferred + struct Currency { + address currencyAddress; + uint256 amount; + } + + /// Transfer a token through XCM based on its currencyId + /// + /// @dev The token transfer burns/transfers the corresponding amount before sending + /// @param currencyAddress The ERC20 address of the currency we want to transfer + /// @param amount The amount of tokens we want to transfer + /// @param destination The Multilocation to which we want to send the tokens + /// @param weight The weight we want to buy in the destination chain + function transfer( + address currencyAddress, + uint256 amount, + Multilocation memory destination, + uint64 weight + ) external returns (bool); + + /// Transfer a token through XCM based on its currencyId specifying fee + /// + /// @dev The token transfer burns/transfers the corresponding amount before sending + /// @param currencyAddress The ERC20 address of the currency we want to transfer + /// @param amount The amount of tokens we want to transfer + /// @param destination The Multilocation to which we want to send the tokens + /// @param weight The weight we want to buy in the destination chain + function transfer_with_fee( + address currencyAddress, + uint256 amount, + uint256 fee, + Multilocation memory destination, + uint64 weight + ) external returns (bool); + + /// Transfer a token through XCM based on its MultiLocation + /// + /// @dev The token transfer burns/transfers the corresponding amount before sending + /// @param asset The asset we want to transfer, defined by its multilocation. + /// Currently only Concrete Fungible assets + /// @param amount The amount of tokens we want to transfer + /// @param destination The Multilocation to which we want to send the tokens + /// @param weight The weight we want to buy in the destination chain + function transfer_multiasset( + Multilocation memory asset, + uint256 amount, + Multilocation memory destination, + uint64 weight + ) external returns (bool); + + /// Transfer a token through XCM based on its MultiLocation specifying fee + /// + /// @dev The token transfer burns/transfers the corresponding amount before sending + /// @param asset The asset we want to transfer, defined by its multilocation. + /// Currently only Concrete Fungible assets + /// @param amount The amount of tokens we want to transfer + /// @param destination The Multilocation to which we want to send the tokens + /// @param weight The weight we want to buy in the destination chain + function transfer_multiasset_with_fee( + Multilocation memory asset, + uint256 amount, + uint256 fee, + Multilocation memory destination, + uint64 weight + ) external returns (bool); + + /// Transfer several tokens at once through XCM based on its address specifying fee + /// + /// @dev The token transfer burns/transfers the corresponding amount before sending + /// @param currencies The currencies we want to transfer, defined by their address and amount. + /// @param feeItem Which of the currencies to be used as fee + /// @param destination The Multilocation to which we want to send the tokens + /// @param weight The weight we want to buy in the destination chain + function transfer_multi_currencies( + Currency[] memory currencies, + uint32 feeItem, + Multilocation memory destination, + uint64 weight + ) external returns (bool); + + /// Transfer several tokens at once through XCM based on its location specifying fee + /// + /// @dev The token transfer burns/transfers the corresponding amount before sending + /// @param assets The assets we want to transfer, defined by their location and amount. + /// @param feeItem Which of the currencies to be used as fee + /// @param destination The Multilocation to which we want to send the tokens + /// @param weight The weight we want to buy in the destination chain + function transfer_multi_assets( + MultiAsset[] memory assets, + uint32 feeItem, + Multilocation memory destination, + uint64 weight + ) external returns (bool); +} diff --git a/precompiles/xcm/src/lib.rs b/precompiles/xcm/src/lib.rs index a8453b36a5..1ca025f1eb 100644 --- a/precompiles/xcm/src/lib.rs +++ b/precompiles/xcm/src/lib.rs @@ -22,22 +22,29 @@ use fp_evm::{PrecompileHandle, PrecompileOutput}; use frame_support::{ dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, pallet_prelude::Weight, - traits::Get, + traits::{ConstU32, Get}, }; +pub const XCM_SIZE_LIMIT: u32 = 2u32.pow(16); +type GetXcmSizeLimit = ConstU32; + use pallet_evm::{AddressMapping, Precompile}; +use parity_scale_codec::DecodeLimit; use sp_core::{H160, H256, U256}; + use sp_std::marker::PhantomData; use sp_std::prelude::*; -use xcm::latest::prelude::*; +use xcm::{latest::prelude::*, VersionedMultiAsset, VersionedMultiAssets, VersionedMultiLocation}; use xcm_executor::traits::Convert; use pallet_evm_precompile_assets_erc20::AddressToAssetId; use precompile_utils::{ - revert, succeed, Address, Bytes, EvmDataWriter, EvmResult, FunctionModifier, - PrecompileHandleExt, RuntimeHelper, + bytes::BoundedBytes, + data::BoundedVec, + revert, succeed, + xcm::{Currency, EvmMultiAsset}, + Address, Bytes, EvmDataWriter, EvmResult, FunctionModifier, PrecompileHandleExt, RuntimeHelper, }; - #[cfg(test)] mod mock; #[cfg(test)] @@ -48,30 +55,66 @@ mod tests; pub enum Action { AssetsWithdrawNative = "assets_withdraw(address[],uint256[],bytes32,bool,uint256,uint256)", AssetsWithdrawEvm = "assets_withdraw(address[],uint256[],address,bool,uint256,uint256)", - RemoteTransact = "remote_transact(uint256,bool,address,uint256,bytes,uint64)", + RemoteTransactOld = "remote_transact(uint256,bool,address,uint256,bytes,uint64)", AssetsReserveTransferNative = "assets_reserve_transfer(address[],uint256[],bytes32,bool,uint256,uint256)", AssetsReserveTransferEvm = "assets_reserve_transfer(address[],uint256[],address,bool,uint256,uint256)", + AssetsWithdraw = "assets_withdraw(address[],uint256[],(uint8,bytes[]),(uint8,bytes[]),uint256)", + RemoteTransactNew = "remote_transact((uint8,bytes[]),address,uint256,bytes,uint64)", + AssetsReserveTransfer = + "assets_reserve_transfer(address[],uint256[],(uint8,bytes[]),(uint8,bytes[]),uint256)", + SendXCM = "send_xcm((uint8,bytes[]),bytes)", + XtokensTransfer = "transfer(address,uint256,(uint8,bytes[]),uint64)", + XtokensTransferWithFee = "transfer_with_fee(address,uint256,uint256,(uint8,bytes[]),uint64)", + XtokensTransferMultiasset = + "transfer_multiasset((uint8,bytes[]),uint256,(uint8,bytes[]),uint64)", + XtokensTransferMultiassetWithFee = + "transfer_multiasset_with_fee((uint8,bytes[]),uint256,uint256,(uint8,bytes[]),uint64)", + XtokensTransferMulticurrencies = + "transfer_multi_currencies((address,uint256)[],uint32,(uint8,bytes[]),uint64)", + XtokensTransferMultiassets = + "transfet_multi_assets(((uint8,bytes[]),uint256)[],uint32,(uint8,bytes[]),uint64)", } /// Dummy H160 address representing native currency (e.g. ASTR or SDN) const NATIVE_ADDRESS: H160 = H160::zero(); +/// Dummy default 64KB +const DEFAULT_PROOF_SIZE: u64 = 1024 * 64; + +pub type XBalanceOf = ::Balance; + +pub struct GetMaxAssets(PhantomData); +impl Get for GetMaxAssets +where + R: orml_xtokens::Config, +{ + fn get() -> u32 { + ::MaxAssetsForTransfer::get() as u32 + } +} /// A precompile that expose XCM related functions. pub struct XcmPrecompile(PhantomData<(T, C)>); -impl Precompile for XcmPrecompile +impl Precompile for XcmPrecompile where - R: pallet_evm::Config + Runtime: pallet_evm::Config + pallet_xcm::Config + pallet_assets::Config - + AddressToAssetId<::AssetId>, - <::RuntimeCall as Dispatchable>::RuntimeOrigin: - From>, - ::RuntimeCall: - From> + Dispatchable + GetDispatchInfo, - C: Convert::AssetId>, + + orml_xtokens::Config + + AddressToAssetId<::AssetId>, + <::RuntimeCall as Dispatchable>::RuntimeOrigin: + From>, + ::AccountId: Into<[u8; 32]>, + ::RuntimeCall: From> + + From> + + Dispatchable + + GetDispatchInfo, + XBalanceOf: TryFrom + Into, + ::CurrencyId: + From<::AssetId>, + C: Convert::AssetId>, { fn execute(handle: &mut impl PrecompileHandle) -> EvmResult { log::trace!(target: "xcm-precompile", "In XCM precompile"); @@ -83,16 +126,28 @@ where // Dispatch the call match selector { Action::AssetsWithdrawNative => { - Self::assets_withdraw(handle, BeneficiaryType::Account32) + Self::assets_withdraw_v1(handle, BeneficiaryType::Account32) + } + Action::AssetsWithdrawEvm => { + Self::assets_withdraw_v1(handle, BeneficiaryType::Account20) } - Action::AssetsWithdrawEvm => Self::assets_withdraw(handle, BeneficiaryType::Account20), - Action::RemoteTransact => Self::remote_transact(handle), + Action::RemoteTransactOld => Self::remote_transact_v1(handle), Action::AssetsReserveTransferNative => { - Self::assets_reserve_transfer(handle, BeneficiaryType::Account32) + Self::assets_reserve_transfer_v1(handle, BeneficiaryType::Account32) } Action::AssetsReserveTransferEvm => { - Self::assets_reserve_transfer(handle, BeneficiaryType::Account20) + Self::assets_reserve_transfer_v1(handle, BeneficiaryType::Account20) } + Action::AssetsWithdraw => Self::assets_withdraw(handle), + Action::RemoteTransactNew => Self::remote_transact(handle), + Action::AssetsReserveTransfer => Self::assets_reserve_transfer(handle), + Action::SendXCM => Self::send_xcm(handle), + Action::XtokensTransfer => Self::transfer(handle), + Action::XtokensTransferWithFee => Self::transfer_with_fee(handle), + Action::XtokensTransferMultiasset => Self::transfer_multiasset(handle), + Action::XtokensTransferMultiassetWithFee => Self::transfer_multiasset_with_fee(handle), + Action::XtokensTransferMulticurrencies => Self::transfer_multi_currencies(handle), + Action::XtokensTransferMultiassets => Self::transfer_multi_assets(handle), } } } @@ -105,19 +160,26 @@ enum BeneficiaryType { Account20, } -impl XcmPrecompile +impl XcmPrecompile where - R: pallet_evm::Config + Runtime: pallet_evm::Config + pallet_xcm::Config + + orml_xtokens::Config + pallet_assets::Config - + AddressToAssetId<::AssetId>, - <::RuntimeCall as Dispatchable>::RuntimeOrigin: - From>, - ::RuntimeCall: - From> + Dispatchable + GetDispatchInfo, - C: Convert::AssetId>, + + AddressToAssetId<::AssetId>, + <::RuntimeCall as Dispatchable>::RuntimeOrigin: + From>, + ::AccountId: Into<[u8; 32]>, + ::RuntimeCall: From> + + From> + + Dispatchable + + GetDispatchInfo, + XBalanceOf: TryFrom + Into, + ::CurrencyId: + From<::AssetId>, + C: Convert::AssetId>, { - fn assets_withdraw( + fn assets_withdraw_v1( handle: &mut impl PrecompileHandle, beneficiary_type: BeneficiaryType, ) -> EvmResult { @@ -130,7 +192,7 @@ where .iter() .cloned() .filter_map(|address| { - R::address_to_asset_id(address.into()).and_then(|x| C::reverse_ref(x).ok()) + Runtime::address_to_asset_id(address.into()).and_then(|x| C::reverse_ref(x).ok()) }) .collect(); let amounts_raw = input.read::>()?; @@ -188,8 +250,11 @@ where .into(); // Build call with origin. - let origin = Some(R::AddressMapping::into_account_id(handle.context().caller)).into(); - let call = pallet_xcm::Call::::reserve_withdraw_assets { + let origin = Some(Runtime::AddressMapping::into_account_id( + handle.context().caller, + )) + .into(); + let call = pallet_xcm::Call::::reserve_withdraw_assets { dest: Box::new(dest.into()), beneficiary: Box::new(beneficiary.into()), assets: Box::new(assets.into()), @@ -197,12 +262,12 @@ where }; // Dispatch a call. - RuntimeHelper::::try_dispatch(handle, origin, call)?; + RuntimeHelper::::try_dispatch(handle, origin, call)?; Ok(succeed(EvmDataWriter::new().write(true).build())) } - fn remote_transact(handle: &mut impl PrecompileHandle) -> EvmResult { + fn remote_transact_v1(handle: &mut impl PrecompileHandle) -> EvmResult { let mut input = handle.read_input()?; input.expect_arguments(6)?; @@ -234,7 +299,7 @@ where if address == NATIVE_ADDRESS { Here.into() } else { - let fee_asset_id = R::address_to_asset_id(address) + let fee_asset_id = Runtime::address_to_asset_id(address) .ok_or(revert("Failed to resolve fee asset id from address"))?; C::reverse_ref(fee_asset_id).map_err(|_| { revert("Failed to resolve fee asset multilocation from local id") @@ -247,7 +312,7 @@ where } let fee_amount = fee_amount.low_u128(); - let context = R::UniversalLocation::get(); + let context = ::UniversalLocation::get(); let fee_multilocation = MultiAsset { id: Concrete(fee_asset), fun: Fungible(fee_amount), @@ -265,7 +330,7 @@ where }, Transact { origin_kind: OriginKind::SovereignAccount, - require_weight_at_most: Weight::from_parts(transact_weight, 0), + require_weight_at_most: Weight::from_ref_time(transact_weight), call: remote_call.into(), }, ]); @@ -273,19 +338,22 @@ where log::trace!(target: "xcm-precompile:remote_transact", "Processed arguments: dest: {:?}, fee asset: {:?}, XCM: {:?}", dest, fee_multilocation, xcm); // Build call with origin. - let origin = Some(R::AddressMapping::into_account_id(handle.context().caller)).into(); - let call = pallet_xcm::Call::::send { + let origin = Some(Runtime::AddressMapping::into_account_id( + handle.context().caller, + )) + .into(); + let call = pallet_xcm::Call::::send { dest: Box::new(dest.into()), message: Box::new(xcm::VersionedXcm::V3(xcm)), }; // Dispatch a call. - RuntimeHelper::::try_dispatch(handle, origin, call)?; + RuntimeHelper::::try_dispatch(handle, origin, call)?; Ok(succeed(EvmDataWriter::new().write(true).build())) } - fn assets_reserve_transfer( + fn assets_reserve_transfer_v1( handle: &mut impl PrecompileHandle, beneficiary_type: BeneficiaryType, ) -> EvmResult { @@ -304,7 +372,7 @@ where if address == NATIVE_ADDRESS { Some(Here.into()) } else { - R::address_to_asset_id(address).and_then(|x| C::reverse_ref(x).ok()) + Runtime::address_to_asset_id(address).and_then(|x| C::reverse_ref(x).ok()) } }) .collect(); @@ -365,8 +433,76 @@ where .into(); // Build call with origin. - let origin = Some(R::AddressMapping::into_account_id(handle.context().caller)).into(); - let call = pallet_xcm::Call::::reserve_transfer_assets { + let origin = Some(Runtime::AddressMapping::into_account_id( + handle.context().caller, + )) + .into(); + let call = pallet_xcm::Call::::reserve_transfer_assets { + dest: Box::new(dest.into()), + beneficiary: Box::new(beneficiary.into()), + assets: Box::new(assets.into()), + fee_asset_item, + }; + + // Dispatch a call. + RuntimeHelper::::try_dispatch(handle, origin, call)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + fn assets_withdraw(handle: &mut impl PrecompileHandle) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(6)?; + + // Read arguments and check it + let assets: Vec = input + .read::>()? + .iter() + .cloned() + .filter_map(|address| { + Runtime::address_to_asset_id(address.into()).and_then(|x| C::reverse_ref(x).ok()) + }) + .collect(); + let amounts_raw = input.read::>()?; + if amounts_raw.iter().any(|x| *x > u128::MAX.into()) { + return Err(revert("Asset amount is too big")); + } + let amounts: Vec = amounts_raw.iter().map(|x| x.low_u128()).collect(); + + // Check that assets list is valid: + // * all assets resolved to multi-location + // * all assets has corresponded amount + if assets.len() != amounts.len() || assets.is_empty() { + return Err(revert("Assets resolution failure.")); + } + + let beneficiary: MultiLocation = input.read::()?; + let dest: MultiLocation = input.read::()?; + + let fee_asset_item: u32 = input.read::()?.low_u32(); + + log::trace!(target: "xcm-precompile::asset_withdraw", "Raw arguments: assets: {:?}, asset_amount: {:?} \ + beneficiart: {:?}, destination: {:?}, fee_index: {}", + assets, amounts_raw, beneficiary, dest, fee_asset_item); + + if fee_asset_item as usize > assets.len() { + return Err(revert("Bad fee index.")); + } + + let assets: MultiAssets = assets + .iter() + .cloned() + .zip(amounts.iter().cloned()) + .map(Into::into) + .collect::>() + .into(); + + // Build call with origin. + let origin = Some(Runtime::AddressMapping::into_account_id( + handle.context().caller, + )) + .into(); + let call = pallet_xcm::Call::::reserve_withdraw_assets { dest: Box::new(dest.into()), beneficiary: Box::new(beneficiary.into()), assets: Box::new(assets.into()), @@ -374,7 +510,464 @@ where }; // Dispatch a call. - RuntimeHelper::::try_dispatch(handle, origin, call)?; + RuntimeHelper::::try_dispatch(handle, origin, call)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + fn remote_transact(handle: &mut impl PrecompileHandle) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(6)?; + + let dest: MultiLocation = input.read::()?; + let fee_asset_addr = input.read::
()?; + let fee_amount = input.read::()?; + + let remote_call: Vec = input.read::()?.into(); + let transact_weight = input.read::()?; + + log::trace!(target: "xcm-precompile::remote_transact", "Raw arguments: dest: {:?}, fee_asset_addr: {:?} \ + fee_amount: {:?}, remote_call: {:?}, transact_weight: {}", + dest, fee_asset_addr, fee_amount, remote_call, transact_weight); + + let fee_asset = { + let address: H160 = fee_asset_addr.into(); + + // Special case where zero address maps to native token by convention. + if address == NATIVE_ADDRESS { + Here.into() + } else { + let fee_asset_id = Runtime::address_to_asset_id(address) + .ok_or(revert("Failed to resolve fee asset id from address"))?; + C::reverse_ref(fee_asset_id).map_err(|_| { + revert("Failed to resolve fee asset multilocation from local id") + })? + } + }; + + if fee_amount > u128::MAX.into() { + return Err(revert("Fee amount is too big")); + } + let fee_amount = fee_amount.low_u128(); + + let context = ::UniversalLocation::get(); + let fee_multilocation = MultiAsset { + id: Concrete(fee_asset), + fun: Fungible(fee_amount), + }; + let fee_multilocation = fee_multilocation + .reanchored(&dest, context) + .map_err(|_| revert("Failed to reanchor fee asset"))?; + + // Prepare XCM + let xcm = Xcm(vec![ + WithdrawAsset(fee_multilocation.clone().into()), + BuyExecution { + fees: fee_multilocation.clone().into(), + weight_limit: WeightLimit::Unlimited, + }, + Transact { + origin_kind: OriginKind::SovereignAccount, + require_weight_at_most: Weight::from_ref_time(transact_weight), + call: remote_call.into(), + }, + ]); + + log::trace!(target: "xcm-precompile:remote_transact", "Processed arguments: dest: {:?}, fee asset: {:?}, XCM: {:?}", dest, fee_multilocation, xcm); + + // Build call with origin. + let origin = Some(Runtime::AddressMapping::into_account_id( + handle.context().caller, + )) + .into(); + let call = pallet_xcm::Call::::send { + dest: Box::new(dest.into()), + message: Box::new(xcm::VersionedXcm::V3(xcm)), + }; + + // Dispatch a call. + RuntimeHelper::::try_dispatch(handle, origin, call)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + fn assets_reserve_transfer(handle: &mut impl PrecompileHandle) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(6)?; + + // Read arguments and check it + let assets: Vec = input + .read::>()? + .iter() + .cloned() + .filter_map(|address| { + let address: H160 = address.into(); + + // Special case where zero address maps to native token by convention. + if address == NATIVE_ADDRESS { + Some(Here.into()) + } else { + Runtime::address_to_asset_id(address).and_then(|x| C::reverse_ref(x).ok()) + } + }) + .collect(); + let amounts_raw = input.read::>()?; + if amounts_raw.iter().any(|x| *x > u128::MAX.into()) { + return Err(revert("Asset amount is too big")); + } + let amounts: Vec = amounts_raw.iter().map(|x| x.low_u128()).collect(); + + log::trace!(target: "xcm-precompile:assets_reserve_transfer", "Processed arguments: assets {:?}, amounts: {:?}", assets, amounts); + + // Check that assets list is valid: + // * all assets resolved to multi-location + // * all assets has corresponded amount + if assets.len() != amounts.len() || assets.is_empty() { + return Err(revert("Assets resolution failure.")); + } + + let beneficiary: MultiLocation = input.read::()?; + let dest: MultiLocation = input.read::()?; + + let fee_asset_item: u32 = input.read::()?.low_u32(); + + if fee_asset_item as usize > assets.len() { + return Err(revert("Bad fee index.")); + } + + let assets: MultiAssets = assets + .iter() + .cloned() + .zip(amounts.iter().cloned()) + .map(Into::into) + .collect::>() + .into(); + + // Build call with origin. + let origin = Some(Runtime::AddressMapping::into_account_id( + handle.context().caller, + )) + .into(); + let call = pallet_xcm::Call::::reserve_transfer_assets { + dest: Box::new(dest.into()), + beneficiary: Box::new(beneficiary.into()), + assets: Box::new(assets.into()), + fee_asset_item, + }; + + // Dispatch a call. + RuntimeHelper::::try_dispatch(handle, origin, call)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + fn send_xcm(handle: &mut impl PrecompileHandle) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(3)?; + + // Raw call arguments + let dest: MultiLocation = input.read::()?; + let xcm_call: Vec = input.read::>()?.into(); + + log::trace!(target:"xcm-precompile::send_xcm", "Raw arguments: dest: {:?}, xcm_call: {:?}", dest, xcm_call); + + let xcm = xcm::VersionedXcm::<()>::decode_all_with_depth_limit( + xcm::MAX_XCM_DECODE_DEPTH, + &mut xcm_call.as_slice(), + ) + .map_err(|_| revert("Failed to decode xcm instructions"))?; + + // Build call with origin. + let origin = Some(Runtime::AddressMapping::into_account_id( + handle.context().caller, + )) + .into(); + let call = pallet_xcm::Call::::send { + dest: Box::new(dest.into()), + message: Box::new(xcm), + }; + log::trace!(target: "xcm-send_xcm", "Processed arguments: XCM call: {:?}", call); + // Dispatch a call. + RuntimeHelper::::try_dispatch(handle, origin, call)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + fn transfer(handle: &mut impl PrecompileHandle) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(5)?; + + // Read call arguments + let currency_address = input.read::
()?; + let amount_of_tokens = input + .read::()? + .try_into() + .map_err(|_| revert("error converting amount_of_tokens, maybe value too large"))?; + let destination = input.read::()?; + let weight = input.read::()?; + + let asset_id = Runtime::address_to_asset_id(currency_address.into()) + .ok_or(revert("Failed to resolve fee asset id from address"))?; + let dest_weight_limit = if weight == u64::MAX { + WeightLimit::Unlimited + } else { + WeightLimit::Limited(Weight::from_parts(weight, DEFAULT_PROOF_SIZE)) + }; + + let call = orml_xtokens::Call::::transfer { + currency_id: asset_id.into(), + amount: amount_of_tokens, + dest: Box::new(VersionedMultiLocation::V3(destination)), + dest_weight_limit, + }; + + let origin = Some(Runtime::AddressMapping::into_account_id( + handle.context().caller, + )) + .into(); + + // Dispatch a call. + RuntimeHelper::::try_dispatch(handle, origin, call)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + fn transfer_with_fee(handle: &mut impl PrecompileHandle) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(6)?; + + // Read call arguments + let currency_address = input.read::
()?; + let amount_of_tokens = input + .read::()? + .try_into() + .map_err(|_| revert("error converting amount_of_tokens, maybe value too large"))?; + let fee = input + .read::()? + .try_into() + .map_err(|_| revert("can't convert fee"))?; + + let destination = input.read::()?; + let weight = input.read::()?; + + let asset_id = Runtime::address_to_asset_id(currency_address.into()) + .ok_or(revert("Failed to resolve fee asset id from address"))?; + let dest_weight_limit = if weight == u64::MAX { + WeightLimit::Unlimited + } else { + WeightLimit::Limited(Weight::from_parts(weight, DEFAULT_PROOF_SIZE)) + }; + + let call = orml_xtokens::Call::::transfer_with_fee { + currency_id: asset_id.into(), + amount: amount_of_tokens, + fee, + dest: Box::new(VersionedMultiLocation::V3(destination)), + dest_weight_limit, + }; + + let origin = Some(Runtime::AddressMapping::into_account_id( + handle.context().caller, + )) + .into(); + + // Dispatch a call. + RuntimeHelper::::try_dispatch(handle, origin, call)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + fn transfer_multiasset(handle: &mut impl PrecompileHandle) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(5)?; + + // Read call arguments + let asset_location = input.read::()?; + let amount_of_tokens = input + .read::()? + .try_into() + .map_err(|_| revert("error converting amount_of_tokens, maybe value too large"))?; + let destination = input.read::()?; + let weight = input.read::()?; + + let dest_weight_limit = if weight == u64::MAX { + WeightLimit::Unlimited + } else { + WeightLimit::Limited(Weight::from_parts(weight, DEFAULT_PROOF_SIZE)) + }; + + let call = orml_xtokens::Call::::transfer_multiasset { + asset: Box::new(VersionedMultiAsset::V3(MultiAsset { + id: AssetId::Concrete(asset_location), + fun: Fungibility::Fungible(amount_of_tokens), + })), + dest: Box::new(VersionedMultiLocation::V3(destination)), + dest_weight_limit, + }; + + let origin = Some(Runtime::AddressMapping::into_account_id( + handle.context().caller, + )) + .into(); + + // Dispatch a call. + RuntimeHelper::::try_dispatch(handle, origin, call)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + fn transfer_multiasset_with_fee( + handle: &mut impl PrecompileHandle, + ) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(6)?; + + // Read call arguments + let asset_location = input.read::()?; + let amount_of_tokens = input + .read::()? + .try_into() + .map_err(|_| revert("error converting amount_of_tokens, maybe value too large"))?; + let fee = input + .read::()? + .try_into() + .map_err(|_| revert("can't convert fee"))?; + let destination = input.read::()?; + let weight = input.read::()?; + + let dest_weight_limit = if weight == u64::MAX { + WeightLimit::Unlimited + } else { + WeightLimit::Limited(Weight::from_parts(weight, DEFAULT_PROOF_SIZE)) + }; + + let call = orml_xtokens::Call::::transfer_multiasset_with_fee { + asset: Box::new(VersionedMultiAsset::V3(MultiAsset { + id: AssetId::Concrete(asset_location), + fun: Fungibility::Fungible(amount_of_tokens), + })), + fee: Box::new(VersionedMultiAsset::V3(MultiAsset { + id: AssetId::Concrete(asset_location), + fun: Fungibility::Fungible(fee), + })), + dest: Box::new(VersionedMultiLocation::V3(destination)), + dest_weight_limit, + }; + + let origin = Some(Runtime::AddressMapping::into_account_id( + handle.context().caller, + )) + .into(); + + // Dispatch a call. + RuntimeHelper::::try_dispatch(handle, origin, call)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + fn transfer_multi_currencies( + handle: &mut impl PrecompileHandle, + ) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(5)?; + + let currencies: Vec<_> = input + .read::>>()? + .into(); + let fee_item = input.read::()?; + let destination = input.read::()?; + let weight = input.read::()?; + + let currencies = currencies + .into_iter() + .map(|currency| { + let currency_address: H160 = currency.get_address().into(); + let amount = currency + .get_amount() + .try_into() + .map_err(|_| revert("value too large: in currency"))?; + + Ok(( + Runtime::address_to_asset_id(currency_address.into()) + .ok_or(revert("can't convert into currency id"))? + .into(), + amount, + )) + }) + .collect::>()?; + let dest_weight_limit = if weight == u64::MAX { + WeightLimit::Unlimited + } else { + WeightLimit::Limited(Weight::from_parts(weight, DEFAULT_PROOF_SIZE)) + }; + + let call = orml_xtokens::Call::::transfer_multicurrencies { + currencies, + fee_item, + dest: Box::new(VersionedMultiLocation::V3(destination)), + dest_weight_limit, + }; + + let origin = Some(Runtime::AddressMapping::into_account_id( + handle.context().caller, + )) + .into(); + + // Dispatch a call. + RuntimeHelper::::try_dispatch(handle, origin, call)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + fn transfer_multi_assets(handle: &mut impl PrecompileHandle) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(5)?; + + let assets: Vec<_> = input + .read::>>()? + .into(); + let fee_item = input.read::()?; + let destination = input.read::()?; + let weight = input.read::()?; + + let dest_weight_limit = if weight == u64::MAX { + WeightLimit::Unlimited + } else { + WeightLimit::Limited(Weight::from_parts(weight, DEFAULT_PROOF_SIZE)) + }; + + let multiasset_vec: EvmResult> = assets + .into_iter() + .map(|evm_multiasset| { + let to_balance: u128 = evm_multiasset + .get_amount() + .try_into() + .map_err(|_| revert("value too large in assets"))?; + Ok((evm_multiasset.get_location(), to_balance).into()) + }) + .collect(); + + // Since multiassets sorts them, we need to check whether the index is still correct, + // and error otherwise as there is not much we can do other than that + let multiassets = + MultiAssets::from_sorted_and_deduplicated(multiasset_vec?).map_err(|_| { + revert("In field Assets, Provided assets either not sorted nor deduplicated") + })?; + + let call = orml_xtokens::Call::::transfer_multiassets { + assets: Box::new(VersionedMultiAssets::V3(multiassets)), + fee_item, + dest: Box::new(VersionedMultiLocation::V3(destination)), + dest_weight_limit, + }; + + let origin = Some(Runtime::AddressMapping::into_account_id( + handle.context().caller, + )) + .into(); + + // Dispatch a call. + RuntimeHelper::::try_dispatch(handle, origin, call)?; Ok(succeed(EvmDataWriter::new().write(true).build())) } diff --git a/precompiles/xcm/src/mock.rs b/precompiles/xcm/src/mock.rs index 85f1ddf094..91d3023e47 100644 --- a/precompiles/xcm/src/mock.rs +++ b/precompiles/xcm/src/mock.rs @@ -26,7 +26,6 @@ use frame_support::{ traits::{AsEnsureOriginWithArg, ConstU64, Everything, Nothing}, weights::Weight, }; -use frame_system::EnsureRoot; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; @@ -48,6 +47,9 @@ use xcm_builder::{ AllowTopLevelPaidExecutionFrom, FixedWeightBounds, SignedToAccountId32, TakeWeightCredit, }; use xcm_executor::XcmExecutor; +// orml imports +use orml_traits::location::{RelativeReserveProvider, Reserve}; +use orml_xcm_support::DisabledParachainFee; pub type AccountId = TestAccount; pub type AssetId = u128; @@ -55,6 +57,22 @@ pub type Balance = u128; pub type BlockNumber = u64; pub type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; pub type Block = frame_system::mocking::MockBlock; +pub type CurrencyId = u128; + +/// Multilocations for assetId +const PARENT: MultiLocation = MultiLocation::parent(); +const PARACHAIN: MultiLocation = MultiLocation { + parents: 1, + interior: Junctions::X1(Parachain(10)), +}; +const GENERAL_INDEX: MultiLocation = MultiLocation { + parents: 1, + interior: Junctions::X2(Parachain(10), GeneralIndex(20)), +}; +const LOCAL_ASSET: MultiLocation = MultiLocation { + parents: 0, + interior: Junctions::X1(GeneralIndex(20)), +}; pub const PRECOMPILE_ADDRESS: H160 = H160::repeat_byte(0x7B); pub const ASSET_PRECOMPILE_ADDRESS_PREFIX: &[u8] = &[255u8; 4]; @@ -149,6 +167,49 @@ impl AddressToAssetId for Runtime { } } +pub struct CurrencyIdToMultiLocation; + +impl sp_runtime::traits::Convert> for CurrencyIdToMultiLocation { + fn convert(currency: CurrencyId) -> Option { + match currency { + a if a == 1u128 => Some(PARENT), + a if a == 2u128 => Some(PARACHAIN), + a if a == 3u128 => Some(GENERAL_INDEX), + a if a == 4u128 => Some(LOCAL_ASSET), + _ => None, + } + } +} + +/// Convert `AccountId` to `MultiLocation`. +pub struct AccountIdToMultiLocation; +impl sp_runtime::traits::Convert for AccountIdToMultiLocation { + fn convert(account: AccountId) -> MultiLocation { + X1(AccountId32 { + network: None, + id: account.into(), + }) + .into() + } +} + +/// `MultiAsset` reserve location provider. It's based on `RelativeReserveProvider` and in +/// addition will convert self absolute location to relative location. +pub struct AbsoluteAndRelativeReserveProvider(PhantomData); +impl> Reserve + for AbsoluteAndRelativeReserveProvider +{ + fn reserve(asset: &MultiAsset) -> Option { + RelativeReserveProvider::reserve(asset).map(|reserve_location| { + if reserve_location == AbsoluteLocation::get() { + MultiLocation::here() + } else { + reserve_location + } + }) + } +} + parameter_types! { pub const BlockHashCount: u64 = 250; pub const SS58Prefix: u8 = 42; @@ -184,24 +245,24 @@ impl frame_system::Config for Runtime { #[derive(Debug, Clone, Copy)] pub struct TestPrecompileSet(PhantomData); -impl PrecompileSet for TestPrecompileSet +impl PrecompileSet for TestPrecompileSet where - R: pallet_evm::Config + Runtime: pallet_evm::Config + pallet_xcm::Config + pallet_assets::Config - + AddressToAssetId<::AssetId>, - XcmPrecompile>: Precompile, + + AddressToAssetId<::AssetId>, + XcmPrecompile>: Precompile, { fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { match handle.code_address() { - a if a == PRECOMPILE_ADDRESS => Some( - XcmPrecompile::>::execute(handle), - ), + a if a == PRECOMPILE_ADDRESS => { + Some(XcmPrecompile::>::execute(handle)) + } _ => None, } } - fn is_precompile(&self, address: H160, _gas: u64) -> IsPrecompileResult { + fn is_precompile(&self, address: H160, _remaining_gas: u64) -> IsPrecompileResult { IsPrecompileResult::Answer { is_precompile: address == PRECOMPILE_ADDRESS, extra_cost: 0, @@ -236,8 +297,8 @@ impl pallet_balances::Config for Runtime { type WeightInfo = (); type HoldIdentifier = (); type FreezeIdentifier = (); - type MaxHolds = ConstU32<0>; - type MaxFreezes = ConstU32<0>; + type MaxHolds = (); + type MaxFreezes = (); } // These parameters dont matter much as this will only be called by root with the forced arguments @@ -296,7 +357,7 @@ where parameter_types! { pub const PrecompilesValue: TestPrecompileSet = TestPrecompileSet(PhantomData); - pub WeightPerGas: Weight = Weight::from_parts(1, 0); + pub WeightPerGas: Weight = Weight::from_ref_time(1); } impl pallet_evm::Config for Runtime { @@ -311,7 +372,6 @@ impl pallet_evm::Config for Runtime { type Runner = pallet_evm::runner::stack::Runner; type PrecompilesType = TestPrecompileSet; type PrecompilesValue = PrecompilesValue; - type Timestamp = Timestamp; type ChainId = (); type OnChargeTransaction = (); type BlockGasLimit = (); @@ -320,12 +380,13 @@ impl pallet_evm::Config for Runtime { type OnCreate = (); type WeightInfo = (); type GasLimitPovSizeRatio = ConstU64<4>; + type Timestamp = Timestamp; } parameter_types! { - pub const RelayLocation: MultiLocation = Here.into_location(); + pub RelayNetwork: Option = Some(NetworkId::Polkadot); pub const AnyNetwork: Option = None; - pub UniversalLocation: InteriorMultiLocation = Here; + pub UniversalLocation: InteriorMultiLocation = X2(GlobalConsensus(RelayNetwork::get().unwrap()), Parachain(123)); pub Ancestry: MultiLocation = Here.into(); pub UnitWeightCost: u64 = 1_000; pub const MaxAssetsIntoHolding: u32 = 64; @@ -387,6 +448,14 @@ impl xcm_executor::Config for XcmConfig { parameter_types! { pub static AdvertisedXcmVersion: XcmVersion = 3; + pub const MaxAssetsForTransfer: usize = 2; + pub const SelfLocation: MultiLocation = Here.into_location(); + pub SelfLocationAbsolute: MultiLocation = MultiLocation { + parents: 1, + interior: X1( + Parachain(123) + ) + }; } pub type LocalOriginToLocation = SignedToAccountId32; @@ -455,9 +524,27 @@ impl pallet_xcm::Config for Runtime { type CurrencyMatcher = (); type MaxLockers = frame_support::traits::ConstU32<8>; type WeightInfo = pallet_xcm::TestWeightInfo; + type AdminOrigin = frame_system::EnsureRoot; #[cfg(feature = "runtime-benchmarks")] type ReachableDest = ReachableDest; - type AdminOrigin = EnsureRoot; +} + +impl orml_xtokens::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type CurrencyId = AssetId; + type CurrencyIdConvert = CurrencyIdToMultiLocation; + type AccountIdToMultiLocation = AccountIdToMultiLocation; + type SelfLocation = SelfLocation; + type XcmExecutor = XcmExecutor; + type Weigher = FixedWeightBounds; + type BaseXcmWeight = UnitWeightCost; + type UniversalLocation = UniversalLocation; + type MaxAssetsForTransfer = MaxAssetsForTransfer; + // Default impl. Refer to `orml-xtokens` docs for more details. + type MinXcmFee = DisabledParachainFee; + type MultiLocationsFilter = Everything; + type ReserveProvider = AbsoluteAndRelativeReserveProvider; } // Configure a mock runtime to test the pallet. @@ -473,6 +560,7 @@ construct_runtime!( Evm: pallet_evm, Timestamp: pallet_timestamp, XcmPallet: pallet_xcm, + Xtokens: orml_xtokens, } ); @@ -490,3 +578,9 @@ impl ExtBuilder { ext } } +pub(crate) fn events() -> Vec { + System::events() + .into_iter() + .map(|r| r.event) + .collect::>() +} diff --git a/precompiles/xcm/src/tests.rs b/precompiles/xcm/src/tests.rs index 4bfebd3c26..ceda9eb930 100644 --- a/precompiles/xcm/src/tests.rs +++ b/precompiles/xcm/src/tests.rs @@ -16,174 +16,575 @@ // You should have received a copy of the GNU General Public License // along with Astar. If not, see . -use assert_matches::assert_matches; - use crate::mock::*; use crate::*; +use xcm::latest::{ + AssetId, Fungibility, Junction, Junctions, MultiAsset, MultiAssets, MultiLocation, +}; +use orml_xtokens::Event as XtokensEvent; +use parity_scale_codec::Encode; use precompile_utils::testing::*; use precompile_utils::EvmDataWriter; -use sp_core::H160; +use sp_core::{H160, H256}; +use sp_runtime::traits::Convert; +use xcm::VersionedXcm; fn precompiles() -> TestPrecompileSet { PrecompilesValue::get() } -#[test] -fn wrong_assets_len_or_fee_index_reverts() { - ExtBuilder::default().build().execute_with(|| { - precompiles() - .prepare_test( - TestAccount::Alice, - PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsWithdrawNative) - .write(vec![Address::from(H160::repeat_byte(0xF1))]) - .write(Vec::::new()) - .write(H256::repeat_byte(0xF1)) - .write(true) - .write(U256::from(0_u64)) - .write(U256::from(0_u64)) - .build(), - ) - .expect_no_logs() - .execute_reverts(|output| output == b"Assets resolution failure."); - - precompiles() - .prepare_test( - TestAccount::Alice, - PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsWithdrawNative) - .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) - .write(vec![U256::from(42000u64)]) - .write(H256::repeat_byte(0xF1)) - .write(true) - .write(U256::from(0_u64)) - .write(U256::from(2_u64)) - .build(), - ) - .expect_no_logs() - .execute_reverts(|output| output == b"Bad fee index."); - }); -} +mod xcm_old_interface_test { + use super::*; + #[test] + fn wrong_assets_len_or_fee_index_reverts() { + ExtBuilder::default().build().execute_with(|| { + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsWithdrawNative) + .write(vec![Address::from(H160::repeat_byte(0xF1))]) + .write(Vec::::new()) + .write(H256::repeat_byte(0xF1)) + .write(true) + .write(U256::from(0_u64)) + .write(U256::from(0_u64)) + .build(), + ) + .expect_no_logs() + .execute_reverts(|output| output == b"Assets resolution failure."); -#[test] -fn assets_withdraw_works() { - ExtBuilder::default().build().execute_with(|| { - // SS58 - precompiles() - .prepare_test( - TestAccount::Alice, - PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsWithdrawNative) - .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) - .write(vec![U256::from(42000u64)]) - .write(H256::repeat_byte(0xF1)) - .write(true) - .write(U256::from(0_u64)) - .write(U256::from(0_u64)) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); - - // H160 - precompiles() - .prepare_test( - TestAccount::Alice, - PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsWithdrawEvm) - .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) - .write(vec![U256::from(42000u64)]) - .write(Address::from(H160::repeat_byte(0xDE))) - .write(true) - .write(U256::from(0_u64)) - .write(U256::from(0_u64)) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); - }); -} + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsWithdrawNative) + .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) + .write(vec![U256::from(42000u64)]) + .write(H256::repeat_byte(0xF1)) + .write(true) + .write(U256::from(0_u64)) + .write(U256::from(2_u64)) + .build(), + ) + .expect_no_logs() + .execute_reverts(|output| output == b"Bad fee index."); + }); + } -#[test] -fn remote_transact_works() { - ExtBuilder::default().build().execute_with(|| { - // SS58 - precompiles() - .prepare_test( - TestAccount::Alice, - PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::RemoteTransact) - .write(U256::from(0_u64)) - .write(true) - .write(Address::from(Runtime::asset_id_to_address(1_u128))) - .write(U256::from(367)) - .write(vec![0xff_u8, 0xaa, 0x77, 0x00]) - .write(U256::from(3_000_000_000u64)) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); - }); + #[test] + fn assets_withdraw_works() { + ExtBuilder::default().build().execute_with(|| { + // SS58 + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsWithdrawNative) + .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) + .write(vec![U256::from(42000u64)]) + .write(H256::repeat_byte(0xF1)) + .write(true) + .write(U256::from(0_u64)) + .write(U256::from(0_u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + // H160 + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsWithdrawEvm) + .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) + .write(vec![U256::from(42000u64)]) + .write(Address::from(H160::repeat_byte(0xDE))) + .write(true) + .write(U256::from(0_u64)) + .write(U256::from(0_u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + }); + } + + #[test] + fn remote_transact_works() { + ExtBuilder::default().build().execute_with(|| { + // SS58 + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::RemoteTransactOld) + .write(U256::from(0_u64)) + .write(true) + .write(Address::from(Runtime::asset_id_to_address(1_u128))) + .write(U256::from(367)) + .write(vec![0xff_u8, 0xaa, 0x77, 0x00]) + .write(U256::from(3_000_000_000u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + }); + } + + #[test] + fn reserve_transfer_assets_works() { + ExtBuilder::default().build().execute_with(|| { + // SS58 + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsReserveTransferNative) + .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) + .write(vec![U256::from(42000u64)]) + .write(H256::repeat_byte(0xF1)) + .write(true) + .write(U256::from(0_u64)) + .write(U256::from(0_u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + // H160 + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsReserveTransferEvm) + .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) + .write(vec![U256::from(42000u64)]) + .write(Address::from(H160::repeat_byte(0xDE))) + .write(true) + .write(U256::from(0_u64)) + .write(U256::from(0_u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + }); + + for (location, Xcm(instructions)) in take_sent_xcm() { + assert_eq!( + location, + MultiLocation { + parents: 1, + interior: Here + } + ); + + let non_native_asset = MultiAsset { + fun: Fungible(42000), + id: xcm::v3::AssetId::from(MultiLocation { + parents: 0, + interior: Here, + }), + }; + + assert!(matches!( + instructions.as_slice(), + [ + ReserveAssetDeposited(assets), + ClearOrigin, + BuyExecution { + fees, + .. + }, + DepositAsset { + beneficiary: MultiLocation { + parents: 0, + interior: X1(_), + }, + .. + } + ] + + if fees.contains(&non_native_asset) && assets.contains(&non_native_asset) + )); + } + } + + #[test] + fn reserve_transfer_currency_works() { + ExtBuilder::default().build().execute_with(|| { + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsReserveTransferNative) + .write(vec![Address::from(H160::zero())]) // zero address by convention + .write(vec![U256::from(42000u64)]) + .write(H256::repeat_byte(0xF1)) + .write(true) + .write(U256::from(0_u64)) + .write(U256::from(0_u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsReserveTransferEvm) + .write(vec![Address::from(H160::zero())]) // zero address by convention + .write(vec![U256::from(42000u64)]) + .write(Address::from(H160::repeat_byte(0xDE))) + .write(true) + .write(U256::from(0_u64)) + .write(U256::from(0_u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + }); + + for (location, Xcm(instructions)) in take_sent_xcm() { + assert_eq!( + location, + MultiLocation { + parents: 1, + interior: Here + } + ); + + let native_asset = MultiAsset { + fun: Fungible(42000), + id: xcm::v3::AssetId::from(MultiLocation { + parents: 0, + interior: X1(Parachain(123)), + }), + }; + + assert!(matches!( + instructions.as_slice(), + [ + ReserveAssetDeposited(assets), + ClearOrigin, + BuyExecution { + fees, + .. + }, + DepositAsset { + beneficiary: MultiLocation { + parents: 0, + interior: X1(_), + }, + .. + } + ] + if fees.contains(&native_asset) && assets.contains(&native_asset) + )); + } + } } +mod xcm_new_interface_test { + use super::*; + #[test] + fn wrong_assets_len_or_fee_index_reverts() { + ExtBuilder::default().build().execute_with(|| { + let dest: MultiLocation = MultiLocation { + parents: 1, + interior: Junctions::X1(Junction::Parachain(2000u32)), + }; -#[test] -fn reserve_transfer_assets_works() { - ExtBuilder::default().build().execute_with(|| { - // SS58 - precompiles() - .prepare_test( - TestAccount::Alice, - PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsReserveTransferNative) - .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) - .write(vec![U256::from(42000u64)]) - .write(H256::repeat_byte(0xF1)) - .write(true) - .write(U256::from(0_u64)) - .write(U256::from(0_u64)) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); - - // H160 - precompiles() - .prepare_test( - TestAccount::Alice, - PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsReserveTransferEvm) - .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) - .write(vec![U256::from(42000u64)]) - .write(Address::from(H160::repeat_byte(0xDE))) - .write(true) - .write(U256::from(0_u64)) - .write(U256::from(0_u64)) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); - }); - - for (location, Xcm(instructions)) in take_sent_xcm() { - assert_eq!( - location, - MultiLocation { + let beneficiary: MultiLocation = MultiLocation { + parents: 0, + interior: Junctions::X1(AccountId32 { + network: None, + id: H256::repeat_byte(0xF1).into(), + }), + }; + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsWithdraw) + .write(vec![Address::from(H160::repeat_byte(0xF1))]) + .write(Vec::::new()) + .write(beneficiary) + .write(dest) + .write(U256::from(0_u64)) + .build(), + ) + .expect_no_logs() + .execute_reverts(|output| output == b"Assets resolution failure."); + + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsWithdraw) + .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) + .write(vec![U256::from(42000u64)]) + .write(beneficiary) + .write(dest) + .write(U256::from(2_u64)) + .build(), + ) + .expect_no_logs() + .execute_reverts(|output| output == b"Bad fee index."); + }); + } + + #[test] + fn assets_withdraw_works() { + ExtBuilder::default().build().execute_with(|| { + let dest: MultiLocation = MultiLocation { parents: 1, - interior: Here - } - ); + interior: Junctions::Here, + }; - let non_native_asset = MultiAsset { - fun: Fungible(42000), - id: xcm::v3::AssetId::from(MultiLocation { + let beneficiary: MultiLocation = MultiLocation { parents: 0, - interior: Here, - }), - }; + interior: Junctions::X1(AccountId32 { + network: None, + id: H256::repeat_byte(0xF1).into(), + }), + }; + + // SS58 + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsWithdraw) + .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) + .write(vec![U256::from(42000u64)]) + .write(beneficiary) + .write(dest) + .write(U256::from(0_u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + // H160 + let beneficiary: MultiLocation = MultiLocation { + parents: 0, + interior: Junctions::X1(AccountKey20 { + network: None, + key: H160::repeat_byte(0xDE).into(), + }), + }; + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsWithdraw) + .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) + .write(vec![U256::from(42000u64)]) + .write(beneficiary) + .write(dest) + .write(U256::from(0_u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + }); + } + + #[test] + fn remote_transact_works() { + ExtBuilder::default().build().execute_with(|| { + let multilocation = MultiLocation { + parents: 1, + interior: Junctions::X1(Junction::Parachain(2000u32)), + }; + // SS58 + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::RemoteTransactNew) + .write(multilocation) + .write(Address::from(Runtime::asset_id_to_address(1_u128))) + .write(U256::from(367)) + .write(vec![0xff_u8, 0xaa, 0x77, 0x00]) + .write(U256::from(3_000_000_000u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + }); + } + + #[test] + fn reserve_transfer_assets_works() { + ExtBuilder::default().build().execute_with(|| { + let dest: MultiLocation = MultiLocation { + parents: 1, + interior: Junctions::Here, + }; + + let beneficiary: MultiLocation = MultiLocation { + parents: 0, + interior: Junctions::X1(AccountId32 { + network: None, + id: H256::repeat_byte(0xF1).into(), + }), + }; + // SS58 + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsReserveTransfer) + .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) + .write(vec![U256::from(42000u64)]) + .write(beneficiary) + .write(dest) + .write(U256::from(0_u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + // H160 + let beneficiary: MultiLocation = MultiLocation { + parents: 0, + interior: Junctions::X1(AccountKey20 { + network: None, + key: H160::repeat_byte(0xDE).into(), + }), + }; + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsReserveTransfer) + .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) + .write(vec![U256::from(42000u64)]) + .write(beneficiary) + .write(dest) + .write(U256::from(0_u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + }); + + for (location, Xcm(instructions)) in take_sent_xcm() { + assert_eq!( + location, + MultiLocation { + parents: 1, + interior: Here + } + ); + + let non_native_asset = MultiAsset { + fun: Fungible(42000), + id: xcm::v3::AssetId::from(MultiLocation { + parents: 0, + interior: Here, + }), + }; + + assert!(matches!( + instructions.as_slice(), + [ + ReserveAssetDeposited(assets), + ClearOrigin, + BuyExecution { + fees, + .. + }, + DepositAsset { + beneficiary: MultiLocation { + parents: 0, + interior: X1(_), + }, + .. + } + ] - assert_matches!( - instructions.as_slice(), + if fees.contains(&non_native_asset) && assets.contains(&non_native_asset) + )); + } + } + + #[test] + fn reserve_transfer_currency_works() { + ExtBuilder::default().build().execute_with(|| { + let dest: MultiLocation = MultiLocation { + parents: 1, + interior: Junctions::Here, + }; + + let beneficiary: MultiLocation = MultiLocation { + parents: 0, + interior: Junctions::X1(AccountId32 { + network: None, + id: H256::repeat_byte(0xF1).into(), + }), + }; + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsReserveTransfer) + .write(vec![Address::from(H160::zero())]) // zero address by convention + .write(vec![U256::from(42000u64)]) + .write(beneficiary) + .write(dest) + .write(U256::from(0_u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + let beneficiary: MultiLocation = MultiLocation { + parents: 0, + interior: Junctions::X1(AccountKey20 { + network: None, + key: H160::repeat_byte(0xDE).into(), + }), + }; + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::AssetsReserveTransfer) + .write(vec![Address::from(H160::zero())]) // zero address by convention + .write(vec![U256::from(42000u64)]) + .write(beneficiary) + .write(dest) + .write(U256::from(0_u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + }); + + for (location, Xcm(instructions)) in take_sent_xcm() { + assert_eq!( + location, + MultiLocation { + parents: 1, + interior: Here + } + ); + + let native_asset = MultiAsset { + fun: Fungible(42000), + id: xcm::v3::AssetId::from(MultiLocation { + parents: 0, + interior: X1(Parachain(123)), + }), + }; + + assert!(matches!( + instructions.as_slice(), [ ReserveAssetDeposited(assets), ClearOrigin, @@ -200,83 +601,485 @@ fn reserve_transfer_assets_works() { } ] - if fees.contains(&non_native_asset) && assets.contains(&non_native_asset) - ); + if fees.contains(&native_asset) && assets.contains(&native_asset) + )); + } + } + + #[test] + fn test_send_clear_origin() { + ExtBuilder::default().build().execute_with(|| { + let dest: MultiLocation = MultiLocation { + parents: 1, + interior: Junctions::X1(Junction::AccountId32 { + network: None, + id: H256::repeat_byte(0xF1).into(), + }), + }; + let xcm_to_send = VersionedXcm::<()>::V3(Xcm(vec![ClearOrigin])).encode(); + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::SendXCM) + .write(dest) + .write(Bytes::from(xcm_to_send.as_slice())) + .build(), + ) + // Fixed: TestWeightInfo + (BaseXcmWeight * MessageLen) + .expect_cost(100001000) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + let sent_messages = take_sent_xcm(); + let (_, sent_message) = sent_messages.first().unwrap(); + // Lets make sure the message is as expected + assert!(sent_message.0.contains(&ClearOrigin)); + }) } } -#[test] -fn reserve_transfer_currency_works() { - ExtBuilder::default().build().execute_with(|| { - precompiles() - .prepare_test( - TestAccount::Alice, - PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsReserveTransferNative) - .write(vec![Address::from(H160::zero())]) // zero address by convention - .write(vec![U256::from(42000u64)]) - .write(H256::repeat_byte(0xF1)) - .write(true) - .write(U256::from(0_u64)) - .write(U256::from(0_u64)) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); - - precompiles() - .prepare_test( - TestAccount::Alice, - PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsReserveTransferEvm) - .write(vec![Address::from(H160::zero())]) // zero address by convention - .write(vec![U256::from(42000u64)]) - .write(Address::from(H160::repeat_byte(0xDE))) - .write(true) - .write(U256::from(0_u64)) - .write(U256::from(0_u64)) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); - }); - - for (location, Xcm(instructions)) in take_sent_xcm() { - assert_eq!( - location, - MultiLocation { +mod xtokens_interface_test { + use super::*; + #[test] + fn xtokens_transfer_works() { + ExtBuilder::default().build().execute_with(|| { + let parent_destination = MultiLocation { + parents: 1, + interior: Junctions::X1(Junction::AccountId32 { + network: None, + id: [1u8; 32], + }), + }; + + let sibling_parachain_location = MultiLocation { + parents: 1, + interior: Junctions::X2( + Junction::Parachain(10), + Junction::AccountId32 { + network: None, + id: [1u8; 32], + }, + ), + }; + + // sending relay token back to relay chain + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::XtokensTransfer) + .write(Address::from(Runtime::asset_id_to_address(1u128))) // zero address by convention + .write(U256::from(42000u64)) + .write(parent_destination) + .write(U256::from(3_000_000_000u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + let expected_asset: MultiAsset = MultiAsset { + id: AssetId::Concrete(CurrencyIdToMultiLocation::convert(1).unwrap()), + fun: Fungibility::Fungible(42000), + }; + + let expected: crate::mock::RuntimeEvent = + mock::RuntimeEvent::Xtokens(XtokensEvent::TransferredMultiAssets { + sender: TestAccount::Alice.into(), + assets: vec![expected_asset.clone()].into(), + fee: expected_asset, + dest: parent_destination, + }) + .into(); + assert!(events().contains(&expected)); + + // sending parachain token back to parachain + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::XtokensTransfer) + .write(Address::from(Runtime::asset_id_to_address(2u128))) // zero address by convention + .write(U256::from(42000u64)) + .write(sibling_parachain_location) + .write(U256::from(3_000_000_000u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + let expected_asset: MultiAsset = MultiAsset { + id: AssetId::Concrete(CurrencyIdToMultiLocation::convert(2).unwrap()), + fun: Fungibility::Fungible(42000), + }; + + let expected: crate::mock::RuntimeEvent = + mock::RuntimeEvent::Xtokens(XtokensEvent::TransferredMultiAssets { + sender: TestAccount::Alice.into(), + assets: vec![expected_asset.clone()].into(), + fee: expected_asset, + dest: sibling_parachain_location, + }) + .into(); + assert!(events().contains(&expected)); + }); + } + + #[test] + fn xtokens_transfer_with_fee_works() { + ExtBuilder::default().build().execute_with(|| { + let parent_destination = MultiLocation { + parents: 1, + interior: Junctions::X1(Junction::AccountId32 { + network: None, + id: [1u8; 32], + }), + }; + + // sending relay token back to relay chain + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::XtokensTransferWithFee) + .write(Address::from(Runtime::asset_id_to_address(1u128))) // zero address by convention + .write(U256::from(42000u64)) + .write(U256::from(50)) + .write(parent_destination) + .write(U256::from(3_000_000_000u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + let expected_asset: MultiAsset = MultiAsset { + id: AssetId::Concrete(CurrencyIdToMultiLocation::convert(1).unwrap()), + fun: Fungibility::Fungible(42000), + }; + let expected_fee: MultiAsset = MultiAsset { + id: AssetId::Concrete(CurrencyIdToMultiLocation::convert(1).unwrap()), + fun: Fungibility::Fungible(50), + }; + + let expected: crate::mock::RuntimeEvent = + mock::RuntimeEvent::Xtokens(XtokensEvent::TransferredMultiAssets { + sender: TestAccount::Alice.into(), + assets: vec![expected_asset.clone(), expected_fee.clone()].into(), + fee: expected_fee, + dest: parent_destination, + }) + .into(); + assert!(events().contains(&expected)); + }); + } + + #[test] + fn transfer_multiasset_works() { + ExtBuilder::default().build().execute_with(|| { + let relay_token_location = MultiLocation { parents: 1, - interior: Here - } + interior: Junctions::Here, + }; + let relay_destination = MultiLocation { + parents: 1, + interior: Junctions::X1(Junction::AccountId32 { + network: None, + id: [1u8; 32], + }), + }; + let para_destination = MultiLocation { + parents: 1, + interior: Junctions::X2( + Junction::Parachain(10), + Junction::AccountId32 { + network: None, + id: [1u8; 32], + }, + ), + }; + + let amount = 4200u64; + // relay token to relay + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::XtokensTransferMultiasset) + .write(relay_token_location) // zero address by convention + .write(U256::from(amount)) + .write(relay_destination) + .write(U256::from(3_000_000_000u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + let expected_asset: MultiAsset = MultiAsset { + id: AssetId::Concrete(relay_token_location), + fun: Fungibility::Fungible(amount.into()), + }; + let expected: crate::mock::RuntimeEvent = + mock::RuntimeEvent::Xtokens(XtokensEvent::TransferredMultiAssets { + sender: TestAccount::Alice.into(), + assets: vec![expected_asset.clone()].into(), + fee: expected_asset, + dest: relay_destination, + }) + .into(); + + // Assert that the events vector contains the one expected + assert!(events().contains(&expected)); + + // relay to para + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::XtokensTransferMultiasset) + .write(relay_token_location) // zero address by convention + .write(U256::from(amount)) + .write(para_destination) + .write(U256::from(3_000_000_000u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + let expected_asset: MultiAsset = MultiAsset { + id: AssetId::Concrete(relay_token_location), + fun: Fungibility::Fungible(amount.into()), + }; + let expected: crate::mock::RuntimeEvent = + mock::RuntimeEvent::Xtokens(XtokensEvent::TransferredMultiAssets { + sender: TestAccount::Alice.into(), + assets: vec![expected_asset.clone()].into(), + fee: expected_asset, + dest: para_destination, + }) + .into(); + + // Assert that the events vector contains the one expected + assert!(events().contains(&expected)); + }); + } + + #[test] + fn transfer_multi_currencies_works() { + let destination = MultiLocation::new( + 1, + Junctions::X1(Junction::AccountId32 { + network: None, + id: [1u8; 32], + }), ); + // NOTE: Currently only support `ToReserve` with relay-chain asset as fee. other case + // like `NonReserve` or `SelfReserve` with relay-chain fee is not support. + let currencies: Vec = vec![ + ( + Address::from(Runtime::asset_id_to_address(2u128)), + U256::from(500), + ) + .into(), + ( + Address::from(Runtime::asset_id_to_address(3u128)), + U256::from(500), + ) + .into(), + ]; - let native_asset = MultiAsset { - fun: Fungible(42000), - id: xcm::v3::AssetId::from(MultiLocation { - parents: 0, - interior: X1(OnlyChild), + ExtBuilder::default().build().execute_with(|| { + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::XtokensTransferMulticurrencies) + .write(currencies) // zero address by convention + .write(U256::from(0)) + .write(destination) + .write(U256::from(3_000_000_000u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + let expected_asset_1: MultiAsset = MultiAsset { + id: AssetId::Concrete(CurrencyIdToMultiLocation::convert(2u128).unwrap()), + fun: Fungibility::Fungible(500), + }; + let expected_asset_2: MultiAsset = MultiAsset { + id: AssetId::Concrete(CurrencyIdToMultiLocation::convert(3u128).unwrap()), + fun: Fungibility::Fungible(500), + }; + + let expected: crate::mock::RuntimeEvent = + mock::RuntimeEvent::Xtokens(XtokensEvent::TransferredMultiAssets { + sender: TestAccount::Alice.into(), + assets: vec![expected_asset_1.clone(), expected_asset_2].into(), + fee: expected_asset_1, + dest: destination, + }) + .into(); + assert!(events().contains(&expected)); + }); + } + + #[test] + fn transfer_multi_currencies_cannot_insert_more_than_max() { + let destination = MultiLocation::new( + 1, + Junctions::X1(Junction::AccountId32 { + network: None, + id: [1u8; 32], }), - }; + ); + // we only allow upto 2 currencies to be transfered + let currencies: Vec = vec![ + ( + Address::from(Runtime::asset_id_to_address(2u128)), + U256::from(500), + ) + .into(), + ( + Address::from(Runtime::asset_id_to_address(3u128)), + U256::from(500), + ) + .into(), + ( + Address::from(Runtime::asset_id_to_address(4u128)), + U256::from(500), + ) + .into(), + ]; - assert_matches!( - instructions.as_slice(), - [ - ReserveAssetDeposited(assets), - ClearOrigin, - BuyExecution { - fees, - .. + ExtBuilder::default().build().execute_with(|| { + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::XtokensTransferMulticurrencies) + .write(currencies) // zero address by convention + .write(U256::from(0)) + .write(destination) + .write(U256::from(3_000_000_000u64)) + .build(), + ) + .expect_no_logs() + .execute_reverts(|output| { + output == b"value too large : Array has more than max items allowed" + }); + }); + } + + #[test] + fn transfer_multiassets_works() { + let destination = MultiLocation::new( + 1, + Junctions::X2( + Junction::Parachain(2), + Junction::AccountId32 { + network: None, + id: [1u8; 32], }, - DepositAsset { - beneficiary: MultiLocation { - parents: 0, - interior: X1(_), - }, - .. - } - ] + ), + ); - if fees.contains(&native_asset) && assets.contains(&native_asset) + let asset_1_location = MultiLocation::new( + 1, + Junctions::X2(Junction::Parachain(2), Junction::GeneralIndex(0u128)), + ); + let asset_2_location = MultiLocation::new( + 1, + Junctions::X2(Junction::Parachain(2), Junction::GeneralIndex(1u128)), + ); + + let assets: Vec = vec![ + (asset_1_location.clone(), U256::from(500)).into(), + (asset_2_location.clone(), U256::from(500)).into(), + ]; + + let multiassets = MultiAssets::from_sorted_and_deduplicated(vec![ + (asset_1_location.clone(), 500).into(), + (asset_2_location, 500).into(), + ]) + .unwrap(); + + ExtBuilder::default().build().execute_with(|| { + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::XtokensTransferMultiassets) + .write(assets) // zero address by convention + .write(U256::from(0)) + .write(destination) + .write(U256::from(3_000_000_000u64)) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + let expected: crate::mock::RuntimeEvent = + mock::RuntimeEvent::Xtokens(XtokensEvent::TransferredMultiAssets { + sender: TestAccount::Alice.into(), + assets: multiassets, + fee: (asset_1_location, 500).into(), + dest: destination, + }) + .into(); + assert!(events().contains(&expected)); + }); + } + + #[test] + fn transfer_multiassets_cannot_insert_more_than_max() { + // We have definaed MaxAssetsForTransfer = 2, + // so any number greater than MaxAssetsForTransfer will result in error + let destination = MultiLocation::new( + 1, + Junctions::X2( + Junction::Parachain(2), + Junction::AccountId32 { + network: None, + id: [1u8; 32], + }, + ), + ); + + let asset_1_location = MultiLocation::new( + 1, + Junctions::X2(Junction::Parachain(2), Junction::GeneralIndex(0u128)), ); + let asset_2_location = MultiLocation::new( + 1, + Junctions::X2(Junction::Parachain(2), Junction::GeneralIndex(1u128)), + ); + let asset_3_location = MultiLocation::new( + 1, + Junctions::X2(Junction::Parachain(2), Junction::GeneralIndex(3u128)), + ); + + let assets: Vec = vec![ + (asset_1_location.clone(), U256::from(500)).into(), + (asset_2_location.clone(), U256::from(500)).into(), + (asset_3_location.clone(), U256::from(500)).into(), + ]; + + ExtBuilder::default().build().execute_with(|| { + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::XtokensTransferMultiassets) + .write(assets) // zero address by convention + .write(U256::from(0)) + .write(destination) + .write(U256::from(3_000_000_000u64)) + .build(), + ) + .expect_no_logs() + .execute_reverts(|output| { + output == b"value too large : Array has more than max items allowed" + }); + }); } } From 06c65c5d89efd4a1fad9f2da066fb4865c0c8aed Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Mon, 7 Aug 2023 15:53:56 +0530 Subject: [PATCH 02/19] update version --- Cargo.lock | 2 +- precompiles/xcm/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a9b4cc8ff0..2ae9cb2b92 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7429,7 +7429,7 @@ dependencies = [ [[package]] name = "pallet-evm-precompile-xcm" -version = "0.9.0" +version = "0.9.1" dependencies = [ "assert_matches", "derive_more", diff --git a/precompiles/xcm/Cargo.toml b/precompiles/xcm/Cargo.toml index 66540f926e..16b372c34f 100644 --- a/precompiles/xcm/Cargo.toml +++ b/precompiles/xcm/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "pallet-evm-precompile-xcm" description = "Basic XCM support for EVM." -version = "0.9.0" +version = "0.9.1" authors.workspace = true edition.workspace = true homepage.workspace = true From 9c61ba893b6ac8596fa6f6553253b3b1c99a20ca Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Mon, 7 Aug 2023 16:16:05 +0530 Subject: [PATCH 03/19] remove xcm-precompile from astar runtime --- precompiles/xcm/src/lib.rs | 2 +- runtime/astar/src/precompiles.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/precompiles/xcm/src/lib.rs b/precompiles/xcm/src/lib.rs index 1ca025f1eb..134edcae09 100644 --- a/precompiles/xcm/src/lib.rs +++ b/precompiles/xcm/src/lib.rs @@ -95,7 +95,7 @@ where } } /// A precompile that expose XCM related functions. -pub struct XcmPrecompile(PhantomData<(T, C)>); +pub struct XcmPrecompile(PhantomData<(Runtime, C)>); impl Precompile for XcmPrecompile where diff --git a/runtime/astar/src/precompiles.rs b/runtime/astar/src/precompiles.rs index 0fd6df0cb1..c3c9cfc855 100644 --- a/runtime/astar/src/precompiles.rs +++ b/runtime/astar/src/precompiles.rs @@ -33,7 +33,7 @@ use pallet_evm_precompile_sha3fips::Sha3FIPS256; use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripemd160, Sha256}; use pallet_evm_precompile_sr25519::Sr25519Precompile; use pallet_evm_precompile_substrate_ecdsa::SubstrateEcdsaPrecompile; -use pallet_evm_precompile_xcm::XcmPrecompile; +// use pallet_evm_precompile_xcm::XcmPrecompile; use sp_core::H160; use sp_std::fmt::Debug; use sp_std::marker::PhantomData; @@ -69,7 +69,7 @@ impl PrecompileSet for AstarNetworkPrecompiles where Erc20AssetsPrecompileSet: PrecompileSet, DappsStakingWrapper: Precompile, - XcmPrecompile: Precompile, + // XcmPrecompile: Precompile, Dispatch: Precompile, R: pallet_evm::Config + pallet_assets::Config @@ -112,8 +112,8 @@ where a if a == hash(20482) => Some(Sr25519Precompile::::execute(handle)), // SubstrateEcdsa 0x5003 a if a == hash(20483) => Some(SubstrateEcdsaPrecompile::::execute(handle)), - // Xcm 0x5004 - a if a == hash(20484) => Some(XcmPrecompile::::execute(handle)), + // // Xcm 0x5004 + // a if a == hash(20484) => Some(XcmPrecompile::::execute(handle)), // If the address matches asset prefix, the we route through the asset precompile set a if &a.to_fixed_bytes()[0..4] == ASSET_PRECOMPILE_ADDRESS_PREFIX => { Erc20AssetsPrecompileSet::::new().execute(handle) From 6309b53d780c10ffa7427ff2899ee9e583c1b62e Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Wed, 9 Aug 2023 09:21:25 +0530 Subject: [PATCH 04/19] random proof size for weights --- precompiles/xcm/src/lib.rs | 4 ++-- precompiles/xcm/src/mock.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/precompiles/xcm/src/lib.rs b/precompiles/xcm/src/lib.rs index 134edcae09..ae2748853b 100644 --- a/precompiles/xcm/src/lib.rs +++ b/precompiles/xcm/src/lib.rs @@ -330,7 +330,7 @@ where }, Transact { origin_kind: OriginKind::SovereignAccount, - require_weight_at_most: Weight::from_ref_time(transact_weight), + require_weight_at_most: Weight::from_parts(transact_weight,DEFAULT_PROOF_SIZE), call: remote_call.into(), }, ]); @@ -568,7 +568,7 @@ where }, Transact { origin_kind: OriginKind::SovereignAccount, - require_weight_at_most: Weight::from_ref_time(transact_weight), + require_weight_at_most: Weight::from_parts(transact_weight,DEFAULT_PROOF_SIZE), call: remote_call.into(), }, ]); diff --git a/precompiles/xcm/src/mock.rs b/precompiles/xcm/src/mock.rs index 91d3023e47..7b7ab4c47d 100644 --- a/precompiles/xcm/src/mock.rs +++ b/precompiles/xcm/src/mock.rs @@ -357,7 +357,7 @@ where parameter_types! { pub const PrecompilesValue: TestPrecompileSet = TestPrecompileSet(PhantomData); - pub WeightPerGas: Weight = Weight::from_ref_time(1); + pub WeightPerGas: Weight = Weight::from_parts(1,0); } impl pallet_evm::Config for Runtime { From 2a21e1e93f43f30e7ef8f1285987ce811511a15a Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Wed, 9 Aug 2023 09:40:13 +0530 Subject: [PATCH 05/19] add logs --- precompiles/utils/src/xcm.rs | 2 +- precompiles/xcm/src/lib.rs | 28 ++++++++++++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/precompiles/utils/src/xcm.rs b/precompiles/utils/src/xcm.rs index 96942a4b64..9d1012feaf 100644 --- a/precompiles/utils/src/xcm.rs +++ b/precompiles/utils/src/xcm.rs @@ -353,7 +353,7 @@ impl EvmData for MultiLocation { <(u8, Junctions)>::has_static_size() } } - +#[derive(Debug)] pub struct EvmMultiAsset { location: MultiLocation, amount: U256, diff --git a/precompiles/xcm/src/lib.rs b/precompiles/xcm/src/lib.rs index ae2748853b..73505acc37 100644 --- a/precompiles/xcm/src/lib.rs +++ b/precompiles/xcm/src/lib.rs @@ -330,7 +330,7 @@ where }, Transact { origin_kind: OriginKind::SovereignAccount, - require_weight_at_most: Weight::from_parts(transact_weight,DEFAULT_PROOF_SIZE), + require_weight_at_most: Weight::from_parts(transact_weight, DEFAULT_PROOF_SIZE), call: remote_call.into(), }, ]); @@ -568,7 +568,7 @@ where }, Transact { origin_kind: OriginKind::SovereignAccount, - require_weight_at_most: Weight::from_parts(transact_weight,DEFAULT_PROOF_SIZE), + require_weight_at_most: Weight::from_parts(transact_weight, DEFAULT_PROOF_SIZE), call: remote_call.into(), }, ]); @@ -714,6 +714,10 @@ where WeightLimit::Limited(Weight::from_parts(weight, DEFAULT_PROOF_SIZE)) }; + log::trace!(target: "xcm-precompile::transfer", "Raw arguments: currency_address: {:?}, amount_of_tokens: {:?}, destination: {:?}, \ + weight: {:?}, calculated asset_id: {:?}", + currency_address, amount_of_tokens, destination, weight, asset_id); + let call = orml_xtokens::Call::::transfer { currency_id: asset_id.into(), amount: amount_of_tokens, @@ -758,6 +762,10 @@ where WeightLimit::Limited(Weight::from_parts(weight, DEFAULT_PROOF_SIZE)) }; + log::trace!(target: "xcm-precompile::transfer_with_fee", "Raw arguments: currency_address: {:?}, amount_of_tokens: {:?}, destination: {:?}, \ + weight: {:?}, calculated asset_id: {:?}", + currency_address, amount_of_tokens, destination, weight, asset_id); + let call = orml_xtokens::Call::::transfer_with_fee { currency_id: asset_id.into(), amount: amount_of_tokens, @@ -796,6 +804,10 @@ where WeightLimit::Limited(Weight::from_parts(weight, DEFAULT_PROOF_SIZE)) }; + log::trace!(target: "xcm-precompile::transfer_multiasset", "Raw arguments: asset_location: {:?}, amount_of_tokens: {:?}, destination: {:?}, \ + weight: {:?}", + asset_location, amount_of_tokens, destination, weight); + let call = orml_xtokens::Call::::transfer_multiasset { asset: Box::new(VersionedMultiAsset::V3(MultiAsset { id: AssetId::Concrete(asset_location), @@ -841,6 +853,10 @@ where WeightLimit::Limited(Weight::from_parts(weight, DEFAULT_PROOF_SIZE)) }; + log::trace!(target: "xcm-precompile::transfer_multiasset_with_fee", "Raw arguments: asset_location: {:?}, amount_of_tokens: {:?}, fee{:?}, destination: {:?}, \ + weight: {:?}", + asset_location, amount_of_tokens, fee, destination, weight); + let call = orml_xtokens::Call::::transfer_multiasset_with_fee { asset: Box::new(VersionedMultiAsset::V3(MultiAsset { id: AssetId::Concrete(asset_location), @@ -901,6 +917,10 @@ where WeightLimit::Limited(Weight::from_parts(weight, DEFAULT_PROOF_SIZE)) }; + log::trace!(target: "xcm-precompile::transfer_multi_currencies", "Raw arguments: currencies: {:?}, fee_item{:?}, destination: {:?}, \ + weight: {:?}", + currencies, fee_item, destination, weight); + let call = orml_xtokens::Call::::transfer_multicurrencies { currencies, fee_item, @@ -936,6 +956,10 @@ where WeightLimit::Limited(Weight::from_parts(weight, DEFAULT_PROOF_SIZE)) }; + log::trace!(target: "xcm-precompile::transfer_multi_assets", "Raw arguments: assets: {:?}, fee_item{:?}, destination: {:?}, \ + weight: {:?}", + assets, fee_item, destination, weight); + let multiasset_vec: EvmResult> = assets .into_iter() .map(|evm_multiasset| { From 9719ae9380dbcde6e6466b96497829d7cde44abd Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Thu, 10 Aug 2023 15:46:19 +0530 Subject: [PATCH 06/19] add WeightV2 struct, implement EvmData traits --- precompiles/utils/src/xcm.rs | 39 +++++++++++++++++++++++++++++- precompiles/xcm/XCM_v2.sol | 7 +++++- precompiles/xcm/Xtokens.sol | 17 ++++++++----- precompiles/xcm/src/lib.rs | 46 ++++++++++++++++++------------------ precompiles/xcm/src/tests.rs | 32 +++++++++++++++++-------- 5 files changed, 100 insertions(+), 41 deletions(-) diff --git a/precompiles/utils/src/xcm.rs b/precompiles/utils/src/xcm.rs index 9d1012feaf..d6072dc125 100644 --- a/precompiles/utils/src/xcm.rs +++ b/precompiles/utils/src/xcm.rs @@ -26,7 +26,7 @@ use crate::Address; use sp_core::U256; use { crate::{bytes::*, revert, EvmData, EvmDataReader, EvmDataWriter, EvmResult}, - frame_support::{ensure, traits::ConstU32}, + frame_support::{ensure, pallet_prelude::Weight, traits::ConstU32}, sp_core::H256, sp_std::vec::Vec, xcm::latest::{Junction, Junctions, MultiLocation, NetworkId}, @@ -353,6 +353,43 @@ impl EvmData for MultiLocation { <(u8, Junctions)>::has_static_size() } } + +#[derive(Debug, Clone)] +pub struct WeightV2 { + ref_time: u64, + proof_size: u64, +} +impl WeightV2 { + pub fn from(ref_time: u64, proof_size: u64) -> Self { + WeightV2 { + ref_time, + proof_size, + } + } + pub fn get_weight(&self) -> Weight { + Weight::from_parts(self.ref_time, self.proof_size) + } + pub fn is_max(&self) -> bool { + self.ref_time == u64::MAX + } +} +impl EvmData for WeightV2 { + fn read(reader: &mut EvmDataReader) -> EvmResult { + let (ref_time, proof_size) = reader.read()?; + Ok(WeightV2 { + ref_time, + proof_size, + }) + } + + fn write(writer: &mut EvmDataWriter, value: Self) { + EvmData::write(writer, (value.ref_time, value.proof_size)); + } + + fn has_static_size() -> bool { + <(U256, U256)>::has_static_size() + } +} #[derive(Debug)] pub struct EvmMultiAsset { location: MultiLocation, diff --git a/precompiles/xcm/XCM_v2.sol b/precompiles/xcm/XCM_v2.sol index 24233a7526..d0d36acc30 100644 --- a/precompiles/xcm/XCM_v2.sol +++ b/precompiles/xcm/XCM_v2.sol @@ -9,6 +9,11 @@ interface XCM { uint8 parents; bytes[] interior; } + + struct WeightV2{ + uint64 ref_time; + uint64 proof_size; + } /** * @dev Withdraw assets using PalletXCM call. @@ -45,7 +50,7 @@ interface XCM { address payment_asset_id, uint256 payment_amount, bytes calldata call, - uint64 transact_weight + WeightV2 transact_weight ) external returns (bool); /** diff --git a/precompiles/xcm/Xtokens.sol b/precompiles/xcm/Xtokens.sol index 2d082890b5..a666c2f24f 100644 --- a/precompiles/xcm/Xtokens.sol +++ b/precompiles/xcm/Xtokens.sol @@ -12,6 +12,11 @@ interface Xtokens { bytes[] interior; } + struct WeightV2{ + uint64 ref_time; + uint64 proof_size; + } + // A MultiAsset is defined by a multilocation and an amount struct MultiAsset { Multilocation location; @@ -35,7 +40,7 @@ interface Xtokens { address currencyAddress, uint256 amount, Multilocation memory destination, - uint64 weight + WeightV2 memory weight ) external returns (bool); /// Transfer a token through XCM based on its currencyId specifying fee @@ -50,7 +55,7 @@ interface Xtokens { uint256 amount, uint256 fee, Multilocation memory destination, - uint64 weight + WeightV2 memory weight ) external returns (bool); /// Transfer a token through XCM based on its MultiLocation @@ -65,7 +70,7 @@ interface Xtokens { Multilocation memory asset, uint256 amount, Multilocation memory destination, - uint64 weight + WeightV2 memory weight ) external returns (bool); /// Transfer a token through XCM based on its MultiLocation specifying fee @@ -81,7 +86,7 @@ interface Xtokens { uint256 amount, uint256 fee, Multilocation memory destination, - uint64 weight + WeightV2 memory weight ) external returns (bool); /// Transfer several tokens at once through XCM based on its address specifying fee @@ -95,7 +100,7 @@ interface Xtokens { Currency[] memory currencies, uint32 feeItem, Multilocation memory destination, - uint64 weight + WeightV2 memory weight ) external returns (bool); /// Transfer several tokens at once through XCM based on its location specifying fee @@ -109,6 +114,6 @@ interface Xtokens { MultiAsset[] memory assets, uint32 feeItem, Multilocation memory destination, - uint64 weight + WeightV2 memory weight ) external returns (bool); } diff --git a/precompiles/xcm/src/lib.rs b/precompiles/xcm/src/lib.rs index 73505acc37..f9c469b8af 100644 --- a/precompiles/xcm/src/lib.rs +++ b/precompiles/xcm/src/lib.rs @@ -42,7 +42,7 @@ use precompile_utils::{ bytes::BoundedBytes, data::BoundedVec, revert, succeed, - xcm::{Currency, EvmMultiAsset}, + xcm::{Currency, EvmMultiAsset, WeightV2}, Address, Bytes, EvmDataWriter, EvmResult, FunctionModifier, PrecompileHandleExt, RuntimeHelper, }; #[cfg(test)] @@ -80,7 +80,7 @@ pub enum Action { /// Dummy H160 address representing native currency (e.g. ASTR or SDN) const NATIVE_ADDRESS: H160 = H160::zero(); /// Dummy default 64KB -const DEFAULT_PROOF_SIZE: u64 = 1024 * 64; +const DEFAULT_PROOF_SIZE: u64 = 1024 * 256; pub type XBalanceOf = ::Balance; @@ -524,10 +524,10 @@ where let fee_amount = input.read::()?; let remote_call: Vec = input.read::()?.into(); - let transact_weight = input.read::()?; + let transact_weight = input.read::()?; log::trace!(target: "xcm-precompile::remote_transact", "Raw arguments: dest: {:?}, fee_asset_addr: {:?} \ - fee_amount: {:?}, remote_call: {:?}, transact_weight: {}", + fee_amount: {:?}, remote_call: {:?}, transact_weight: {:?}", dest, fee_asset_addr, fee_amount, remote_call, transact_weight); let fee_asset = { @@ -568,7 +568,7 @@ where }, Transact { origin_kind: OriginKind::SovereignAccount, - require_weight_at_most: Weight::from_parts(transact_weight, DEFAULT_PROOF_SIZE), + require_weight_at_most: transact_weight.get_weight(), call: remote_call.into(), }, ]); @@ -704,14 +704,14 @@ where .try_into() .map_err(|_| revert("error converting amount_of_tokens, maybe value too large"))?; let destination = input.read::()?; - let weight = input.read::()?; + let weight = input.read::()?; let asset_id = Runtime::address_to_asset_id(currency_address.into()) .ok_or(revert("Failed to resolve fee asset id from address"))?; - let dest_weight_limit = if weight == u64::MAX { + let dest_weight_limit = if weight.is_max() { WeightLimit::Unlimited } else { - WeightLimit::Limited(Weight::from_parts(weight, DEFAULT_PROOF_SIZE)) + WeightLimit::Limited(weight.get_weight()) }; log::trace!(target: "xcm-precompile::transfer", "Raw arguments: currency_address: {:?}, amount_of_tokens: {:?}, destination: {:?}, \ @@ -752,14 +752,14 @@ where .map_err(|_| revert("can't convert fee"))?; let destination = input.read::()?; - let weight = input.read::()?; + let weight = input.read::()?; let asset_id = Runtime::address_to_asset_id(currency_address.into()) .ok_or(revert("Failed to resolve fee asset id from address"))?; - let dest_weight_limit = if weight == u64::MAX { + let dest_weight_limit = if weight.is_max() { WeightLimit::Unlimited } else { - WeightLimit::Limited(Weight::from_parts(weight, DEFAULT_PROOF_SIZE)) + WeightLimit::Limited(weight.get_weight()) }; log::trace!(target: "xcm-precompile::transfer_with_fee", "Raw arguments: currency_address: {:?}, amount_of_tokens: {:?}, destination: {:?}, \ @@ -796,12 +796,12 @@ where .try_into() .map_err(|_| revert("error converting amount_of_tokens, maybe value too large"))?; let destination = input.read::()?; - let weight = input.read::()?; + let weight = input.read::()?; - let dest_weight_limit = if weight == u64::MAX { + let dest_weight_limit = if weight.is_max() { WeightLimit::Unlimited } else { - WeightLimit::Limited(Weight::from_parts(weight, DEFAULT_PROOF_SIZE)) + WeightLimit::Limited(weight.get_weight()) }; log::trace!(target: "xcm-precompile::transfer_multiasset", "Raw arguments: asset_location: {:?}, amount_of_tokens: {:?}, destination: {:?}, \ @@ -845,12 +845,12 @@ where .try_into() .map_err(|_| revert("can't convert fee"))?; let destination = input.read::()?; - let weight = input.read::()?; + let weight = input.read::()?; - let dest_weight_limit = if weight == u64::MAX { + let dest_weight_limit = if weight.is_max() { WeightLimit::Unlimited } else { - WeightLimit::Limited(Weight::from_parts(weight, DEFAULT_PROOF_SIZE)) + WeightLimit::Limited(weight.get_weight()) }; log::trace!(target: "xcm-precompile::transfer_multiasset_with_fee", "Raw arguments: asset_location: {:?}, amount_of_tokens: {:?}, fee{:?}, destination: {:?}, \ @@ -892,7 +892,7 @@ where .into(); let fee_item = input.read::()?; let destination = input.read::()?; - let weight = input.read::()?; + let weight = input.read::()?; let currencies = currencies .into_iter() @@ -911,10 +911,10 @@ where )) }) .collect::>()?; - let dest_weight_limit = if weight == u64::MAX { + let dest_weight_limit = if weight.is_max() { WeightLimit::Unlimited } else { - WeightLimit::Limited(Weight::from_parts(weight, DEFAULT_PROOF_SIZE)) + WeightLimit::Limited(weight.get_weight()) }; log::trace!(target: "xcm-precompile::transfer_multi_currencies", "Raw arguments: currencies: {:?}, fee_item{:?}, destination: {:?}, \ @@ -948,12 +948,12 @@ where .into(); let fee_item = input.read::()?; let destination = input.read::()?; - let weight = input.read::()?; + let weight = input.read::()?; - let dest_weight_limit = if weight == u64::MAX { + let dest_weight_limit = if weight.is_max() { WeightLimit::Unlimited } else { - WeightLimit::Limited(Weight::from_parts(weight, DEFAULT_PROOF_SIZE)) + WeightLimit::Limited(weight.get_weight()) }; log::trace!(target: "xcm-precompile::transfer_multi_assets", "Raw arguments: assets: {:?}, fee_item{:?}, destination: {:?}, \ diff --git a/precompiles/xcm/src/tests.rs b/precompiles/xcm/src/tests.rs index ceda9eb930..f672a83c1a 100644 --- a/precompiles/xcm/src/tests.rs +++ b/precompiles/xcm/src/tests.rs @@ -400,6 +400,8 @@ mod xcm_new_interface_test { parents: 1, interior: Junctions::X1(Junction::Parachain(2000u32)), }; + + let weight = WeightV2::from(3_000_000_000u64, 1024); // SS58 precompiles() .prepare_test( @@ -410,7 +412,7 @@ mod xcm_new_interface_test { .write(Address::from(Runtime::asset_id_to_address(1_u128))) .write(U256::from(367)) .write(vec![0xff_u8, 0xaa, 0x77, 0x00]) - .write(U256::from(3_000_000_000u64)) + .write(weight) .build(), ) .expect_no_logs() @@ -643,6 +645,8 @@ mod xtokens_interface_test { use super::*; #[test] fn xtokens_transfer_works() { + let weight = WeightV2::from(3_000_000_000u64, 1024); + ExtBuilder::default().build().execute_with(|| { let parent_destination = MultiLocation { parents: 1, @@ -672,7 +676,7 @@ mod xtokens_interface_test { .write(Address::from(Runtime::asset_id_to_address(1u128))) // zero address by convention .write(U256::from(42000u64)) .write(parent_destination) - .write(U256::from(3_000_000_000u64)) + .write(weight.clone()) .build(), ) .expect_no_logs() @@ -702,7 +706,7 @@ mod xtokens_interface_test { .write(Address::from(Runtime::asset_id_to_address(2u128))) // zero address by convention .write(U256::from(42000u64)) .write(sibling_parachain_location) - .write(U256::from(3_000_000_000u64)) + .write(weight) .build(), ) .expect_no_logs() @@ -727,6 +731,7 @@ mod xtokens_interface_test { #[test] fn xtokens_transfer_with_fee_works() { + let weight = WeightV2::from(3_000_000_000u64, 1024); ExtBuilder::default().build().execute_with(|| { let parent_destination = MultiLocation { parents: 1, @@ -746,7 +751,7 @@ mod xtokens_interface_test { .write(U256::from(42000u64)) .write(U256::from(50)) .write(parent_destination) - .write(U256::from(3_000_000_000u64)) + .write(weight) .build(), ) .expect_no_logs() @@ -775,6 +780,7 @@ mod xtokens_interface_test { #[test] fn transfer_multiasset_works() { + let weight = WeightV2::from(3_000_000_000u64, 1024); ExtBuilder::default().build().execute_with(|| { let relay_token_location = MultiLocation { parents: 1, @@ -808,7 +814,7 @@ mod xtokens_interface_test { .write(relay_token_location) // zero address by convention .write(U256::from(amount)) .write(relay_destination) - .write(U256::from(3_000_000_000u64)) + .write(weight.clone()) .build(), ) .expect_no_logs() @@ -839,7 +845,7 @@ mod xtokens_interface_test { .write(relay_token_location) // zero address by convention .write(U256::from(amount)) .write(para_destination) - .write(U256::from(3_000_000_000u64)) + .write(weight) .build(), ) .expect_no_logs() @@ -872,6 +878,9 @@ mod xtokens_interface_test { id: [1u8; 32], }), ); + + let weight = WeightV2::from(3_000_000_000u64, 1024); + // NOTE: Currently only support `ToReserve` with relay-chain asset as fee. other case // like `NonReserve` or `SelfReserve` with relay-chain fee is not support. let currencies: Vec = vec![ @@ -896,7 +905,7 @@ mod xtokens_interface_test { .write(currencies) // zero address by convention .write(U256::from(0)) .write(destination) - .write(U256::from(3_000_000_000u64)) + .write(weight) .build(), ) .expect_no_logs() @@ -932,6 +941,7 @@ mod xtokens_interface_test { id: [1u8; 32], }), ); + let weight = WeightV2::from(3_000_000_000u64, 1024); // we only allow upto 2 currencies to be transfered let currencies: Vec = vec![ ( @@ -960,7 +970,7 @@ mod xtokens_interface_test { .write(currencies) // zero address by convention .write(U256::from(0)) .write(destination) - .write(U256::from(3_000_000_000u64)) + .write(weight) .build(), ) .expect_no_logs() @@ -982,6 +992,7 @@ mod xtokens_interface_test { }, ), ); + let weight = WeightV2::from(3_000_000_000u64, 1024); let asset_1_location = MultiLocation::new( 1, @@ -1012,7 +1023,7 @@ mod xtokens_interface_test { .write(assets) // zero address by convention .write(U256::from(0)) .write(destination) - .write(U256::from(3_000_000_000u64)) + .write(weight) .build(), ) .expect_no_logs() @@ -1044,6 +1055,7 @@ mod xtokens_interface_test { }, ), ); + let weight = WeightV2::from(3_000_000_000u64, 1024); let asset_1_location = MultiLocation::new( 1, @@ -1073,7 +1085,7 @@ mod xtokens_interface_test { .write(assets) // zero address by convention .write(U256::from(0)) .write(destination) - .write(U256::from(3_000_000_000u64)) + .write(weight) .build(), ) .expect_no_logs() From a737b873304dea75b9d4397e60d3a622829c3741 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Mon, 11 Sep 2023 12:00:42 +0200 Subject: [PATCH 07/19] minor changes --- precompiles/xcm/XCM_v2.sol | 2 +- precompiles/xcm/src/lib.rs | 14 ++++++++------ runtime/astar/src/precompiles.rs | 4 ++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/precompiles/xcm/XCM_v2.sol b/precompiles/xcm/XCM_v2.sol index d0d36acc30..9f83747e58 100644 --- a/precompiles/xcm/XCM_v2.sol +++ b/precompiles/xcm/XCM_v2.sol @@ -50,7 +50,7 @@ interface XCM { address payment_asset_id, uint256 payment_amount, bytes calldata call, - WeightV2 transact_weight + WeightV2 memory transact_weight ) external returns (bool); /** diff --git a/precompiles/xcm/src/lib.rs b/precompiles/xcm/src/lib.rs index f9c469b8af..8086b9ff13 100644 --- a/precompiles/xcm/src/lib.rs +++ b/precompiles/xcm/src/lib.rs @@ -481,10 +481,6 @@ where let fee_asset_item: u32 = input.read::()?.low_u32(); - log::trace!(target: "xcm-precompile::asset_withdraw", "Raw arguments: assets: {:?}, asset_amount: {:?} \ - beneficiart: {:?}, destination: {:?}, fee_index: {}", - assets, amounts_raw, beneficiary, dest, fee_asset_item); - if fee_asset_item as usize > assets.len() { return Err(revert("Bad fee index.")); } @@ -502,6 +498,11 @@ where handle.context().caller, )) .into(); + + log::trace!(target: "xcm-precompile::asset_withdraw", "Raw arguments: assets: {:?}, asset_amount: {:?} \ + beneficiary: {:?}, destination: {:?}, fee_index: {}", + assets, amounts_raw, beneficiary, dest, fee_asset_item); + let call = pallet_xcm::Call::::reserve_withdraw_assets { dest: Box::new(dest.into()), beneficiary: Box::new(beneficiary.into()), @@ -617,8 +618,6 @@ where } let amounts: Vec = amounts_raw.iter().map(|x| x.low_u128()).collect(); - log::trace!(target: "xcm-precompile:assets_reserve_transfer", "Processed arguments: assets {:?}, amounts: {:?}", assets, amounts); - // Check that assets list is valid: // * all assets resolved to multi-location // * all assets has corresponded amount @@ -648,6 +647,9 @@ where handle.context().caller, )) .into(); + + log::trace!(target: "xcm-precompile:assets_reserve_transfer", "Processed arguments: assets {:?}, amounts: {:?}, beneficiary: {:?}, destination: {:?}, fee_index: {}", assets, amounts, beneficiary, dest, fee_asset_item); + let call = pallet_xcm::Call::::reserve_transfer_assets { dest: Box::new(dest.into()), beneficiary: Box::new(beneficiary.into()), diff --git a/runtime/astar/src/precompiles.rs b/runtime/astar/src/precompiles.rs index 4e1dec541c..cc0d81180e 100644 --- a/runtime/astar/src/precompiles.rs +++ b/runtime/astar/src/precompiles.rs @@ -34,7 +34,7 @@ use pallet_evm_precompile_sha3fips::Sha3FIPS256; use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripemd160, Sha256}; use pallet_evm_precompile_sr25519::Sr25519Precompile; use pallet_evm_precompile_substrate_ecdsa::SubstrateEcdsaPrecompile; -// use pallet_evm_precompile_xcm::XcmPrecompile; +use pallet_evm_precompile_xcm::XcmPrecompile; use sp_core::H160; use sp_std::fmt::Debug; use sp_std::marker::PhantomData; @@ -73,7 +73,7 @@ where Erc20AssetsPrecompileSet: PrecompileSet, DappsStakingWrapper: Precompile, BatchPrecompile: Precompile, - // XcmPrecompile: Precompile, + XcmPrecompile: Precompile, Dispatch: Precompile, R: pallet_evm::Config + pallet_assets::Config From f074988966fcb9a3e320b4320a164a238dba5219 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Thu, 21 Sep 2023 16:39:13 +0530 Subject: [PATCH 08/19] fix bug --- precompiles/utils/src/bytes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/precompiles/utils/src/bytes.rs b/precompiles/utils/src/bytes.rs index ba86df9297..f1e6994b96 100644 --- a/precompiles/utils/src/bytes.rs +++ b/precompiles/utils/src/bytes.rs @@ -135,7 +135,7 @@ impl> EvmData for BoundedBytesString { writer.write_pointer( EvmDataWriter::new() .write(U256::from(length)) - .write(value) + .write_raw_bytes(&value) .build(), ); } From 379ea27741f5fb7309c8b3ea70c18e67923459a6 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Sun, 1 Oct 2023 16:39:58 +0530 Subject: [PATCH 09/19] typo fix and refactor --- precompiles/utils/src/data.rs | 7 +------ precompiles/xcm/XCM_v2.sol | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/precompiles/utils/src/data.rs b/precompiles/utils/src/data.rs index ed80a99c0d..3d4c1242cd 100644 --- a/precompiles/utils/src/data.rs +++ b/precompiles/utils/src/data.rs @@ -175,11 +175,6 @@ impl<'a> EvmDataReader<'a> { }) } - /// Return Option<&[u8]> from a given range for EvmDataReader - pub fn get_input_from_range(&self, range: Range) -> Option<&[u8]> { - self.input.get(range) - } - /// Read remaining bytes pub fn read_till_end(&mut self) -> EvmResult<&[u8]> { let range = self.move_cursor(self.input.len() - self.cursor)?; @@ -195,7 +190,7 @@ impl<'a> EvmDataReader<'a> { /// Move the reading cursor with provided length, and return a range from the previous cursor /// location to the new one. /// Checks cursor overflows. - pub fn move_cursor(&mut self, len: usize) -> EvmResult> { + fn move_cursor(&mut self, len: usize) -> EvmResult> { let start = self.cursor; let end = self .cursor diff --git a/precompiles/xcm/XCM_v2.sol b/precompiles/xcm/XCM_v2.sol index 9f83747e58..a87ebfa554 100644 --- a/precompiles/xcm/XCM_v2.sol +++ b/precompiles/xcm/XCM_v2.sol @@ -39,7 +39,7 @@ interface XCM { /** * @dev Execute a transaction on a remote chain. * @param destination - Multilocation of destination chain - * @param payment_asset_id - ETH address of the local asset derivate used to pay for execution in the destination chain + * @param payment_asset_id - Address of the local asset derivate used to pay for execution in the destination chain * @param payment_amount - amount of payment asset to use for execution payment - should cover cost of XCM instructions + Transact call weight. * @param call - encoded call data (must be decodable by remote chain) * @param transact_weight - max weight that the encoded call is allowed to consume in the destination chain From 1066173ea170daa62c6da99be4e197c101aca147 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Mon, 2 Oct 2023 10:35:04 +0530 Subject: [PATCH 10/19] cleaning interface --- precompiles/xcm/XCM.sol | 5 ----- precompiles/xcm/XCM_v2.sol | 4 ---- 2 files changed, 9 deletions(-) diff --git a/precompiles/xcm/XCM.sol b/precompiles/xcm/XCM.sol index 19af7ae9b7..6e7f676441 100644 --- a/precompiles/xcm/XCM.sol +++ b/precompiles/xcm/XCM.sol @@ -5,7 +5,6 @@ pragma solidity ^0.8.0; */ interface XCM { /** - * @dev Withdraw assets using PalletXCM call. * @param asset_id - list of XC20 asset addresses * @param asset_amount - list of transfer amounts (must match with asset addresses above) * @param recipient_account_id - SS58 public key of the destination account @@ -28,7 +27,6 @@ interface XCM { ) external returns (bool); /** - * @dev Withdraw assets using PalletXCM call. * @param asset_id - list of XC20 asset addresses * @param asset_amount - list of transfer amounts (must match with asset addresses above) * @param recipient_account_id - ETH address of the destination account @@ -51,7 +49,6 @@ interface XCM { ) external returns (bool); /** - * @dev Execute a transaction on a remote chain. * @param parachain_id - destination parachain Id (ignored if is_relay is true) * @param is_relay - if true, destination is relay_chain, if false it is parachain (see previous argument) * @param payment_asset_id - ETH address of the local asset derivate used to pay for execution in the destination chain @@ -70,7 +67,6 @@ interface XCM { ) external returns (bool); /** - * @dev Reserve transfer assets using PalletXCM call. * @param asset_id - list of XC20 asset addresses * @param asset_amount - list of transfer amounts (must match with asset addresses above) * @param recipient_account_id - SS58 public key of the destination account @@ -93,7 +89,6 @@ interface XCM { ) external returns (bool); /** - * @dev Reserve transfer using PalletXCM call. * @param asset_id - list of XC20 asset addresses * @param asset_amount - list of transfer amounts (must match with asset addresses above) * @param recipient_account_id - ETH address of the destination account diff --git a/precompiles/xcm/XCM_v2.sol b/precompiles/xcm/XCM_v2.sol index a87ebfa554..d29d367d8f 100644 --- a/precompiles/xcm/XCM_v2.sol +++ b/precompiles/xcm/XCM_v2.sol @@ -16,7 +16,6 @@ interface XCM { } /** - * @dev Withdraw assets using PalletXCM call. * @param asset_id - list of XC20 asset addresses * @param asset_amount - list of transfer amounts (must match with asset addresses above) * @param beneficiary - Multilocation of beneficiary in respect to destination parachain @@ -37,7 +36,6 @@ interface XCM { ) external returns (bool); /** - * @dev Execute a transaction on a remote chain. * @param destination - Multilocation of destination chain * @param payment_asset_id - Address of the local asset derivate used to pay for execution in the destination chain * @param payment_amount - amount of payment asset to use for execution payment - should cover cost of XCM instructions + Transact call weight. @@ -54,7 +52,6 @@ interface XCM { ) external returns (bool); /** - * @dev Reserve transfer assets using PalletXCM call. * @param asset_id - list of XC20 asset addresses * @param asset_amount - list of transfer amounts (must match with asset addresses above) * @param beneficiary - Multilocation of beneficiary in respect to destination parachain @@ -74,7 +71,6 @@ interface XCM { ) external returns (bool); /** - * @dev send xcm using PalletXCM call. * @param destination - Multilocation of destination chain where to send this call * @param xcm_call - encoded xcm call you want to send to destination **/ From 338413bafcb59f63f0c26665a068e51faea427b1 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Mon, 2 Oct 2023 23:12:28 +0530 Subject: [PATCH 11/19] add xcm const to primitives --- precompiles/xcm/Cargo.toml | 1 + precompiles/xcm/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/precompiles/xcm/Cargo.toml b/precompiles/xcm/Cargo.toml index 16b372c34f..9e792d419b 100644 --- a/precompiles/xcm/Cargo.toml +++ b/precompiles/xcm/Cargo.toml @@ -13,6 +13,7 @@ num_enum = { workspace = true } pallet-evm-precompile-assets-erc20 = { workspace = true } pallet-xcm = { workspace = true } precompile-utils = { workspace = true } +astar-primitives = { workspace = true } # Substrate frame-support = { workspace = true } diff --git a/precompiles/xcm/src/lib.rs b/precompiles/xcm/src/lib.rs index 8086b9ff13..05d78bb8fd 100644 --- a/precompiles/xcm/src/lib.rs +++ b/precompiles/xcm/src/lib.rs @@ -24,7 +24,7 @@ use frame_support::{ pallet_prelude::Weight, traits::{ConstU32, Get}, }; -pub const XCM_SIZE_LIMIT: u32 = 2u32.pow(16); +use astar_primitives::xcm::constants::XCM_SIZE_LIMIT; type GetXcmSizeLimit = ConstU32; use pallet_evm::{AddressMapping, Precompile}; From b0074496d4f859df43fa55f2fe91414165c09adb Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Mon, 2 Oct 2023 23:13:38 +0530 Subject: [PATCH 12/19] fmt and refactor --- Cargo.lock | 1 + precompiles/xcm/Cargo.toml | 2 +- precompiles/xcm/src/lib.rs | 2 +- primitives/src/xcm/constants.rs | 19 +++++++++++++++++++ primitives/src/xcm/mod.rs | 4 ++-- 5 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 primitives/src/xcm/constants.rs diff --git a/Cargo.lock b/Cargo.lock index 77c0a75eb8..a882131bbd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7481,6 +7481,7 @@ name = "pallet-evm-precompile-xcm" version = "0.9.1" dependencies = [ "assert_matches", + "astar-primitives", "derive_more", "fp-evm", "frame-support", diff --git a/precompiles/xcm/Cargo.toml b/precompiles/xcm/Cargo.toml index 9e792d419b..808015f530 100644 --- a/precompiles/xcm/Cargo.toml +++ b/precompiles/xcm/Cargo.toml @@ -8,12 +8,12 @@ homepage.workspace = true repository.workspace = true [dependencies] +astar-primitives = { workspace = true } log = { workspace = true } num_enum = { workspace = true } pallet-evm-precompile-assets-erc20 = { workspace = true } pallet-xcm = { workspace = true } precompile-utils = { workspace = true } -astar-primitives = { workspace = true } # Substrate frame-support = { workspace = true } diff --git a/precompiles/xcm/src/lib.rs b/precompiles/xcm/src/lib.rs index 05d78bb8fd..a416fda76b 100644 --- a/precompiles/xcm/src/lib.rs +++ b/precompiles/xcm/src/lib.rs @@ -18,13 +18,13 @@ #![cfg_attr(not(feature = "std"), no_std)] +use astar_primitives::xcm::constants::XCM_SIZE_LIMIT; use fp_evm::{PrecompileHandle, PrecompileOutput}; use frame_support::{ dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, pallet_prelude::Weight, traits::{ConstU32, Get}, }; -use astar_primitives::xcm::constants::XCM_SIZE_LIMIT; type GetXcmSizeLimit = ConstU32; use pallet_evm::{AddressMapping, Precompile}; diff --git a/primitives/src/xcm/constants.rs b/primitives/src/xcm/constants.rs new file mode 100644 index 0000000000..7e80e6dc54 --- /dev/null +++ b/primitives/src/xcm/constants.rs @@ -0,0 +1,19 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar 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. + +// Astar 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 Astar. If not, see . + +pub const XCM_SIZE_LIMIT: u32 = 2u32.pow(16); diff --git a/primitives/src/xcm/mod.rs b/primitives/src/xcm/mod.rs index e59e01283b..fed6123939 100644 --- a/primitives/src/xcm/mod.rs +++ b/primitives/src/xcm/mod.rs @@ -30,8 +30,6 @@ //! Please refer to implementation below for more info. //! -#![cfg_attr(not(feature = "std"), no_std)] - use crate::AccountId; use frame_support::{ @@ -54,6 +52,8 @@ use pallet_xc_asset_config::{ExecutionPaymentRate, XcAssetLocation}; #[cfg(test)] mod tests; +pub mod constants; + /// Used to convert between cross-chain asset multilocation and local asset Id. /// /// This implementation relies on `XcAssetConfig` pallet to handle mapping. From 9b97501b75de0bd2ab7733d7cbcdb199312d006f Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Tue, 3 Oct 2023 22:19:55 +0530 Subject: [PATCH 13/19] refactor and remove old xcmv2.sol --- precompiles/xcm/XCM.sol | 16 ++ precompiles/xcm/XCM_v2.sol | 146 ++++++++------ precompiles/xcm/Xtokens.sol | 119 ------------ precompiles/xcm/src/mock.rs | 9 +- precompiles/xcm/src/tests.rs | 325 +------------------------------- primitives/src/xcm/constants.rs | 19 -- primitives/src/xcm/mod.rs | 2 +- 7 files changed, 116 insertions(+), 520 deletions(-) delete mode 100644 precompiles/xcm/Xtokens.sol delete mode 100644 primitives/src/xcm/constants.rs diff --git a/precompiles/xcm/XCM.sol b/precompiles/xcm/XCM.sol index 6e7f676441..e41f7dff86 100644 --- a/precompiles/xcm/XCM.sol +++ b/precompiles/xcm/XCM.sol @@ -4,6 +4,13 @@ pragma solidity ^0.8.0; * @title XCM interface. */ interface XCM { + + // A multilocation is defined by its number of parents and the encoded junctions (interior) + struct Multilocation { + uint8 parents; + bytes[] interior; + } + /** * @param asset_id - list of XC20 asset addresses * @param asset_amount - list of transfer amounts (must match with asset addresses above) @@ -109,4 +116,13 @@ interface XCM { uint256 parachain_id, uint256 fee_index ) external returns (bool); + + /** + * @param destination - Multilocation of destination chain where to send this call + * @param xcm_call - encoded xcm call you want to send to destination + **/ + function send_xcm( + Multilocation memory destination, + bytes memory xcm_call + ) external returns (bool); } diff --git a/precompiles/xcm/XCM_v2.sol b/precompiles/xcm/XCM_v2.sol index d29d367d8f..f9afcfdc2a 100644 --- a/precompiles/xcm/XCM_v2.sol +++ b/precompiles/xcm/XCM_v2.sol @@ -1,5 +1,7 @@ + pragma solidity ^0.8.0; + /** * @title XCM interface. */ @@ -14,68 +16,104 @@ interface XCM { uint64 ref_time; uint64 proof_size; } - - /** - * @param asset_id - list of XC20 asset addresses - * @param asset_amount - list of transfer amounts (must match with asset addresses above) - * @param beneficiary - Multilocation of beneficiary in respect to destination parachain - * @param destination - Multilocation of destination chain - * @param fee_index - index of asset_id item that should be used as a XCM fee - * @return bool confirmation whether the XCM message sent. - * - * How method check that assets list is valid: - * - all assets resolved to multi-location (on runtime level) - * - all assets has corresponded amount (lenght of assets list matched to amount list) - */ - function assets_withdraw( - address[] calldata asset_id, - uint256[] calldata asset_amount, - Multilocation memory beneficiary, + + // A MultiAsset is defined by a multilocation and an amount + struct EvmMultiAsset { + Multilocation location; + uint256 amount; + } + + // A Currency is defined by address and the amount to be transferred + struct Currency { + address currencyAddress; + uint256 amount; + } + + /// Transfer a token through XCM based on its currencyId + /// + /// @dev The token transfer burns/transfers the corresponding amount before sending + /// @param currencyAddress The ERC20 address of the currency we want to transfer + /// @param amount The amount of tokens we want to transfer + /// @param destination The Multilocation to which we want to send the tokens + /// @param weight The weight we want to buy in the destination chain + function transfer( + address currencyAddress, + uint256 amount, + Multilocation memory destination, + WeightV2 memory weight + ) external returns (bool); + + /// Transfer a token through XCM based on its currencyId specifying fee + /// + /// @dev The token transfer burns/transfers the corresponding amount before sending + /// @param currencyAddress The ERC20 address of the currency we want to transfer + /// @param amount The amount of tokens we want to transfer + /// @param destination The Multilocation to which we want to send the tokens + /// @param weight The weight we want to buy in the destination chain + function transfer_with_fee( + address currencyAddress, + uint256 amount, + uint256 fee, + Multilocation memory destination, + WeightV2 memory weight + ) external returns (bool); + + /// Transfer a token through XCM based on its MultiLocation + /// + /// @dev The token transfer burns/transfers the corresponding amount before sending + /// @param asset The asset we want to transfer, defined by its multilocation. + /// Currently only Concrete Fungible assets + /// @param amount The amount of tokens we want to transfer + /// @param destination The Multilocation to which we want to send the tokens + /// @param weight The weight we want to buy in the destination chain + function transfer_multiasset( + Multilocation memory asset, + uint256 amount, Multilocation memory destination, - uint256 fee_index + WeightV2 memory weight ) external returns (bool); - /** - * @param destination - Multilocation of destination chain - * @param payment_asset_id - Address of the local asset derivate used to pay for execution in the destination chain - * @param payment_amount - amount of payment asset to use for execution payment - should cover cost of XCM instructions + Transact call weight. - * @param call - encoded call data (must be decodable by remote chain) - * @param transact_weight - max weight that the encoded call is allowed to consume in the destination chain - * @return bool confirmation whether the XCM message sent. - */ - function remote_transact( + /// Transfer a token through XCM based on its MultiLocation specifying fee + /// + /// @dev The token transfer burns/transfers the corresponding amount before sending + /// @param asset The asset we want to transfer, defined by its multilocation. + /// Currently only Concrete Fungible assets + /// @param amount The amount of tokens we want to transfer + /// @param destination The Multilocation to which we want to send the tokens + /// @param weight The weight we want to buy in the destination chain + function transfer_multiasset_with_fee( + Multilocation memory asset, + uint256 amount, + uint256 fee, Multilocation memory destination, - address payment_asset_id, - uint256 payment_amount, - bytes calldata call, - WeightV2 memory transact_weight + WeightV2 memory weight ) external returns (bool); - /** - * @param asset_id - list of XC20 asset addresses - * @param asset_amount - list of transfer amounts (must match with asset addresses above) - * @param beneficiary - Multilocation of beneficiary in respect to destination parachain - * @param destination - Multilocation of destination chain - * @param fee_index - index of asset_id item that should be used as a XCM fee - * @return A boolean confirming whether the XCM message sent. - * How method check that assets list is valid: - * - all assets resolved to multi-location (on runtime level) - * - all assets has corresponded amount (lenght of assets list matched to amount list) - */ - function assets_reserve_transfer( - address[] calldata asset_id, - uint256[] calldata asset_amount, - Multilocation memory beneficiary, + /// Transfer several tokens at once through XCM based on its address specifying fee + /// + /// @dev The token transfer burns/transfers the corresponding amount before sending + /// @param currencies The currencies we want to transfer, defined by their address and amount. + /// @param feeItem Which of the currencies to be used as fee + /// @param destination The Multilocation to which we want to send the tokens + /// @param weight The weight we want to buy in the destination chain + function transfer_multi_currencies( + Currency[] memory currencies, + uint32 feeItem, Multilocation memory destination, - uint256 fee_index + WeightV2 memory weight ) external returns (bool); - /** - * @param destination - Multilocation of destination chain where to send this call - * @param xcm_call - encoded xcm call you want to send to destination - **/ - function send_xcm( + /// Transfer several tokens at once through XCM based on its location specifying fee + /// + /// @dev The token transfer burns/transfers the corresponding amount before sending + /// @param assets The assets we want to transfer, defined by their location and amount. + /// @param feeItem Which of the currencies to be used as fee + /// @param destination The Multilocation to which we want to send the tokens + /// @param weight The weight we want to buy in the destination chain + function transfer_multi_assets( + EvmMultiAsset[] memory assets, + uint32 feeItem, Multilocation memory destination, - bytes memory xcm_call + WeightV2 memory weight ) external returns (bool); -} \ No newline at end of file +} diff --git a/precompiles/xcm/Xtokens.sol b/precompiles/xcm/Xtokens.sol deleted file mode 100644 index a666c2f24f..0000000000 --- a/precompiles/xcm/Xtokens.sol +++ /dev/null @@ -1,119 +0,0 @@ - -pragma solidity ^0.8.0; - - -/** - * @title Xtokens interface. - */ -interface Xtokens { - // A multilocation is defined by its number of parents and the encoded junctions (interior) - struct Multilocation { - uint8 parents; - bytes[] interior; - } - - struct WeightV2{ - uint64 ref_time; - uint64 proof_size; - } - - // A MultiAsset is defined by a multilocation and an amount - struct MultiAsset { - Multilocation location; - uint256 amount; - } - - // A Currency is defined by address and the amount to be transferred - struct Currency { - address currencyAddress; - uint256 amount; - } - - /// Transfer a token through XCM based on its currencyId - /// - /// @dev The token transfer burns/transfers the corresponding amount before sending - /// @param currencyAddress The ERC20 address of the currency we want to transfer - /// @param amount The amount of tokens we want to transfer - /// @param destination The Multilocation to which we want to send the tokens - /// @param weight The weight we want to buy in the destination chain - function transfer( - address currencyAddress, - uint256 amount, - Multilocation memory destination, - WeightV2 memory weight - ) external returns (bool); - - /// Transfer a token through XCM based on its currencyId specifying fee - /// - /// @dev The token transfer burns/transfers the corresponding amount before sending - /// @param currencyAddress The ERC20 address of the currency we want to transfer - /// @param amount The amount of tokens we want to transfer - /// @param destination The Multilocation to which we want to send the tokens - /// @param weight The weight we want to buy in the destination chain - function transfer_with_fee( - address currencyAddress, - uint256 amount, - uint256 fee, - Multilocation memory destination, - WeightV2 memory weight - ) external returns (bool); - - /// Transfer a token through XCM based on its MultiLocation - /// - /// @dev The token transfer burns/transfers the corresponding amount before sending - /// @param asset The asset we want to transfer, defined by its multilocation. - /// Currently only Concrete Fungible assets - /// @param amount The amount of tokens we want to transfer - /// @param destination The Multilocation to which we want to send the tokens - /// @param weight The weight we want to buy in the destination chain - function transfer_multiasset( - Multilocation memory asset, - uint256 amount, - Multilocation memory destination, - WeightV2 memory weight - ) external returns (bool); - - /// Transfer a token through XCM based on its MultiLocation specifying fee - /// - /// @dev The token transfer burns/transfers the corresponding amount before sending - /// @param asset The asset we want to transfer, defined by its multilocation. - /// Currently only Concrete Fungible assets - /// @param amount The amount of tokens we want to transfer - /// @param destination The Multilocation to which we want to send the tokens - /// @param weight The weight we want to buy in the destination chain - function transfer_multiasset_with_fee( - Multilocation memory asset, - uint256 amount, - uint256 fee, - Multilocation memory destination, - WeightV2 memory weight - ) external returns (bool); - - /// Transfer several tokens at once through XCM based on its address specifying fee - /// - /// @dev The token transfer burns/transfers the corresponding amount before sending - /// @param currencies The currencies we want to transfer, defined by their address and amount. - /// @param feeItem Which of the currencies to be used as fee - /// @param destination The Multilocation to which we want to send the tokens - /// @param weight The weight we want to buy in the destination chain - function transfer_multi_currencies( - Currency[] memory currencies, - uint32 feeItem, - Multilocation memory destination, - WeightV2 memory weight - ) external returns (bool); - - /// Transfer several tokens at once through XCM based on its location specifying fee - /// - /// @dev The token transfer burns/transfers the corresponding amount before sending - /// @param assets The assets we want to transfer, defined by their location and amount. - /// @param feeItem Which of the currencies to be used as fee - /// @param destination The Multilocation to which we want to send the tokens - /// @param weight The weight we want to buy in the destination chain - function transfer_multi_assets( - MultiAsset[] memory assets, - uint32 feeItem, - Multilocation memory destination, - WeightV2 memory weight - ) external returns (bool); -} diff --git a/precompiles/xcm/src/mock.rs b/precompiles/xcm/src/mock.rs index d45b3b3573..f792866b8e 100644 --- a/precompiles/xcm/src/mock.rs +++ b/precompiles/xcm/src/mock.rs @@ -172,10 +172,10 @@ pub struct CurrencyIdToMultiLocation; impl sp_runtime::traits::Convert> for CurrencyIdToMultiLocation { fn convert(currency: CurrencyId) -> Option { match currency { - a if a == 1u128 => Some(PARENT), - a if a == 2u128 => Some(PARACHAIN), - a if a == 3u128 => Some(GENERAL_INDEX), - a if a == 4u128 => Some(LOCAL_ASSET), + 1u128 => Some(PARENT), + 2u128 => Some(PARACHAIN), + 3u128 => Some(GENERAL_INDEX), + 4u128 => Some(LOCAL_ASSET), _ => None, } } @@ -580,6 +580,7 @@ impl ExtBuilder { ext } } + pub(crate) fn events() -> Vec { System::events() .into_iter() diff --git a/precompiles/xcm/src/tests.rs b/precompiles/xcm/src/tests.rs index f672a83c1a..e44f875965 100644 --- a/precompiles/xcm/src/tests.rs +++ b/precompiles/xcm/src/tests.rs @@ -120,7 +120,7 @@ mod xcm_old_interface_test { .prepare_test( TestAccount::Alice, PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::RemoteTransactOld) + EvmDataWriter::new_with_selector(Action::RemoteTransact) .write(U256::from(0_u64)) .write(true) .write(Address::from(Runtime::asset_id_to_address(1_u128))) @@ -286,327 +286,6 @@ mod xcm_old_interface_test { )); } } -} -mod xcm_new_interface_test { - use super::*; - #[test] - fn wrong_assets_len_or_fee_index_reverts() { - ExtBuilder::default().build().execute_with(|| { - let dest: MultiLocation = MultiLocation { - parents: 1, - interior: Junctions::X1(Junction::Parachain(2000u32)), - }; - - let beneficiary: MultiLocation = MultiLocation { - parents: 0, - interior: Junctions::X1(AccountId32 { - network: None, - id: H256::repeat_byte(0xF1).into(), - }), - }; - precompiles() - .prepare_test( - TestAccount::Alice, - PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsWithdraw) - .write(vec![Address::from(H160::repeat_byte(0xF1))]) - .write(Vec::::new()) - .write(beneficiary) - .write(dest) - .write(U256::from(0_u64)) - .build(), - ) - .expect_no_logs() - .execute_reverts(|output| output == b"Assets resolution failure."); - - precompiles() - .prepare_test( - TestAccount::Alice, - PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsWithdraw) - .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) - .write(vec![U256::from(42000u64)]) - .write(beneficiary) - .write(dest) - .write(U256::from(2_u64)) - .build(), - ) - .expect_no_logs() - .execute_reverts(|output| output == b"Bad fee index."); - }); - } - - #[test] - fn assets_withdraw_works() { - ExtBuilder::default().build().execute_with(|| { - let dest: MultiLocation = MultiLocation { - parents: 1, - interior: Junctions::Here, - }; - - let beneficiary: MultiLocation = MultiLocation { - parents: 0, - interior: Junctions::X1(AccountId32 { - network: None, - id: H256::repeat_byte(0xF1).into(), - }), - }; - - // SS58 - precompiles() - .prepare_test( - TestAccount::Alice, - PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsWithdraw) - .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) - .write(vec![U256::from(42000u64)]) - .write(beneficiary) - .write(dest) - .write(U256::from(0_u64)) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); - - // H160 - let beneficiary: MultiLocation = MultiLocation { - parents: 0, - interior: Junctions::X1(AccountKey20 { - network: None, - key: H160::repeat_byte(0xDE).into(), - }), - }; - precompiles() - .prepare_test( - TestAccount::Alice, - PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsWithdraw) - .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) - .write(vec![U256::from(42000u64)]) - .write(beneficiary) - .write(dest) - .write(U256::from(0_u64)) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); - }); - } - - #[test] - fn remote_transact_works() { - ExtBuilder::default().build().execute_with(|| { - let multilocation = MultiLocation { - parents: 1, - interior: Junctions::X1(Junction::Parachain(2000u32)), - }; - - let weight = WeightV2::from(3_000_000_000u64, 1024); - // SS58 - precompiles() - .prepare_test( - TestAccount::Alice, - PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::RemoteTransactNew) - .write(multilocation) - .write(Address::from(Runtime::asset_id_to_address(1_u128))) - .write(U256::from(367)) - .write(vec![0xff_u8, 0xaa, 0x77, 0x00]) - .write(weight) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); - }); - } - - #[test] - fn reserve_transfer_assets_works() { - ExtBuilder::default().build().execute_with(|| { - let dest: MultiLocation = MultiLocation { - parents: 1, - interior: Junctions::Here, - }; - - let beneficiary: MultiLocation = MultiLocation { - parents: 0, - interior: Junctions::X1(AccountId32 { - network: None, - id: H256::repeat_byte(0xF1).into(), - }), - }; - // SS58 - precompiles() - .prepare_test( - TestAccount::Alice, - PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsReserveTransfer) - .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) - .write(vec![U256::from(42000u64)]) - .write(beneficiary) - .write(dest) - .write(U256::from(0_u64)) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); - - // H160 - let beneficiary: MultiLocation = MultiLocation { - parents: 0, - interior: Junctions::X1(AccountKey20 { - network: None, - key: H160::repeat_byte(0xDE).into(), - }), - }; - precompiles() - .prepare_test( - TestAccount::Alice, - PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsReserveTransfer) - .write(vec![Address::from(Runtime::asset_id_to_address(1u128))]) - .write(vec![U256::from(42000u64)]) - .write(beneficiary) - .write(dest) - .write(U256::from(0_u64)) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); - }); - - for (location, Xcm(instructions)) in take_sent_xcm() { - assert_eq!( - location, - MultiLocation { - parents: 1, - interior: Here - } - ); - - let non_native_asset = MultiAsset { - fun: Fungible(42000), - id: xcm::v3::AssetId::from(MultiLocation { - parents: 0, - interior: Here, - }), - }; - - assert!(matches!( - instructions.as_slice(), - [ - ReserveAssetDeposited(assets), - ClearOrigin, - BuyExecution { - fees, - .. - }, - DepositAsset { - beneficiary: MultiLocation { - parents: 0, - interior: X1(_), - }, - .. - } - ] - - if fees.contains(&non_native_asset) && assets.contains(&non_native_asset) - )); - } - } - - #[test] - fn reserve_transfer_currency_works() { - ExtBuilder::default().build().execute_with(|| { - let dest: MultiLocation = MultiLocation { - parents: 1, - interior: Junctions::Here, - }; - - let beneficiary: MultiLocation = MultiLocation { - parents: 0, - interior: Junctions::X1(AccountId32 { - network: None, - id: H256::repeat_byte(0xF1).into(), - }), - }; - precompiles() - .prepare_test( - TestAccount::Alice, - PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsReserveTransfer) - .write(vec![Address::from(H160::zero())]) // zero address by convention - .write(vec![U256::from(42000u64)]) - .write(beneficiary) - .write(dest) - .write(U256::from(0_u64)) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); - - let beneficiary: MultiLocation = MultiLocation { - parents: 0, - interior: Junctions::X1(AccountKey20 { - network: None, - key: H160::repeat_byte(0xDE).into(), - }), - }; - precompiles() - .prepare_test( - TestAccount::Alice, - PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::AssetsReserveTransfer) - .write(vec![Address::from(H160::zero())]) // zero address by convention - .write(vec![U256::from(42000u64)]) - .write(beneficiary) - .write(dest) - .write(U256::from(0_u64)) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); - }); - - for (location, Xcm(instructions)) in take_sent_xcm() { - assert_eq!( - location, - MultiLocation { - parents: 1, - interior: Here - } - ); - - let native_asset = MultiAsset { - fun: Fungible(42000), - id: xcm::v3::AssetId::from(MultiLocation { - parents: 0, - interior: X1(Parachain(123)), - }), - }; - - assert!(matches!( - instructions.as_slice(), - [ - ReserveAssetDeposited(assets), - ClearOrigin, - BuyExecution { - fees, - .. - }, - DepositAsset { - beneficiary: MultiLocation { - parents: 0, - interior: X1(_), - }, - .. - } - ] - - if fees.contains(&native_asset) && assets.contains(&native_asset) - )); - } - } #[test] fn test_send_clear_origin() { @@ -641,7 +320,7 @@ mod xcm_new_interface_test { } } -mod xtokens_interface_test { +mod xcm_new_interface_test { use super::*; #[test] fn xtokens_transfer_works() { diff --git a/primitives/src/xcm/constants.rs b/primitives/src/xcm/constants.rs deleted file mode 100644 index 7e80e6dc54..0000000000 --- a/primitives/src/xcm/constants.rs +++ /dev/null @@ -1,19 +0,0 @@ -// This file is part of Astar. - -// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later - -// Astar 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. - -// Astar 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 Astar. If not, see . - -pub const XCM_SIZE_LIMIT: u32 = 2u32.pow(16); diff --git a/primitives/src/xcm/mod.rs b/primitives/src/xcm/mod.rs index fed6123939..a0945c7532 100644 --- a/primitives/src/xcm/mod.rs +++ b/primitives/src/xcm/mod.rs @@ -52,7 +52,7 @@ use pallet_xc_asset_config::{ExecutionPaymentRate, XcAssetLocation}; #[cfg(test)] mod tests; -pub mod constants; +pub const XCM_SIZE_LIMIT: u32 = 2u32.pow(16); /// Used to convert between cross-chain asset multilocation and local asset Id. /// From d3768dc856b97f213e01f2710e3133cf8e39865b Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Tue, 3 Oct 2023 22:23:33 +0530 Subject: [PATCH 14/19] remove old funcs and edits --- precompiles/xcm/src/lib.rs | 258 +++---------------------------------- 1 file changed, 15 insertions(+), 243 deletions(-) diff --git a/precompiles/xcm/src/lib.rs b/precompiles/xcm/src/lib.rs index a416fda76b..c2fad1fb9e 100644 --- a/precompiles/xcm/src/lib.rs +++ b/precompiles/xcm/src/lib.rs @@ -18,7 +18,7 @@ #![cfg_attr(not(feature = "std"), no_std)] -use astar_primitives::xcm::constants::XCM_SIZE_LIMIT; +use astar_primitives::xcm::XCM_SIZE_LIMIT; use fp_evm::{PrecompileHandle, PrecompileOutput}; use frame_support::{ dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, @@ -55,15 +55,11 @@ mod tests; pub enum Action { AssetsWithdrawNative = "assets_withdraw(address[],uint256[],bytes32,bool,uint256,uint256)", AssetsWithdrawEvm = "assets_withdraw(address[],uint256[],address,bool,uint256,uint256)", - RemoteTransactOld = "remote_transact(uint256,bool,address,uint256,bytes,uint64)", + RemoteTransact = "remote_transact(uint256,bool,address,uint256,bytes,uint64)", AssetsReserveTransferNative = "assets_reserve_transfer(address[],uint256[],bytes32,bool,uint256,uint256)", AssetsReserveTransferEvm = "assets_reserve_transfer(address[],uint256[],address,bool,uint256,uint256)", - AssetsWithdraw = "assets_withdraw(address[],uint256[],(uint8,bytes[]),(uint8,bytes[]),uint256)", - RemoteTransactNew = "remote_transact((uint8,bytes[]),address,uint256,bytes,uint64)", - AssetsReserveTransfer = - "assets_reserve_transfer(address[],uint256[],(uint8,bytes[]),(uint8,bytes[]),uint256)", SendXCM = "send_xcm((uint8,bytes[]),bytes)", XtokensTransfer = "transfer(address,uint256,(uint8,bytes[]),uint64)", XtokensTransferWithFee = "transfer_with_fee(address,uint256,uint256,(uint8,bytes[]),uint64)", @@ -79,7 +75,7 @@ pub enum Action { /// Dummy H160 address representing native currency (e.g. ASTR or SDN) const NATIVE_ADDRESS: H160 = H160::zero(); -/// Dummy default 64KB + const DEFAULT_PROOF_SIZE: u64 = 1024 * 256; pub type XBalanceOf = ::Balance; @@ -131,16 +127,13 @@ where Action::AssetsWithdrawEvm => { Self::assets_withdraw_v1(handle, BeneficiaryType::Account20) } - Action::RemoteTransactOld => Self::remote_transact_v1(handle), + Action::RemoteTransact => Self::remote_transact_v1(handle), Action::AssetsReserveTransferNative => { Self::assets_reserve_transfer_v1(handle, BeneficiaryType::Account32) } Action::AssetsReserveTransferEvm => { Self::assets_reserve_transfer_v1(handle, BeneficiaryType::Account20) } - Action::AssetsWithdraw => Self::assets_withdraw(handle), - Action::RemoteTransactNew => Self::remote_transact(handle), - Action::AssetsReserveTransfer => Self::assets_reserve_transfer(handle), Action::SendXCM => Self::send_xcm(handle), Action::XtokensTransfer => Self::transfer(handle), Action::XtokensTransferWithFee => Self::transfer_with_fee(handle), @@ -313,10 +306,7 @@ where let fee_amount = fee_amount.low_u128(); let context = ::UniversalLocation::get(); - let fee_multilocation = MultiAsset { - id: Concrete(fee_asset), - fun: Fungible(fee_amount), - }; + let fee_multilocation: MultiAsset = (fee_asset, fee_amount).into(); let fee_multilocation = fee_multilocation .reanchored(&dest, context) .map_err(|_| revert("Failed to reanchor fee asset"))?; @@ -450,219 +440,6 @@ where Ok(succeed(EvmDataWriter::new().write(true).build())) } - fn assets_withdraw(handle: &mut impl PrecompileHandle) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(6)?; - - // Read arguments and check it - let assets: Vec = input - .read::>()? - .iter() - .cloned() - .filter_map(|address| { - Runtime::address_to_asset_id(address.into()).and_then(|x| C::reverse_ref(x).ok()) - }) - .collect(); - let amounts_raw = input.read::>()?; - if amounts_raw.iter().any(|x| *x > u128::MAX.into()) { - return Err(revert("Asset amount is too big")); - } - let amounts: Vec = amounts_raw.iter().map(|x| x.low_u128()).collect(); - - // Check that assets list is valid: - // * all assets resolved to multi-location - // * all assets has corresponded amount - if assets.len() != amounts.len() || assets.is_empty() { - return Err(revert("Assets resolution failure.")); - } - - let beneficiary: MultiLocation = input.read::()?; - let dest: MultiLocation = input.read::()?; - - let fee_asset_item: u32 = input.read::()?.low_u32(); - - if fee_asset_item as usize > assets.len() { - return Err(revert("Bad fee index.")); - } - - let assets: MultiAssets = assets - .iter() - .cloned() - .zip(amounts.iter().cloned()) - .map(Into::into) - .collect::>() - .into(); - - // Build call with origin. - let origin = Some(Runtime::AddressMapping::into_account_id( - handle.context().caller, - )) - .into(); - - log::trace!(target: "xcm-precompile::asset_withdraw", "Raw arguments: assets: {:?}, asset_amount: {:?} \ - beneficiary: {:?}, destination: {:?}, fee_index: {}", - assets, amounts_raw, beneficiary, dest, fee_asset_item); - - let call = pallet_xcm::Call::::reserve_withdraw_assets { - dest: Box::new(dest.into()), - beneficiary: Box::new(beneficiary.into()), - assets: Box::new(assets.into()), - fee_asset_item, - }; - - // Dispatch a call. - RuntimeHelper::::try_dispatch(handle, origin, call)?; - - Ok(succeed(EvmDataWriter::new().write(true).build())) - } - - fn remote_transact(handle: &mut impl PrecompileHandle) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(6)?; - - let dest: MultiLocation = input.read::()?; - let fee_asset_addr = input.read::
()?; - let fee_amount = input.read::()?; - - let remote_call: Vec = input.read::()?.into(); - let transact_weight = input.read::()?; - - log::trace!(target: "xcm-precompile::remote_transact", "Raw arguments: dest: {:?}, fee_asset_addr: {:?} \ - fee_amount: {:?}, remote_call: {:?}, transact_weight: {:?}", - dest, fee_asset_addr, fee_amount, remote_call, transact_weight); - - let fee_asset = { - let address: H160 = fee_asset_addr.into(); - - // Special case where zero address maps to native token by convention. - if address == NATIVE_ADDRESS { - Here.into() - } else { - let fee_asset_id = Runtime::address_to_asset_id(address) - .ok_or(revert("Failed to resolve fee asset id from address"))?; - C::reverse_ref(fee_asset_id).map_err(|_| { - revert("Failed to resolve fee asset multilocation from local id") - })? - } - }; - - if fee_amount > u128::MAX.into() { - return Err(revert("Fee amount is too big")); - } - let fee_amount = fee_amount.low_u128(); - - let context = ::UniversalLocation::get(); - let fee_multilocation = MultiAsset { - id: Concrete(fee_asset), - fun: Fungible(fee_amount), - }; - let fee_multilocation = fee_multilocation - .reanchored(&dest, context) - .map_err(|_| revert("Failed to reanchor fee asset"))?; - - // Prepare XCM - let xcm = Xcm(vec![ - WithdrawAsset(fee_multilocation.clone().into()), - BuyExecution { - fees: fee_multilocation.clone().into(), - weight_limit: WeightLimit::Unlimited, - }, - Transact { - origin_kind: OriginKind::SovereignAccount, - require_weight_at_most: transact_weight.get_weight(), - call: remote_call.into(), - }, - ]); - - log::trace!(target: "xcm-precompile:remote_transact", "Processed arguments: dest: {:?}, fee asset: {:?}, XCM: {:?}", dest, fee_multilocation, xcm); - - // Build call with origin. - let origin = Some(Runtime::AddressMapping::into_account_id( - handle.context().caller, - )) - .into(); - let call = pallet_xcm::Call::::send { - dest: Box::new(dest.into()), - message: Box::new(xcm::VersionedXcm::V3(xcm)), - }; - - // Dispatch a call. - RuntimeHelper::::try_dispatch(handle, origin, call)?; - - Ok(succeed(EvmDataWriter::new().write(true).build())) - } - - fn assets_reserve_transfer(handle: &mut impl PrecompileHandle) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(6)?; - - // Read arguments and check it - let assets: Vec = input - .read::>()? - .iter() - .cloned() - .filter_map(|address| { - let address: H160 = address.into(); - - // Special case where zero address maps to native token by convention. - if address == NATIVE_ADDRESS { - Some(Here.into()) - } else { - Runtime::address_to_asset_id(address).and_then(|x| C::reverse_ref(x).ok()) - } - }) - .collect(); - let amounts_raw = input.read::>()?; - if amounts_raw.iter().any(|x| *x > u128::MAX.into()) { - return Err(revert("Asset amount is too big")); - } - let amounts: Vec = amounts_raw.iter().map(|x| x.low_u128()).collect(); - - // Check that assets list is valid: - // * all assets resolved to multi-location - // * all assets has corresponded amount - if assets.len() != amounts.len() || assets.is_empty() { - return Err(revert("Assets resolution failure.")); - } - - let beneficiary: MultiLocation = input.read::()?; - let dest: MultiLocation = input.read::()?; - - let fee_asset_item: u32 = input.read::()?.low_u32(); - - if fee_asset_item as usize > assets.len() { - return Err(revert("Bad fee index.")); - } - - let assets: MultiAssets = assets - .iter() - .cloned() - .zip(amounts.iter().cloned()) - .map(Into::into) - .collect::>() - .into(); - - // Build call with origin. - let origin = Some(Runtime::AddressMapping::into_account_id( - handle.context().caller, - )) - .into(); - - log::trace!(target: "xcm-precompile:assets_reserve_transfer", "Processed arguments: assets {:?}, amounts: {:?}, beneficiary: {:?}, destination: {:?}, fee_index: {}", assets, amounts, beneficiary, dest, fee_asset_item); - - let call = pallet_xcm::Call::::reserve_transfer_assets { - dest: Box::new(dest.into()), - beneficiary: Box::new(beneficiary.into()), - assets: Box::new(assets.into()), - fee_asset_item, - }; - - // Dispatch a call. - RuntimeHelper::::try_dispatch(handle, origin, call)?; - - Ok(succeed(EvmDataWriter::new().write(true).build())) - } - fn send_xcm(handle: &mut impl PrecompileHandle) -> EvmResult { let mut input = handle.read_input()?; input.expect_arguments(3)?; @@ -793,7 +570,7 @@ where // Read call arguments let asset_location = input.read::()?; - let amount_of_tokens = input + let amount_of_tokens: u128 = input .read::()? .try_into() .map_err(|_| revert("error converting amount_of_tokens, maybe value too large"))?; @@ -811,10 +588,9 @@ where asset_location, amount_of_tokens, destination, weight); let call = orml_xtokens::Call::::transfer_multiasset { - asset: Box::new(VersionedMultiAsset::V3(MultiAsset { - id: AssetId::Concrete(asset_location), - fun: Fungibility::Fungible(amount_of_tokens), - })), + asset: Box::new(VersionedMultiAsset::V3( + (asset_location, amount_of_tokens).into(), + )), dest: Box::new(VersionedMultiLocation::V3(destination)), dest_weight_limit, }; @@ -838,11 +614,11 @@ where // Read call arguments let asset_location = input.read::()?; - let amount_of_tokens = input + let amount_of_tokens: u128 = input .read::()? .try_into() .map_err(|_| revert("error converting amount_of_tokens, maybe value too large"))?; - let fee = input + let fee: u128 = input .read::()? .try_into() .map_err(|_| revert("can't convert fee"))?; @@ -860,14 +636,10 @@ where asset_location, amount_of_tokens, fee, destination, weight); let call = orml_xtokens::Call::::transfer_multiasset_with_fee { - asset: Box::new(VersionedMultiAsset::V3(MultiAsset { - id: AssetId::Concrete(asset_location), - fun: Fungibility::Fungible(amount_of_tokens), - })), - fee: Box::new(VersionedMultiAsset::V3(MultiAsset { - id: AssetId::Concrete(asset_location), - fun: Fungibility::Fungible(fee), - })), + asset: Box::new(VersionedMultiAsset::V3( + (asset_location, amount_of_tokens).into(), + )), + fee: Box::new(VersionedMultiAsset::V3((asset_location, fee).into())), dest: Box::new(VersionedMultiLocation::V3(destination)), dest_weight_limit, }; From 30050efceff10f42ec6d3da97941f40b35942d5a Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Tue, 3 Oct 2023 22:32:23 +0530 Subject: [PATCH 15/19] update Action for Weightv2 --- precompiles/xcm/src/lib.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/precompiles/xcm/src/lib.rs b/precompiles/xcm/src/lib.rs index c2fad1fb9e..28e16f56cc 100644 --- a/precompiles/xcm/src/lib.rs +++ b/precompiles/xcm/src/lib.rs @@ -61,16 +61,16 @@ pub enum Action { AssetsReserveTransferEvm = "assets_reserve_transfer(address[],uint256[],address,bool,uint256,uint256)", SendXCM = "send_xcm((uint8,bytes[]),bytes)", - XtokensTransfer = "transfer(address,uint256,(uint8,bytes[]),uint64)", - XtokensTransferWithFee = "transfer_with_fee(address,uint256,uint256,(uint8,bytes[]),uint64)", + XtokensTransfer = "transfer(address,uint256,(uint8,bytes[]),(uint64,uint64))", + XtokensTransferWithFee = + "transfer_with_fee(address,uint256,uint256,(uint8,bytes[]),(uint64,uint64))", XtokensTransferMultiasset = - "transfer_multiasset((uint8,bytes[]),uint256,(uint8,bytes[]),uint64)", - XtokensTransferMultiassetWithFee = - "transfer_multiasset_with_fee((uint8,bytes[]),uint256,uint256,(uint8,bytes[]),uint64)", + "transfer_multiasset((uint8,bytes[]),uint256,(uint8,bytes[]),(uint64,uint64))", + XtokensTransferMultiassetWithFee = "transfer_multiasset_with_fee((uint8,bytes[]),uint256,uint256,(uint8,bytes[]),(uint64,uint64))", XtokensTransferMulticurrencies = - "transfer_multi_currencies((address,uint256)[],uint32,(uint8,bytes[]),uint64)", + "transfer_multi_currencies((address,uint256)[],uint32,(uint8,bytes[]),(uint64,uint64))", XtokensTransferMultiassets = - "transfet_multi_assets(((uint8,bytes[]),uint256)[],uint32,(uint8,bytes[]),uint64)", + "transfet_multi_assets(((uint8,bytes[]),uint256)[],uint32,(uint8,bytes[]),(uint64,uint64))", } /// Dummy H160 address representing native currency (e.g. ASTR or SDN) From b2c23391594466695fb2307c56edb7b233c417bd Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Tue, 3 Oct 2023 22:59:29 +0530 Subject: [PATCH 16/19] max weight info added --- precompiles/xcm/XCM_v2.sol | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/precompiles/xcm/XCM_v2.sol b/precompiles/xcm/XCM_v2.sol index f9afcfdc2a..fe5d61091d 100644 --- a/precompiles/xcm/XCM_v2.sol +++ b/precompiles/xcm/XCM_v2.sol @@ -35,7 +35,9 @@ interface XCM { /// @param currencyAddress The ERC20 address of the currency we want to transfer /// @param amount The amount of tokens we want to transfer /// @param destination The Multilocation to which we want to send the tokens - /// @param weight The weight we want to buy in the destination chain + /// @param weight The weight we want to buy in the destination chain, to provide + /// unlimited weight, you should use maximum u64 value (i.e. 18446744073709551615 ) + /// for ref_time function transfer( address currencyAddress, uint256 amount, @@ -49,7 +51,9 @@ interface XCM { /// @param currencyAddress The ERC20 address of the currency we want to transfer /// @param amount The amount of tokens we want to transfer /// @param destination The Multilocation to which we want to send the tokens - /// @param weight The weight we want to buy in the destination chain + /// @param weight The weight we want to buy in the destination chain, to provide + /// unlimited weight, you should use maximum u64 value (i.e. 18446744073709551615 ) + /// for ref_time function transfer_with_fee( address currencyAddress, uint256 amount, @@ -65,7 +69,9 @@ interface XCM { /// Currently only Concrete Fungible assets /// @param amount The amount of tokens we want to transfer /// @param destination The Multilocation to which we want to send the tokens - /// @param weight The weight we want to buy in the destination chain + /// @param weight The weight we want to buy in the destination chain, to provide + /// unlimited weight, you should use maximum u64 value (i.e. 18446744073709551615 ) + /// for ref_time function transfer_multiasset( Multilocation memory asset, uint256 amount, @@ -80,7 +86,9 @@ interface XCM { /// Currently only Concrete Fungible assets /// @param amount The amount of tokens we want to transfer /// @param destination The Multilocation to which we want to send the tokens - /// @param weight The weight we want to buy in the destination chain + /// @param weight The weight we want to buy in the destination chain, to provide + /// unlimited weight, you should use maximum u64 value (i.e. 18446744073709551615 ) + /// for ref_time function transfer_multiasset_with_fee( Multilocation memory asset, uint256 amount, @@ -95,7 +103,9 @@ interface XCM { /// @param currencies The currencies we want to transfer, defined by their address and amount. /// @param feeItem Which of the currencies to be used as fee /// @param destination The Multilocation to which we want to send the tokens - /// @param weight The weight we want to buy in the destination chain + /// @param weight The weight we want to buy in the destination chain, to provide + /// unlimited weight, you should use maximum u64 value (i.e. 18446744073709551615 ) + /// for ref_time function transfer_multi_currencies( Currency[] memory currencies, uint32 feeItem, @@ -109,7 +119,9 @@ interface XCM { /// @param assets The assets we want to transfer, defined by their location and amount. /// @param feeItem Which of the currencies to be used as fee /// @param destination The Multilocation to which we want to send the tokens - /// @param weight The weight we want to buy in the destination chain + /// @param weight The weight we want to buy in the destination chain, to provide + /// unlimited weight, you should use maximum u64 value (i.e. 18446744073709551615 ) + /// for ref_time function transfer_multi_assets( EvmMultiAsset[] memory assets, uint32 feeItem, From 40e36d5f92fb2ccff0831288a31cb96f41ac0809 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Tue, 3 Oct 2023 23:03:24 +0530 Subject: [PATCH 17/19] send_xcm into xcmv2 --- precompiles/xcm/XCM.sol | 14 -------------- precompiles/xcm/XCM_v2.sol | 9 +++++++++ 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/precompiles/xcm/XCM.sol b/precompiles/xcm/XCM.sol index e41f7dff86..408e1e07ef 100644 --- a/precompiles/xcm/XCM.sol +++ b/precompiles/xcm/XCM.sol @@ -5,12 +5,6 @@ pragma solidity ^0.8.0; */ interface XCM { - // A multilocation is defined by its number of parents and the encoded junctions (interior) - struct Multilocation { - uint8 parents; - bytes[] interior; - } - /** * @param asset_id - list of XC20 asset addresses * @param asset_amount - list of transfer amounts (must match with asset addresses above) @@ -117,12 +111,4 @@ interface XCM { uint256 fee_index ) external returns (bool); - /** - * @param destination - Multilocation of destination chain where to send this call - * @param xcm_call - encoded xcm call you want to send to destination - **/ - function send_xcm( - Multilocation memory destination, - bytes memory xcm_call - ) external returns (bool); } diff --git a/precompiles/xcm/XCM_v2.sol b/precompiles/xcm/XCM_v2.sol index fe5d61091d..53aa556d88 100644 --- a/precompiles/xcm/XCM_v2.sol +++ b/precompiles/xcm/XCM_v2.sol @@ -128,4 +128,13 @@ interface XCM { Multilocation memory destination, WeightV2 memory weight ) external returns (bool); + + /** + * @param destination - Multilocation of destination chain where to send this call + * @param xcm_call - encoded xcm call you want to send to destination + **/ + function send_xcm( + Multilocation memory destination, + bytes memory xcm_call + ) external returns (bool); } From 20893c880c1cf9557d0a24b523e2467e8775486a Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Wed, 4 Oct 2023 16:26:39 +0530 Subject: [PATCH 18/19] refactor, weight unlimite logic change --- precompiles/utils/src/xcm.rs | 5 +++-- precompiles/xcm/XCM_v2.sol | 38 +++++++++++++++--------------------- precompiles/xcm/src/lib.rs | 13 ++++++------ precompiles/xcm/src/tests.rs | 35 ++++++++++++++++++++++++++++++++- 4 files changed, 60 insertions(+), 31 deletions(-) diff --git a/precompiles/utils/src/xcm.rs b/precompiles/utils/src/xcm.rs index d6072dc125..16897c774a 100644 --- a/precompiles/utils/src/xcm.rs +++ b/precompiles/utils/src/xcm.rs @@ -24,6 +24,7 @@ use crate::Address; use sp_core::U256; +use sp_runtime::traits::Zero; use { crate::{bytes::*, revert, EvmData, EvmDataReader, EvmDataWriter, EvmResult}, frame_support::{ensure, pallet_prelude::Weight, traits::ConstU32}, @@ -369,8 +370,8 @@ impl WeightV2 { pub fn get_weight(&self) -> Weight { Weight::from_parts(self.ref_time, self.proof_size) } - pub fn is_max(&self) -> bool { - self.ref_time == u64::MAX + pub fn is_zero(&self) -> bool { + self.ref_time.is_zero() } } impl EvmData for WeightV2 { diff --git a/precompiles/xcm/XCM_v2.sol b/precompiles/xcm/XCM_v2.sol index 53aa556d88..51487986a6 100644 --- a/precompiles/xcm/XCM_v2.sol +++ b/precompiles/xcm/XCM_v2.sol @@ -18,7 +18,7 @@ interface XCM { } // A MultiAsset is defined by a multilocation and an amount - struct EvmMultiAsset { + struct MultiAsset { Multilocation location; uint256 amount; } @@ -29,15 +29,14 @@ interface XCM { uint256 amount; } - /// Transfer a token through XCM based on its currencyId + /// Transfer a token through XCM based on its address /// /// @dev The token transfer burns/transfers the corresponding amount before sending /// @param currencyAddress The ERC20 address of the currency we want to transfer /// @param amount The amount of tokens we want to transfer /// @param destination The Multilocation to which we want to send the tokens - /// @param weight The weight we want to buy in the destination chain, to provide - /// unlimited weight, you should use maximum u64 value (i.e. 18446744073709551615 ) - /// for ref_time + /// @param weight The weight we want to buy in the destination chain, to set the + /// weightlimit to Unlimited, you should use the value 0 for ref_time function transfer( address currencyAddress, uint256 amount, @@ -45,15 +44,14 @@ interface XCM { WeightV2 memory weight ) external returns (bool); - /// Transfer a token through XCM based on its currencyId specifying fee + /// Transfer a token through XCM based on its address specifying fee /// /// @dev The token transfer burns/transfers the corresponding amount before sending /// @param currencyAddress The ERC20 address of the currency we want to transfer /// @param amount The amount of tokens we want to transfer /// @param destination The Multilocation to which we want to send the tokens - /// @param weight The weight we want to buy in the destination chain, to provide - /// unlimited weight, you should use maximum u64 value (i.e. 18446744073709551615 ) - /// for ref_time + /// @param weight The weight we want to buy in the destination chain, to set the + /// weightlimit to Unlimited, you should use the value 0 for ref_time function transfer_with_fee( address currencyAddress, uint256 amount, @@ -69,9 +67,8 @@ interface XCM { /// Currently only Concrete Fungible assets /// @param amount The amount of tokens we want to transfer /// @param destination The Multilocation to which we want to send the tokens - /// @param weight The weight we want to buy in the destination chain, to provide - /// unlimited weight, you should use maximum u64 value (i.e. 18446744073709551615 ) - /// for ref_time + /// @param weight The weight we want to buy in the destination chain, to set the + /// weightlimit to Unlimited, you should use the value 0 for ref_time function transfer_multiasset( Multilocation memory asset, uint256 amount, @@ -86,9 +83,8 @@ interface XCM { /// Currently only Concrete Fungible assets /// @param amount The amount of tokens we want to transfer /// @param destination The Multilocation to which we want to send the tokens - /// @param weight The weight we want to buy in the destination chain, to provide - /// unlimited weight, you should use maximum u64 value (i.e. 18446744073709551615 ) - /// for ref_time + /// @param weight The weight we want to buy in the destination chain, to set the + /// weightlimit to Unlimited, you should use the value 0 for ref_time function transfer_multiasset_with_fee( Multilocation memory asset, uint256 amount, @@ -103,9 +99,8 @@ interface XCM { /// @param currencies The currencies we want to transfer, defined by their address and amount. /// @param feeItem Which of the currencies to be used as fee /// @param destination The Multilocation to which we want to send the tokens - /// @param weight The weight we want to buy in the destination chain, to provide - /// unlimited weight, you should use maximum u64 value (i.e. 18446744073709551615 ) - /// for ref_time + /// @param weight The weight we want to buy in the destination chain, to set the + /// weightlimit to Unlimited, you should use the value 0 for ref_time function transfer_multi_currencies( Currency[] memory currencies, uint32 feeItem, @@ -119,11 +114,10 @@ interface XCM { /// @param assets The assets we want to transfer, defined by their location and amount. /// @param feeItem Which of the currencies to be used as fee /// @param destination The Multilocation to which we want to send the tokens - /// @param weight The weight we want to buy in the destination chain, to provide - /// unlimited weight, you should use maximum u64 value (i.e. 18446744073709551615 ) - /// for ref_time + /// @param weight The weight we want to buy in the destination chain, to set the + /// weightlimit to Unlimited, you should use the value 0 for ref_time function transfer_multi_assets( - EvmMultiAsset[] memory assets, + MultiAsset[] memory assets, uint32 feeItem, Multilocation memory destination, WeightV2 memory weight diff --git a/precompiles/xcm/src/lib.rs b/precompiles/xcm/src/lib.rs index 28e16f56cc..8da693ff9e 100644 --- a/precompiles/xcm/src/lib.rs +++ b/precompiles/xcm/src/lib.rs @@ -76,6 +76,7 @@ pub enum Action { /// Dummy H160 address representing native currency (e.g. ASTR or SDN) const NATIVE_ADDRESS: H160 = H160::zero(); +/// Default proof_size of 256KB const DEFAULT_PROOF_SIZE: u64 = 1024 * 256; pub type XBalanceOf = ::Balance; @@ -487,7 +488,7 @@ where let asset_id = Runtime::address_to_asset_id(currency_address.into()) .ok_or(revert("Failed to resolve fee asset id from address"))?; - let dest_weight_limit = if weight.is_max() { + let dest_weight_limit = if weight.is_zero() { WeightLimit::Unlimited } else { WeightLimit::Limited(weight.get_weight()) @@ -535,7 +536,7 @@ where let asset_id = Runtime::address_to_asset_id(currency_address.into()) .ok_or(revert("Failed to resolve fee asset id from address"))?; - let dest_weight_limit = if weight.is_max() { + let dest_weight_limit = if weight.is_zero() { WeightLimit::Unlimited } else { WeightLimit::Limited(weight.get_weight()) @@ -577,7 +578,7 @@ where let destination = input.read::()?; let weight = input.read::()?; - let dest_weight_limit = if weight.is_max() { + let dest_weight_limit = if weight.is_zero() { WeightLimit::Unlimited } else { WeightLimit::Limited(weight.get_weight()) @@ -625,7 +626,7 @@ where let destination = input.read::()?; let weight = input.read::()?; - let dest_weight_limit = if weight.is_max() { + let dest_weight_limit = if weight.is_zero() { WeightLimit::Unlimited } else { WeightLimit::Limited(weight.get_weight()) @@ -685,7 +686,7 @@ where )) }) .collect::>()?; - let dest_weight_limit = if weight.is_max() { + let dest_weight_limit = if weight.is_zero() { WeightLimit::Unlimited } else { WeightLimit::Limited(weight.get_weight()) @@ -724,7 +725,7 @@ where let destination = input.read::()?; let weight = input.read::()?; - let dest_weight_limit = if weight.is_max() { + let dest_weight_limit = if weight.is_zero() { WeightLimit::Unlimited } else { WeightLimit::Limited(weight.get_weight()) diff --git a/precompiles/xcm/src/tests.rs b/precompiles/xcm/src/tests.rs index e44f875965..91fa0e7ee0 100644 --- a/precompiles/xcm/src/tests.rs +++ b/precompiles/xcm/src/tests.rs @@ -482,6 +482,7 @@ mod xcm_new_interface_test { }, ), }; + let native_token_location: MultiLocation = (Here).into(); let amount = 4200u64; // relay token to relay @@ -524,7 +525,7 @@ mod xcm_new_interface_test { .write(relay_token_location) // zero address by convention .write(U256::from(amount)) .write(para_destination) - .write(weight) + .write(weight.clone()) .build(), ) .expect_no_logs() @@ -545,6 +546,38 @@ mod xcm_new_interface_test { // Assert that the events vector contains the one expected assert!(events().contains(&expected)); + + // native token to para + + precompiles() + .prepare_test( + TestAccount::Alice, + PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::XtokensTransferMultiasset) + .write(native_token_location) // zero address by convention + .write(U256::from(amount)) + .write(para_destination) + .write(weight.clone()) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + let expected_asset: MultiAsset = MultiAsset { + id: AssetId::Concrete(native_token_location), + fun: Fungibility::Fungible(amount.into()), + }; + let expected: crate::mock::RuntimeEvent = + mock::RuntimeEvent::Xtokens(XtokensEvent::TransferredMultiAssets { + sender: TestAccount::Alice.into(), + assets: vec![expected_asset.clone()].into(), + fee: expected_asset, + dest: para_destination, + }) + .into(); + + // Assert that the events vector contains the one expected + assert!(events().contains(&expected)); }); } From 3447d5f6e23357eded85f923cd7966a9e0c2f099 Mon Sep 17 00:00:00 2001 From: Deepanshu Hooda Date: Wed, 4 Oct 2023 19:10:08 +0530 Subject: [PATCH 19/19] add fee description and other fix --- Cargo.lock | 2 +- precompiles/xcm/Cargo.toml | 2 +- precompiles/xcm/XCM_v2.sol | 2 ++ precompiles/xcm/src/lib.rs | 14 +++++++------- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a882131bbd..81cd56d6d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7478,7 +7478,7 @@ dependencies = [ [[package]] name = "pallet-evm-precompile-xcm" -version = "0.9.1" +version = "0.10.0" dependencies = [ "assert_matches", "astar-primitives", diff --git a/precompiles/xcm/Cargo.toml b/precompiles/xcm/Cargo.toml index 808015f530..63df2c623a 100644 --- a/precompiles/xcm/Cargo.toml +++ b/precompiles/xcm/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "pallet-evm-precompile-xcm" description = "Basic XCM support for EVM." -version = "0.9.1" +version = "0.10.0" authors.workspace = true edition.workspace = true homepage.workspace = true diff --git a/precompiles/xcm/XCM_v2.sol b/precompiles/xcm/XCM_v2.sol index 51487986a6..fa035ef2d1 100644 --- a/precompiles/xcm/XCM_v2.sol +++ b/precompiles/xcm/XCM_v2.sol @@ -49,6 +49,7 @@ interface XCM { /// @dev The token transfer burns/transfers the corresponding amount before sending /// @param currencyAddress The ERC20 address of the currency we want to transfer /// @param amount The amount of tokens we want to transfer + /// @param fee The amount to be spent to pay for execution in destination chain /// @param destination The Multilocation to which we want to send the tokens /// @param weight The weight we want to buy in the destination chain, to set the /// weightlimit to Unlimited, you should use the value 0 for ref_time @@ -82,6 +83,7 @@ interface XCM { /// @param asset The asset we want to transfer, defined by its multilocation. /// Currently only Concrete Fungible assets /// @param amount The amount of tokens we want to transfer + /// @param fee The amount to be spent to pay for execution in destination chain /// @param destination The Multilocation to which we want to send the tokens /// @param weight The weight we want to buy in the destination chain, to set the /// weightlimit to Unlimited, you should use the value 0 for ref_time diff --git a/precompiles/xcm/src/lib.rs b/precompiles/xcm/src/lib.rs index 8da693ff9e..b0c14204a3 100644 --- a/precompiles/xcm/src/lib.rs +++ b/precompiles/xcm/src/lib.rs @@ -443,7 +443,7 @@ where fn send_xcm(handle: &mut impl PrecompileHandle) -> EvmResult { let mut input = handle.read_input()?; - input.expect_arguments(3)?; + input.expect_arguments(2)?; // Raw call arguments let dest: MultiLocation = input.read::()?; @@ -475,7 +475,7 @@ where fn transfer(handle: &mut impl PrecompileHandle) -> EvmResult { let mut input = handle.read_input()?; - input.expect_arguments(5)?; + input.expect_arguments(4)?; // Read call arguments let currency_address = input.read::
()?; @@ -518,7 +518,7 @@ where fn transfer_with_fee(handle: &mut impl PrecompileHandle) -> EvmResult { let mut input = handle.read_input()?; - input.expect_arguments(6)?; + input.expect_arguments(5)?; // Read call arguments let currency_address = input.read::
()?; @@ -567,7 +567,7 @@ where fn transfer_multiasset(handle: &mut impl PrecompileHandle) -> EvmResult { let mut input = handle.read_input()?; - input.expect_arguments(5)?; + input.expect_arguments(4)?; // Read call arguments let asset_location = input.read::()?; @@ -611,7 +611,7 @@ where handle: &mut impl PrecompileHandle, ) -> EvmResult { let mut input = handle.read_input()?; - input.expect_arguments(6)?; + input.expect_arguments(5)?; // Read call arguments let asset_location = input.read::()?; @@ -660,7 +660,7 @@ where handle: &mut impl PrecompileHandle, ) -> EvmResult { let mut input = handle.read_input()?; - input.expect_arguments(5)?; + input.expect_arguments(4)?; let currencies: Vec<_> = input .read::>>()? @@ -716,7 +716,7 @@ where fn transfer_multi_assets(handle: &mut impl PrecompileHandle) -> EvmResult { let mut input = handle.read_input()?; - input.expect_arguments(5)?; + input.expect_arguments(4)?; let assets: Vec<_> = input .read::>>()?