From 50221310d1649c5e32967b2320aa43ee3ac67007 Mon Sep 17 00:00:00 2001 From: cdamian <17934949+cdamian@users.noreply.github.com> Date: Fri, 14 Jul 2023 17:03:43 +0300 Subject: [PATCH 1/9] connectors-gateway: Add Ethereum Transaction pallet --- Cargo.lock | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index b3454a87f4..68d26e064e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7259,6 +7259,31 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-ethereum-transaction" +version = "0.0.1" +dependencies = [ + "cfg-traits", + "ethabi", + "ethereum", + "fp-ethereum", + "fp-evm", + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-balances", + "pallet-ethereum", + "pallet-evm", + "pallet-evm-precompile-simple", + "pallet-timestamp", + "parity-scale-codec 3.4.0", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-evm" version = "6.0.0-dev" From d42f195e1cef0777283fbb1890ed84f52cedf0ed Mon Sep 17 00:00:00 2001 From: cdamian <17934949+cdamian@users.noreply.github.com> Date: Wed, 19 Jul 2023 15:21:59 +0300 Subject: [PATCH 2/9] connectors-gateway: Add gateway routers --- Cargo.lock | 35 ++ libs/mocks/src/ethereum_transaction.rs | 45 ++ libs/mocks/src/lib.rs | 2 + .../connectors-gateway-routers/Cargo.toml | 100 ++++ .../src/axelar_evm.rs | 251 +++++++++ .../src/ethereum_xcm.rs | 264 ++++++++++ .../connectors-gateway-routers/src/lib.rs | 80 +++ .../connectors-gateway-routers/src/mock.rs | 475 +++++++++++++++++ .../connectors-gateway-routers/src/tests.rs | 483 ++++++++++++++++++ runtime/common/src/lib.rs | 1 - runtime/common/src/routers.rs | 25 - runtime/development/Cargo.toml | 4 + runtime/development/src/connectors.rs | 3 +- 13 files changed, 1740 insertions(+), 28 deletions(-) create mode 100644 libs/mocks/src/ethereum_transaction.rs create mode 100644 pallets/connectors-gateway/connectors-gateway-routers/Cargo.toml create mode 100644 pallets/connectors-gateway/connectors-gateway-routers/src/axelar_evm.rs create mode 100644 pallets/connectors-gateway/connectors-gateway-routers/src/ethereum_xcm.rs create mode 100644 pallets/connectors-gateway/connectors-gateway-routers/src/lib.rs create mode 100644 pallets/connectors-gateway/connectors-gateway-routers/src/mock.rs create mode 100644 pallets/connectors-gateway/connectors-gateway-routers/src/tests.rs delete mode 100644 runtime/common/src/routers.rs diff --git a/Cargo.lock b/Cargo.lock index 68d26e064e..286696e002 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1544,6 +1544,40 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "connectors-gateway-routers" +version = "0.0.1" +dependencies = [ + "cfg-mocks", + "cfg-primitives", + "cfg-traits", + "cfg-types", + "cumulus-primitives-core", + "ethabi", + "frame-support", + "frame-system", + "orml-traits", + "pallet-balances", + "pallet-connectors-gateway", + "pallet-ethereum", + "pallet-ethereum-transaction", + "pallet-evm", + "pallet-evm-chain-id", + "pallet-evm-precompile-simple", + "pallet-timestamp", + "pallet-xcm-transactor", + "parity-scale-codec 3.4.0", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "xcm", + "xcm-builder", + "xcm-executor", + "xcm-primitives", +] + [[package]] name = "const-oid" version = "0.9.2" @@ -2605,6 +2639,7 @@ dependencies = [ "cfg-traits", "cfg-types", "chainbridge", + "connectors-gateway-routers", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", diff --git a/libs/mocks/src/ethereum_transaction.rs b/libs/mocks/src/ethereum_transaction.rs new file mode 100644 index 0000000000..50c6c8b517 --- /dev/null +++ b/libs/mocks/src/ethereum_transaction.rs @@ -0,0 +1,45 @@ +#[frame_support::pallet] +pub mod pallet { + use cfg_traits::ethereum::EthereumTransactor; + use frame_support::pallet_prelude::*; + use mock_builder::{execute_call, register_call}; + use sp_core::{H160, U256}; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::storage] + pub(super) type CallIds = StorageMap< + _, + Blake2_128Concat, + ::Output, + mock_builder::CallId, + >; + + impl Pallet { + pub fn mock_call( + f: impl Fn(H160, H160, &[u8], U256, U256, U256) -> DispatchResult + 'static, + ) { + register_call!(move |(from, to, data, value, gas_price, gas_limit)| f( + from, to, data, value, gas_price, gas_limit + )); + } + } + + impl EthereumTransactor for Pallet { + fn call( + from: H160, + to: H160, + data: &[u8], + value: U256, + gas_price: U256, + gas_limit: U256, + ) -> DispatchResultWithPostInfo { + execute_call!((from, to, data, value, gas_price, gas_limit)) + } + } +} diff --git a/libs/mocks/src/lib.rs b/libs/mocks/src/lib.rs index 6a95ef439e..2e9b7ec339 100644 --- a/libs/mocks/src/lib.rs +++ b/libs/mocks/src/lib.rs @@ -2,6 +2,7 @@ mod change_guard; mod connectors; mod connectors_gateway_routers; mod data; +mod ethereum_transaction; mod fees; mod permissions; mod pools; @@ -12,6 +13,7 @@ pub use change_guard::pallet_mock_change_guard; pub use connectors::{pallet as pallet_mock_connectors, MessageMock}; pub use connectors_gateway_routers::{pallet as pallet_mock_routers, RouterMock}; pub use data::pallet as pallet_mock_data; +pub use ethereum_transaction::pallet as pallet_mock_ethereum_transaction; pub use fees::pallet as pallet_mock_fees; pub use permissions::pallet as pallet_mock_permissions; pub use pools::pallet as pallet_mock_pools; diff --git a/pallets/connectors-gateway/connectors-gateway-routers/Cargo.toml b/pallets/connectors-gateway/connectors-gateway-routers/Cargo.toml new file mode 100644 index 0000000000..4781e3c378 --- /dev/null +++ b/pallets/connectors-gateway/connectors-gateway-routers/Cargo.toml @@ -0,0 +1,100 @@ +[package] +authors = ["Centrifuge "] +description = "Centrifuge Connectors Gateway Routers" +edition = "2021" +license = "LGPL-3.0" +name = "connectors-gateway-routers" +repository = "https://github.com/centrifuge/centrifuge-chain" +version = "0.0.1" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"], default-features = false } +frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.38" } +frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.38" } +scale-info = { version = "2.3.0", default-features = false, features = ["derive"] } +sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.38" } + +# Substrate +sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.38" } +sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.38" } + +# XCM +pallet-xcm-transactor = { git = "https://github.com/PureStake/moonbeam", default-features = false, rev = "00b3e3d97806e889b02e1bcb4b69e65433dd805d" } +xcm = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.38" } +xcm-primitives = { git = "https://github.com/PureStake/moonbeam", default-features = false, rev = "00b3e3d97806e889b02e1bcb4b69e65433dd805d" } + +# EVM +ethabi = { version = "16.0", default-features = false } +pallet-ethereum = { git = "https://github.com/PureStake/frontier", default-features = false, branch = "moonbeam-polkadot-v0.9.38" } +pallet-evm = { git = "https://github.com/PureStake/frontier", default-features = false, branch = "moonbeam-polkadot-v0.9.38" } + +# Custom crates +cfg-traits = { path = "../../../libs/traits", default-features = false } +cfg-types = { path = "../../../libs/types", default-features = false } + +# Local pallets +pallet-connectors-gateway = { path = "../.", default-features = false } +pallet-ethereum-transaction = { path = "../../ethereum-transaction", default-features = false } + +[dev-dependencies] +cumulus-primitives-core = { git = "https://github.com/purestake/cumulus", branch = "moonbeam-polkadot-v0.9.38", default-features = false } + +xcm-builder = { git = "https://github.com/purestake/polkadot", branch = "moonbeam-polkadot-v0.9.38", default-features = false } +xcm-executor = { git = "https://github.com/purestake/polkadot", branch = "moonbeam-polkadot-v0.9.38", default-features = false } + +pallet-evm-chain-id = { git = "https://github.com/PureStake/frontier", default-features = false, branch = "moonbeam-polkadot-v0.9.38" } +pallet-evm-precompile-simple = { git = "https://github.com/PureStake/frontier", default-features = false, branch = "moonbeam-polkadot-v0.9.38" } +pallet-timestamp = { git = "https://github.com/purestake/substrate", branch = "moonbeam-polkadot-v0.9.38" } + +sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } + +orml-traits = { git = "https://github.com/purestake/open-runtime-module-library", branch = "moonbeam-polkadot-v0.9.38", default-features = false } + +cfg-mocks = { path = "../../../libs/mocks" } +cfg-primitives = { path = "../../../libs/primitives" } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } + +[features] +default = ["std"] +runtime-benchmarks = [ + "cfg-traits/runtime-benchmarks", + "cfg-types/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-connectors-gateway/runtime-benchmarks", + "pallet-ethereum/runtime-benchmarks", + "pallet-ethereum-transaction/runtime-benchmarks", + "pallet-xcm-transactor/runtime-benchmarks", + "xcm-primitives/runtime-benchmarks", +] +std = [ + "codec/std", + "cfg-types/std", + "cfg-traits/std", + "frame-support/std", + "frame-system/std", + "sp-std/std", + "sp-core/std", + "xcm/std", + "pallet-connectors-gateway/std", + "pallet-xcm-transactor/std", + "pallet-ethereum/std", + "pallet-ethereum-transaction/std", + "xcm-primitives/std", + "ethabi/std", + "scale-info/std", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "cfg-traits/try-runtime", + "cfg-types/try-runtime", + "pallet-connectors-gateway/try-runtime", + "pallet-ethereum/try-runtime", + "pallet-ethereum-transaction/try-runtime", + "pallet-xcm-transactor/try-runtime", +] diff --git a/pallets/connectors-gateway/connectors-gateway-routers/src/axelar_evm.rs b/pallets/connectors-gateway/connectors-gateway-routers/src/axelar_evm.rs new file mode 100644 index 0000000000..671346deae --- /dev/null +++ b/pallets/connectors-gateway/connectors-gateway-routers/src/axelar_evm.rs @@ -0,0 +1,251 @@ +// Copyright 2021 Centrifuge Foundation (centrifuge.io). +// +// This file is part of the Centrifuge chain project. +// Centrifuge 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 (see http://www.gnu.org/licenses). +// Centrifuge 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. +use cfg_traits::{connectors::Codec, ethereum::EthereumTransactor}; +use codec::{Decode, Encode, MaxEncodedLen}; +use ethabi::{Contract, Function, Param, ParamType, Token}; +use frame_support::{ + dispatch::{DispatchError, DispatchResult}, + ensure, +}; +use scale_info::{ + prelude::string::{String, ToString}, + TypeInfo, +}; +use sp_core::{H160, H256, U256}; +use sp_runtime::traits::{BlakeTwo256, Hash}; +use sp_std::{collections::btree_map::BTreeMap, marker::PhantomData, vec, vec::Vec}; + +use crate::{AccountIdOf, MessageOf, CONNECTORS_FUNCTION_NAME, CONNECTORS_MESSAGE_PARAM}; + +/// The router used for executing the Connectors contract via Axelar. +#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub struct AxelarEVMRouter +where + T: frame_system::Config + + pallet_connectors_gateway::Config + + pallet_ethereum_transaction::Config + + pallet_evm::Config, +{ + pub domain: EVMDomain, + pub _marker: PhantomData, +} + +/// The EVMDomain holds all relevant information for validating and executing +/// the call to the Axelar contract. +#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub struct EVMDomain { + /// The chain to which the router will send the message to. + pub chain: EVMChain, + + /// The address of the Axelar contract deployed in our EVM. + pub axelar_contract_address: H160, + + /// The `BlakeTwo256` hash of the Axelar contract code. + /// This is used during router initialization to ensure that the correct + /// contract code is used. + pub axelar_contract_hash: H256, + + /// The address of the Connectors contract that we are going to call through + /// the Axelar contract. + pub connectors_contract_address: H160, + + /// The values used when executing the EVM call to the Axelar contract. + pub fee_values: FeeValues, +} + +/// The FeeValues holds all information related to the transaction costs. +#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub struct FeeValues { + /// The value used when executing the EVM call. + pub value: U256, + + /// The gas price used when executing the EVM call. + pub gas_price: U256, + + /// The gas limit used when executing the EVM call. + pub gas_limit: U256, +} + +/// EVMChain holds all supported EVM chains. +#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub enum EVMChain { + Ethereum, +} + +/// Required due to the naming convention defined by Axelar here: +/// +impl ToString for EVMChain { + fn to_string(&self) -> String { + match self { + EVMChain::Ethereum => "Ethereum".to_string(), + } + } +} + +const AXELAR_FUNCTION_NAME: &'static str = "callContract"; +const AXELAR_DESTINATION_CHAIN_PARAM: &'static str = "destinationChain"; +const AXELAR_DESTINATION_CONTRACT_ADDRESS_PARAM: &'static str = "destinationContractAddress"; +const AXELAR_PAYLOAD_PARAM: &'static str = "payload"; + +impl AxelarEVMRouter +where + T: frame_system::Config + + pallet_connectors_gateway::Config + + pallet_ethereum_transaction::Config + + pallet_evm::Config, + T::AccountId: AsRef<[u8; 32]>, +{ + /// Performs an extra check to ensure that the actual contract is deployed + /// at the provided address and that the contract code hash matches. + pub fn do_init(&self) -> DispatchResult { + let code = pallet_evm::AccountCodes::::get(self.domain.axelar_contract_address); + + ensure!( + BlakeTwo256::hash_of(&code) == self.domain.axelar_contract_hash, + DispatchError::Other("Axelar contract code does not match"), + ); + + Ok(()) + } + + /// Encodes the Connectors message to the required format, + /// then executes the EVM call using the Ethereum transaction pallet. + /// + /// NOTE - there sender account ID provided here will be converted to an EVM + /// address via truncating. When the call is processed by the underlying EVM + /// pallet, this EVM address will be converted back into a substrate account + /// which will be charged for the transaction. This converted substrate + /// account is not the same as the original account. + pub fn do_send(&self, sender: AccountIdOf, msg: MessageOf) -> DispatchResult { + let eth_msg = self.get_eth_msg(msg).map_err(DispatchError::Other)?; + + let sender_evm_address = H160::from_slice(&sender.as_ref()[0..20]); + + // TODO(cdamian): This returns a `DispatchResultWithPostInfo`. Should we + // propagate that to another layer that will eventually charge for the + // weight in the PostDispatchInfo? + as EthereumTransactor>::call( + sender_evm_address, + self.domain.axelar_contract_address, + eth_msg.as_slice(), + self.domain.fee_values.value, + self.domain.fee_values.gas_price, + self.domain.fee_values.gas_limit, + ) + .map_err(|e| e.error)?; + + Ok(()) + } + + /// Encodes the provided message into the format required for submitting it + /// to the Axelar contract which in turn submits it to the Connectors + /// contract. + fn get_eth_msg(&self, msg: MessageOf) -> Result, &'static str> { + // `AxelarEVMRouter` -> `callContract` on the Axelar Gateway contract + // deployed in the Centrifuge EVM pallet. + // + // Axelar Gateway contract -> `handle` on the Connectors gateway contract + // deployed on Ethereum. + + // Connectors Call: + // + // function handle(bytes memory _message) external onlyRouter {} + + #[allow(deprecated)] + let encoded_connectors_contract = Contract { + constructor: None, + functions: BTreeMap::>::from([( + CONNECTORS_FUNCTION_NAME.to_string(), + vec![Function { + name: CONNECTORS_FUNCTION_NAME.into(), + inputs: vec![Param { + name: CONNECTORS_MESSAGE_PARAM.into(), + kind: ParamType::Bytes, + internal_type: None, + }], + outputs: vec![], + constant: false, + state_mutability: Default::default(), + }], + )]), + events: Default::default(), + errors: Default::default(), + receive: false, + fallback: false, + } + .function(CONNECTORS_FUNCTION_NAME) + .map_err(|_| "cannot retrieve Connectors contract function")? + .encode_input(&[Token::Bytes(msg.serialize())]) + .map_err(|_| "cannot encode input for Connectors contract function")?; + + // Axelar Call: + // + // function callContract( + // string calldata destinationChain, + // string calldata destinationContractAddress, + // bytes calldata payload, + // ) external { + // emit ContractCall( + // msg.sender, + // destinationChain, + // destinationContractAddress, + // keccak256(payload), + // payload, + // ); + // } + + #[allow(deprecated)] + let encoded_axelar_contract = Contract { + constructor: None, + functions: BTreeMap::>::from([( + AXELAR_FUNCTION_NAME.into(), + vec![Function { + name: AXELAR_FUNCTION_NAME.into(), + inputs: vec![ + Param { + name: AXELAR_DESTINATION_CHAIN_PARAM.into(), + kind: ParamType::String, + internal_type: None, + }, + Param { + name: AXELAR_DESTINATION_CONTRACT_ADDRESS_PARAM.into(), + kind: ParamType::String, + internal_type: None, + }, + Param { + name: AXELAR_PAYLOAD_PARAM.into(), + kind: ParamType::Bytes, + internal_type: None, + }, + ], + outputs: vec![], + constant: false, + state_mutability: Default::default(), + }], + )]), + events: Default::default(), + errors: Default::default(), + receive: false, + fallback: false, + } + .function(AXELAR_FUNCTION_NAME) + .map_err(|_| "cannot retrieve Axelar contract function")? + .encode_input(&[ + Token::String(self.domain.chain.to_string()), + Token::String(self.domain.connectors_contract_address.to_string()), + Token::Bytes(encoded_connectors_contract), + ]) + .map_err(|_| "cannot encode input for Axelar contract function")?; + + Ok(encoded_axelar_contract) + } +} diff --git a/pallets/connectors-gateway/connectors-gateway-routers/src/ethereum_xcm.rs b/pallets/connectors-gateway/connectors-gateway-routers/src/ethereum_xcm.rs new file mode 100644 index 0000000000..3fe6240bc2 --- /dev/null +++ b/pallets/connectors-gateway/connectors-gateway-routers/src/ethereum_xcm.rs @@ -0,0 +1,264 @@ +// Copyright 2021 Centrifuge Foundation (centrifuge.io). +// +// This file is part of the Centrifuge chain project. +// Centrifuge 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 (see http://www.gnu.org/licenses). +// Centrifuge 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. + +use core::convert::TryFrom; + +use cfg_traits::connectors::Codec; +use codec::{Decode, Encode, MaxEncodedLen}; +use ethabi::{Bytes, Contract}; +use frame_support::{ + dispatch::DispatchResult, sp_runtime::DispatchError, traits::OriginTrait, weights::Weight, +}; +use pallet_xcm_transactor::{Currency, CurrencyPayment, TransactWeights}; +use scale_info::TypeInfo; +use sp_core::{bounded::BoundedVec, ConstU32, H160, U256}; +use sp_std::{boxed::Box, marker::PhantomData, vec, vec::Vec}; +use xcm::{ + v2::{MultiLocation, OriginKind}, + VersionedMultiLocation, +}; + +use crate::{ + AccountIdOf, CurrencyIdOf, MessageOf, CONNECTORS_FUNCTION_NAME, CONNECTORS_MESSAGE_PARAM, +}; + +/// The router used for submitting a Connectors message via Moonbeam XCM. +#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub struct EthereumXCMRouter +where + T: frame_system::Config + pallet_xcm_transactor::Config + pallet_connectors_gateway::Config, +{ + pub xcm_domain: XcmDomain>, + pub _marker: PhantomData, +} + +impl EthereumXCMRouter +where + T: frame_system::Config + pallet_xcm_transactor::Config + pallet_connectors_gateway::Config, +{ + /// Sets the weight information for the provided XCM domain location, and + /// the fee per second for the provided fee asset location. + pub fn do_init(&self) -> DispatchResult { + pallet_xcm_transactor::Pallet::::set_transact_info( + ::RuntimeOrigin::root(), + self.xcm_domain.location.clone(), + self.xcm_domain.transact_info.transact_extra_weight, + self.xcm_domain.transact_info.max_weight, + self.xcm_domain.transact_info.transact_extra_weight_signed, + )?; + + pallet_xcm_transactor::Pallet::::set_fee_per_second( + ::RuntimeOrigin::root(), + self.xcm_domain.fee_asset_location.clone(), + self.xcm_domain.fee_per_second, + ) + } + + /// Encodes the Connectors message to the required format and executes the + /// call via the XCM transactor pallet. + pub fn do_send(&self, sender: AccountIdOf, msg: MessageOf) -> DispatchResult { + let contract_call = get_encoded_contract_call(msg.serialize()) + .map_err(|_| DispatchError::Other("encoded contract call retrieval"))?; + + let ethereum_xcm_call = + get_encoded_ethereum_xcm_call::(self.xcm_domain.clone(), contract_call) + .map_err(|_| DispatchError::Other("encoded ethereum xcm call retrieval"))?; + + pallet_xcm_transactor::Pallet::::transact_through_sovereign( + ::RuntimeOrigin::root(), + // The destination to which the message should be sent. + self.xcm_domain.location.clone(), + // The sender will pay for this transaction. + sender, + // The currency in which we want to pay fees. + CurrencyPayment { + currency: Currency::AsCurrencyId(self.xcm_domain.fee_currency.clone()), + fee_amount: None, + }, + // The call to be executed in the destination chain. + ethereum_xcm_call, + OriginKind::SovereignAccount, + TransactWeights { + // Convert the max gas_limit into a max transact weight following + // Moonbeam's formula. + transact_required_weight_at_most: Weight::from_all( + self.xcm_domain.max_gas_limit * 25_000 + 100_000_000, + ), + overall_weight: None, + }, + )?; + + Ok(()) + } +} + +/// Build the encoded `ethereum_xcm::transact(eth_tx)` call that should +/// request to execute `evm_call`. +/// +/// * `xcm_domain` - All the necessary info regarding the xcm-based domain +/// where this `ethereum_xcm` call is to be executed +/// * `evm_call` - The encoded EVM call calling ConnectorsXcmRouter::handle(msg) +pub(crate) fn get_encoded_ethereum_xcm_call( + xcm_domain: XcmDomain>, + evm_call: Vec, +) -> Result, ()> +where + T: frame_system::Config + pallet_xcm_transactor::Config + pallet_connectors_gateway::Config, +{ + let input = + BoundedVec::>::try_from( + evm_call, + ) + .map_err(|_| ())?; + + let mut encoded: Vec = Vec::new(); + + encoded.append( + &mut xcm_domain + .ethereum_xcm_transact_call_index + .clone() + .into_inner(), + ); + encoded.append( + &mut xcm_primitives::EthereumXcmTransaction::V1(xcm_primitives::EthereumXcmTransactionV1 { + gas_limit: U256::from(xcm_domain.max_gas_limit), + fee_payment: xcm_primitives::EthereumXcmFee::Auto, + action: pallet_ethereum::TransactionAction::Call(xcm_domain.contract_address), + value: U256::zero(), + input, + access_list: None, + }) + .encode(), + ); + + Ok(encoded) +} + +/// Return the encoded contract call, i.e, +/// ConnectorsXcmRouter::handle(encoded_msg). +pub(crate) fn get_encoded_contract_call(encoded_msg: Vec) -> Result { + let contract = get_xcm_router_contract(); + let encoded_contract_call = contract + .function(CONNECTORS_FUNCTION_NAME) + .map_err(|_| ())? + .encode_input(&[ethabi::Token::Bytes(encoded_msg)]) + .map_err(|_| ())?; + + Ok(encoded_contract_call) +} + +/// The ConnectorsXcmRouter Abi as in ethabi::Contract +/// Note: We only concern ourselves with the `handle` function of the +/// contract since that's all we need to build the calls for remote EVM +/// execution. +pub(crate) fn get_xcm_router_contract() -> Contract { + use sp_std::collections::btree_map::BTreeMap; + + let mut functions = BTreeMap::new(); + #[allow(deprecated)] + functions.insert( + CONNECTORS_FUNCTION_NAME.into(), + vec![ethabi::Function { + name: CONNECTORS_FUNCTION_NAME.into(), + inputs: vec![ethabi::Param { + name: CONNECTORS_MESSAGE_PARAM.into(), + kind: ethabi::ParamType::Bytes, + internal_type: None, + }], + outputs: vec![], + constant: false, + state_mutability: Default::default(), + }], + ); + + Contract { + constructor: None, + functions, + events: Default::default(), + errors: Default::default(), + receive: false, + fallback: false, + } +} + +/// XcmDomain gathers all the required fields to build and send remote +/// calls to a specific XCM-based Domain. +#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo)] +pub struct XcmDomain { + /// The XCM multilocation of the domain + pub location: Box, + + /// The ethereum_xcm::Call::transact call index on a given domain. + /// It should contain the pallet index + the `transact` call index, to which + /// we will append the eth_tx param. You can obtain this value by building + /// an ethereum_xcm::transact call with Polkadot JS on the target chain. + pub ethereum_xcm_transact_call_index: + BoundedVec>, + + /// The ConnectorsXcmRouter contract address on a given domain + pub contract_address: H160, + + /// The max gas_limit we want to propose for a remote evm execution + pub max_gas_limit: u64, + + /// The XCM transact info that will be stored in the + /// `TransactInfoWithWeightLimit` storage of the XCM transactor pallet. + pub transact_info: XcmTransactInfo, + + /// The currency in which execution fees will be paid on + pub fee_currency: CurrencyId, + + /// The fee per second that will be stored in the + /// `DestinationAssetFeePerSecond` storage of the XCM transactor pallet. + pub fee_per_second: u128, + + /// The location of the asset used for paying XCM fees. + pub fee_asset_location: Box, +} + +#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +/// XcmTransactInfo hold all the weight related information required for the XCM +/// transactor pallet. +pub struct XcmTransactInfo { + pub transact_extra_weight: Weight, + pub max_weight: Weight, + pub transact_extra_weight_signed: Option, +} + +/// NOTE: Remove this custom implementation once the following underlying data +/// implements MaxEncodedLen: +/// * Polkadot Repo: xcm::VersionedMultiLocation +/// * PureStake Repo: pallet_xcm_transactor::Config::CurrencyId +impl MaxEncodedLen for XcmDomain +where + XcmDomain: Encode, +{ + fn max_encoded_len() -> usize { + // The domain's `VersionedMultiLocation` (custom bound) + MultiLocation::max_encoded_len() + // From the enum wrapping of `VersionedMultiLocation` for the XCM domain location. + .saturating_add(1) + // From the enum wrapping of `VersionedMultiLocation` for the asset fee location. + .saturating_add(1) + // The ethereum xcm call index (default bound) + .saturating_add(BoundedVec::< + u8, + ConstU32<{ xcm_primitives::MAX_ETHEREUM_XCM_INPUT_SIZE }>, + >::max_encoded_len()) + // The contract address (default bound) + .saturating_add(H160::max_encoded_len()) + // The fee currency (custom bound) + .saturating_add(cfg_types::tokens::CurrencyId::max_encoded_len()) + // The XcmTransactInfo + .saturating_add(XcmTransactInfo::max_encoded_len()) + } +} diff --git a/pallets/connectors-gateway/connectors-gateway-routers/src/lib.rs b/pallets/connectors-gateway/connectors-gateway-routers/src/lib.rs new file mode 100644 index 0000000000..a0d6224834 --- /dev/null +++ b/pallets/connectors-gateway/connectors-gateway-routers/src/lib.rs @@ -0,0 +1,80 @@ +// Copyright 2021 Centrifuge Foundation (centrifuge.io). +// +// This file is part of the Centrifuge chain project. +// Centrifuge 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 (see http://www.gnu.org/licenses). +// Centrifuge 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. +#![cfg_attr(not(feature = "std"), no_std)] + +use cfg_traits::connectors::Router; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::dispatch::DispatchResult; +use scale_info::TypeInfo; + +use crate::{axelar_evm::AxelarEVMRouter, ethereum_xcm::EthereumXCMRouter}; + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +pub mod axelar_evm; +pub mod ethereum_xcm; + +pub use axelar_evm::*; +pub use ethereum_xcm::*; + +type CurrencyIdOf = ::CurrencyId; +type MessageOf = ::Message; +type AccountIdOf = ::AccountId; + +const CONNECTORS_FUNCTION_NAME: &'static str = "handle"; +const CONNECTORS_MESSAGE_PARAM: &'static str = "message"; + +/// The routers used for outgoing messages. +#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub enum DomainRouter +where + T: frame_system::Config + + pallet_xcm_transactor::Config + + pallet_connectors_gateway::Config + + pallet_ethereum_transaction::Config + + pallet_evm::Config, + T::AccountId: AsRef<[u8; 32]>, +{ + EthereumXCM(EthereumXCMRouter), + AxelarEVM(AxelarEVMRouter), +} + +impl Router for DomainRouter +where + T: frame_system::Config + + pallet_xcm_transactor::Config + + pallet_connectors_gateway::Config + + pallet_ethereum_transaction::Config + + pallet_evm::Config, + T::AccountId: AsRef<[u8; 32]>, +{ + type Message = MessageOf; + type Sender = AccountIdOf; + + fn init(&self) -> DispatchResult { + match self { + DomainRouter::EthereumXCM(r) => r.do_init(), + DomainRouter::AxelarEVM(r) => r.do_init(), + } + } + + fn send(&self, sender: Self::Sender, message: Self::Message) -> DispatchResult { + match self { + DomainRouter::EthereumXCM(r) => r.do_send(sender, message), + DomainRouter::AxelarEVM(r) => r.do_send(sender, message), + } + } +} diff --git a/pallets/connectors-gateway/connectors-gateway-routers/src/mock.rs b/pallets/connectors-gateway/connectors-gateway-routers/src/mock.rs new file mode 100644 index 0000000000..f77d882d60 --- /dev/null +++ b/pallets/connectors-gateway/connectors-gateway-routers/src/mock.rs @@ -0,0 +1,475 @@ +use std::str::FromStr; + +use cfg_mocks::{pallet_mock_connectors, pallet_mock_routers, MessageMock, RouterMock}; +use cfg_types::domain_address::DomainAddress; +use codec::{Decode, Encode}; +use cumulus_primitives_core::{ + Instruction, MultiAsset, MultiLocation, PalletInstance, Parachain, SendError, Xcm, XcmHash, +}; +use frame_support::{ + parameter_types, + traits::{FindAuthor, PalletInfo as PalletInfoTrait}, + weights::Weight, +}; +use frame_system::EnsureRoot; +use pallet_connectors_gateway::EnsureLocal; +use pallet_ethereum::IntermediateStateRoot; +use pallet_evm::{ + runner::stack::Runner, AddressMapping, EnsureAddressNever, EnsureAddressRoot, FeeCalculator, + FixedGasWeightMapping, Precompile, PrecompileHandle, PrecompileResult, PrecompileSet, + SubstrateBlockHashMapping, +}; +use sp_core::{crypto::AccountId32, ByteArray, ConstU16, ConstU32, ConstU64, H160, H256, U256}; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, + ConsensusEngineId, +}; +use sp_std::{cell::RefCell, marker::PhantomData}; +use xcm::{ + latest::{ + Error as XcmError, InteriorMultiLocation, NetworkId, Result as XcmResult, SendResult, + XcmContext, + }, + v3::{Junction, Junctions, MultiAssets, SendXcm}, +}; +use xcm_executor::{ + traits::{TransactAsset, WeightBounds}, + Assets, +}; +use xcm_primitives::{ + HrmpAvailableCalls, HrmpEncodeCall, UtilityAvailableCalls, UtilityEncodeCall, XcmTransact, +}; + +pub type Balance = u128; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Balances: pallet_balances, + MockConnectors: pallet_mock_connectors, + ConnectorsGateway: pallet_connectors_gateway, + XcmTransactor: pallet_xcm_transactor::{Pallet, Call, Event}, + EVM: pallet_evm::{Pallet, Call, Storage, Config, Event}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage}, + Ethereum: pallet_ethereum::{Pallet, Call, Storage, Event, Origin}, + EthereumTransaction: pallet_ethereum_transaction, + } +); + +frame_support::parameter_types! { + pub const MaxConnectorsPerDomain: u32 = 3; + pub const MaxIncomingMessageSize: u32 = 1024; +} + +impl frame_system::Config for Runtime { + type AccountData = pallet_balances::AccountData; + type AccountId = AccountId32; + type BaseCallFilter = frame_support::traits::Everything; + type BlockHashCount = ConstU64<250>; + type BlockLength = (); + type BlockNumber = u64; + type BlockWeights = (); + type DbWeight = (); + type Hash = H256; + type Hashing = BlakeTwo256; + type Header = Header; + type Index = u64; + type Lookup = IdentityLookup; + type MaxConsumers = ConstU32<16>; + type OnKilledAccount = (); + type OnNewAccount = (); + type OnSetCode = (); + type PalletInfo = PalletInfo; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; + type SS58Prefix = ConstU16<42>; + type SystemWeightInfo = (); + type Version = (); +} + +impl pallet_balances::Config for Runtime { + type AccountStore = System; + type Balance = Balance; + type DustRemoval = (); + type ExistentialDeposit = (); + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = (); + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); +} + +impl pallet_mock_connectors::Config for Runtime { + type DomainAddress = DomainAddress; + type Message = MessageMock; +} + +impl pallet_ethereum_transaction::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} + +impl pallet_mock_routers::Config for Runtime {} + +impl pallet_connectors_gateway::Config for Runtime { + type AdminOrigin = EnsureRoot; + type InboundQueue = MockConnectors; + type LocalEVMOrigin = EnsureLocal; + type MaxIncomingMessageSize = MaxIncomingMessageSize; + type Message = MessageMock; + type Router = RouterMock; + type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; + type WeightInfo = (); +} + +parameter_types! { + pub const MinimumPeriod: u64 = 1000; +} + +impl pallet_timestamp::Config for Runtime { + type MinimumPeriod = MinimumPeriod; + type Moment = u64; + type OnTimestampSet = (); + type WeightInfo = (); +} + +/////////////////////// +// EVM pallet mocks. // +/////////////////////// + +pub struct FixedGasPrice; +impl FeeCalculator for FixedGasPrice { + fn min_gas_price() -> (U256, Weight) { + // Return some meaningful gas price and weight + (1_000_000_000u128.into(), Weight::from_ref_time(7u64)) + } +} + +/// Identity address mapping. +pub struct IdentityAddressMapping; + +impl AddressMapping for IdentityAddressMapping { + fn into_account_id(address: H160) -> AccountId32 { + let tag = b"EVM"; + let mut bytes = [0; 32]; + bytes[0..20].copy_from_slice(address.as_bytes()); + bytes[20..28].copy_from_slice(&1u64.to_be_bytes()); + bytes[28..31].copy_from_slice(tag); + + AccountId32::from_slice(bytes.as_slice()).unwrap() + } +} + +pub struct FindAuthorTruncated; +impl FindAuthor for FindAuthorTruncated { + fn find_author<'a, I>(_digests: I) -> Option + where + I: 'a + IntoIterator, + { + Some(H160::from_str("1234500000000000000000000000000000000000").unwrap()) + } +} + +pub struct MockPrecompileSet; + +impl PrecompileSet for MockPrecompileSet { + /// Tries to execute a precompile in the precompile set. + /// If the provided address is not a precompile, returns None. + fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { + let address = handle.code_address(); + + if address == H160::from_low_u64_be(1) { + return Some(pallet_evm_precompile_simple::Identity::execute(handle)); + } + + None + } + + /// Check if the given address is a precompile. Should only be called to + /// perform the check while not executing the precompile afterward, since + /// `execute` already performs a check internally. + fn is_precompile(&self, address: H160) -> bool { + address == H160::from_low_u64_be(1) + } +} + +parameter_types! { + pub BlockGasLimit: U256 = U256::max_value(); + pub WeightPerGas: Weight = Weight::from_ref_time(20_000); + pub MockPrecompiles: MockPrecompileSet = MockPrecompileSet; +} + +impl pallet_evm::Config for Runtime { + type AddressMapping = IdentityAddressMapping; + type BlockGasLimit = BlockGasLimit; + type BlockHashMapping = SubstrateBlockHashMapping; + type CallOrigin = EnsureAddressRoot; + type ChainId = (); + type Currency = Balances; + type FeeCalculator = FixedGasPrice; + type FindAuthor = FindAuthorTruncated; + type GasWeightMapping = FixedGasWeightMapping; + type OnChargeTransaction = (); + type OnCreate = (); + type PrecompilesType = MockPrecompileSet; + type PrecompilesValue = MockPrecompiles; + type Runner = Runner; + type RuntimeEvent = RuntimeEvent; + type WeightPerGas = WeightPerGas; + type WithdrawOrigin = EnsureAddressNever; +} + +impl pallet_ethereum::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type StateRoot = IntermediateStateRoot; +} + +/////////////////////////// +// XCM transactor mocks. // +/////////////////////////// + +// Transactors for the mock runtime. Only relay chain +#[derive(Clone, Eq, Debug, PartialEq, Ord, PartialOrd, Encode, Decode, scale_info::TypeInfo)] +pub enum Transactors { + Relay, +} + +#[cfg(feature = "runtime-benchmarks")] +impl Default for Transactors { + fn default() -> Self { + Transactors::Relay + } +} + +impl XcmTransact for Transactors { + fn destination(self) -> MultiLocation { + match self { + Transactors::Relay => MultiLocation::parent(), + } + } +} + +impl UtilityEncodeCall for Transactors { + fn encode_call(self, call: UtilityAvailableCalls) -> Vec { + match self { + Transactors::Relay => match call { + UtilityAvailableCalls::AsDerivative(a, b) => { + let mut call = + RelayCall::Utility(UtilityCall::AsDerivative(a.clone())).encode(); + call.append(&mut b.clone()); + call + } + }, + } + } +} + +pub struct AccountIdToMultiLocation; +impl sp_runtime::traits::Convert for AccountIdToMultiLocation { + fn convert(_account: AccountId32) -> MultiLocation { + let as_h160: H160 = H160::repeat_byte(0xAA); + MultiLocation::new( + 0, + Junctions::X1(Junction::AccountKey20 { + network: None, + key: as_h160.as_fixed_bytes().clone(), + }), + ) + } +} + +pub struct DummyAssetTransactor; +impl TransactAsset for DummyAssetTransactor { + fn deposit_asset(_what: &MultiAsset, _who: &MultiLocation, _context: &XcmContext) -> XcmResult { + Ok(()) + } + + fn withdraw_asset( + _what: &MultiAsset, + _who: &MultiLocation, + _context: Option<&XcmContext>, + ) -> Result { + Ok(Assets::default()) + } +} + +pub struct CurrencyIdToMultiLocation; + +pub type AssetId = u128; + +#[derive(Clone, Eq, Debug, PartialEq, Ord, PartialOrd, Encode, Decode, scale_info::TypeInfo)] +pub enum CurrencyId { + SelfReserve, + OtherReserve(AssetId), +} + +impl sp_runtime::traits::Convert> for CurrencyIdToMultiLocation { + fn convert(currency: CurrencyId) -> Option { + match currency { + CurrencyId::SelfReserve => { + let multi: MultiLocation = SelfReserve::get(); + Some(multi) + } + // To distinguish between relay and others, specially for reserve asset + CurrencyId::OtherReserve(asset) => { + if asset == 0 { + Some(MultiLocation::parent()) + } else { + Some(MultiLocation::new(1, Junctions::X1(Parachain(2)))) + } + } + } + } +} + +pub struct MockHrmpEncoder; + +impl HrmpEncodeCall for MockHrmpEncoder { + fn hrmp_encode_call(call: HrmpAvailableCalls) -> Result, XcmError> { + match call { + HrmpAvailableCalls::InitOpenChannel(_, _, _) => { + Ok(RelayCall::Hrmp(HrmpCall::Init()).encode()) + } + HrmpAvailableCalls::AcceptOpenChannel(_) => { + Ok(RelayCall::Hrmp(HrmpCall::Accept()).encode()) + } + HrmpAvailableCalls::CloseChannel(_) => Ok(RelayCall::Hrmp(HrmpCall::Close()).encode()), + HrmpAvailableCalls::CancelOpenRequest(_, _) => { + Ok(RelayCall::Hrmp(HrmpCall::Close()).encode()) + } + } + } +} + +// Simulates sending a XCM message +thread_local! { + pub static SENT_XCM: RefCell> = RefCell::new(Vec::new()); +} +pub fn sent_xcm() -> Vec<(MultiLocation, xcm::v3::opaque::Xcm)> { + SENT_XCM.with(|q| (*q.borrow()).clone()) +} +pub struct TestSendXcm; +impl SendXcm for TestSendXcm { + type Ticket = (); + + fn validate( + destination: &mut Option, + message: &mut Option, + ) -> SendResult { + SENT_XCM.with(|q| { + q.borrow_mut() + .push((destination.clone().unwrap(), message.clone().unwrap())) + }); + Ok(((), MultiAssets::new())) + } + + fn deliver(_: Self::Ticket) -> Result { + Ok(XcmHash::default()) + } +} + +#[derive(Encode, Decode)] +pub enum RelayCall { + #[codec(index = 0u8)] + // the index should match the position of the module in `construct_runtime!` + Utility(UtilityCall), + #[codec(index = 1u8)] + // the index should match the position of the module in `construct_runtime!` + Hrmp(HrmpCall), +} + +#[derive(Encode, Decode)] +pub enum UtilityCall { + #[codec(index = 0u8)] + AsDerivative(u16), +} + +#[derive(Encode, Decode)] +pub enum HrmpCall { + #[codec(index = 0u8)] + Init(), + #[codec(index = 1u8)] + Accept(), + #[codec(index = 2u8)] + Close(), +} + +pub type MaxHrmpRelayFee = xcm_builder::Case; + +pub struct DummyWeigher(PhantomData); + +impl WeightBounds for DummyWeigher { + fn weight(_message: &mut Xcm) -> Result { + Ok(Weight::zero()) + } + + fn instr_weight(_instruction: &Instruction) -> Result { + Ok(Weight::zero()) + } +} + +parameter_types! { + pub const RelayNetwork: NetworkId = NetworkId::Polkadot; + + pub ParachainId: cumulus_primitives_core::ParaId = 100.into(); + + pub SelfLocation: MultiLocation = + MultiLocation::new(1, Junctions::X1(Parachain(ParachainId::get().into()))); + + pub SelfReserve: MultiLocation = MultiLocation::new( + 1, + Junctions::X2( + Parachain(ParachainId::get().into()), + PalletInstance( + ::PalletInfo::index::().unwrap() as u8 + ) + )); + + pub const BaseXcmWeight: xcm::latest::Weight = xcm::latest::Weight::from_ref_time(1000); + + pub MaxFee: MultiAsset = (MultiLocation::parent(), 1_000_000_000_000u128).into(); + + pub UniversalLocation: InteriorMultiLocation = RelayNetwork::get().into(); +} + +impl pallet_xcm_transactor::Config for Runtime { + type AccountIdToMultiLocation = AccountIdToMultiLocation; + type AssetTransactor = DummyAssetTransactor; + type Balance = Balance; + type BaseXcmWeight = BaseXcmWeight; + type CurrencyId = CurrencyId; + type CurrencyIdToMultiLocation = CurrencyIdToMultiLocation; + type DerivativeAddressRegistrationOrigin = EnsureRoot; + type HrmpEncoder = MockHrmpEncoder; + type HrmpManipulatorOrigin = EnsureRoot; + type MaxHrmpFee = MaxHrmpRelayFee; + type ReserveProvider = orml_traits::location::RelativeReserveProvider; + type RuntimeEvent = RuntimeEvent; + type SelfLocation = SelfLocation; + type SovereignAccountDispatcherOrigin = EnsureRoot; + type Transactor = Transactors; + type UniversalLocation = UniversalLocation; + type Weigher = DummyWeigher; + type WeightInfo = (); + type XcmSender = TestSendXcm; +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let storage = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(storage); + ext.execute_with(|| frame_system::Pallet::::set_block_number(1)); + + ext +} diff --git a/pallets/connectors-gateway/connectors-gateway-routers/src/tests.rs b/pallets/connectors-gateway/connectors-gateway-routers/src/tests.rs new file mode 100644 index 0000000000..9b245a3d18 --- /dev/null +++ b/pallets/connectors-gateway/connectors-gateway-routers/src/tests.rs @@ -0,0 +1,483 @@ +use cfg_mocks::MessageMock; +use cfg_primitives::CFG; +use cfg_traits::connectors::{Codec, Router}; +use cumulus_primitives_core::MultiLocation; +use frame_support::{assert_noop, assert_ok, traits::fungible::Mutate}; +use pallet_evm::AddressMapping; +use pallet_xcm_transactor::RemoteTransactInfoWithMaxWeight; +use sp_core::{bounded_vec, crypto::AccountId32, H160, U256}; +use sp_runtime::{ + traits::{BlakeTwo256, Convert, Hash}, + DispatchError, +}; +use xcm::{ + lts::WeightLimit, + v2::OriginKind, + v3::{Instruction::*, MultiAsset, Weight}, +}; + +use super::mock::*; +use crate::{ + axelar_evm::AxelarEVMRouter, + ethereum_xcm::{get_encoded_contract_call, get_encoded_ethereum_xcm_call, EthereumXCMRouter}, + DomainRouter, EVMChain, EVMDomain, FeeValues, XcmDomain, XcmTransactInfo, +}; + +mod axelar_evm { + use super::*; + + mod init { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + let axelar_contract_address = H160::from_low_u64_be(1); + let axelar_contract_code = [0; 32].to_vec(); + let axelar_contract_hash = BlakeTwo256::hash_of(&axelar_contract_code); + let connectors_contract_address = H160::from_low_u64_be(2); + + pallet_evm::AccountCodes::::insert( + axelar_contract_address, + axelar_contract_code, + ); + + let evm_domain = EVMDomain { + chain: EVMChain::Ethereum, + axelar_contract_address, + axelar_contract_hash, + connectors_contract_address, + fee_values: FeeValues { + value: U256::from(10), + gas_limit: U256::from(10), + gas_price: U256::from(10), + }, + }; + + let domain_router = + DomainRouter::::AxelarEVM(AxelarEVMRouter:: { + domain: evm_domain, + _marker: Default::default(), + }); + + assert_ok!(domain_router.init()); + }); + } + + #[test] + fn failure() { + new_test_ext().execute_with(|| { + let axelar_contract_address = H160::from_low_u64_be(1); + let axelar_contract_code = [0; 32].to_vec(); + let axelar_contract_hash = BlakeTwo256::hash_of(&axelar_contract_code); + let connectors_contract_address = H160::from_low_u64_be(2); + + let evm_domain = EVMDomain { + chain: EVMChain::Ethereum, + axelar_contract_address, + axelar_contract_hash, + connectors_contract_address, + fee_values: FeeValues { + value: U256::from(10), + gas_limit: U256::from(10), + gas_price: U256::from(10), + }, + }; + + let domain_router = + DomainRouter::::AxelarEVM(AxelarEVMRouter:: { + domain: evm_domain, + _marker: Default::default(), + }); + + assert_noop!( + domain_router.init(), + DispatchError::Other("Axelar contract code does not match") + ); + + pallet_evm::AccountCodes::::insert( + axelar_contract_address, + [1; 32].to_vec(), + ); + + assert_noop!( + domain_router.init(), + DispatchError::Other("Axelar contract code does not match") + ); + }); + } + } + + mod send { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + let sender: AccountId32 = [0; 32].into(); + let sender_h160: H160 = + H160::from_slice(&>::as_ref(&sender)[0..20]); + let derived_sender = IdentityAddressMapping::into_account_id(sender_h160); + + Balances::mint_into(&derived_sender.into(), 1_000_000 * CFG).unwrap(); + + let axelar_contract_address = H160::from_low_u64_be(1); + let axelar_contract_code = [0; 32].to_vec(); + let axelar_contract_hash = BlakeTwo256::hash_of(&axelar_contract_code); + let connectors_contract_address = H160::from_low_u64_be(2); + + let transaction_call_cost = + ::config().gas_transaction_call; + + let evm_domain = EVMDomain { + chain: EVMChain::Ethereum, + axelar_contract_address, + axelar_contract_hash, + connectors_contract_address, + fee_values: FeeValues { + value: U256::from(10), + gas_limit: U256::from(transaction_call_cost + 10_000), + gas_price: U256::from(10), + }, + }; + + let domain_router = + DomainRouter::::AxelarEVM(AxelarEVMRouter:: { + domain: evm_domain, + _marker: Default::default(), + }); + + let msg = MessageMock::Second; + + assert_ok!(domain_router.send(sender, msg)); + }); + } + + #[test] + fn insufficient_balance() { + new_test_ext().execute_with(|| { + let sender: AccountId32 = [0; 32].into(); + + let axelar_contract_address = H160::from_low_u64_be(1); + let axelar_contract_code = [0; 32].to_vec(); + let axelar_contract_hash = BlakeTwo256::hash_of(&axelar_contract_code); + let connectors_contract_address = H160::from_low_u64_be(2); + + let evm_domain = EVMDomain { + chain: EVMChain::Ethereum, + axelar_contract_address, + axelar_contract_hash, + connectors_contract_address, + fee_values: FeeValues { + value: U256::from(1), + gas_limit: U256::from(10), + gas_price: U256::from(1), + }, + }; + + let domain_router = + DomainRouter::::AxelarEVM(AxelarEVMRouter:: { + domain: evm_domain, + _marker: Default::default(), + }); + + let msg = MessageMock::Second; + + let res = domain_router.send(sender, msg); + assert_eq!( + res.err().unwrap(), + pallet_evm::Error::::BalanceLow.into() + ); + }); + } + } +} + +mod ethereum_xcm { + use super::*; + + mod init { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + let currency_id = CurrencyId::OtherReserve(1); + let dest = CurrencyIdToMultiLocation::convert(currency_id.clone()).unwrap(); + + let xcm_domain = XcmDomain { + location: Box::new(dest.clone().into_versioned()), + ethereum_xcm_transact_call_index: bounded_vec![0], + contract_address: H160::from_slice([0; 20].as_slice()), + max_gas_limit: 10, + transact_info: XcmTransactInfo { + transact_extra_weight: 1.into(), + max_weight: 100_000_000_000.into(), + transact_extra_weight_signed: None, + }, + fee_currency: currency_id, + fee_per_second: 1u128, + fee_asset_location: Box::new(dest.clone().into_versioned()), + }; + + let domain_router = + DomainRouter::::EthereumXCM(EthereumXCMRouter:: { + xcm_domain: xcm_domain.clone(), + _marker: Default::default(), + }); + + assert_ok!(domain_router.init()); + + let res = pallet_xcm_transactor::TransactInfoWithWeightLimit::::get( + dest.clone(), + ) + .unwrap(); + + assert_eq!( + res.transact_extra_weight, + xcm_domain.transact_info.transact_extra_weight + ); + assert_eq!(res.max_weight, xcm_domain.transact_info.max_weight); + assert_eq!( + res.transact_extra_weight_signed, + xcm_domain.transact_info.transact_extra_weight_signed + ); + + assert_eq!( + pallet_xcm_transactor::DestinationAssetFeePerSecond::::get(dest), + Some(xcm_domain.fee_per_second), + ); + }); + } + } + + mod send { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + let currency_id = CurrencyId::OtherReserve(1); + let dest = CurrencyIdToMultiLocation::convert(currency_id.clone()).unwrap(); + + let xcm_domain = XcmDomain { + location: Box::new(dest.clone().into_versioned()), + ethereum_xcm_transact_call_index: bounded_vec![0], + contract_address: H160::from_slice([0; 20].as_slice()), + max_gas_limit: 10, + transact_info: XcmTransactInfo { + transact_extra_weight: 1.into(), + max_weight: 100_000_000_000.into(), + transact_extra_weight_signed: None, + }, + fee_currency: currency_id, + fee_per_second: 1u128, + fee_asset_location: Box::new(dest.clone().into_versioned()), + }; + + let domain_router = + DomainRouter::::EthereumXCM(EthereumXCMRouter:: { + xcm_domain: xcm_domain.clone(), + _marker: Default::default(), + }); + + // Manually insert the transact weight info in the `TransactInfoWithWeightLimit` + // storage. + + pallet_xcm_transactor::TransactInfoWithWeightLimit::::insert( + dest.clone(), + RemoteTransactInfoWithMaxWeight { + transact_extra_weight: xcm_domain + .transact_info + .transact_extra_weight + .clone(), + max_weight: xcm_domain.transact_info.max_weight.clone(), + transact_extra_weight_signed: None, + }, + ); + + // Manually insert the fee per second in the `DestinationAssetFeePerSecond` + // storage. + + pallet_xcm_transactor::DestinationAssetFeePerSecond::::insert( + dest, + xcm_domain.fee_per_second.clone(), + ); + + let sender: AccountId32 = [0; 32].into(); + let msg = MessageMock::Second; + + assert_ok!(domain_router.send(sender, msg)); + + let sent_messages = sent_xcm(); + assert_eq!(sent_messages.len(), 1); + + let weight_limit = xcm_domain.max_gas_limit * 25_000 + 100_000_000; + + let (_, xcm) = sent_messages.first().unwrap(); + assert!(xcm.0.contains(&WithdrawAsset( + (MultiAsset { + id: xcm::v3::AssetId::Concrete(MultiLocation::here()), + fun: xcm::v3::Fungibility::Fungible(1), + }) + .into() + ))); + + assert!(xcm.0.contains(&BuyExecution { + fees: MultiAsset { + id: xcm::v3::AssetId::Concrete(MultiLocation::here()), + fun: xcm::v3::Fungibility::Fungible(1), + }, + weight_limit: WeightLimit::Limited(Weight::from_all( + weight_limit + xcm_domain.transact_info.transact_extra_weight.ref_time() + )), + })); + + let msg = MessageMock::Second; + let contract_call = get_encoded_contract_call(msg.serialize()).unwrap(); + let expected_call = + get_encoded_ethereum_xcm_call::(xcm_domain.clone(), contract_call) + .unwrap(); + + assert!(xcm.0.contains(&Transact { + origin_kind: OriginKind::SovereignAccount, + require_weight_at_most: Weight::from_parts(weight_limit, weight_limit), + call: expected_call.into(), + })); + }); + } + + #[test] + fn success_with_init() { + new_test_ext().execute_with(|| { + let currency_id = CurrencyId::OtherReserve(1); + let dest = CurrencyIdToMultiLocation::convert(currency_id.clone()).unwrap(); + + let xcm_domain = XcmDomain { + location: Box::new(dest.clone().into_versioned()), + ethereum_xcm_transact_call_index: bounded_vec![0], + contract_address: H160::from_slice([0; 20].as_slice()), + max_gas_limit: 10, + transact_info: XcmTransactInfo { + transact_extra_weight: 1.into(), + max_weight: 100_000_000_000.into(), + transact_extra_weight_signed: None, + }, + fee_currency: currency_id, + fee_per_second: 1u128, + fee_asset_location: Box::new(dest.clone().into_versioned()), + }; + + let domain_router = + DomainRouter::::EthereumXCM(EthereumXCMRouter:: { + xcm_domain: xcm_domain.clone(), + _marker: Default::default(), + }); + + assert_ok!(domain_router.init()); + + let sender: AccountId32 = [0; 32].into(); + let msg = MessageMock::Second; + + assert_ok!(domain_router.send(sender, msg)); + }); + } + + #[test] + fn transactor_info_not_set() { + new_test_ext().execute_with(|| { + let currency_id = CurrencyId::OtherReserve(1); + let dest = CurrencyIdToMultiLocation::convert(currency_id.clone()).unwrap(); + + let xcm_domain = XcmDomain { + location: Box::new(dest.clone().into_versioned()), + ethereum_xcm_transact_call_index: bounded_vec![0], + contract_address: H160::from_slice([0; 20].as_slice()), + max_gas_limit: 10, + transact_info: XcmTransactInfo { + transact_extra_weight: 1.into(), + max_weight: 100_000_000_000.into(), + transact_extra_weight_signed: None, + }, + fee_currency: currency_id, + fee_per_second: 1u128, + fee_asset_location: Box::new(dest.clone().into_versioned()), + }; + + let domain_router = + DomainRouter::::EthereumXCM(EthereumXCMRouter:: { + xcm_domain: xcm_domain.clone(), + _marker: Default::default(), + }); + + // Manually insert the fee per second in the `DestinationAssetFeePerSecond` + // storage. + + pallet_xcm_transactor::DestinationAssetFeePerSecond::::insert( + dest, + xcm_domain.fee_per_second.clone(), + ); + + let sender: AccountId32 = [0; 32].into(); + let msg = MessageMock::Second; + + assert_noop!( + domain_router.send(sender, msg), + pallet_xcm_transactor::Error::::TransactorInfoNotSet, + ); + }); + } + + #[test] + fn fee_per_second_not_set() { + new_test_ext().execute_with(|| { + let currency_id = CurrencyId::OtherReserve(1); + let dest = CurrencyIdToMultiLocation::convert(currency_id.clone()).unwrap(); + + let xcm_domain = XcmDomain { + location: Box::new(dest.clone().into_versioned()), + ethereum_xcm_transact_call_index: bounded_vec![0], + contract_address: H160::from_slice([0; 20].as_slice()), + max_gas_limit: 10, + transact_info: XcmTransactInfo { + transact_extra_weight: 1.into(), + max_weight: 100_000_000_000.into(), + transact_extra_weight_signed: None, + }, + fee_currency: currency_id, + fee_per_second: 1u128, + fee_asset_location: Box::new(dest.clone().into_versioned()), + }; + + let domain_router = + DomainRouter::::EthereumXCM(EthereumXCMRouter:: { + xcm_domain: xcm_domain.clone(), + _marker: Default::default(), + }); + + // Manually insert the transact weight info in the `TransactInfoWithWeightLimit` + // storage. + + pallet_xcm_transactor::TransactInfoWithWeightLimit::::insert( + dest.clone(), + RemoteTransactInfoWithMaxWeight { + transact_extra_weight: xcm_domain + .transact_info + .transact_extra_weight + .clone(), + max_weight: xcm_domain.transact_info.max_weight.clone(), + transact_extra_weight_signed: None, + }, + ); + + let sender: AccountId32 = [0; 32].into(); + let msg = MessageMock::Second; + + assert_noop!( + domain_router.send(sender, msg), + pallet_xcm_transactor::Error::::FeePerSecondNotSet, + ); + }); + } + } +} diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 749516013e..a1eb702001 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -21,7 +21,6 @@ mod tests; pub mod account_conversion; pub mod apis; pub mod evm; -pub mod routers; #[macro_export] macro_rules! production_or_benchmark { diff --git a/runtime/common/src/routers.rs b/runtime/common/src/routers.rs deleted file mode 100644 index b73cc855ff..0000000000 --- a/runtime/common/src/routers.rs +++ /dev/null @@ -1,25 +0,0 @@ -use cfg_primitives::{AccountId, Balance, PoolId, TrancheId}; -use cfg_traits::connectors::Router; -use cfg_types::{domain_address::Domain, fixed_point::Rate}; -use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::dispatch::DispatchResult; -use pallet_connectors::Message; -use scale_info::TypeInfo; - -type ConnectorsMessage = Message; - -// TODO(cdamian): This will be removed when the gateway routers are added. -#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -pub struct DummyRouter(); -impl Router for DummyRouter { - type Message = ConnectorsMessage; - type Sender = AccountId; - - fn init(&self) -> DispatchResult { - Ok(()) - } - - fn send(&self, _sender: Self::Sender, _message: Self::Message) -> DispatchResult { - Ok(()) - } -} diff --git a/runtime/development/Cargo.toml b/runtime/development/Cargo.toml index 1fc5034d08..f9c1db2e34 100644 --- a/runtime/development/Cargo.toml +++ b/runtime/development/Cargo.toml @@ -112,6 +112,7 @@ runtime-common = { path = "../common", default-features = false } chainbridge = { git = "https://github.com/centrifuge/chainbridge-substrate.git", default-features = false, branch = "polkadot-v0.9.38" } # our custom pallets +connectors-gateway-routers = { path = "../../pallets/connectors-gateway/connectors-gateway-routers", default-features = false } pallet-anchors = { path = "../../pallets/anchors", default-features = false } pallet-block-rewards = { path = "../../pallets/block-rewards", default-features = false } pallet-bridge = { path = "../../pallets/bridge", default-features = false } @@ -190,6 +191,7 @@ std = [ "pallet-collective/std", "pallet-connectors/std", "pallet-connectors-gateway/std", + "pallet-connectors-gateway/std", "pallet-crowdloan-claim/std", "pallet-crowdloan-reward/std", "pallet-data-collector/std", @@ -317,6 +319,7 @@ runtime-benchmarks = [ "pallet-bridge/runtime-benchmarks", "pallet-connectors/runtime-benchmarks", "pallet-connectors-gateway/runtime-benchmarks", + "pallet-connectors-gateway/runtime-benchmarks", "pallet-democracy/runtime-benchmarks", "pallet-elections-phragmen/runtime-benchmarks", "pallet-identity/runtime-benchmarks", @@ -340,6 +343,7 @@ runtime-benchmarks = [ try-runtime = [ "pallet-connectors/try-runtime", "pallet-connectors-gateway/try-runtime", + "pallet-connectors-gateway/try-runtime", "cfg-primitives/try-runtime", "cfg-traits/try-runtime", "cfg-primitives/try-runtime", diff --git a/runtime/development/src/connectors.rs b/runtime/development/src/connectors.rs index 70eecc69ba..41d06b172b 100644 --- a/runtime/development/src/connectors.rs +++ b/runtime/development/src/connectors.rs @@ -14,7 +14,6 @@ use cfg_primitives::{AccountId, Balance, PoolId, TrancheId}; use cfg_types::{domain_address::Domain, fixed_point::Rate}; use frame_support::parameter_types; use frame_system::EnsureRoot; -use runtime_common::routers::DummyRouter; use super::{Runtime, RuntimeEvent, RuntimeOrigin}; use crate::Connectors; @@ -31,7 +30,7 @@ impl pallet_connectors_gateway::Config for Runtime { type LocalEVMOrigin = pallet_connectors_gateway::EnsureLocal; type MaxIncomingMessageSize = MaxIncomingMessageSize; type Message = ConnectorsMessage; - type Router = DummyRouter; + type Router = connectors_gateway_routers::DomainRouter; type RuntimeEvent = RuntimeEvent; type RuntimeOrigin = RuntimeOrigin; type WeightInfo = (); From 5097ee22a98ac2f4670724970934d977c7c1ada9 Mon Sep 17 00:00:00 2001 From: cdamian <17934949+cdamian@users.noreply.github.com> Date: Tue, 1 Aug 2023 14:37:30 +0300 Subject: [PATCH 3/9] gateway-routers: Add generic routers, Axelar XCM router, update tests --- Cargo.lock | 25 - libs/mocks/src/ethereum_transaction.rs | 45 - libs/mocks/src/lib.rs | 2 - .../src/axelar_evm.rs | 326 ++--- .../src/axelar_xcm.rs | 63 + .../src/ethereum_xcm.rs | 185 +-- .../connectors-gateway-routers/src/lib.rs | 15 +- .../src/router/evm.rs | 105 ++ .../src/router/mod.rs | 18 + .../src/router/xcm.rs | 197 +++ .../connectors-gateway-routers/src/tests.rs | 1103 +++++++++++++---- 11 files changed, 1395 insertions(+), 689 deletions(-) delete mode 100644 libs/mocks/src/ethereum_transaction.rs create mode 100644 pallets/connectors-gateway/connectors-gateway-routers/src/axelar_xcm.rs create mode 100644 pallets/connectors-gateway/connectors-gateway-routers/src/router/evm.rs create mode 100644 pallets/connectors-gateway/connectors-gateway-routers/src/router/mod.rs create mode 100644 pallets/connectors-gateway/connectors-gateway-routers/src/router/xcm.rs diff --git a/Cargo.lock b/Cargo.lock index 286696e002..2b05da7f00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7294,31 +7294,6 @@ dependencies = [ "sp-std", ] -[[package]] -name = "pallet-ethereum-transaction" -version = "0.0.1" -dependencies = [ - "cfg-traits", - "ethabi", - "ethereum", - "fp-ethereum", - "fp-evm", - "frame-benchmarking", - "frame-support", - "frame-system", - "pallet-balances", - "pallet-ethereum", - "pallet-evm", - "pallet-evm-precompile-simple", - "pallet-timestamp", - "parity-scale-codec 3.4.0", - "scale-info", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std", -] - [[package]] name = "pallet-evm" version = "6.0.0-dev" diff --git a/libs/mocks/src/ethereum_transaction.rs b/libs/mocks/src/ethereum_transaction.rs deleted file mode 100644 index 50c6c8b517..0000000000 --- a/libs/mocks/src/ethereum_transaction.rs +++ /dev/null @@ -1,45 +0,0 @@ -#[frame_support::pallet] -pub mod pallet { - use cfg_traits::ethereum::EthereumTransactor; - use frame_support::pallet_prelude::*; - use mock_builder::{execute_call, register_call}; - use sp_core::{H160, U256}; - - #[pallet::config] - pub trait Config: frame_system::Config {} - - #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] - pub struct Pallet(_); - - #[pallet::storage] - pub(super) type CallIds = StorageMap< - _, - Blake2_128Concat, - ::Output, - mock_builder::CallId, - >; - - impl Pallet { - pub fn mock_call( - f: impl Fn(H160, H160, &[u8], U256, U256, U256) -> DispatchResult + 'static, - ) { - register_call!(move |(from, to, data, value, gas_price, gas_limit)| f( - from, to, data, value, gas_price, gas_limit - )); - } - } - - impl EthereumTransactor for Pallet { - fn call( - from: H160, - to: H160, - data: &[u8], - value: U256, - gas_price: U256, - gas_limit: U256, - ) -> DispatchResultWithPostInfo { - execute_call!((from, to, data, value, gas_price, gas_limit)) - } - } -} diff --git a/libs/mocks/src/lib.rs b/libs/mocks/src/lib.rs index 2e9b7ec339..6a95ef439e 100644 --- a/libs/mocks/src/lib.rs +++ b/libs/mocks/src/lib.rs @@ -2,7 +2,6 @@ mod change_guard; mod connectors; mod connectors_gateway_routers; mod data; -mod ethereum_transaction; mod fees; mod permissions; mod pools; @@ -13,7 +12,6 @@ pub use change_guard::pallet_mock_change_guard; pub use connectors::{pallet as pallet_mock_connectors, MessageMock}; pub use connectors_gateway_routers::{pallet as pallet_mock_routers, RouterMock}; pub use data::pallet as pallet_mock_data; -pub use ethereum_transaction::pallet as pallet_mock_ethereum_transaction; pub use fees::pallet as pallet_mock_fees; pub use permissions::pallet as pallet_mock_permissions; pub use pools::pallet as pallet_mock_pools; diff --git a/pallets/connectors-gateway/connectors-gateway-routers/src/axelar_evm.rs b/pallets/connectors-gateway/connectors-gateway-routers/src/axelar_evm.rs index 671346deae..ae3fe2d9c7 100644 --- a/pallets/connectors-gateway/connectors-gateway-routers/src/axelar_evm.rs +++ b/pallets/connectors-gateway/connectors-gateway-routers/src/axelar_evm.rs @@ -9,71 +9,22 @@ // 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. -use cfg_traits::{connectors::Codec, ethereum::EthereumTransactor}; +use cfg_traits::connectors::Codec; use codec::{Decode, Encode, MaxEncodedLen}; use ethabi::{Contract, Function, Param, ParamType, Token}; -use frame_support::{ - dispatch::{DispatchError, DispatchResult}, - ensure, -}; +use frame_support::dispatch::{DispatchError, DispatchResult}; use scale_info::{ prelude::string::{String, ToString}, TypeInfo, }; -use sp_core::{H160, H256, U256}; -use sp_runtime::traits::{BlakeTwo256, Hash}; +use sp_core::H160; use sp_std::{collections::btree_map::BTreeMap, marker::PhantomData, vec, vec::Vec}; -use crate::{AccountIdOf, MessageOf, CONNECTORS_FUNCTION_NAME, CONNECTORS_MESSAGE_PARAM}; - -/// The router used for executing the Connectors contract via Axelar. -#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -pub struct AxelarEVMRouter -where - T: frame_system::Config - + pallet_connectors_gateway::Config - + pallet_ethereum_transaction::Config - + pallet_evm::Config, -{ - pub domain: EVMDomain, - pub _marker: PhantomData, -} - -/// The EVMDomain holds all relevant information for validating and executing -/// the call to the Axelar contract. -#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -pub struct EVMDomain { - /// The chain to which the router will send the message to. - pub chain: EVMChain, - - /// The address of the Axelar contract deployed in our EVM. - pub axelar_contract_address: H160, - - /// The `BlakeTwo256` hash of the Axelar contract code. - /// This is used during router initialization to ensure that the correct - /// contract code is used. - pub axelar_contract_hash: H256, - - /// The address of the Connectors contract that we are going to call through - /// the Axelar contract. - pub connectors_contract_address: H160, - - /// The values used when executing the EVM call to the Axelar contract. - pub fee_values: FeeValues, -} - -/// The FeeValues holds all information related to the transaction costs. -#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -pub struct FeeValues { - /// The value used when executing the EVM call. - pub value: U256, - - /// The gas price used when executing the EVM call. - pub gas_price: U256, - - /// The gas limit used when executing the EVM call. - pub gas_limit: U256, -} +use crate::{ + router::evm::EVMRouter, AccountIdOf, MessageOf, AXELAR_DESTINATION_CHAIN_PARAM, + AXELAR_DESTINATION_CONTRACT_ADDRESS_PARAM, AXELAR_FUNCTION_NAME, AXELAR_PAYLOAD_PARAM, + CONNECTORS_FUNCTION_NAME, CONNECTORS_MESSAGE_PARAM, +}; /// EVMChain holds all supported EVM chains. #[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] @@ -91,10 +42,20 @@ impl ToString for EVMChain { } } -const AXELAR_FUNCTION_NAME: &'static str = "callContract"; -const AXELAR_DESTINATION_CHAIN_PARAM: &'static str = "destinationChain"; -const AXELAR_DESTINATION_CONTRACT_ADDRESS_PARAM: &'static str = "destinationContractAddress"; -const AXELAR_PAYLOAD_PARAM: &'static str = "payload"; +/// The router used for executing the Connectors contract via Axelar. +#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub struct AxelarEVMRouter +where + T: frame_system::Config + + pallet_connectors_gateway::Config + + pallet_ethereum_transaction::Config + + pallet_evm::Config, +{ + pub router: EVMRouter, + pub evm_chain: EVMChain, + pub connectors_contract_address: H160, + pub _marker: PhantomData, +} impl AxelarEVMRouter where @@ -104,148 +65,127 @@ where + pallet_evm::Config, T::AccountId: AsRef<[u8; 32]>, { - /// Performs an extra check to ensure that the actual contract is deployed - /// at the provided address and that the contract code hash matches. + /// Calls the init function on the EVM router. pub fn do_init(&self) -> DispatchResult { - let code = pallet_evm::AccountCodes::::get(self.domain.axelar_contract_address); - - ensure!( - BlakeTwo256::hash_of(&code) == self.domain.axelar_contract_hash, - DispatchError::Other("Axelar contract code does not match"), - ); - - Ok(()) + self.router.do_init() } /// Encodes the Connectors message to the required format, - /// then executes the EVM call using the Ethereum transaction pallet. - /// - /// NOTE - there sender account ID provided here will be converted to an EVM - /// address via truncating. When the call is processed by the underlying EVM - /// pallet, this EVM address will be converted back into a substrate account - /// which will be charged for the transaction. This converted substrate - /// account is not the same as the original account. + /// then executes the EVM call using the generic EVM router. pub fn do_send(&self, sender: AccountIdOf, msg: MessageOf) -> DispatchResult { - let eth_msg = self.get_eth_msg(msg).map_err(DispatchError::Other)?; - - let sender_evm_address = H160::from_slice(&sender.as_ref()[0..20]); - - // TODO(cdamian): This returns a `DispatchResultWithPostInfo`. Should we - // propagate that to another layer that will eventually charge for the - // weight in the PostDispatchInfo? - as EthereumTransactor>::call( - sender_evm_address, - self.domain.axelar_contract_address, - eth_msg.as_slice(), - self.domain.fee_values.value, - self.domain.fee_values.gas_price, - self.domain.fee_values.gas_limit, + let eth_msg = get_axelar_encoded_msg( + msg.serialize(), + self.evm_chain.to_string(), + self.connectors_contract_address.to_string(), ) - .map_err(|e| e.error)?; + .map_err(DispatchError::Other)?; - Ok(()) + self.router.do_send(sender, eth_msg) } +} - /// Encodes the provided message into the format required for submitting it - /// to the Axelar contract which in turn submits it to the Connectors - /// contract. - fn get_eth_msg(&self, msg: MessageOf) -> Result, &'static str> { - // `AxelarEVMRouter` -> `callContract` on the Axelar Gateway contract - // deployed in the Centrifuge EVM pallet. - // - // Axelar Gateway contract -> `handle` on the Connectors gateway contract - // deployed on Ethereum. - - // Connectors Call: - // - // function handle(bytes memory _message) external onlyRouter {} - - #[allow(deprecated)] - let encoded_connectors_contract = Contract { - constructor: None, - functions: BTreeMap::>::from([( - CONNECTORS_FUNCTION_NAME.to_string(), - vec![Function { - name: CONNECTORS_FUNCTION_NAME.into(), - inputs: vec![Param { - name: CONNECTORS_MESSAGE_PARAM.into(), +/// Encodes the provided message into the format required for submitting it +/// to the Axelar contract which in turn submits it to the Connectors +/// contract. +pub(crate) fn get_axelar_encoded_msg( + serialized_msg: Vec, + target_chain: String, + target_contract: String, +) -> Result, &'static str> { + // Centrifuge -> `callContract` on the Axelar Gateway contract. + // + // Axelar Gateway contract -> `handle` on the Connectors gateway contract + // deployed on Ethereum. + + // Connectors Call: + // + // function handle(bytes memory _message) external onlyRouter {} + + #[allow(deprecated)] + let encoded_connectors_contract = Contract { + constructor: None, + functions: BTreeMap::>::from([( + CONNECTORS_FUNCTION_NAME.to_string(), + vec![Function { + name: CONNECTORS_FUNCTION_NAME.into(), + inputs: vec![Param { + name: CONNECTORS_MESSAGE_PARAM.into(), + kind: ParamType::Bytes, + internal_type: None, + }], + outputs: vec![], + constant: false, + state_mutability: Default::default(), + }], + )]), + events: Default::default(), + errors: Default::default(), + receive: false, + fallback: false, + } + .function(CONNECTORS_FUNCTION_NAME) + .map_err(|_| "cannot retrieve Connectors contract function")? + .encode_input(&[Token::Bytes(serialized_msg)]) + .map_err(|_| "cannot encode input for Connectors contract function")?; + + // Axelar Call: + // + // function callContract( + // string calldata destinationChain, + // string calldata destinationContractAddress, + // bytes calldata payload, + // ) external { + // emit ContractCall( + // msg.sender, + // destinationChain, + // destinationContractAddress, + // keccak256(payload), + // payload, + // ); + // } + + #[allow(deprecated)] + let encoded_axelar_contract = Contract { + constructor: None, + functions: BTreeMap::>::from([( + AXELAR_FUNCTION_NAME.into(), + vec![Function { + name: AXELAR_FUNCTION_NAME.into(), + inputs: vec![ + Param { + name: AXELAR_DESTINATION_CHAIN_PARAM.into(), + kind: ParamType::String, + internal_type: None, + }, + Param { + name: AXELAR_DESTINATION_CONTRACT_ADDRESS_PARAM.into(), + kind: ParamType::String, + internal_type: None, + }, + Param { + name: AXELAR_PAYLOAD_PARAM.into(), kind: ParamType::Bytes, internal_type: None, - }], - outputs: vec![], - constant: false, - state_mutability: Default::default(), - }], - )]), - events: Default::default(), - errors: Default::default(), - receive: false, - fallback: false, - } - .function(CONNECTORS_FUNCTION_NAME) - .map_err(|_| "cannot retrieve Connectors contract function")? - .encode_input(&[Token::Bytes(msg.serialize())]) - .map_err(|_| "cannot encode input for Connectors contract function")?; - - // Axelar Call: - // - // function callContract( - // string calldata destinationChain, - // string calldata destinationContractAddress, - // bytes calldata payload, - // ) external { - // emit ContractCall( - // msg.sender, - // destinationChain, - // destinationContractAddress, - // keccak256(payload), - // payload, - // ); - // } - - #[allow(deprecated)] - let encoded_axelar_contract = Contract { - constructor: None, - functions: BTreeMap::>::from([( - AXELAR_FUNCTION_NAME.into(), - vec![Function { - name: AXELAR_FUNCTION_NAME.into(), - inputs: vec![ - Param { - name: AXELAR_DESTINATION_CHAIN_PARAM.into(), - kind: ParamType::String, - internal_type: None, - }, - Param { - name: AXELAR_DESTINATION_CONTRACT_ADDRESS_PARAM.into(), - kind: ParamType::String, - internal_type: None, - }, - Param { - name: AXELAR_PAYLOAD_PARAM.into(), - kind: ParamType::Bytes, - internal_type: None, - }, - ], - outputs: vec![], - constant: false, - state_mutability: Default::default(), - }], - )]), - events: Default::default(), - errors: Default::default(), - receive: false, - fallback: false, - } - .function(AXELAR_FUNCTION_NAME) - .map_err(|_| "cannot retrieve Axelar contract function")? - .encode_input(&[ - Token::String(self.domain.chain.to_string()), - Token::String(self.domain.connectors_contract_address.to_string()), - Token::Bytes(encoded_connectors_contract), - ]) - .map_err(|_| "cannot encode input for Axelar contract function")?; - - Ok(encoded_axelar_contract) + }, + ], + outputs: vec![], + constant: false, + state_mutability: Default::default(), + }], + )]), + events: Default::default(), + errors: Default::default(), + receive: false, + fallback: false, } + .function(AXELAR_FUNCTION_NAME) + .map_err(|_| "cannot retrieve Axelar contract function")? + .encode_input(&[ + Token::String(target_chain), + Token::String(target_contract), + Token::Bytes(encoded_connectors_contract), + ]) + .map_err(|_| "cannot encode input for Axelar contract function")?; + + Ok(encoded_axelar_contract) } diff --git a/pallets/connectors-gateway/connectors-gateway-routers/src/axelar_xcm.rs b/pallets/connectors-gateway/connectors-gateway-routers/src/axelar_xcm.rs new file mode 100644 index 0000000000..4c55b279ea --- /dev/null +++ b/pallets/connectors-gateway/connectors-gateway-routers/src/axelar_xcm.rs @@ -0,0 +1,63 @@ +// Copyright 2021 Centrifuge Foundation (centrifuge.io). +// +// This file is part of the Centrifuge chain project. +// Centrifuge 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 (see http://www.gnu.org/licenses). +// Centrifuge 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. + +use cfg_traits::connectors::Codec; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::dispatch::DispatchResult; +use scale_info::{prelude::string::ToString, TypeInfo}; +use sp_core::H160; +use sp_runtime::DispatchError; +use sp_std::marker::PhantomData; + +use crate::{ + axelar_evm::get_axelar_encoded_msg, + router::{XCMRouter, XcmDomain}, + AccountIdOf, CurrencyIdOf, EVMChain, MessageOf, +}; + +pub type AxelarXcmDomain = XcmDomain>; + +/// The router used for submitting a Connectors message using Axelar via +/// Moonbeam XCM. +#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub struct AxelarXCMRouter +where + T: frame_system::Config + pallet_xcm_transactor::Config + pallet_connectors_gateway::Config, +{ + pub router: XCMRouter, + pub axelar_target_chain: EVMChain, + pub axelar_target_contract: H160, + pub _marker: PhantomData, +} + +impl AxelarXCMRouter +where + T: frame_system::Config + pallet_xcm_transactor::Config + pallet_connectors_gateway::Config, +{ + /// Calls the init function on the EVM router. + pub fn do_init(&self) -> DispatchResult { + self.router.do_init() + } + + /// Encodes the Connectors message to the required format, + /// then executes the EVM call using the generic XCM router. + pub fn do_send(&self, sender: AccountIdOf, msg: MessageOf) -> DispatchResult { + let contract_call = get_axelar_encoded_msg( + msg.serialize(), + self.axelar_target_chain.to_string(), + self.axelar_target_contract.to_string(), + ) + .map_err(|_| DispatchError::Other("encoded contract call retrieval"))?; + + self.router.do_send(sender, contract_call) + } +} diff --git a/pallets/connectors-gateway/connectors-gateway-routers/src/ethereum_xcm.rs b/pallets/connectors-gateway/connectors-gateway-routers/src/ethereum_xcm.rs index 3fe6240bc2..b9cac33e23 100644 --- a/pallets/connectors-gateway/connectors-gateway-routers/src/ethereum_xcm.rs +++ b/pallets/connectors-gateway/connectors-gateway-routers/src/ethereum_xcm.rs @@ -9,26 +9,15 @@ // 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. - -use core::convert::TryFrom; - use cfg_traits::connectors::Codec; use codec::{Decode, Encode, MaxEncodedLen}; use ethabi::{Bytes, Contract}; -use frame_support::{ - dispatch::DispatchResult, sp_runtime::DispatchError, traits::OriginTrait, weights::Weight, -}; -use pallet_xcm_transactor::{Currency, CurrencyPayment, TransactWeights}; +use frame_support::{dispatch::DispatchResult, sp_runtime::DispatchError}; use scale_info::TypeInfo; -use sp_core::{bounded::BoundedVec, ConstU32, H160, U256}; -use sp_std::{boxed::Box, marker::PhantomData, vec, vec::Vec}; -use xcm::{ - v2::{MultiLocation, OriginKind}, - VersionedMultiLocation, -}; +use sp_std::{collections::btree_map::BTreeMap, marker::PhantomData, vec, vec::Vec}; use crate::{ - AccountIdOf, CurrencyIdOf, MessageOf, CONNECTORS_FUNCTION_NAME, CONNECTORS_MESSAGE_PARAM, + router::XCMRouter, AccountIdOf, MessageOf, CONNECTORS_FUNCTION_NAME, CONNECTORS_MESSAGE_PARAM, }; /// The router used for submitting a Connectors message via Moonbeam XCM. @@ -37,7 +26,7 @@ pub struct EthereumXCMRouter where T: frame_system::Config + pallet_xcm_transactor::Config + pallet_connectors_gateway::Config, { - pub xcm_domain: XcmDomain>, + pub router: XCMRouter, pub _marker: PhantomData, } @@ -45,104 +34,21 @@ impl EthereumXCMRouter where T: frame_system::Config + pallet_xcm_transactor::Config + pallet_connectors_gateway::Config, { - /// Sets the weight information for the provided XCM domain location, and - /// the fee per second for the provided fee asset location. + /// Calls the init function on the EVM router. pub fn do_init(&self) -> DispatchResult { - pallet_xcm_transactor::Pallet::::set_transact_info( - ::RuntimeOrigin::root(), - self.xcm_domain.location.clone(), - self.xcm_domain.transact_info.transact_extra_weight, - self.xcm_domain.transact_info.max_weight, - self.xcm_domain.transact_info.transact_extra_weight_signed, - )?; - - pallet_xcm_transactor::Pallet::::set_fee_per_second( - ::RuntimeOrigin::root(), - self.xcm_domain.fee_asset_location.clone(), - self.xcm_domain.fee_per_second, - ) + self.router.do_init() } /// Encodes the Connectors message to the required format and executes the - /// call via the XCM transactor pallet. + /// call via the XCM router. pub fn do_send(&self, sender: AccountIdOf, msg: MessageOf) -> DispatchResult { let contract_call = get_encoded_contract_call(msg.serialize()) .map_err(|_| DispatchError::Other("encoded contract call retrieval"))?; - let ethereum_xcm_call = - get_encoded_ethereum_xcm_call::(self.xcm_domain.clone(), contract_call) - .map_err(|_| DispatchError::Other("encoded ethereum xcm call retrieval"))?; - - pallet_xcm_transactor::Pallet::::transact_through_sovereign( - ::RuntimeOrigin::root(), - // The destination to which the message should be sent. - self.xcm_domain.location.clone(), - // The sender will pay for this transaction. - sender, - // The currency in which we want to pay fees. - CurrencyPayment { - currency: Currency::AsCurrencyId(self.xcm_domain.fee_currency.clone()), - fee_amount: None, - }, - // The call to be executed in the destination chain. - ethereum_xcm_call, - OriginKind::SovereignAccount, - TransactWeights { - // Convert the max gas_limit into a max transact weight following - // Moonbeam's formula. - transact_required_weight_at_most: Weight::from_all( - self.xcm_domain.max_gas_limit * 25_000 + 100_000_000, - ), - overall_weight: None, - }, - )?; - - Ok(()) + self.router.do_send(sender, contract_call) } } -/// Build the encoded `ethereum_xcm::transact(eth_tx)` call that should -/// request to execute `evm_call`. -/// -/// * `xcm_domain` - All the necessary info regarding the xcm-based domain -/// where this `ethereum_xcm` call is to be executed -/// * `evm_call` - The encoded EVM call calling ConnectorsXcmRouter::handle(msg) -pub(crate) fn get_encoded_ethereum_xcm_call( - xcm_domain: XcmDomain>, - evm_call: Vec, -) -> Result, ()> -where - T: frame_system::Config + pallet_xcm_transactor::Config + pallet_connectors_gateway::Config, -{ - let input = - BoundedVec::>::try_from( - evm_call, - ) - .map_err(|_| ())?; - - let mut encoded: Vec = Vec::new(); - - encoded.append( - &mut xcm_domain - .ethereum_xcm_transact_call_index - .clone() - .into_inner(), - ); - encoded.append( - &mut xcm_primitives::EthereumXcmTransaction::V1(xcm_primitives::EthereumXcmTransactionV1 { - gas_limit: U256::from(xcm_domain.max_gas_limit), - fee_payment: xcm_primitives::EthereumXcmFee::Auto, - action: pallet_ethereum::TransactionAction::Call(xcm_domain.contract_address), - value: U256::zero(), - input, - access_list: None, - }) - .encode(), - ); - - Ok(encoded) -} - /// Return the encoded contract call, i.e, /// ConnectorsXcmRouter::handle(encoded_msg). pub(crate) fn get_encoded_contract_call(encoded_msg: Vec) -> Result { @@ -161,8 +67,6 @@ pub(crate) fn get_encoded_contract_call(encoded_msg: Vec) -> Result Contract { - use sp_std::collections::btree_map::BTreeMap; - let mut functions = BTreeMap::new(); #[allow(deprecated)] functions.insert( @@ -189,76 +93,3 @@ pub(crate) fn get_xcm_router_contract() -> Contract { fallback: false, } } - -/// XcmDomain gathers all the required fields to build and send remote -/// calls to a specific XCM-based Domain. -#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo)] -pub struct XcmDomain { - /// The XCM multilocation of the domain - pub location: Box, - - /// The ethereum_xcm::Call::transact call index on a given domain. - /// It should contain the pallet index + the `transact` call index, to which - /// we will append the eth_tx param. You can obtain this value by building - /// an ethereum_xcm::transact call with Polkadot JS on the target chain. - pub ethereum_xcm_transact_call_index: - BoundedVec>, - - /// The ConnectorsXcmRouter contract address on a given domain - pub contract_address: H160, - - /// The max gas_limit we want to propose for a remote evm execution - pub max_gas_limit: u64, - - /// The XCM transact info that will be stored in the - /// `TransactInfoWithWeightLimit` storage of the XCM transactor pallet. - pub transact_info: XcmTransactInfo, - - /// The currency in which execution fees will be paid on - pub fee_currency: CurrencyId, - - /// The fee per second that will be stored in the - /// `DestinationAssetFeePerSecond` storage of the XCM transactor pallet. - pub fee_per_second: u128, - - /// The location of the asset used for paying XCM fees. - pub fee_asset_location: Box, -} - -#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -/// XcmTransactInfo hold all the weight related information required for the XCM -/// transactor pallet. -pub struct XcmTransactInfo { - pub transact_extra_weight: Weight, - pub max_weight: Weight, - pub transact_extra_weight_signed: Option, -} - -/// NOTE: Remove this custom implementation once the following underlying data -/// implements MaxEncodedLen: -/// * Polkadot Repo: xcm::VersionedMultiLocation -/// * PureStake Repo: pallet_xcm_transactor::Config::CurrencyId -impl MaxEncodedLen for XcmDomain -where - XcmDomain: Encode, -{ - fn max_encoded_len() -> usize { - // The domain's `VersionedMultiLocation` (custom bound) - MultiLocation::max_encoded_len() - // From the enum wrapping of `VersionedMultiLocation` for the XCM domain location. - .saturating_add(1) - // From the enum wrapping of `VersionedMultiLocation` for the asset fee location. - .saturating_add(1) - // The ethereum xcm call index (default bound) - .saturating_add(BoundedVec::< - u8, - ConstU32<{ xcm_primitives::MAX_ETHEREUM_XCM_INPUT_SIZE }>, - >::max_encoded_len()) - // The contract address (default bound) - .saturating_add(H160::max_encoded_len()) - // The fee currency (custom bound) - .saturating_add(cfg_types::tokens::CurrencyId::max_encoded_len()) - // The XcmTransactInfo - .saturating_add(XcmTransactInfo::max_encoded_len()) - } -} diff --git a/pallets/connectors-gateway/connectors-gateway-routers/src/lib.rs b/pallets/connectors-gateway/connectors-gateway-routers/src/lib.rs index a0d6224834..2fb8c68a0a 100644 --- a/pallets/connectors-gateway/connectors-gateway-routers/src/lib.rs +++ b/pallets/connectors-gateway/connectors-gateway-routers/src/lib.rs @@ -25,17 +25,25 @@ mod mock; mod tests; pub mod axelar_evm; +pub mod axelar_xcm; pub mod ethereum_xcm; +pub mod router; pub use axelar_evm::*; +pub use axelar_xcm::*; pub use ethereum_xcm::*; type CurrencyIdOf = ::CurrencyId; type MessageOf = ::Message; type AccountIdOf = ::AccountId; -const CONNECTORS_FUNCTION_NAME: &'static str = "handle"; -const CONNECTORS_MESSAGE_PARAM: &'static str = "message"; +const CONNECTORS_FUNCTION_NAME: &str = "handle"; +const CONNECTORS_MESSAGE_PARAM: &str = "message"; + +const AXELAR_FUNCTION_NAME: &str = "callContract"; +const AXELAR_DESTINATION_CHAIN_PARAM: &str = "destinationChain"; +const AXELAR_DESTINATION_CONTRACT_ADDRESS_PARAM: &str = "destinationContractAddress"; +const AXELAR_PAYLOAD_PARAM: &str = "payload"; /// The routers used for outgoing messages. #[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] @@ -50,6 +58,7 @@ where { EthereumXCM(EthereumXCMRouter), AxelarEVM(AxelarEVMRouter), + AxelarXCM(AxelarXCMRouter), } impl Router for DomainRouter @@ -68,6 +77,7 @@ where match self { DomainRouter::EthereumXCM(r) => r.do_init(), DomainRouter::AxelarEVM(r) => r.do_init(), + DomainRouter::AxelarXCM(r) => r.do_init(), } } @@ -75,6 +85,7 @@ where match self { DomainRouter::EthereumXCM(r) => r.do_send(sender, message), DomainRouter::AxelarEVM(r) => r.do_send(sender, message), + DomainRouter::AxelarXCM(r) => r.do_send(sender, message), } } } diff --git a/pallets/connectors-gateway/connectors-gateway-routers/src/router/evm.rs b/pallets/connectors-gateway/connectors-gateway-routers/src/router/evm.rs new file mode 100644 index 0000000000..751ef01db6 --- /dev/null +++ b/pallets/connectors-gateway/connectors-gateway-routers/src/router/evm.rs @@ -0,0 +1,105 @@ +// Copyright 2021 Centrifuge Foundation (centrifuge.io). +// +// This file is part of the Centrifuge chain project. +// Centrifuge 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 (see http://www.gnu.org/licenses). +// Centrifuge 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. + +use cfg_traits::ethereum::EthereumTransactor; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{ + dispatch::{DispatchError, DispatchResult}, + ensure, +}; +use scale_info::TypeInfo; +use sp_core::{H160, H256, U256}; +use sp_runtime::traits::{BlakeTwo256, Hash}; +use sp_std::{marker::PhantomData, vec::Vec}; + +/// A generic router used for executing EVM calls. +#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub struct EVMRouter +where + T: frame_system::Config + pallet_ethereum_transaction::Config + pallet_evm::Config, +{ + pub evm_domain: EVMDomain, + pub _marker: PhantomData, +} + +impl EVMRouter +where + T: frame_system::Config + pallet_ethereum_transaction::Config + pallet_evm::Config, + T::AccountId: AsRef<[u8; 32]>, +{ + /// Performs an extra check to ensure that the actual contract is deployed + /// at the provided address and that the contract code hash matches. + pub fn do_init(&self) -> DispatchResult { + let code = pallet_evm::AccountCodes::::get(self.evm_domain.target_contract_address); + + ensure!( + BlakeTwo256::hash_of(&code) == self.evm_domain.target_contract_hash, + DispatchError::Other("Target contract code does not match"), + ); + + Ok(()) + } + + /// NOTE - the sender account ID provided here will be converted to an EVM + /// address via truncating. When the call is processed by the underlying EVM + /// pallet, this EVM address will be converted back into a substrate account + /// which will be charged for the transaction. This converted substrate + /// account is not the same as the original account. + pub fn do_send(&self, sender: T::AccountId, msg: Vec) -> DispatchResult { + let sender_evm_address = H160::from_slice(&sender.as_ref()[0..20]); + + // TODO(cdamian): This returns a `DispatchResultWithPostInfo`. Should we + // propagate that to another layer that will eventually charge for the + // weight in the PostDispatchInfo? + as EthereumTransactor>::call( + sender_evm_address, + self.evm_domain.target_contract_address, + msg.as_slice(), + self.evm_domain.fee_values.value, + self.evm_domain.fee_values.gas_price, + self.evm_domain.fee_values.gas_limit, + ) + .map_err(|e| e.error)?; + + Ok(()) + } +} + +/// The EVMDomain holds all relevant information for validating and executing +/// the EVM call. +#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub struct EVMDomain { + /// The address of the contract deployed in our EVM. + pub target_contract_address: H160, + + /// The `BlakeTwo256` hash of the target contract code. + /// + /// This is used during router initialization to ensure that the correct + /// contract code is used. + pub target_contract_hash: H256, + + /// The values used when executing the EVM call. + pub fee_values: FeeValues, +} + +/// The FeeValues holds all information related to the transaction costs. +#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub struct FeeValues { + /// The value used when executing the EVM call. + pub value: U256, + + /// The gas price used when executing the EVM call. + pub gas_price: U256, + + /// The gas limit used when executing the EVM call. + pub gas_limit: U256, +} diff --git a/pallets/connectors-gateway/connectors-gateway-routers/src/router/mod.rs b/pallets/connectors-gateway/connectors-gateway-routers/src/router/mod.rs new file mode 100644 index 0000000000..21f36ab80b --- /dev/null +++ b/pallets/connectors-gateway/connectors-gateway-routers/src/router/mod.rs @@ -0,0 +1,18 @@ +// Copyright 2021 Centrifuge Foundation (centrifuge.io). +// +// This file is part of the Centrifuge chain project. +// Centrifuge 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 (see http://www.gnu.org/licenses). +// Centrifuge 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. + +pub mod evm; +pub mod xcm; + +pub use evm::*; + +pub use self::xcm::*; diff --git a/pallets/connectors-gateway/connectors-gateway-routers/src/router/xcm.rs b/pallets/connectors-gateway/connectors-gateway-routers/src/router/xcm.rs new file mode 100644 index 0000000000..f89d82ab5c --- /dev/null +++ b/pallets/connectors-gateway/connectors-gateway-routers/src/router/xcm.rs @@ -0,0 +1,197 @@ +// Copyright 2021 Centrifuge Foundation (centrifuge.io). +// +// This file is part of the Centrifuge chain project. +// Centrifuge 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 (see http://www.gnu.org/licenses). +// Centrifuge 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. + +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{ + dispatch::{DispatchResult, Weight}, + traits::OriginTrait, +}; +use pallet_xcm_transactor::{Currency, CurrencyPayment, TransactWeights}; +use scale_info::TypeInfo; +use sp_core::{bounded::BoundedVec, ConstU32, H160, U256}; +use sp_runtime::DispatchError; +use sp_std::{boxed::Box, marker::PhantomData, vec::Vec}; +use xcm::{ + latest::{MultiLocation, OriginKind}, + VersionedMultiLocation, +}; + +/// A generic router used for executing XCM calls. +#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub struct XCMRouter +where + T: frame_system::Config + pallet_xcm_transactor::Config, +{ + pub xcm_domain: XcmDomain, + pub _marker: PhantomData, +} + +impl XCMRouter +where + T: frame_system::Config + pallet_xcm_transactor::Config, +{ + /// Sets the weight information for the provided XCM domain location, and + /// the fee per second for the provided fee asset location. + pub fn do_init(&self) -> DispatchResult { + pallet_xcm_transactor::Pallet::::set_transact_info( + ::RuntimeOrigin::root(), + self.xcm_domain.location.clone(), + self.xcm_domain.transact_info.transact_extra_weight, + self.xcm_domain.transact_info.max_weight, + self.xcm_domain.transact_info.transact_extra_weight_signed, + )?; + + pallet_xcm_transactor::Pallet::::set_fee_per_second( + ::RuntimeOrigin::root(), + self.xcm_domain.fee_asset_location.clone(), + self.xcm_domain.fee_per_second, + ) + } + + /// Encodes the Connectors message to the required format and executes the + /// call via the XCM transactor pallet. + pub fn do_send(&self, sender: T::AccountId, msg: Vec) -> DispatchResult { + let ethereum_xcm_call = get_encoded_ethereum_xcm_call::(self.xcm_domain.clone(), msg) + .map_err(|_| DispatchError::Other("encoded ethereum xcm call retrieval"))?; + + pallet_xcm_transactor::Pallet::::transact_through_sovereign( + ::RuntimeOrigin::root(), + // The destination to which the message should be sent. + self.xcm_domain.location.clone(), + // The sender will pay for this transaction. + sender, + // The currency in which we want to pay fees. + CurrencyPayment { + currency: Currency::AsCurrencyId(self.xcm_domain.fee_currency.clone()), + fee_amount: None, + }, + // The call to be executed in the destination chain. + ethereum_xcm_call, + OriginKind::SovereignAccount, + TransactWeights { + // Convert the max gas_limit into a max transact weight following + // Moonbeam's formula. + transact_required_weight_at_most: Weight::from_all( + self.xcm_domain.max_gas_limit * 25_000 + 100_000_000, + ), + overall_weight: None, + }, + )?; + + Ok(()) + } +} + +pub(crate) fn get_encoded_ethereum_xcm_call( + xcm_domain: XcmDomain, + msg: Vec, +) -> Result, ()> +where + T: frame_system::Config + pallet_xcm_transactor::Config, +{ + let input = + BoundedVec::>::try_from(msg) + .map_err(|_| ())?; + + let mut encoded: Vec = Vec::new(); + + encoded.append(&mut xcm_domain.ethereum_xcm_transact_call_index.into_inner()); + + encoded.append( + &mut xcm_primitives::EthereumXcmTransaction::V1(xcm_primitives::EthereumXcmTransactionV1 { + gas_limit: U256::from(xcm_domain.max_gas_limit), + fee_payment: xcm_primitives::EthereumXcmFee::Auto, + action: pallet_ethereum::TransactionAction::Call(xcm_domain.contract_address), + value: U256::zero(), + input, + access_list: None, + }) + .encode(), + ); + + Ok(encoded) +} + +/// XcmDomain gathers all the required fields to build and send remote +/// calls to a specific XCM-based Domain. +#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo)] +pub struct XcmDomain { + /// The XCM multilocation of the domain. + pub location: Box, + + /// The ethereum_xcm::Call::transact call index on a given domain. + /// It should contain the pallet index + the `transact` call index, to which + /// we will append the eth_tx param. + /// + /// You can obtain this value by building an ethereum_xcm::transact call + /// with Polkadot JS on the target chain. + pub ethereum_xcm_transact_call_index: + BoundedVec>, + + /// The target contract address on a given domain. + pub contract_address: H160, + + /// The max gas_limit we want to propose for a remote evm execution + pub max_gas_limit: u64, + + /// The XCM transact info that will be stored in the + /// `TransactInfoWithWeightLimit` storage of the XCM transactor pallet. + pub transact_info: XcmTransactInfo, + + /// The currency in which execution fees will be paid on + pub fee_currency: CurrencyId, + + /// The fee per second that will be stored in the + /// `DestinationAssetFeePerSecond` storage of the XCM transactor pallet. + pub fee_per_second: u128, + + /// The location of the asset used for paying XCM fees. + pub fee_asset_location: Box, +} + +#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +/// XcmTransactInfo hold all the weight related information required for the XCM +/// transactor pallet. +pub struct XcmTransactInfo { + pub transact_extra_weight: Weight, + pub max_weight: Weight, + pub transact_extra_weight_signed: Option, +} + +/// NOTE: Remove this custom implementation once the following underlying data +/// implements MaxEncodedLen: +/// * Polkadot Repo: xcm::VersionedMultiLocation +/// * PureStake Repo: pallet_xcm_transactor::Config::CurrencyId +impl MaxEncodedLen for XcmDomain +where + XcmDomain: Encode, +{ + fn max_encoded_len() -> usize { + // The domain's `VersionedMultiLocation` (custom bound) + MultiLocation::max_encoded_len() + // From the enum wrapping of `VersionedMultiLocation` for the XCM domain location. + .saturating_add(1) + // From the enum wrapping of `VersionedMultiLocation` for the asset fee location. + .saturating_add(1) + // The ethereum xcm call index (default bound) + .saturating_add(BoundedVec::< + u8, + ConstU32<{ xcm_primitives::MAX_ETHEREUM_XCM_INPUT_SIZE }>, + >::max_encoded_len()) + // The contract address (default bound) + .saturating_add(H160::max_encoded_len()) + // The fee currency (custom bound) + .saturating_add(cfg_types::tokens::CurrencyId::max_encoded_len()) + // The XcmTransactInfo + .saturating_add(XcmTransactInfo::max_encoded_len()) + } +} diff --git a/pallets/connectors-gateway/connectors-gateway-routers/src/tests.rs b/pallets/connectors-gateway/connectors-gateway-routers/src/tests.rs index 9b245a3d18..8510e63a1e 100644 --- a/pallets/connectors-gateway/connectors-gateway-routers/src/tests.rs +++ b/pallets/connectors-gateway/connectors-gateway-routers/src/tests.rs @@ -1,3 +1,8 @@ +use ::xcm::{ + lts::WeightLimit, + v2::OriginKind, + v3::{Instruction::*, MultiAsset, Weight}, +}; use cfg_mocks::MessageMock; use cfg_primitives::CFG; use cfg_traits::connectors::{Codec, Router}; @@ -5,58 +10,502 @@ use cumulus_primitives_core::MultiLocation; use frame_support::{assert_noop, assert_ok, traits::fungible::Mutate}; use pallet_evm::AddressMapping; use pallet_xcm_transactor::RemoteTransactInfoWithMaxWeight; -use sp_core::{bounded_vec, crypto::AccountId32, H160, U256}; +use sp_core::{bounded_vec, crypto::AccountId32, H160, H256, U256}; use sp_runtime::{ traits::{BlakeTwo256, Convert, Hash}, DispatchError, }; -use xcm::{ - lts::WeightLimit, - v2::OriginKind, - v3::{Instruction::*, MultiAsset, Weight}, -}; use super::mock::*; use crate::{ - axelar_evm::AxelarEVMRouter, - ethereum_xcm::{get_encoded_contract_call, get_encoded_ethereum_xcm_call, EthereumXCMRouter}, - DomainRouter, EVMChain, EVMDomain, FeeValues, XcmDomain, XcmTransactInfo, + axelar_evm::{get_axelar_encoded_msg, AxelarEVMRouter}, + axelar_xcm::AxelarXCMRouter, + ethereum_xcm::{get_encoded_contract_call, EthereumXCMRouter}, + router::*, + DomainRouter, EVMChain, }; -mod axelar_evm { +mod evm_router { + use util::*; + use super::*; + mod util { + use super::*; + + pub struct EVMRouterTestData { + pub test_contract_address: H160, + pub test_contract_code: Vec, + pub test_contract_hash: H256, + pub evm_domain: EVMDomain, + pub sender: AccountId32, + pub sender_h160: H160, + pub derived_sender: AccountId32, + pub msg: Vec, + } + + pub fn get_test_data() -> EVMRouterTestData { + let test_contract_address = H160::from_low_u64_be(1); + let test_contract_code = [0; 32].to_vec(); + let test_contract_hash = BlakeTwo256::hash_of(&test_contract_code); + + let evm_domain = EVMDomain { + target_contract_address: test_contract_address, + target_contract_hash: test_contract_hash, + fee_values: FeeValues { + value: U256::from(10), + gas_limit: U256::from(10), + gas_price: U256::from(10), + }, + }; + + let sender: AccountId32 = [0; 32].into(); + let sender_h160: H160 = + H160::from_slice(&>::as_ref(&sender)[0..20]); + let derived_sender = IdentityAddressMapping::into_account_id(sender_h160); + + let msg = vec![0, 1, 2]; + + EVMRouterTestData { + test_contract_address, + test_contract_code, + test_contract_hash, + evm_domain, + sender, + sender_h160, + derived_sender, + msg, + } + } + } + mod init { use super::*; #[test] fn success() { new_test_ext().execute_with(|| { - let axelar_contract_address = H160::from_low_u64_be(1); - let axelar_contract_code = [0; 32].to_vec(); - let axelar_contract_hash = BlakeTwo256::hash_of(&axelar_contract_code); - let connectors_contract_address = H160::from_low_u64_be(2); + let test_data = get_test_data(); + + pallet_evm::AccountCodes::::insert( + test_data.test_contract_address, + test_data.test_contract_code, + ); + + let router = EVMRouter:: { + evm_domain: test_data.evm_domain, + _marker: Default::default(), + }; + + assert_ok!(router.do_init()); + }); + } + + #[test] + fn failure() { + new_test_ext().execute_with(|| { + let test_data = get_test_data(); + + let router = EVMRouter:: { + evm_domain: test_data.evm_domain, + _marker: Default::default(), + }; + + assert_noop!( + router.do_init(), + DispatchError::Other("Target contract code does not match") + ); pallet_evm::AccountCodes::::insert( - axelar_contract_address, - axelar_contract_code, - ); - - let evm_domain = EVMDomain { - chain: EVMChain::Ethereum, - axelar_contract_address, - axelar_contract_hash, - connectors_contract_address, - fee_values: FeeValues { - value: U256::from(10), - gas_limit: U256::from(10), - gas_price: U256::from(10), + test_data.test_contract_address, + [1; 32].to_vec(), + ); + + assert_noop!( + router.do_init(), + DispatchError::Other("Target contract code does not match") + ); + }); + } + } + + mod send { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + let mut test_data = get_test_data(); + + Balances::mint_into(&test_data.derived_sender.into(), 1_000_000 * CFG).unwrap(); + + let transaction_call_cost = + ::config().gas_transaction_call; + + test_data.evm_domain.fee_values.gas_limit = + U256::from(transaction_call_cost + 10_000); + + let router = EVMRouter:: { + evm_domain: test_data.evm_domain, + _marker: Default::default(), + }; + + assert_ok!(router.do_send(test_data.sender, test_data.msg)); + }); + } + + #[test] + fn insufficient_balance() { + new_test_ext().execute_with(|| { + let test_data = get_test_data(); + + let router = EVMRouter:: { + evm_domain: test_data.evm_domain, + _marker: Default::default(), + }; + + let res = router.do_send(test_data.sender, test_data.msg); + + assert_eq!( + res.err().unwrap(), + pallet_evm::Error::::BalanceLow.into() + ); + }); + } + } +} + +mod xcm_router { + use util::*; + + use super::*; + + mod util { + use super::*; + + pub struct XCMRouterTestData { + pub currency_id: CurrencyId, + pub dest: MultiLocation, + pub xcm_domain: XcmDomain<::CurrencyId>, + pub sender: AccountId32, + pub msg: Vec, + } + + pub fn get_test_data() -> XCMRouterTestData { + let currency_id = CurrencyId::OtherReserve(1); + let dest = CurrencyIdToMultiLocation::convert(currency_id.clone()).unwrap(); + + let xcm_domain = XcmDomain { + location: Box::new(dest.clone().into_versioned()), + ethereum_xcm_transact_call_index: bounded_vec![0], + contract_address: H160::from_slice([0; 20].as_slice()), + max_gas_limit: 10, + transact_info: XcmTransactInfo { + transact_extra_weight: 1.into(), + max_weight: 100_000_000_000.into(), + transact_extra_weight_signed: None, + }, + fee_currency: currency_id.clone(), + fee_per_second: 1u128, + fee_asset_location: Box::new(dest.clone().into_versioned()), + }; + + let sender: AccountId32 = [0; 32].into(); + + let msg = vec![0, 1, 2]; + + XCMRouterTestData { + currency_id, + dest, + xcm_domain, + sender, + msg, + } + } + } + + mod init { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + let test_data = get_test_data(); + + let router = XCMRouter:: { + xcm_domain: test_data.xcm_domain.clone(), + _marker: Default::default(), + }; + + assert_ok!(router.do_init()); + + let res = pallet_xcm_transactor::TransactInfoWithWeightLimit::::get( + test_data.dest.clone(), + ) + .unwrap(); + + assert_eq!( + res.transact_extra_weight, + test_data.xcm_domain.transact_info.transact_extra_weight + ); + assert_eq!( + res.max_weight, + test_data.xcm_domain.transact_info.max_weight + ); + assert_eq!( + res.transact_extra_weight_signed, + test_data + .xcm_domain + .transact_info + .transact_extra_weight_signed + ); + + assert_eq!( + pallet_xcm_transactor::DestinationAssetFeePerSecond::::get( + test_data.dest + ), + Some(test_data.xcm_domain.fee_per_second), + ); + }); + } + } + + mod send { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + let test_data = get_test_data(); + + let router = XCMRouter:: { + xcm_domain: test_data.xcm_domain.clone(), + _marker: Default::default(), + }; + + // Manually insert the transact weight info in the `TransactInfoWithWeightLimit` + // storage. + + pallet_xcm_transactor::TransactInfoWithWeightLimit::::insert( + test_data.dest.clone(), + RemoteTransactInfoWithMaxWeight { + transact_extra_weight: test_data + .xcm_domain + .transact_info + .transact_extra_weight + .clone(), + max_weight: test_data.xcm_domain.transact_info.max_weight.clone(), + transact_extra_weight_signed: None, }, + ); + + // Manually insert the fee per second in the `DestinationAssetFeePerSecond` + // storage. + + pallet_xcm_transactor::DestinationAssetFeePerSecond::::insert( + test_data.dest, + test_data.xcm_domain.fee_per_second.clone(), + ); + + assert_ok!(router.do_send(test_data.sender, test_data.msg.clone())); + + let sent_messages = sent_xcm(); + assert_eq!(sent_messages.len(), 1); + + let weight_limit = test_data.xcm_domain.max_gas_limit * 25_000 + 100_000_000; + + let (_, xcm) = sent_messages.first().unwrap(); + assert!(xcm.0.contains(&WithdrawAsset( + (MultiAsset { + id: ::xcm::v3::AssetId::Concrete(MultiLocation::here()), + fun: ::xcm::v3::Fungibility::Fungible(1), + }) + .into() + ))); + + assert!(xcm.0.contains(&BuyExecution { + fees: MultiAsset { + id: ::xcm::v3::AssetId::Concrete(MultiLocation::here()), + fun: ::xcm::v3::Fungibility::Fungible(1), + }, + weight_limit: WeightLimit::Limited(Weight::from_all( + weight_limit + + test_data + .xcm_domain + .transact_info + .transact_extra_weight + .ref_time() + )), + })); + + let expected_call = get_encoded_ethereum_xcm_call::( + test_data.xcm_domain.clone(), + test_data.msg, + ) + .unwrap(); + + assert!(xcm.0.contains(&Transact { + origin_kind: OriginKind::SovereignAccount, + require_weight_at_most: Weight::from_parts(weight_limit, weight_limit), + call: expected_call.into(), + })); + }); + } + + #[test] + fn success_with_init() { + new_test_ext().execute_with(|| { + let test_data = get_test_data(); + + let router = XCMRouter:: { + xcm_domain: test_data.xcm_domain.clone(), + _marker: Default::default(), }; + assert_ok!(router.do_init()); + + assert_ok!(router.do_send(test_data.sender, test_data.msg)); + }); + } + + #[test] + fn transactor_info_not_set() { + new_test_ext().execute_with(|| { + let test_data = get_test_data(); + + let router = XCMRouter:: { + xcm_domain: test_data.xcm_domain.clone(), + _marker: Default::default(), + }; + + // Manually insert the fee per second in the `DestinationAssetFeePerSecond` + // storage. + + pallet_xcm_transactor::DestinationAssetFeePerSecond::::insert( + test_data.dest, + test_data.xcm_domain.fee_per_second.clone(), + ); + + assert_noop!( + router.do_send(test_data.sender, test_data.msg), + pallet_xcm_transactor::Error::::TransactorInfoNotSet, + ); + }); + } + + #[test] + fn fee_per_second_not_set() { + new_test_ext().execute_with(|| { + let test_data = get_test_data(); + + let router = XCMRouter:: { + xcm_domain: test_data.xcm_domain.clone(), + _marker: Default::default(), + }; + + // Manually insert the transact weight info in the `TransactInfoWithWeightLimit` + // storage. + + pallet_xcm_transactor::TransactInfoWithWeightLimit::::insert( + test_data.dest.clone(), + RemoteTransactInfoWithMaxWeight { + transact_extra_weight: test_data + .xcm_domain + .transact_info + .transact_extra_weight + .clone(), + max_weight: test_data.xcm_domain.transact_info.max_weight.clone(), + transact_extra_weight_signed: None, + }, + ); + + assert_noop!( + router.do_send(test_data.sender, test_data.msg), + pallet_xcm_transactor::Error::::FeePerSecondNotSet, + ); + }); + } + } +} + +mod axelar_evm { + use util::*; + + use super::*; + + mod util { + use super::*; + + pub struct AxelarEVMTestData { + pub axelar_contract_address: H160, + pub axelar_contract_code: Vec, + pub axelar_contract_hash: H256, + pub connectors_contract_address: H160, + pub evm_domain: EVMDomain, + pub sender: AccountId32, + pub sender_h160: H160, + pub derived_sender: AccountId32, + pub msg: MessageMock, + } + + pub fn get_test_data() -> AxelarEVMTestData { + let axelar_contract_address = H160::from_low_u64_be(1); + let axelar_contract_code = [0; 32].to_vec(); + let axelar_contract_hash = BlakeTwo256::hash_of(&axelar_contract_code); + let connectors_contract_address = H160::from_low_u64_be(2); + + let evm_domain = EVMDomain { + target_contract_address: axelar_contract_address, + target_contract_hash: axelar_contract_hash, + fee_values: FeeValues { + value: U256::from(10), + gas_limit: U256::from(10), + gas_price: U256::from(10), + }, + }; + + let sender: AccountId32 = [0; 32].into(); + let sender_h160: H160 = + H160::from_slice(&>::as_ref(&sender)[0..20]); + let derived_sender = IdentityAddressMapping::into_account_id(sender_h160); + + let msg = MessageMock::Second; + + AxelarEVMTestData { + axelar_contract_address, + axelar_contract_code, + axelar_contract_hash, + connectors_contract_address, + evm_domain, + sender, + sender_h160, + derived_sender, + msg, + } + } + } + + mod init { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + let test_data = get_test_data(); + + pallet_evm::AccountCodes::::insert( + test_data.axelar_contract_address, + test_data.axelar_contract_code, + ); + let domain_router = DomainRouter::::AxelarEVM(AxelarEVMRouter:: { - domain: evm_domain, + router: EVMRouter { + evm_domain: test_data.evm_domain, + _marker: Default::default(), + }, + evm_chain: EVMChain::Ethereum, + connectors_contract_address: test_data.connectors_contract_address, _marker: Default::default(), }); @@ -67,42 +516,32 @@ mod axelar_evm { #[test] fn failure() { new_test_ext().execute_with(|| { - let axelar_contract_address = H160::from_low_u64_be(1); - let axelar_contract_code = [0; 32].to_vec(); - let axelar_contract_hash = BlakeTwo256::hash_of(&axelar_contract_code); - let connectors_contract_address = H160::from_low_u64_be(2); - - let evm_domain = EVMDomain { - chain: EVMChain::Ethereum, - axelar_contract_address, - axelar_contract_hash, - connectors_contract_address, - fee_values: FeeValues { - value: U256::from(10), - gas_limit: U256::from(10), - gas_price: U256::from(10), - }, - }; + let test_data = get_test_data(); let domain_router = DomainRouter::::AxelarEVM(AxelarEVMRouter:: { - domain: evm_domain, + router: EVMRouter { + evm_domain: test_data.evm_domain, + _marker: Default::default(), + }, + evm_chain: EVMChain::Ethereum, + connectors_contract_address: test_data.connectors_contract_address, _marker: Default::default(), }); assert_noop!( domain_router.init(), - DispatchError::Other("Axelar contract code does not match") + DispatchError::Other("Target contract code does not match") ); pallet_evm::AccountCodes::::insert( - axelar_contract_address, + test_data.axelar_contract_address, [1; 32].to_vec(), ); assert_noop!( domain_router.init(), - DispatchError::Other("Axelar contract code does not match") + DispatchError::Other("Target contract code does not match") ); }); } @@ -114,76 +553,49 @@ mod axelar_evm { #[test] fn success() { new_test_ext().execute_with(|| { - let sender: AccountId32 = [0; 32].into(); - let sender_h160: H160 = - H160::from_slice(&>::as_ref(&sender)[0..20]); - let derived_sender = IdentityAddressMapping::into_account_id(sender_h160); - - Balances::mint_into(&derived_sender.into(), 1_000_000 * CFG).unwrap(); + let mut test_data = get_test_data(); - let axelar_contract_address = H160::from_low_u64_be(1); - let axelar_contract_code = [0; 32].to_vec(); - let axelar_contract_hash = BlakeTwo256::hash_of(&axelar_contract_code); - let connectors_contract_address = H160::from_low_u64_be(2); + Balances::mint_into(&test_data.derived_sender.into(), 1_000_000 * CFG).unwrap(); let transaction_call_cost = ::config().gas_transaction_call; - let evm_domain = EVMDomain { - chain: EVMChain::Ethereum, - axelar_contract_address, - axelar_contract_hash, - connectors_contract_address, - fee_values: FeeValues { - value: U256::from(10), - gas_limit: U256::from(transaction_call_cost + 10_000), - gas_price: U256::from(10), - }, - }; + test_data.evm_domain.fee_values.gas_limit = + U256::from(transaction_call_cost + 10_000); let domain_router = DomainRouter::::AxelarEVM(AxelarEVMRouter:: { - domain: evm_domain, + router: EVMRouter { + evm_domain: test_data.evm_domain, + _marker: Default::default(), + }, + evm_chain: EVMChain::Ethereum, + connectors_contract_address: test_data.connectors_contract_address, _marker: Default::default(), }); - let msg = MessageMock::Second; - - assert_ok!(domain_router.send(sender, msg)); + assert_ok!(domain_router.send(test_data.sender, test_data.msg)); }); } #[test] fn insufficient_balance() { new_test_ext().execute_with(|| { - let sender: AccountId32 = [0; 32].into(); - - let axelar_contract_address = H160::from_low_u64_be(1); - let axelar_contract_code = [0; 32].to_vec(); - let axelar_contract_hash = BlakeTwo256::hash_of(&axelar_contract_code); - let connectors_contract_address = H160::from_low_u64_be(2); - - let evm_domain = EVMDomain { - chain: EVMChain::Ethereum, - axelar_contract_address, - axelar_contract_hash, - connectors_contract_address, - fee_values: FeeValues { - value: U256::from(1), - gas_limit: U256::from(10), - gas_price: U256::from(1), - }, - }; + let test_data = get_test_data(); let domain_router = DomainRouter::::AxelarEVM(AxelarEVMRouter:: { - domain: evm_domain, + router: EVMRouter { + evm_domain: test_data.evm_domain, + _marker: Default::default(), + }, + evm_chain: EVMChain::Ethereum, + connectors_contract_address: test_data.connectors_contract_address, _marker: Default::default(), }); - let msg = MessageMock::Second; + let res = domain_router.send(test_data.sender, test_data.msg); - let res = domain_router.send(sender, msg); assert_eq!( res.err().unwrap(), pallet_evm::Error::::BalanceLow.into() @@ -193,59 +605,108 @@ mod axelar_evm { } } -mod ethereum_xcm { +mod axelar_xcm { + use util::*; + use super::*; + mod util { + use super::*; + + pub struct AxelarXCMTestData { + pub currency_id: CurrencyId, + pub dest: MultiLocation, + pub xcm_domain: XcmDomain<::CurrencyId>, + pub axelar_target_chain: EVMChain, + pub axelar_target_contract: H160, + pub sender: AccountId32, + pub msg: MessageMock, + } + + pub fn get_test_data() -> AxelarXCMTestData { + let currency_id = CurrencyId::OtherReserve(1); + let dest = CurrencyIdToMultiLocation::convert(currency_id.clone()).unwrap(); + + let xcm_domain = XcmDomain { + location: Box::new(dest.clone().into_versioned()), + ethereum_xcm_transact_call_index: bounded_vec![0], + contract_address: H160::from_slice([0; 20].as_slice()), + max_gas_limit: 10, + transact_info: XcmTransactInfo { + transact_extra_weight: 1.into(), + max_weight: 100_000_000_000.into(), + transact_extra_weight_signed: None, + }, + fee_currency: currency_id.clone(), + fee_per_second: 1u128, + fee_asset_location: Box::new(dest.clone().into_versioned()), + }; + let axelar_target_chain = EVMChain::Ethereum; + let axelar_target_contract = H160::from_low_u64_be(1); + + let sender: AccountId32 = [0; 32].into(); + + let msg = MessageMock::First; + + AxelarXCMTestData { + currency_id, + dest, + xcm_domain, + axelar_target_chain, + axelar_target_contract, + sender, + msg, + } + } + } + mod init { use super::*; #[test] fn success() { new_test_ext().execute_with(|| { - let currency_id = CurrencyId::OtherReserve(1); - let dest = CurrencyIdToMultiLocation::convert(currency_id.clone()).unwrap(); - - let xcm_domain = XcmDomain { - location: Box::new(dest.clone().into_versioned()), - ethereum_xcm_transact_call_index: bounded_vec![0], - contract_address: H160::from_slice([0; 20].as_slice()), - max_gas_limit: 10, - transact_info: XcmTransactInfo { - transact_extra_weight: 1.into(), - max_weight: 100_000_000_000.into(), - transact_extra_weight_signed: None, - }, - fee_currency: currency_id, - fee_per_second: 1u128, - fee_asset_location: Box::new(dest.clone().into_versioned()), - }; + let test_data = get_test_data(); let domain_router = - DomainRouter::::EthereumXCM(EthereumXCMRouter:: { - xcm_domain: xcm_domain.clone(), + DomainRouter::::AxelarXCM(AxelarXCMRouter:: { + router: XCMRouter { + xcm_domain: test_data.xcm_domain.clone(), + _marker: Default::default(), + }, + axelar_target_chain: test_data.axelar_target_chain, + axelar_target_contract: test_data.axelar_target_contract, _marker: Default::default(), }); assert_ok!(domain_router.init()); let res = pallet_xcm_transactor::TransactInfoWithWeightLimit::::get( - dest.clone(), + test_data.dest.clone(), ) .unwrap(); assert_eq!( res.transact_extra_weight, - xcm_domain.transact_info.transact_extra_weight + test_data.xcm_domain.transact_info.transact_extra_weight + ); + assert_eq!( + res.max_weight, + test_data.xcm_domain.transact_info.max_weight ); - assert_eq!(res.max_weight, xcm_domain.transact_info.max_weight); assert_eq!( res.transact_extra_weight_signed, - xcm_domain.transact_info.transact_extra_weight_signed + test_data + .xcm_domain + .transact_info + .transact_extra_weight_signed ); assert_eq!( - pallet_xcm_transactor::DestinationAssetFeePerSecond::::get(dest), - Some(xcm_domain.fee_per_second), + pallet_xcm_transactor::DestinationAssetFeePerSecond::::get( + test_data.dest + ), + Some(test_data.xcm_domain.fee_per_second), ); }); } @@ -257,87 +718,64 @@ mod ethereum_xcm { #[test] fn success() { new_test_ext().execute_with(|| { - let currency_id = CurrencyId::OtherReserve(1); - let dest = CurrencyIdToMultiLocation::convert(currency_id.clone()).unwrap(); - - let xcm_domain = XcmDomain { - location: Box::new(dest.clone().into_versioned()), - ethereum_xcm_transact_call_index: bounded_vec![0], - contract_address: H160::from_slice([0; 20].as_slice()), - max_gas_limit: 10, - transact_info: XcmTransactInfo { - transact_extra_weight: 1.into(), - max_weight: 100_000_000_000.into(), - transact_extra_weight_signed: None, - }, - fee_currency: currency_id, - fee_per_second: 1u128, - fee_asset_location: Box::new(dest.clone().into_versioned()), - }; + let test_data = get_test_data(); let domain_router = - DomainRouter::::EthereumXCM(EthereumXCMRouter:: { - xcm_domain: xcm_domain.clone(), + DomainRouter::::AxelarXCM(AxelarXCMRouter:: { + router: XCMRouter { + xcm_domain: test_data.xcm_domain.clone(), + _marker: Default::default(), + }, + axelar_target_chain: test_data.axelar_target_chain.clone(), + axelar_target_contract: test_data.axelar_target_contract, _marker: Default::default(), }); - // Manually insert the transact weight info in the `TransactInfoWithWeightLimit` - // storage. - - pallet_xcm_transactor::TransactInfoWithWeightLimit::::insert( - dest.clone(), - RemoteTransactInfoWithMaxWeight { - transact_extra_weight: xcm_domain - .transact_info - .transact_extra_weight - .clone(), - max_weight: xcm_domain.transact_info.max_weight.clone(), - transact_extra_weight_signed: None, - }, - ); - - // Manually insert the fee per second in the `DestinationAssetFeePerSecond` - // storage. - - pallet_xcm_transactor::DestinationAssetFeePerSecond::::insert( - dest, - xcm_domain.fee_per_second.clone(), - ); - - let sender: AccountId32 = [0; 32].into(); - let msg = MessageMock::Second; + assert_ok!(domain_router.init()); - assert_ok!(domain_router.send(sender, msg)); + assert_ok!(domain_router.send(test_data.sender, test_data.msg.clone())); let sent_messages = sent_xcm(); assert_eq!(sent_messages.len(), 1); - let weight_limit = xcm_domain.max_gas_limit * 25_000 + 100_000_000; + let weight_limit = test_data.xcm_domain.max_gas_limit * 25_000 + 100_000_000; let (_, xcm) = sent_messages.first().unwrap(); assert!(xcm.0.contains(&WithdrawAsset( (MultiAsset { - id: xcm::v3::AssetId::Concrete(MultiLocation::here()), - fun: xcm::v3::Fungibility::Fungible(1), + id: ::xcm::v3::AssetId::Concrete(MultiLocation::here()), + fun: ::xcm::v3::Fungibility::Fungible(1), }) .into() ))); assert!(xcm.0.contains(&BuyExecution { fees: MultiAsset { - id: xcm::v3::AssetId::Concrete(MultiLocation::here()), - fun: xcm::v3::Fungibility::Fungible(1), + id: ::xcm::v3::AssetId::Concrete(MultiLocation::here()), + fun: ::xcm::v3::Fungibility::Fungible(1), }, weight_limit: WeightLimit::Limited(Weight::from_all( - weight_limit + xcm_domain.transact_info.transact_extra_weight.ref_time() + weight_limit + + test_data + .xcm_domain + .transact_info + .transact_extra_weight + .ref_time() )), })); - let msg = MessageMock::Second; - let contract_call = get_encoded_contract_call(msg.serialize()).unwrap(); - let expected_call = - get_encoded_ethereum_xcm_call::(xcm_domain.clone(), contract_call) - .unwrap(); + let contract_call = get_axelar_encoded_msg( + test_data.msg.serialize(), + test_data.axelar_target_chain.to_string(), + test_data.axelar_target_contract.to_string(), + ) + .unwrap(); + + let expected_call = get_encoded_ethereum_xcm_call::( + test_data.xcm_domain.clone(), + contract_call, + ) + .unwrap(); assert!(xcm.0.contains(&Transact { origin_kind: OriginKind::SovereignAccount, @@ -348,65 +786,258 @@ mod ethereum_xcm { } #[test] - fn success_with_init() { + fn transactor_info_not_set() { + new_test_ext().execute_with(|| { + let test_data = get_test_data(); + + let domain_router = + DomainRouter::::AxelarXCM(AxelarXCMRouter:: { + router: XCMRouter { + xcm_domain: test_data.xcm_domain.clone(), + _marker: Default::default(), + }, + axelar_target_chain: test_data.axelar_target_chain, + axelar_target_contract: test_data.axelar_target_contract, + _marker: Default::default(), + }); + + // Manually insert the fee per second in the `DestinationAssetFeePerSecond` + // storage. + + pallet_xcm_transactor::DestinationAssetFeePerSecond::::insert( + test_data.dest, + test_data.xcm_domain.fee_per_second.clone(), + ); + + assert_noop!( + domain_router.send(test_data.sender, test_data.msg), + pallet_xcm_transactor::Error::::TransactorInfoNotSet, + ); + }); + } + + #[test] + fn fee_per_second_not_set() { new_test_ext().execute_with(|| { - let currency_id = CurrencyId::OtherReserve(1); - let dest = CurrencyIdToMultiLocation::convert(currency_id.clone()).unwrap(); - - let xcm_domain = XcmDomain { - location: Box::new(dest.clone().into_versioned()), - ethereum_xcm_transact_call_index: bounded_vec![0], - contract_address: H160::from_slice([0; 20].as_slice()), - max_gas_limit: 10, - transact_info: XcmTransactInfo { - transact_extra_weight: 1.into(), - max_weight: 100_000_000_000.into(), + let test_data = get_test_data(); + + let domain_router = + DomainRouter::::AxelarXCM(AxelarXCMRouter:: { + router: XCMRouter { + xcm_domain: test_data.xcm_domain.clone(), + _marker: Default::default(), + }, + axelar_target_chain: test_data.axelar_target_chain, + axelar_target_contract: test_data.axelar_target_contract, + _marker: Default::default(), + }); + + // Manually insert the transact weight info in the `TransactInfoWithWeightLimit` + // storage. + + pallet_xcm_transactor::TransactInfoWithWeightLimit::::insert( + test_data.dest.clone(), + RemoteTransactInfoWithMaxWeight { + transact_extra_weight: test_data + .xcm_domain + .transact_info + .transact_extra_weight + .clone(), + max_weight: test_data.xcm_domain.transact_info.max_weight.clone(), transact_extra_weight_signed: None, }, - fee_currency: currency_id, - fee_per_second: 1u128, - fee_asset_location: Box::new(dest.clone().into_versioned()), - }; + ); + + assert_noop!( + domain_router.send(test_data.sender, test_data.msg), + pallet_xcm_transactor::Error::::FeePerSecondNotSet, + ); + }); + } + } +} + +mod ethereum_xcm { + use util::*; + + use super::*; + + mod util { + use super::*; + + pub struct EthereumXCMTestData { + pub currency_id: CurrencyId, + pub dest: MultiLocation, + pub xcm_domain: XcmDomain<::CurrencyId>, + pub axelar_target_chain: EVMChain, + pub axelar_target_contract: H160, + pub sender: AccountId32, + pub msg: MessageMock, + } + + pub fn get_test_data() -> EthereumXCMTestData { + let currency_id = CurrencyId::OtherReserve(1); + let dest = CurrencyIdToMultiLocation::convert(currency_id.clone()).unwrap(); + + let xcm_domain = XcmDomain { + location: Box::new(dest.clone().into_versioned()), + ethereum_xcm_transact_call_index: bounded_vec![0], + contract_address: H160::from_slice([0; 20].as_slice()), + max_gas_limit: 10, + transact_info: XcmTransactInfo { + transact_extra_weight: 1.into(), + max_weight: 100_000_000_000.into(), + transact_extra_weight_signed: None, + }, + fee_currency: currency_id.clone(), + fee_per_second: 1u128, + fee_asset_location: Box::new(dest.clone().into_versioned()), + }; + let axelar_target_chain = EVMChain::Ethereum; + let axelar_target_contract = H160::from_low_u64_be(1); + + let sender: AccountId32 = [0; 32].into(); + + let msg = MessageMock::First; + + EthereumXCMTestData { + currency_id, + dest, + xcm_domain, + axelar_target_chain, + axelar_target_contract, + sender, + msg, + } + } + } + + mod init { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + let test_data = get_test_data(); + + let domain_router = + DomainRouter::::EthereumXCM(EthereumXCMRouter:: { + router: XCMRouter { + xcm_domain: test_data.xcm_domain.clone(), + _marker: Default::default(), + }, + _marker: Default::default(), + }); + + assert_ok!(domain_router.init()); + + let res = pallet_xcm_transactor::TransactInfoWithWeightLimit::::get( + test_data.dest.clone(), + ) + .unwrap(); + + assert_eq!( + res.transact_extra_weight, + test_data.xcm_domain.transact_info.transact_extra_weight + ); + assert_eq!( + res.max_weight, + test_data.xcm_domain.transact_info.max_weight + ); + assert_eq!( + res.transact_extra_weight_signed, + test_data + .xcm_domain + .transact_info + .transact_extra_weight_signed + ); + + assert_eq!( + pallet_xcm_transactor::DestinationAssetFeePerSecond::::get( + test_data.dest + ), + Some(test_data.xcm_domain.fee_per_second), + ); + }); + } + } + + mod send { + use super::*; + + #[test] + fn success_with_init() { + new_test_ext().execute_with(|| { + let test_data = get_test_data(); let domain_router = DomainRouter::::EthereumXCM(EthereumXCMRouter:: { - xcm_domain: xcm_domain.clone(), + router: XCMRouter { + xcm_domain: test_data.xcm_domain.clone(), + _marker: Default::default(), + }, _marker: Default::default(), }); assert_ok!(domain_router.init()); - let sender: AccountId32 = [0; 32].into(); - let msg = MessageMock::Second; + assert_ok!(domain_router.send(test_data.sender, test_data.msg.clone())); - assert_ok!(domain_router.send(sender, msg)); + let sent_messages = sent_xcm(); + assert_eq!(sent_messages.len(), 1); + + let weight_limit = test_data.xcm_domain.max_gas_limit * 25_000 + 100_000_000; + + let (_, xcm) = sent_messages.first().unwrap(); + assert!(xcm.0.contains(&WithdrawAsset( + (MultiAsset { + id: ::xcm::v3::AssetId::Concrete(MultiLocation::here()), + fun: ::xcm::v3::Fungibility::Fungible(1), + }) + .into() + ))); + + assert!(xcm.0.contains(&BuyExecution { + fees: MultiAsset { + id: ::xcm::v3::AssetId::Concrete(MultiLocation::here()), + fun: ::xcm::v3::Fungibility::Fungible(1), + }, + weight_limit: WeightLimit::Limited(Weight::from_all( + weight_limit + + test_data + .xcm_domain + .transact_info + .transact_extra_weight + .ref_time() + )), + })); + + let contract_call = get_encoded_contract_call(test_data.msg.serialize()).unwrap(); + let expected_call = get_encoded_ethereum_xcm_call::( + test_data.xcm_domain.clone(), + contract_call, + ) + .unwrap(); + + assert!(xcm.0.contains(&Transact { + origin_kind: OriginKind::SovereignAccount, + require_weight_at_most: Weight::from_parts(weight_limit, weight_limit), + call: expected_call.into(), + })); }); } #[test] fn transactor_info_not_set() { new_test_ext().execute_with(|| { - let currency_id = CurrencyId::OtherReserve(1); - let dest = CurrencyIdToMultiLocation::convert(currency_id.clone()).unwrap(); - - let xcm_domain = XcmDomain { - location: Box::new(dest.clone().into_versioned()), - ethereum_xcm_transact_call_index: bounded_vec![0], - contract_address: H160::from_slice([0; 20].as_slice()), - max_gas_limit: 10, - transact_info: XcmTransactInfo { - transact_extra_weight: 1.into(), - max_weight: 100_000_000_000.into(), - transact_extra_weight_signed: None, - }, - fee_currency: currency_id, - fee_per_second: 1u128, - fee_asset_location: Box::new(dest.clone().into_versioned()), - }; + let test_data = get_test_data(); let domain_router = DomainRouter::::EthereumXCM(EthereumXCMRouter:: { - xcm_domain: xcm_domain.clone(), + router: XCMRouter { + xcm_domain: test_data.xcm_domain.clone(), + _marker: Default::default(), + }, _marker: Default::default(), }); @@ -414,15 +1045,12 @@ mod ethereum_xcm { // storage. pallet_xcm_transactor::DestinationAssetFeePerSecond::::insert( - dest, - xcm_domain.fee_per_second.clone(), + test_data.dest, + test_data.xcm_domain.fee_per_second.clone(), ); - let sender: AccountId32 = [0; 32].into(); - let msg = MessageMock::Second; - assert_noop!( - domain_router.send(sender, msg), + domain_router.send(test_data.sender, test_data.msg), pallet_xcm_transactor::Error::::TransactorInfoNotSet, ); }); @@ -431,27 +1059,14 @@ mod ethereum_xcm { #[test] fn fee_per_second_not_set() { new_test_ext().execute_with(|| { - let currency_id = CurrencyId::OtherReserve(1); - let dest = CurrencyIdToMultiLocation::convert(currency_id.clone()).unwrap(); - - let xcm_domain = XcmDomain { - location: Box::new(dest.clone().into_versioned()), - ethereum_xcm_transact_call_index: bounded_vec![0], - contract_address: H160::from_slice([0; 20].as_slice()), - max_gas_limit: 10, - transact_info: XcmTransactInfo { - transact_extra_weight: 1.into(), - max_weight: 100_000_000_000.into(), - transact_extra_weight_signed: None, - }, - fee_currency: currency_id, - fee_per_second: 1u128, - fee_asset_location: Box::new(dest.clone().into_versioned()), - }; + let test_data = get_test_data(); let domain_router = DomainRouter::::EthereumXCM(EthereumXCMRouter:: { - xcm_domain: xcm_domain.clone(), + router: XCMRouter { + xcm_domain: test_data.xcm_domain.clone(), + _marker: Default::default(), + }, _marker: Default::default(), }); @@ -459,22 +1074,20 @@ mod ethereum_xcm { // storage. pallet_xcm_transactor::TransactInfoWithWeightLimit::::insert( - dest.clone(), + test_data.dest.clone(), RemoteTransactInfoWithMaxWeight { - transact_extra_weight: xcm_domain + transact_extra_weight: test_data + .xcm_domain .transact_info .transact_extra_weight .clone(), - max_weight: xcm_domain.transact_info.max_weight.clone(), + max_weight: test_data.xcm_domain.transact_info.max_weight.clone(), transact_extra_weight_signed: None, }, ); - let sender: AccountId32 = [0; 32].into(); - let msg = MessageMock::Second; - assert_noop!( - domain_router.send(sender, msg), + domain_router.send(test_data.sender, test_data.msg), pallet_xcm_transactor::Error::::FeePerSecondNotSet, ); }); From bf414728f7a1b38a0e72e2ede91b3ef783924053 Mon Sep 17 00:00:00 2001 From: cdamian <17934949+cdamian@users.noreply.github.com> Date: Wed, 2 Aug 2023 12:04:12 +0300 Subject: [PATCH 4/9] runtime: Remove duplicate dep entries --- runtime/development/Cargo.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/runtime/development/Cargo.toml b/runtime/development/Cargo.toml index f9c1db2e34..5ed10a7cf6 100644 --- a/runtime/development/Cargo.toml +++ b/runtime/development/Cargo.toml @@ -191,7 +191,6 @@ std = [ "pallet-collective/std", "pallet-connectors/std", "pallet-connectors-gateway/std", - "pallet-connectors-gateway/std", "pallet-crowdloan-claim/std", "pallet-crowdloan-reward/std", "pallet-data-collector/std", @@ -319,7 +318,6 @@ runtime-benchmarks = [ "pallet-bridge/runtime-benchmarks", "pallet-connectors/runtime-benchmarks", "pallet-connectors-gateway/runtime-benchmarks", - "pallet-connectors-gateway/runtime-benchmarks", "pallet-democracy/runtime-benchmarks", "pallet-elections-phragmen/runtime-benchmarks", "pallet-identity/runtime-benchmarks", @@ -343,7 +341,6 @@ runtime-benchmarks = [ try-runtime = [ "pallet-connectors/try-runtime", "pallet-connectors-gateway/try-runtime", - "pallet-connectors-gateway/try-runtime", "cfg-primitives/try-runtime", "cfg-traits/try-runtime", "cfg-primitives/try-runtime", From 2d2ddb3d1114bad713f25415fa46e1e348a1c8be Mon Sep 17 00:00:00 2001 From: cdamian <17934949+cdamian@users.noreply.github.com> Date: Wed, 2 Aug 2023 12:04:59 +0300 Subject: [PATCH 5/9] gateway-routers: Use specific types when getting Axelar encoded contract --- .../connectors-gateway-routers/src/axelar_evm.rs | 12 ++++++------ .../connectors-gateway-routers/src/axelar_xcm.rs | 6 +++--- .../connectors-gateway-routers/src/tests.rs | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pallets/connectors-gateway/connectors-gateway-routers/src/axelar_evm.rs b/pallets/connectors-gateway/connectors-gateway-routers/src/axelar_evm.rs index ae3fe2d9c7..2cee5da53b 100644 --- a/pallets/connectors-gateway/connectors-gateway-routers/src/axelar_evm.rs +++ b/pallets/connectors-gateway/connectors-gateway-routers/src/axelar_evm.rs @@ -75,8 +75,8 @@ where pub fn do_send(&self, sender: AccountIdOf, msg: MessageOf) -> DispatchResult { let eth_msg = get_axelar_encoded_msg( msg.serialize(), - self.evm_chain.to_string(), - self.connectors_contract_address.to_string(), + self.evm_chain.clone(), + self.connectors_contract_address, ) .map_err(DispatchError::Other)?; @@ -89,8 +89,8 @@ where /// contract. pub(crate) fn get_axelar_encoded_msg( serialized_msg: Vec, - target_chain: String, - target_contract: String, + target_chain: EVMChain, + target_contract: H160, ) -> Result, &'static str> { // Centrifuge -> `callContract` on the Axelar Gateway contract. // @@ -181,8 +181,8 @@ pub(crate) fn get_axelar_encoded_msg( .function(AXELAR_FUNCTION_NAME) .map_err(|_| "cannot retrieve Axelar contract function")? .encode_input(&[ - Token::String(target_chain), - Token::String(target_contract), + Token::String(target_chain.to_string()), + Token::String(target_contract.to_string()), Token::Bytes(encoded_connectors_contract), ]) .map_err(|_| "cannot encode input for Axelar contract function")?; diff --git a/pallets/connectors-gateway/connectors-gateway-routers/src/axelar_xcm.rs b/pallets/connectors-gateway/connectors-gateway-routers/src/axelar_xcm.rs index 4c55b279ea..8c9e1fcdeb 100644 --- a/pallets/connectors-gateway/connectors-gateway-routers/src/axelar_xcm.rs +++ b/pallets/connectors-gateway/connectors-gateway-routers/src/axelar_xcm.rs @@ -13,7 +13,7 @@ use cfg_traits::connectors::Codec; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::dispatch::DispatchResult; -use scale_info::{prelude::string::ToString, TypeInfo}; +use scale_info::TypeInfo; use sp_core::H160; use sp_runtime::DispatchError; use sp_std::marker::PhantomData; @@ -53,8 +53,8 @@ where pub fn do_send(&self, sender: AccountIdOf, msg: MessageOf) -> DispatchResult { let contract_call = get_axelar_encoded_msg( msg.serialize(), - self.axelar_target_chain.to_string(), - self.axelar_target_contract.to_string(), + self.axelar_target_chain.clone(), + self.axelar_target_contract, ) .map_err(|_| DispatchError::Other("encoded contract call retrieval"))?; diff --git a/pallets/connectors-gateway/connectors-gateway-routers/src/tests.rs b/pallets/connectors-gateway/connectors-gateway-routers/src/tests.rs index 8510e63a1e..20c80df352 100644 --- a/pallets/connectors-gateway/connectors-gateway-routers/src/tests.rs +++ b/pallets/connectors-gateway/connectors-gateway-routers/src/tests.rs @@ -766,8 +766,8 @@ mod axelar_xcm { let contract_call = get_axelar_encoded_msg( test_data.msg.serialize(), - test_data.axelar_target_chain.to_string(), - test_data.axelar_target_contract.to_string(), + test_data.axelar_target_chain.clone(), + test_data.axelar_target_contract, ) .unwrap(); From 8f9e096e8a5c1c5779fe27766a443c0a02e8d9f3 Mon Sep 17 00:00:00 2001 From: cdamian <17934949+cdamian@users.noreply.github.com> Date: Wed, 2 Aug 2023 14:00:51 +0300 Subject: [PATCH 6/9] gateway-routers: Change folder structure --- .../src/router/evm.rs | 105 ------------------ .../src/router/mod.rs | 18 --- .../Cargo.toml | 0 .../src/axelar_evm.rs | 33 ++---- .../src/axelar_xcm.rs | 5 +- .../src/ethereum_xcm.rs | 2 +- .../src/lib.rs | 3 +- .../src/mock.rs | 0 .../router/xcm.rs => routers/src/routers.rs} | 92 ++++++++++++++- .../src/tests.rs | 2 +- 10 files changed, 101 insertions(+), 159 deletions(-) delete mode 100644 pallets/connectors-gateway/connectors-gateway-routers/src/router/evm.rs delete mode 100644 pallets/connectors-gateway/connectors-gateway-routers/src/router/mod.rs rename pallets/connectors-gateway/{connectors-gateway-routers => routers}/Cargo.toml (100%) rename pallets/connectors-gateway/{connectors-gateway-routers => routers}/src/axelar_evm.rs (87%) rename pallets/connectors-gateway/{connectors-gateway-routers => routers}/src/axelar_xcm.rs (94%) rename pallets/connectors-gateway/{connectors-gateway-routers => routers}/src/ethereum_xcm.rs (97%) rename pallets/connectors-gateway/{connectors-gateway-routers => routers}/src/lib.rs (98%) rename pallets/connectors-gateway/{connectors-gateway-routers => routers}/src/mock.rs (100%) rename pallets/connectors-gateway/{connectors-gateway-routers/src/router/xcm.rs => routers/src/routers.rs} (69%) rename pallets/connectors-gateway/{connectors-gateway-routers => routers}/src/tests.rs (99%) diff --git a/pallets/connectors-gateway/connectors-gateway-routers/src/router/evm.rs b/pallets/connectors-gateway/connectors-gateway-routers/src/router/evm.rs deleted file mode 100644 index 751ef01db6..0000000000 --- a/pallets/connectors-gateway/connectors-gateway-routers/src/router/evm.rs +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright 2021 Centrifuge Foundation (centrifuge.io). -// -// This file is part of the Centrifuge chain project. -// Centrifuge 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 (see http://www.gnu.org/licenses). -// Centrifuge 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. - -use cfg_traits::ethereum::EthereumTransactor; -use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::{ - dispatch::{DispatchError, DispatchResult}, - ensure, -}; -use scale_info::TypeInfo; -use sp_core::{H160, H256, U256}; -use sp_runtime::traits::{BlakeTwo256, Hash}; -use sp_std::{marker::PhantomData, vec::Vec}; - -/// A generic router used for executing EVM calls. -#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -pub struct EVMRouter -where - T: frame_system::Config + pallet_ethereum_transaction::Config + pallet_evm::Config, -{ - pub evm_domain: EVMDomain, - pub _marker: PhantomData, -} - -impl EVMRouter -where - T: frame_system::Config + pallet_ethereum_transaction::Config + pallet_evm::Config, - T::AccountId: AsRef<[u8; 32]>, -{ - /// Performs an extra check to ensure that the actual contract is deployed - /// at the provided address and that the contract code hash matches. - pub fn do_init(&self) -> DispatchResult { - let code = pallet_evm::AccountCodes::::get(self.evm_domain.target_contract_address); - - ensure!( - BlakeTwo256::hash_of(&code) == self.evm_domain.target_contract_hash, - DispatchError::Other("Target contract code does not match"), - ); - - Ok(()) - } - - /// NOTE - the sender account ID provided here will be converted to an EVM - /// address via truncating. When the call is processed by the underlying EVM - /// pallet, this EVM address will be converted back into a substrate account - /// which will be charged for the transaction. This converted substrate - /// account is not the same as the original account. - pub fn do_send(&self, sender: T::AccountId, msg: Vec) -> DispatchResult { - let sender_evm_address = H160::from_slice(&sender.as_ref()[0..20]); - - // TODO(cdamian): This returns a `DispatchResultWithPostInfo`. Should we - // propagate that to another layer that will eventually charge for the - // weight in the PostDispatchInfo? - as EthereumTransactor>::call( - sender_evm_address, - self.evm_domain.target_contract_address, - msg.as_slice(), - self.evm_domain.fee_values.value, - self.evm_domain.fee_values.gas_price, - self.evm_domain.fee_values.gas_limit, - ) - .map_err(|e| e.error)?; - - Ok(()) - } -} - -/// The EVMDomain holds all relevant information for validating and executing -/// the EVM call. -#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -pub struct EVMDomain { - /// The address of the contract deployed in our EVM. - pub target_contract_address: H160, - - /// The `BlakeTwo256` hash of the target contract code. - /// - /// This is used during router initialization to ensure that the correct - /// contract code is used. - pub target_contract_hash: H256, - - /// The values used when executing the EVM call. - pub fee_values: FeeValues, -} - -/// The FeeValues holds all information related to the transaction costs. -#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -pub struct FeeValues { - /// The value used when executing the EVM call. - pub value: U256, - - /// The gas price used when executing the EVM call. - pub gas_price: U256, - - /// The gas limit used when executing the EVM call. - pub gas_limit: U256, -} diff --git a/pallets/connectors-gateway/connectors-gateway-routers/src/router/mod.rs b/pallets/connectors-gateway/connectors-gateway-routers/src/router/mod.rs deleted file mode 100644 index 21f36ab80b..0000000000 --- a/pallets/connectors-gateway/connectors-gateway-routers/src/router/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2021 Centrifuge Foundation (centrifuge.io). -// -// This file is part of the Centrifuge chain project. -// Centrifuge 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 (see http://www.gnu.org/licenses). -// Centrifuge 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. - -pub mod evm; -pub mod xcm; - -pub use evm::*; - -pub use self::xcm::*; diff --git a/pallets/connectors-gateway/connectors-gateway-routers/Cargo.toml b/pallets/connectors-gateway/routers/Cargo.toml similarity index 100% rename from pallets/connectors-gateway/connectors-gateway-routers/Cargo.toml rename to pallets/connectors-gateway/routers/Cargo.toml diff --git a/pallets/connectors-gateway/connectors-gateway-routers/src/axelar_evm.rs b/pallets/connectors-gateway/routers/src/axelar_evm.rs similarity index 87% rename from pallets/connectors-gateway/connectors-gateway-routers/src/axelar_evm.rs rename to pallets/connectors-gateway/routers/src/axelar_evm.rs index 2cee5da53b..2dd2cedaf7 100644 --- a/pallets/connectors-gateway/connectors-gateway-routers/src/axelar_evm.rs +++ b/pallets/connectors-gateway/routers/src/axelar_evm.rs @@ -21,7 +21,7 @@ use sp_core::H160; use sp_std::{collections::btree_map::BTreeMap, marker::PhantomData, vec, vec::Vec}; use crate::{ - router::evm::EVMRouter, AccountIdOf, MessageOf, AXELAR_DESTINATION_CHAIN_PARAM, + AccountIdOf, EVMRouter, MessageOf, AXELAR_DESTINATION_CHAIN_PARAM, AXELAR_DESTINATION_CONTRACT_ADDRESS_PARAM, AXELAR_FUNCTION_NAME, AXELAR_PAYLOAD_PARAM, CONNECTORS_FUNCTION_NAME, CONNECTORS_MESSAGE_PARAM, }; @@ -87,20 +87,17 @@ where /// Encodes the provided message into the format required for submitting it /// to the Axelar contract which in turn submits it to the Connectors /// contract. +/// +/// Axelar contract call: +/// +/// +/// Connectors contract call: +/// pub(crate) fn get_axelar_encoded_msg( serialized_msg: Vec, target_chain: EVMChain, target_contract: H160, ) -> Result, &'static str> { - // Centrifuge -> `callContract` on the Axelar Gateway contract. - // - // Axelar Gateway contract -> `handle` on the Connectors gateway contract - // deployed on Ethereum. - - // Connectors Call: - // - // function handle(bytes memory _message) external onlyRouter {} - #[allow(deprecated)] let encoded_connectors_contract = Contract { constructor: None, @@ -128,22 +125,6 @@ pub(crate) fn get_axelar_encoded_msg( .encode_input(&[Token::Bytes(serialized_msg)]) .map_err(|_| "cannot encode input for Connectors contract function")?; - // Axelar Call: - // - // function callContract( - // string calldata destinationChain, - // string calldata destinationContractAddress, - // bytes calldata payload, - // ) external { - // emit ContractCall( - // msg.sender, - // destinationChain, - // destinationContractAddress, - // keccak256(payload), - // payload, - // ); - // } - #[allow(deprecated)] let encoded_axelar_contract = Contract { constructor: None, diff --git a/pallets/connectors-gateway/connectors-gateway-routers/src/axelar_xcm.rs b/pallets/connectors-gateway/routers/src/axelar_xcm.rs similarity index 94% rename from pallets/connectors-gateway/connectors-gateway-routers/src/axelar_xcm.rs rename to pallets/connectors-gateway/routers/src/axelar_xcm.rs index 8c9e1fcdeb..9bd77a0d8c 100644 --- a/pallets/connectors-gateway/connectors-gateway-routers/src/axelar_xcm.rs +++ b/pallets/connectors-gateway/routers/src/axelar_xcm.rs @@ -19,9 +19,8 @@ use sp_runtime::DispatchError; use sp_std::marker::PhantomData; use crate::{ - axelar_evm::get_axelar_encoded_msg, - router::{XCMRouter, XcmDomain}, - AccountIdOf, CurrencyIdOf, EVMChain, MessageOf, + axelar_evm::get_axelar_encoded_msg, AccountIdOf, CurrencyIdOf, EVMChain, MessageOf, XCMRouter, + XcmDomain, }; pub type AxelarXcmDomain = XcmDomain>; diff --git a/pallets/connectors-gateway/connectors-gateway-routers/src/ethereum_xcm.rs b/pallets/connectors-gateway/routers/src/ethereum_xcm.rs similarity index 97% rename from pallets/connectors-gateway/connectors-gateway-routers/src/ethereum_xcm.rs rename to pallets/connectors-gateway/routers/src/ethereum_xcm.rs index b9cac33e23..4836cd8bbf 100644 --- a/pallets/connectors-gateway/connectors-gateway-routers/src/ethereum_xcm.rs +++ b/pallets/connectors-gateway/routers/src/ethereum_xcm.rs @@ -17,7 +17,7 @@ use scale_info::TypeInfo; use sp_std::{collections::btree_map::BTreeMap, marker::PhantomData, vec, vec::Vec}; use crate::{ - router::XCMRouter, AccountIdOf, MessageOf, CONNECTORS_FUNCTION_NAME, CONNECTORS_MESSAGE_PARAM, + AccountIdOf, MessageOf, XCMRouter, CONNECTORS_FUNCTION_NAME, CONNECTORS_MESSAGE_PARAM, }; /// The router used for submitting a Connectors message via Moonbeam XCM. diff --git a/pallets/connectors-gateway/connectors-gateway-routers/src/lib.rs b/pallets/connectors-gateway/routers/src/lib.rs similarity index 98% rename from pallets/connectors-gateway/connectors-gateway-routers/src/lib.rs rename to pallets/connectors-gateway/routers/src/lib.rs index 2fb8c68a0a..7a0f62b204 100644 --- a/pallets/connectors-gateway/connectors-gateway-routers/src/lib.rs +++ b/pallets/connectors-gateway/routers/src/lib.rs @@ -27,11 +27,12 @@ mod tests; pub mod axelar_evm; pub mod axelar_xcm; pub mod ethereum_xcm; -pub mod router; +pub mod routers; pub use axelar_evm::*; pub use axelar_xcm::*; pub use ethereum_xcm::*; +pub use routers::*; type CurrencyIdOf = ::CurrencyId; type MessageOf = ::Message; diff --git a/pallets/connectors-gateway/connectors-gateway-routers/src/mock.rs b/pallets/connectors-gateway/routers/src/mock.rs similarity index 100% rename from pallets/connectors-gateway/connectors-gateway-routers/src/mock.rs rename to pallets/connectors-gateway/routers/src/mock.rs diff --git a/pallets/connectors-gateway/connectors-gateway-routers/src/router/xcm.rs b/pallets/connectors-gateway/routers/src/routers.rs similarity index 69% rename from pallets/connectors-gateway/connectors-gateway-routers/src/router/xcm.rs rename to pallets/connectors-gateway/routers/src/routers.rs index f89d82ab5c..ce049b3991 100644 --- a/pallets/connectors-gateway/connectors-gateway-routers/src/router/xcm.rs +++ b/pallets/connectors-gateway/routers/src/routers.rs @@ -9,22 +9,106 @@ // 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. - +use cfg_traits::ethereum::EthereumTransactor; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ - dispatch::{DispatchResult, Weight}, + dispatch::{DispatchError, DispatchResult, Weight}, + ensure, traits::OriginTrait, }; use pallet_xcm_transactor::{Currency, CurrencyPayment, TransactWeights}; use scale_info::TypeInfo; -use sp_core::{bounded::BoundedVec, ConstU32, H160, U256}; -use sp_runtime::DispatchError; +use sp_core::{bounded::BoundedVec, ConstU32, H160, H256, U256}; +use sp_runtime::traits::{BlakeTwo256, Hash}; use sp_std::{boxed::Box, marker::PhantomData, vec::Vec}; use xcm::{ latest::{MultiLocation, OriginKind}, VersionedMultiLocation, }; +/// A generic router used for executing EVM calls. +#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub struct EVMRouter +where + T: frame_system::Config + pallet_ethereum_transaction::Config + pallet_evm::Config, +{ + pub evm_domain: EVMDomain, + pub _marker: PhantomData, +} + +impl EVMRouter +where + T: frame_system::Config + pallet_ethereum_transaction::Config + pallet_evm::Config, + T::AccountId: AsRef<[u8; 32]>, +{ + /// Performs an extra check to ensure that the actual contract is deployed + /// at the provided address and that the contract code hash matches. + pub fn do_init(&self) -> DispatchResult { + let code = pallet_evm::AccountCodes::::get(self.evm_domain.target_contract_address); + + ensure!( + BlakeTwo256::hash_of(&code) == self.evm_domain.target_contract_hash, + DispatchError::Other("Target contract code does not match"), + ); + + Ok(()) + } + + /// NOTE - the sender account ID provided here will be converted to an EVM + /// address via truncating. When the call is processed by the underlying EVM + /// pallet, this EVM address will be converted back into a substrate account + /// which will be charged for the transaction. This converted substrate + /// account is not the same as the original account. + pub fn do_send(&self, sender: T::AccountId, msg: Vec) -> DispatchResult { + let sender_evm_address = H160::from_slice(&sender.as_ref()[0..20]); + + // TODO(cdamian): This returns a `DispatchResultWithPostInfo`. Should we + // propagate that to another layer that will eventually charge for the + // weight in the PostDispatchInfo? + as EthereumTransactor>::call( + sender_evm_address, + self.evm_domain.target_contract_address, + msg.as_slice(), + self.evm_domain.fee_values.value, + self.evm_domain.fee_values.gas_price, + self.evm_domain.fee_values.gas_limit, + ) + .map_err(|e| e.error)?; + + Ok(()) + } +} + +/// The EVMDomain holds all relevant information for validating and executing +/// the EVM call. +#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub struct EVMDomain { + /// The address of the contract deployed in our EVM. + pub target_contract_address: H160, + + /// The `BlakeTwo256` hash of the target contract code. + /// + /// This is used during router initialization to ensure that the correct + /// contract code is used. + pub target_contract_hash: H256, + + /// The values used when executing the EVM call. + pub fee_values: FeeValues, +} + +/// The FeeValues holds all information related to the transaction costs. +#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub struct FeeValues { + /// The value used when executing the EVM call. + pub value: U256, + + /// The gas price used when executing the EVM call. + pub gas_price: U256, + + /// The gas limit used when executing the EVM call. + pub gas_limit: U256, +} + /// A generic router used for executing XCM calls. #[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] pub struct XCMRouter diff --git a/pallets/connectors-gateway/connectors-gateway-routers/src/tests.rs b/pallets/connectors-gateway/routers/src/tests.rs similarity index 99% rename from pallets/connectors-gateway/connectors-gateway-routers/src/tests.rs rename to pallets/connectors-gateway/routers/src/tests.rs index 20c80df352..303c85e14c 100644 --- a/pallets/connectors-gateway/connectors-gateway-routers/src/tests.rs +++ b/pallets/connectors-gateway/routers/src/tests.rs @@ -21,7 +21,7 @@ use crate::{ axelar_evm::{get_axelar_encoded_msg, AxelarEVMRouter}, axelar_xcm::AxelarXCMRouter, ethereum_xcm::{get_encoded_contract_call, EthereumXCMRouter}, - router::*, + routers::*, DomainRouter, EVMChain, }; From 5a1921b63232408970cf9ea318d4344cb70bc40c Mon Sep 17 00:00:00 2001 From: cdamian <17934949+cdamian@users.noreply.github.com> Date: Wed, 2 Aug 2023 14:01:14 +0300 Subject: [PATCH 7/9] deps: Update dependencies --- Cargo.lock | 1 + Cargo.toml | 1 + runtime/development/Cargo.toml | 7 ++++++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 2b05da7f00..58caa7ccad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2658,6 +2658,7 @@ dependencies = [ "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", + "getrandom 0.2.8", "hex", "hex-literal 0.3.4", "moonbeam-relay-encoder", diff --git a/Cargo.toml b/Cargo.toml index 2b2ad5ea7d..fa93242894 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ members = [ "pallets/block-rewards", "pallets/connectors", "pallets/connectors-gateway", + "pallets/connectors-gateway/routers", "pallets/claims", "pallets/collator-allowlist", "pallets/crowdloan-claim", diff --git a/runtime/development/Cargo.toml b/runtime/development/Cargo.toml index 5ed10a7cf6..2af46c3a82 100644 --- a/runtime/development/Cargo.toml +++ b/runtime/development/Cargo.toml @@ -11,6 +11,7 @@ repository = "https://github.com/centrifuge/centrifuge-chain" [dependencies] # third-party dependencies codec = { package = "parity-scale-codec", version = "3.0", default-features = false, features = ["derive"] } +getrandom = { version = "0.2", features = ["js"] } hex = { version = "0.4.3", default_features = false } hex-literal = { version = "0.3.4", optional = true } scale-info = { version = "2.3.0", default-features = false, features = ["derive"] } @@ -112,7 +113,7 @@ runtime-common = { path = "../common", default-features = false } chainbridge = { git = "https://github.com/centrifuge/chainbridge-substrate.git", default-features = false, branch = "polkadot-v0.9.38" } # our custom pallets -connectors-gateway-routers = { path = "../../pallets/connectors-gateway/connectors-gateway-routers", default-features = false } +connectors-gateway-routers = { path = "../../pallets/connectors-gateway/routers", default-features = false } pallet-anchors = { path = "../../pallets/anchors", default-features = false } pallet-block-rewards = { path = "../../pallets/block-rewards", default-features = false } pallet-bridge = { path = "../../pallets/bridge", default-features = false } @@ -158,6 +159,7 @@ std = [ "chainbridge/std", "chainbridge/std", "codec/std", + "connectors-gateway-routers/std", "cumulus-pallet-aura-ext/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-xcm/std", @@ -267,11 +269,13 @@ std = [ "fp-rpc/std", "fp-self-contained/std", "moonbeam-relay-encoder/std", + "getrandom/std", ] runtime-benchmarks = [ "cfg-types/runtime-benchmarks", "chainbridge/runtime-benchmarks", + "connectors-gateway-routers/runtime-benchmarks", "cumulus-pallet-session-benchmarking/runtime-benchmarks", "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", @@ -339,6 +343,7 @@ runtime-benchmarks = [ ] try-runtime = [ + "connectors-gateway-routers/try-runtime", "pallet-connectors/try-runtime", "pallet-connectors-gateway/try-runtime", "cfg-primitives/try-runtime", From 1a3436fc095621e48c8e7baceb3973695f0619a9 Mon Sep 17 00:00:00 2001 From: cdamian <17934949+cdamian@users.noreply.github.com> Date: Wed, 2 Aug 2023 16:59:00 +0300 Subject: [PATCH 8/9] gateway-routers: Re-structure routers --- pallets/connectors-gateway/routers/src/lib.rs | 276 ++++++++++++++++- .../connectors-gateway/routers/src/routers.rs | 281 ------------------ .../routers/src/{ => routers}/axelar_evm.rs | 0 .../routers/src/{ => routers}/axelar_xcm.rs | 0 .../routers/src/{ => routers}/ethereum_xcm.rs | 0 .../routers/src/routers/mod.rs | 18 ++ .../connectors-gateway/routers/src/tests.rs | 8 +- 7 files changed, 287 insertions(+), 296 deletions(-) delete mode 100644 pallets/connectors-gateway/routers/src/routers.rs rename pallets/connectors-gateway/routers/src/{ => routers}/axelar_evm.rs (100%) rename pallets/connectors-gateway/routers/src/{ => routers}/axelar_xcm.rs (100%) rename pallets/connectors-gateway/routers/src/{ => routers}/ethereum_xcm.rs (100%) create mode 100644 pallets/connectors-gateway/routers/src/routers/mod.rs diff --git a/pallets/connectors-gateway/routers/src/lib.rs b/pallets/connectors-gateway/routers/src/lib.rs index 7a0f62b204..77c8a68501 100644 --- a/pallets/connectors-gateway/routers/src/lib.rs +++ b/pallets/connectors-gateway/routers/src/lib.rs @@ -11,10 +11,22 @@ // GNU General Public License for more details. #![cfg_attr(not(feature = "std"), no_std)] -use cfg_traits::connectors::Router; +use cfg_traits::{connectors::Router, ethereum::EthereumTransactor}; use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::dispatch::DispatchResult; +use frame_support::{ + dispatch::{DispatchError, DispatchResult, Weight}, + ensure, + traits::OriginTrait, +}; +use pallet_xcm_transactor::{Currency, CurrencyPayment, TransactWeights}; use scale_info::TypeInfo; +use sp_core::{bounded::BoundedVec, ConstU32, H160, H256, U256}; +use sp_runtime::traits::{BlakeTwo256, Hash}; +use sp_std::{boxed::Box, marker::PhantomData, vec::Vec}; +use xcm::{ + latest::{MultiLocation, OriginKind}, + VersionedMultiLocation, +}; use crate::{axelar_evm::AxelarEVMRouter, ethereum_xcm::EthereumXCMRouter}; @@ -24,14 +36,8 @@ mod mock; #[cfg(test)] mod tests; -pub mod axelar_evm; -pub mod axelar_xcm; -pub mod ethereum_xcm; pub mod routers; -pub use axelar_evm::*; -pub use axelar_xcm::*; -pub use ethereum_xcm::*; pub use routers::*; type CurrencyIdOf = ::CurrencyId; @@ -90,3 +96,257 @@ where } } } + +/// A generic router used for executing EVM calls. +#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub struct EVMRouter +where + T: frame_system::Config + pallet_ethereum_transaction::Config + pallet_evm::Config, +{ + pub evm_domain: EVMDomain, + pub _marker: PhantomData, +} + +impl EVMRouter +where + T: frame_system::Config + pallet_ethereum_transaction::Config + pallet_evm::Config, + T::AccountId: AsRef<[u8; 32]>, +{ + /// Performs an extra check to ensure that the actual contract is deployed + /// at the provided address and that the contract code hash matches. + pub fn do_init(&self) -> DispatchResult { + let code = pallet_evm::AccountCodes::::get(self.evm_domain.target_contract_address); + + ensure!( + BlakeTwo256::hash_of(&code) == self.evm_domain.target_contract_hash, + DispatchError::Other("Target contract code does not match"), + ); + + Ok(()) + } + + /// NOTE - the sender account ID provided here will be converted to an EVM + /// address via truncating. When the call is processed by the underlying EVM + /// pallet, this EVM address will be converted back into a substrate account + /// which will be charged for the transaction. This converted substrate + /// account is not the same as the original account. + pub fn do_send(&self, sender: T::AccountId, msg: Vec) -> DispatchResult { + let sender_evm_address = H160::from_slice(&sender.as_ref()[0..20]); + + // TODO(cdamian): This returns a `DispatchResultWithPostInfo`. Should we + // propagate that to another layer that will eventually charge for the + // weight in the PostDispatchInfo? + as EthereumTransactor>::call( + sender_evm_address, + self.evm_domain.target_contract_address, + msg.as_slice(), + self.evm_domain.fee_values.value, + self.evm_domain.fee_values.gas_price, + self.evm_domain.fee_values.gas_limit, + ) + .map_err(|e| e.error)?; + + Ok(()) + } +} + +/// The EVMDomain holds all relevant information for validating and executing +/// the EVM call. +#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub struct EVMDomain { + /// The address of the contract deployed in our EVM. + pub target_contract_address: H160, + + /// The `BlakeTwo256` hash of the target contract code. + /// + /// This is used during router initialization to ensure that the correct + /// contract code is used. + pub target_contract_hash: H256, + + /// The values used when executing the EVM call. + pub fee_values: FeeValues, +} + +/// The FeeValues holds all information related to the transaction costs. +#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub struct FeeValues { + /// The value used when executing the EVM call. + pub value: U256, + + /// The gas price used when executing the EVM call. + pub gas_price: U256, + + /// The gas limit used when executing the EVM call. + pub gas_limit: U256, +} + +/// A generic router used for executing XCM calls. +#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub struct XCMRouter +where + T: frame_system::Config + pallet_xcm_transactor::Config, +{ + pub xcm_domain: XcmDomain, + pub _marker: PhantomData, +} + +impl XCMRouter +where + T: frame_system::Config + pallet_xcm_transactor::Config, +{ + /// Sets the weight information for the provided XCM domain location, and + /// the fee per second for the provided fee asset location. + pub fn do_init(&self) -> DispatchResult { + pallet_xcm_transactor::Pallet::::set_transact_info( + ::RuntimeOrigin::root(), + self.xcm_domain.location.clone(), + self.xcm_domain.transact_info.transact_extra_weight, + self.xcm_domain.transact_info.max_weight, + self.xcm_domain.transact_info.transact_extra_weight_signed, + )?; + + pallet_xcm_transactor::Pallet::::set_fee_per_second( + ::RuntimeOrigin::root(), + self.xcm_domain.fee_asset_location.clone(), + self.xcm_domain.fee_per_second, + ) + } + + /// Encodes the Connectors message to the required format and executes the + /// call via the XCM transactor pallet. + pub fn do_send(&self, sender: T::AccountId, msg: Vec) -> DispatchResult { + let ethereum_xcm_call = get_encoded_ethereum_xcm_call::(self.xcm_domain.clone(), msg) + .map_err(|_| DispatchError::Other("encoded ethereum xcm call retrieval"))?; + + pallet_xcm_transactor::Pallet::::transact_through_sovereign( + ::RuntimeOrigin::root(), + // The destination to which the message should be sent. + self.xcm_domain.location.clone(), + // The sender will pay for this transaction. + sender, + // The currency in which we want to pay fees. + CurrencyPayment { + currency: Currency::AsCurrencyId(self.xcm_domain.fee_currency.clone()), + fee_amount: None, + }, + // The call to be executed in the destination chain. + ethereum_xcm_call, + OriginKind::SovereignAccount, + TransactWeights { + // Convert the max gas_limit into a max transact weight following + // Moonbeam's formula. + transact_required_weight_at_most: Weight::from_all( + self.xcm_domain.max_gas_limit * 25_000 + 100_000_000, + ), + overall_weight: None, + }, + )?; + + Ok(()) + } +} + +pub(crate) fn get_encoded_ethereum_xcm_call( + xcm_domain: XcmDomain, + msg: Vec, +) -> Result, ()> +where + T: frame_system::Config + pallet_xcm_transactor::Config, +{ + let input = + BoundedVec::>::try_from(msg) + .map_err(|_| ())?; + + let mut encoded: Vec = Vec::new(); + + encoded.append(&mut xcm_domain.ethereum_xcm_transact_call_index.into_inner()); + + encoded.append( + &mut xcm_primitives::EthereumXcmTransaction::V1(xcm_primitives::EthereumXcmTransactionV1 { + gas_limit: U256::from(xcm_domain.max_gas_limit), + fee_payment: xcm_primitives::EthereumXcmFee::Auto, + action: pallet_ethereum::TransactionAction::Call(xcm_domain.contract_address), + value: U256::zero(), + input, + access_list: None, + }) + .encode(), + ); + + Ok(encoded) +} + +/// XcmDomain gathers all the required fields to build and send remote +/// calls to a specific XCM-based Domain. +#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo)] +pub struct XcmDomain { + /// The XCM multilocation of the domain. + pub location: Box, + + /// The ethereum_xcm::Call::transact call index on a given domain. + /// It should contain the pallet index + the `transact` call index, to which + /// we will append the eth_tx param. + /// + /// You can obtain this value by building an ethereum_xcm::transact call + /// with Polkadot JS on the target chain. + pub ethereum_xcm_transact_call_index: + BoundedVec>, + + /// The target contract address on a given domain. + pub contract_address: H160, + + /// The max gas_limit we want to propose for a remote evm execution + pub max_gas_limit: u64, + + /// The XCM transact info that will be stored in the + /// `TransactInfoWithWeightLimit` storage of the XCM transactor pallet. + pub transact_info: XcmTransactInfo, + + /// The currency in which execution fees will be paid on + pub fee_currency: CurrencyId, + + /// The fee per second that will be stored in the + /// `DestinationAssetFeePerSecond` storage of the XCM transactor pallet. + pub fee_per_second: u128, + + /// The location of the asset used for paying XCM fees. + pub fee_asset_location: Box, +} + +#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +/// XcmTransactInfo hold all the weight related information required for the XCM +/// transactor pallet. +pub struct XcmTransactInfo { + pub transact_extra_weight: Weight, + pub max_weight: Weight, + pub transact_extra_weight_signed: Option, +} + +/// NOTE: Remove this custom implementation once the following underlying data +/// implements MaxEncodedLen: +/// * Polkadot Repo: xcm::VersionedMultiLocation +/// * PureStake Repo: pallet_xcm_transactor::Config::CurrencyId +impl MaxEncodedLen for XcmDomain +where + XcmDomain: Encode, +{ + fn max_encoded_len() -> usize { + // The domain's `VersionedMultiLocation` (custom bound) + MultiLocation::max_encoded_len() + // From the enum wrapping of `VersionedMultiLocation` for the XCM domain location. + .saturating_add(1) + // From the enum wrapping of `VersionedMultiLocation` for the asset fee location. + .saturating_add(1) + // The ethereum xcm call index (default bound) + .saturating_add(BoundedVec::< + u8, + ConstU32<{ xcm_primitives::MAX_ETHEREUM_XCM_INPUT_SIZE }>, + >::max_encoded_len()) + // The contract address (default bound) + .saturating_add(H160::max_encoded_len()) + // The fee currency (custom bound) + .saturating_add(cfg_types::tokens::CurrencyId::max_encoded_len()) + // The XcmTransactInfo + .saturating_add(XcmTransactInfo::max_encoded_len()) + } +} diff --git a/pallets/connectors-gateway/routers/src/routers.rs b/pallets/connectors-gateway/routers/src/routers.rs deleted file mode 100644 index ce049b3991..0000000000 --- a/pallets/connectors-gateway/routers/src/routers.rs +++ /dev/null @@ -1,281 +0,0 @@ -// Copyright 2021 Centrifuge Foundation (centrifuge.io). -// -// This file is part of the Centrifuge chain project. -// Centrifuge 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 (see http://www.gnu.org/licenses). -// Centrifuge 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. -use cfg_traits::ethereum::EthereumTransactor; -use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::{ - dispatch::{DispatchError, DispatchResult, Weight}, - ensure, - traits::OriginTrait, -}; -use pallet_xcm_transactor::{Currency, CurrencyPayment, TransactWeights}; -use scale_info::TypeInfo; -use sp_core::{bounded::BoundedVec, ConstU32, H160, H256, U256}; -use sp_runtime::traits::{BlakeTwo256, Hash}; -use sp_std::{boxed::Box, marker::PhantomData, vec::Vec}; -use xcm::{ - latest::{MultiLocation, OriginKind}, - VersionedMultiLocation, -}; - -/// A generic router used for executing EVM calls. -#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -pub struct EVMRouter -where - T: frame_system::Config + pallet_ethereum_transaction::Config + pallet_evm::Config, -{ - pub evm_domain: EVMDomain, - pub _marker: PhantomData, -} - -impl EVMRouter -where - T: frame_system::Config + pallet_ethereum_transaction::Config + pallet_evm::Config, - T::AccountId: AsRef<[u8; 32]>, -{ - /// Performs an extra check to ensure that the actual contract is deployed - /// at the provided address and that the contract code hash matches. - pub fn do_init(&self) -> DispatchResult { - let code = pallet_evm::AccountCodes::::get(self.evm_domain.target_contract_address); - - ensure!( - BlakeTwo256::hash_of(&code) == self.evm_domain.target_contract_hash, - DispatchError::Other("Target contract code does not match"), - ); - - Ok(()) - } - - /// NOTE - the sender account ID provided here will be converted to an EVM - /// address via truncating. When the call is processed by the underlying EVM - /// pallet, this EVM address will be converted back into a substrate account - /// which will be charged for the transaction. This converted substrate - /// account is not the same as the original account. - pub fn do_send(&self, sender: T::AccountId, msg: Vec) -> DispatchResult { - let sender_evm_address = H160::from_slice(&sender.as_ref()[0..20]); - - // TODO(cdamian): This returns a `DispatchResultWithPostInfo`. Should we - // propagate that to another layer that will eventually charge for the - // weight in the PostDispatchInfo? - as EthereumTransactor>::call( - sender_evm_address, - self.evm_domain.target_contract_address, - msg.as_slice(), - self.evm_domain.fee_values.value, - self.evm_domain.fee_values.gas_price, - self.evm_domain.fee_values.gas_limit, - ) - .map_err(|e| e.error)?; - - Ok(()) - } -} - -/// The EVMDomain holds all relevant information for validating and executing -/// the EVM call. -#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -pub struct EVMDomain { - /// The address of the contract deployed in our EVM. - pub target_contract_address: H160, - - /// The `BlakeTwo256` hash of the target contract code. - /// - /// This is used during router initialization to ensure that the correct - /// contract code is used. - pub target_contract_hash: H256, - - /// The values used when executing the EVM call. - pub fee_values: FeeValues, -} - -/// The FeeValues holds all information related to the transaction costs. -#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -pub struct FeeValues { - /// The value used when executing the EVM call. - pub value: U256, - - /// The gas price used when executing the EVM call. - pub gas_price: U256, - - /// The gas limit used when executing the EVM call. - pub gas_limit: U256, -} - -/// A generic router used for executing XCM calls. -#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -pub struct XCMRouter -where - T: frame_system::Config + pallet_xcm_transactor::Config, -{ - pub xcm_domain: XcmDomain, - pub _marker: PhantomData, -} - -impl XCMRouter -where - T: frame_system::Config + pallet_xcm_transactor::Config, -{ - /// Sets the weight information for the provided XCM domain location, and - /// the fee per second for the provided fee asset location. - pub fn do_init(&self) -> DispatchResult { - pallet_xcm_transactor::Pallet::::set_transact_info( - ::RuntimeOrigin::root(), - self.xcm_domain.location.clone(), - self.xcm_domain.transact_info.transact_extra_weight, - self.xcm_domain.transact_info.max_weight, - self.xcm_domain.transact_info.transact_extra_weight_signed, - )?; - - pallet_xcm_transactor::Pallet::::set_fee_per_second( - ::RuntimeOrigin::root(), - self.xcm_domain.fee_asset_location.clone(), - self.xcm_domain.fee_per_second, - ) - } - - /// Encodes the Connectors message to the required format and executes the - /// call via the XCM transactor pallet. - pub fn do_send(&self, sender: T::AccountId, msg: Vec) -> DispatchResult { - let ethereum_xcm_call = get_encoded_ethereum_xcm_call::(self.xcm_domain.clone(), msg) - .map_err(|_| DispatchError::Other("encoded ethereum xcm call retrieval"))?; - - pallet_xcm_transactor::Pallet::::transact_through_sovereign( - ::RuntimeOrigin::root(), - // The destination to which the message should be sent. - self.xcm_domain.location.clone(), - // The sender will pay for this transaction. - sender, - // The currency in which we want to pay fees. - CurrencyPayment { - currency: Currency::AsCurrencyId(self.xcm_domain.fee_currency.clone()), - fee_amount: None, - }, - // The call to be executed in the destination chain. - ethereum_xcm_call, - OriginKind::SovereignAccount, - TransactWeights { - // Convert the max gas_limit into a max transact weight following - // Moonbeam's formula. - transact_required_weight_at_most: Weight::from_all( - self.xcm_domain.max_gas_limit * 25_000 + 100_000_000, - ), - overall_weight: None, - }, - )?; - - Ok(()) - } -} - -pub(crate) fn get_encoded_ethereum_xcm_call( - xcm_domain: XcmDomain, - msg: Vec, -) -> Result, ()> -where - T: frame_system::Config + pallet_xcm_transactor::Config, -{ - let input = - BoundedVec::>::try_from(msg) - .map_err(|_| ())?; - - let mut encoded: Vec = Vec::new(); - - encoded.append(&mut xcm_domain.ethereum_xcm_transact_call_index.into_inner()); - - encoded.append( - &mut xcm_primitives::EthereumXcmTransaction::V1(xcm_primitives::EthereumXcmTransactionV1 { - gas_limit: U256::from(xcm_domain.max_gas_limit), - fee_payment: xcm_primitives::EthereumXcmFee::Auto, - action: pallet_ethereum::TransactionAction::Call(xcm_domain.contract_address), - value: U256::zero(), - input, - access_list: None, - }) - .encode(), - ); - - Ok(encoded) -} - -/// XcmDomain gathers all the required fields to build and send remote -/// calls to a specific XCM-based Domain. -#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo)] -pub struct XcmDomain { - /// The XCM multilocation of the domain. - pub location: Box, - - /// The ethereum_xcm::Call::transact call index on a given domain. - /// It should contain the pallet index + the `transact` call index, to which - /// we will append the eth_tx param. - /// - /// You can obtain this value by building an ethereum_xcm::transact call - /// with Polkadot JS on the target chain. - pub ethereum_xcm_transact_call_index: - BoundedVec>, - - /// The target contract address on a given domain. - pub contract_address: H160, - - /// The max gas_limit we want to propose for a remote evm execution - pub max_gas_limit: u64, - - /// The XCM transact info that will be stored in the - /// `TransactInfoWithWeightLimit` storage of the XCM transactor pallet. - pub transact_info: XcmTransactInfo, - - /// The currency in which execution fees will be paid on - pub fee_currency: CurrencyId, - - /// The fee per second that will be stored in the - /// `DestinationAssetFeePerSecond` storage of the XCM transactor pallet. - pub fee_per_second: u128, - - /// The location of the asset used for paying XCM fees. - pub fee_asset_location: Box, -} - -#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -/// XcmTransactInfo hold all the weight related information required for the XCM -/// transactor pallet. -pub struct XcmTransactInfo { - pub transact_extra_weight: Weight, - pub max_weight: Weight, - pub transact_extra_weight_signed: Option, -} - -/// NOTE: Remove this custom implementation once the following underlying data -/// implements MaxEncodedLen: -/// * Polkadot Repo: xcm::VersionedMultiLocation -/// * PureStake Repo: pallet_xcm_transactor::Config::CurrencyId -impl MaxEncodedLen for XcmDomain -where - XcmDomain: Encode, -{ - fn max_encoded_len() -> usize { - // The domain's `VersionedMultiLocation` (custom bound) - MultiLocation::max_encoded_len() - // From the enum wrapping of `VersionedMultiLocation` for the XCM domain location. - .saturating_add(1) - // From the enum wrapping of `VersionedMultiLocation` for the asset fee location. - .saturating_add(1) - // The ethereum xcm call index (default bound) - .saturating_add(BoundedVec::< - u8, - ConstU32<{ xcm_primitives::MAX_ETHEREUM_XCM_INPUT_SIZE }>, - >::max_encoded_len()) - // The contract address (default bound) - .saturating_add(H160::max_encoded_len()) - // The fee currency (custom bound) - .saturating_add(cfg_types::tokens::CurrencyId::max_encoded_len()) - // The XcmTransactInfo - .saturating_add(XcmTransactInfo::max_encoded_len()) - } -} diff --git a/pallets/connectors-gateway/routers/src/axelar_evm.rs b/pallets/connectors-gateway/routers/src/routers/axelar_evm.rs similarity index 100% rename from pallets/connectors-gateway/routers/src/axelar_evm.rs rename to pallets/connectors-gateway/routers/src/routers/axelar_evm.rs diff --git a/pallets/connectors-gateway/routers/src/axelar_xcm.rs b/pallets/connectors-gateway/routers/src/routers/axelar_xcm.rs similarity index 100% rename from pallets/connectors-gateway/routers/src/axelar_xcm.rs rename to pallets/connectors-gateway/routers/src/routers/axelar_xcm.rs diff --git a/pallets/connectors-gateway/routers/src/ethereum_xcm.rs b/pallets/connectors-gateway/routers/src/routers/ethereum_xcm.rs similarity index 100% rename from pallets/connectors-gateway/routers/src/ethereum_xcm.rs rename to pallets/connectors-gateway/routers/src/routers/ethereum_xcm.rs diff --git a/pallets/connectors-gateway/routers/src/routers/mod.rs b/pallets/connectors-gateway/routers/src/routers/mod.rs new file mode 100644 index 0000000000..2f932293c9 --- /dev/null +++ b/pallets/connectors-gateway/routers/src/routers/mod.rs @@ -0,0 +1,18 @@ +// Copyright 2021 Centrifuge Foundation (centrifuge.io). +// +// This file is part of the Centrifuge chain project. +// Centrifuge 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 (see http://www.gnu.org/licenses). +// Centrifuge 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. +pub mod axelar_evm; +pub mod axelar_xcm; +pub mod ethereum_xcm; + +pub use axelar_evm::*; +pub use axelar_xcm::*; +pub use ethereum_xcm::*; diff --git a/pallets/connectors-gateway/routers/src/tests.rs b/pallets/connectors-gateway/routers/src/tests.rs index 303c85e14c..9ae736fec0 100644 --- a/pallets/connectors-gateway/routers/src/tests.rs +++ b/pallets/connectors-gateway/routers/src/tests.rs @@ -17,13 +17,7 @@ use sp_runtime::{ }; use super::mock::*; -use crate::{ - axelar_evm::{get_axelar_encoded_msg, AxelarEVMRouter}, - axelar_xcm::AxelarXCMRouter, - ethereum_xcm::{get_encoded_contract_call, EthereumXCMRouter}, - routers::*, - DomainRouter, EVMChain, -}; +use crate::*; mod evm_router { use util::*; From cdb99c068face59ee4ffd0a462b02875b2a0b82c Mon Sep 17 00:00:00 2001 From: cdamian <17934949+cdamian@users.noreply.github.com> Date: Wed, 2 Aug 2023 17:09:53 +0300 Subject: [PATCH 9/9] axelar-evm: Add Goerli to EVMChain --- pallets/connectors-gateway/routers/src/routers/axelar_evm.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pallets/connectors-gateway/routers/src/routers/axelar_evm.rs b/pallets/connectors-gateway/routers/src/routers/axelar_evm.rs index 2dd2cedaf7..33edb3d227 100644 --- a/pallets/connectors-gateway/routers/src/routers/axelar_evm.rs +++ b/pallets/connectors-gateway/routers/src/routers/axelar_evm.rs @@ -30,6 +30,7 @@ use crate::{ #[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] pub enum EVMChain { Ethereum, + Goerli, } /// Required due to the naming convention defined by Axelar here: @@ -38,6 +39,7 @@ impl ToString for EVMChain { fn to_string(&self) -> String { match self { EVMChain::Ethereum => "Ethereum".to_string(), + EVMChain::Goerli => "ethereum-2".to_string(), } } }