From c779524aad5334d95a6b948b8d816c266da10570 Mon Sep 17 00:00:00 2001 From: Frederik Gartenmeister <mustermeiszer@posteo.de> Date: Tue, 12 Sep 2023 23:03:12 +0200 Subject: [PATCH] precompile: Remove extra checks (#1538) * precompile: Remove extra checks * precompile: Update precompile test * precompile: Add checks for account balances in integration test * taplo: Fix * lp-gateway: Ensure contract address is encoded properly * precompile: Add support for hex source address * development: Update spec version * clippy: Fix --------- Co-authored-by: cdamian <17934949+cdamian@users.noreply.github.com> --- Cargo.lock | 6 +- .../axelar-gateway-precompile/Cargo.toml | 1 + .../axelar-gateway-precompile/src/lib.rs | 212 +++++++--------- .../routers/Cargo.toml | 1 + .../routers/src/routers/axelar_evm.rs | 11 +- runtime/common/src/evm/precompile.rs | 2 +- runtime/development/Cargo.toml | 2 +- runtime/development/src/lib.rs | 2 +- runtime/integration-tests/Cargo.toml | 3 + .../pallet.rs => evm/ethereum_transaction.rs} | 1 - .../src/{ethereum_transaction => evm}/mod.rs | 3 +- .../integration-tests/src/evm/precompile.rs | 232 ++++++++++++++++++ runtime/integration-tests/src/lib.rs | 2 +- 13 files changed, 343 insertions(+), 135 deletions(-) rename runtime/integration-tests/src/{ethereum_transaction/pallet.rs => evm/ethereum_transaction.rs} (99%) rename runtime/integration-tests/src/{ethereum_transaction => evm}/mod.rs (93%) create mode 100644 runtime/integration-tests/src/evm/precompile.rs diff --git a/Cargo.lock b/Cargo.lock index bb1017d780..0e05e881ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -610,6 +610,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", + "hex", "pallet-evm", "pallet-liquidity-pools-gateway", "parity-scale-codec 3.6.4", @@ -2681,7 +2682,7 @@ dependencies = [ [[package]] name = "development-runtime" -version = "0.10.21" +version = "0.10.23" dependencies = [ "axelar-gateway-precompile", "cfg-primitives", @@ -5805,6 +5806,7 @@ dependencies = [ "ethabi 16.0.0", "frame-support", "frame-system", + "hex", "lazy_static", "orml-traits", "pallet-balances", @@ -11024,6 +11026,7 @@ name = "runtime-integration-tests" version = "0.1.0" dependencies = [ "altair-runtime", + "axelar-gateway-precompile", "centrifuge-runtime", "cfg-primitives", "cfg-traits", @@ -11031,6 +11034,7 @@ dependencies = [ "cfg-utils", "cumulus-primitives-core", "development-runtime", + "ethabi 16.0.0", "ethereum", "frame-benchmarking", "frame-support", diff --git a/pallets/liquidity-pools-gateway/axelar-gateway-precompile/Cargo.toml b/pallets/liquidity-pools-gateway/axelar-gateway-precompile/Cargo.toml index 63a59815c0..52dacaed9b 100644 --- a/pallets/liquidity-pools-gateway/axelar-gateway-precompile/Cargo.toml +++ b/pallets/liquidity-pools-gateway/axelar-gateway-precompile/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +hex = { version = "0.4.3", default-features = false } 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" } diff --git a/pallets/liquidity-pools-gateway/axelar-gateway-precompile/src/lib.rs b/pallets/liquidity-pools-gateway/axelar-gateway-precompile/src/lib.rs index 2a272a754b..7fe1ceea51 100644 --- a/pallets/liquidity-pools-gateway/axelar-gateway-precompile/src/lib.rs +++ b/pallets/liquidity-pools-gateway/axelar-gateway-precompile/src/lib.rs @@ -12,20 +12,22 @@ #![cfg_attr(not(feature = "std"), no_std)] use cfg_types::domain_address::{Domain, DomainAddress}; -use codec::alloc::string::ToString; -use ethabi::Token; use fp_evm::PrecompileHandle; -use frame_support::{Blake2_256, StorageHasher}; +use frame_support::ensure; use pallet_evm::{ExitError, PrecompileFailure}; use precompile_utils::prelude::*; -use sp_core::{bounded::BoundedVec, ConstU32, H160, H256, U256}; -use sp_runtime::{DispatchError, DispatchResult}; +use sp_core::{bounded::BoundedVec, ConstU32, H256, U256}; +use sp_runtime::{ + traits::{BlakeTwo256, Hash}, + DispatchError, +}; use sp_std::vec::Vec; pub use crate::weights::WeightInfo; pub const MAX_SOURCE_CHAIN_BYTES: u32 = 128; -pub const MAX_SOURCE_ADDRESS_BYTES: u32 = 32; +// Ensure we allow enough to support a hex encoded address with the `0x` prefix. +pub const MAX_SOURCE_ADDRESS_BYTES: u32 = 42; pub const MAX_TOKEN_SYMBOL_BYTES: u32 = 32; pub const MAX_PAYLOAD_BYTES: u32 = 1024; pub const PREFIX_CONTRACT_CALL_APPROVED: [u8; 32] = keccak256!("contract-call-approved"); @@ -47,7 +49,7 @@ pub mod weights; frame_support::RuntimeDebugNoBound, )] pub struct SourceConverter { - domain: Domain, + pub domain: Domain, } impl SourceConverter { @@ -116,7 +118,7 @@ pub mod pallet { } #[pallet::storage] - pub type AxelarGatewayContract<T: Config> = StorageValue<_, H160, ValueQuery>; + pub type GatewayContract<T: Config> = StorageValue<_, H160, ValueQuery>; /// `SourceConversion` is a `hash_of(Vec<u8>)` where the `Vec<u8>` is the /// blake256-hash of the source-chain identifier used by the Axelar network. @@ -142,7 +144,7 @@ pub mod pallet { #[pallet::genesis_build] impl<T: Config> GenesisBuild<T> for GenesisConfig<T> { fn build(&self) { - AxelarGatewayContract::<T>::set(self.gateway) + GatewayContract::<T>::set(self.gateway) } } @@ -174,7 +176,7 @@ pub mod pallet { pub fn set_gateway(origin: OriginFor<T>, address: H160) -> DispatchResult { <T as Config>::AdminOrigin::ensure_origin(origin)?; - AxelarGatewayContract::<T>::set(address); + GatewayContract::<T>::set(address); Self::deposit_event(Event::<T>::GatewaySet { address }); @@ -205,9 +207,8 @@ impl<T: Config> cfg_traits::TryConvert<(Vec<u8>, Vec<u8>), DomainAddress> for Pa fn try_convert(origin: (Vec<u8>, Vec<u8>)) -> Result<DomainAddress, DispatchError> { let (source_chain, source_address) = origin; - let domain_converter = - SourceConversion::<T>::get(H256::from(Blake2_256::hash(&source_chain))) - .ok_or(Error::<T>::NoConverterForSource)?; + let domain_converter = SourceConversion::<T>::get(BlakeTwo256::hash(&source_chain)) + .ok_or(Error::<T>::NoConverterForSource)?; domain_converter .try_convert(&source_address) @@ -246,30 +247,17 @@ where #[precompile::public("execute(bytes32,string,string,bytes)")] fn execute( handle: &mut impl PrecompileHandle, - command_id: H256, + _command_id: H256, source_chain: String<MAX_SOURCE_CHAIN_BYTES>, source_address: String<MAX_SOURCE_ADDRESS_BYTES>, payload: Bytes<MAX_PAYLOAD_BYTES>, ) -> EvmResult { - // CREATE HASH OF PAYLOAD - // - bytes32 payloadHash = keccak256(payload); - let payload_hash = H256::from(sp_io::hashing::keccak_256(payload.as_bytes())); - - // CHECK EVM STORAGE OF GATEWAY - // - keccak256(abi.encode(PREFIX_CONTRACT_CALL_APPROVED, commandId, sourceChain, - // sourceAddress, contractAddress, payloadHash)); - let key = H256::from(sp_io::hashing::keccak_256(ðabi::encode(&[ - Token::FixedBytes(PREFIX_CONTRACT_CALL_APPROVED.into()), - Token::FixedBytes(command_id.as_bytes().into()), - Token::String(source_chain.clone().try_into().map_err(|_| { - RevertReason::read_out_of_bounds("utf-8 encoding failing".to_string()) - })?), - Token::String(source_address.clone().try_into().map_err(|_| { - RevertReason::read_out_of_bounds("utf-8 encoding failing".to_string()) - })?), - Token::Address(handle.context().address), - Token::FixedBytes(payload_hash.as_bytes().into()), - ]))); + ensure!( + handle.context().caller == GatewayContract::<T>::get(), + PrecompileFailure::Error { + exit_status: ExitError::Other("gateway contract address mismatch".into()), + } + ); let msg = BoundedVec::< u8, @@ -279,20 +267,34 @@ where exit_status: ExitError::Other("payload conversion".into()), })?; - Self::execute_call(key, || { - let domain_converter = - SourceConversion::<T>::get(H256::from(Blake2_256::hash(source_chain.as_bytes()))) - .ok_or(Error::<T>::NoConverterForSource)?; + let domain_converter = SourceConversion::<T>::get(BlakeTwo256::hash( + source_chain.as_bytes(), + )) + .ok_or(PrecompileFailure::Error { + exit_status: ExitError::Other("converter for source not found".into()), + })?; - let domain_address = domain_converter - .try_convert(source_address.as_bytes()) - .ok_or(Error::<T>::AccountBytesMismatchForDomain)?; + let source_address_bytes = + get_source_address_bytes(source_address).ok_or(PrecompileFailure::Error { + exit_status: ExitError::Other("invalid source address".into()), + })?; - pallet_liquidity_pools_gateway::Pallet::<T>::process_msg( - pallet_liquidity_pools_gateway::GatewayOrigin::Domain(domain_address).into(), - msg, - ) - }) + let domain_address = domain_converter + .try_convert(source_address_bytes.as_slice()) + .ok_or(PrecompileFailure::Error { + exit_status: ExitError::Other("account bytes mismatch for domain".into()), + })?; + + match pallet_liquidity_pools_gateway::Pallet::<T>::process_msg( + pallet_liquidity_pools_gateway::GatewayOrigin::Domain(domain_address).into(), + msg, + ) + .map(|_| ()) + .map_err(TryDispatchError::Substrate) + { + Err(e) => Err(e.into()), + Ok(()) => Ok(()), + } } // Mimics: @@ -323,98 +325,56 @@ where // TODO: Check whether this is enough or if we should error out Ok(()) } +} - fn execute_call(key: H256, f: impl FnOnce() -> DispatchResult) -> EvmResult { - let gateway = AxelarGatewayContract::<T>::get(); +const EXPECTED_SOURCE_ADDRESS_SIZE: usize = 20; +const HEX_PREFIX: &str = "0x"; - let valid = Self::get_validate_call(gateway, key); +pub(crate) fn get_source_address_bytes( + source_address: String<MAX_SOURCE_ADDRESS_BYTES>, +) -> Option<Vec<u8>> { + if source_address.as_bytes().len() == EXPECTED_SOURCE_ADDRESS_SIZE { + return Some(source_address.as_bytes().to_vec()); + } - if valid { - // Prevent re-entrance - Self::set_validate_call(gateway, key, false); + let str = source_address.as_str().ok()?; - match f().map(|_| ()).map_err(TryDispatchError::Substrate) { - Err(e) => { - Self::set_validate_call(gateway, key, true); - Err(e.into()) - } - Ok(()) => Ok(()), - } - } else { - Err(RevertReason::Custom("Call not validated".to_string()).into()) + // Attempt to hex decode source address. + match hex::decode(str) { + Ok(res) => Some(res), + Err(_) => { + // Strip 0x prefix. + let res = str.strip_prefix(HEX_PREFIX)?; + + hex::decode(res).ok() } } +} - fn get_validate_call(from: H160, key: H256) -> bool { - Self::h256_to_bool(pallet_evm::AccountStorages::<T>::get( - from, - Self::get_index_validate_call(key), - )) - } +#[cfg(test)] +mod tests { + use sp_core::H160; - fn set_validate_call(from: H160, key: H256, valid: bool) { - pallet_evm::AccountStorages::<T>::set( - from, - Self::get_index_validate_call(key), - Self::bool_to_h256(valid), - ) - } + use super::*; - fn get_index_validate_call(key: H256) -> H256 { - // Generate right index: - // - // From the solidty contract of Axelar (EternalStorage.sol) - // mapping(bytes32 => uint256) private _uintStorage; -> Slot 0 - // mapping(bytes32 => string) private _stringStorage; -> Slot 1 - // mapping(bytes32 => address) private _addressStorage; -> Slot 2 - // mapping(bytes32 => bytes) private _bytesStorage; -> Slot 3 - // mapping(bytes32 => bool) private _boolStorage; -> Slot 4 - // mapping(bytes32 => int256) private _intStorage; -> Slot 5 - // - // This means our slot is U256::from(4) - let slot = U256::from(4); - - let mut bytes = Vec::new(); - bytes.extend_from_slice(key.as_bytes()); - - let mut be_bytes: [u8; 32] = [0u8; 32]; - // TODO: Is endnianess correct here? - slot.to_big_endian(&mut be_bytes); - bytes.extend_from_slice(&be_bytes); - - H256::from(sp_io::hashing::keccak_256(&bytes)) - } + #[test] + fn get_source_address_bytes_works() { + let hash = H160::from_low_u64_be(1); - // In Solidity, a boolean value (bool) is stored as a single byte (8 bits) in - // contract storage. The byte value 0x01 represents true, and the byte value - // 0x00 represents false. - // - // When you declare a boolean variable within a contract and store its value in - // storage, the contract reserves one storage slot, which is 32 bytes (256 bits) - // in size. However, only the first byte (8 bits) of that storage slot is used - // to store the boolean value. The remaining 31 bytes are left unused. - fn h256_to_bool(value: H256) -> bool { - let first = value.0[0]; - - // TODO; Should we check the other values too and error out then? - first == 1 - } + let str = String::<MAX_SOURCE_ADDRESS_BYTES>::from(hash.as_fixed_bytes().to_vec()); - // In Solidity, a boolean value (bool) is stored as a single byte (8 bits) in - // contract storage. The byte value 0x01 represents true, and the byte value - // 0x00 represents false. - // - // When you declare a boolean variable within a contract and store its value in - // storage, the contract reserves one storage slot, which is 32 bytes (256 bits) - // in size. However, only the first byte (8 bits) of that storage slot is used - // to store the boolean value. The remaining 31 bytes are left unused. - fn bool_to_h256(value: bool) -> H256 { - let mut bytes: [u8; 32] = [0u8; 32]; - - if value { - bytes[0] = 1; - } + get_source_address_bytes(str).expect("address bytes from H160 works"); + + let str = String::<MAX_SOURCE_ADDRESS_BYTES>::from( + "d47ed02acbbb66ee8a3fe0275bd98add0aa607c3".to_string(), + ); + + get_source_address_bytes(str).expect("address bytes from un-prefixed hex works"); + + let str = String::<MAX_SOURCE_ADDRESS_BYTES>::from( + "0xd47ed02acbbb66ee8a3fe0275bd98add0aa607c3".to_string(), + ); - H256::from(bytes) + get_source_address_bytes(str).expect("address bytes from prefixed hex works"); } } diff --git a/pallets/liquidity-pools-gateway/routers/Cargo.toml b/pallets/liquidity-pools-gateway/routers/Cargo.toml index d0ba8b5395..fae92ee6ea 100644 --- a/pallets/liquidity-pools-gateway/routers/Cargo.toml +++ b/pallets/liquidity-pools-gateway/routers/Cargo.toml @@ -11,6 +11,7 @@ version = "0.0.1" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +hex = { version = "0.4.3", default-features = false } 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" } diff --git a/pallets/liquidity-pools-gateway/routers/src/routers/axelar_evm.rs b/pallets/liquidity-pools-gateway/routers/src/routers/axelar_evm.rs index 137160536f..a5912166fd 100644 --- a/pallets/liquidity-pools-gateway/routers/src/routers/axelar_evm.rs +++ b/pallets/liquidity-pools-gateway/routers/src/routers/axelar_evm.rs @@ -15,7 +15,10 @@ use ethabi::{Contract, Function, Param, ParamType, Token}; use frame_support::dispatch::{DispatchError, DispatchResult}; use frame_system::pallet_prelude::OriginFor; use scale_info::{ - prelude::string::{String, ToString}, + prelude::{ + format, + string::{String, ToString}, + }, TypeInfo, }; use sp_core::{bounded::BoundedVec, ConstU32, H160}; @@ -155,7 +158,11 @@ pub(crate) fn get_axelar_encoded_msg( .map_err(|_| "cannot retrieve Axelar contract function")? .encode_input(&[ Token::String(target_chain_string), - Token::String(target_contract.to_string()), + // Ensure that the target contract is correctly converted to hex. + // + // The `to_string` method on the H160 is returning a string containing an ellipsis, such + // as: 0x1234…7890 + Token::String(format!("0x{}", hex::encode(target_contract.0))), Token::Bytes(encoded_liquidity_pools_contract), ]) .map_err(|_| "cannot encode input for Axelar contract function")?; diff --git a/runtime/common/src/evm/precompile.rs b/runtime/common/src/evm/precompile.rs index cf0987f909..c204976fb6 100644 --- a/runtime/common/src/evm/precompile.rs +++ b/runtime/common/src/evm/precompile.rs @@ -46,7 +46,7 @@ const ECRECOVERPUBLICKEY_ADDR: Addr = addr(1026); /// Liquidity-Pool logic on centrifuge. /// /// The precompile implements -const LP_AXELAR_GATEWAY: Addr = addr(2048); +pub const LP_AXELAR_GATEWAY: Addr = addr(2048); pub struct CentrifugePrecompiles<R>(PhantomData<R>); diff --git a/runtime/development/Cargo.toml b/runtime/development/Cargo.toml index 111f6227b9..775a798b35 100644 --- a/runtime/development/Cargo.toml +++ b/runtime/development/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "development-runtime" -version = "0.10.21" +version = "0.10.23" authors = ["Centrifuge <admin@centrifuge.io>"] edition = "2021" build = "build.rs" diff --git a/runtime/development/src/lib.rs b/runtime/development/src/lib.rs index 191e9a6147..145a8dfe3f 100644 --- a/runtime/development/src/lib.rs +++ b/runtime/development/src/lib.rs @@ -138,7 +138,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("centrifuge-devel"), impl_name: create_runtime_str!("centrifuge-devel"), authoring_version: 1, - spec_version: 1022, + spec_version: 1023, impl_version: 1, #[cfg(not(feature = "disable-runtime-api"))] apis: RUNTIME_API_VERSIONS, diff --git a/runtime/integration-tests/Cargo.toml b/runtime/integration-tests/Cargo.toml index c35567d1d3..3a08c7e180 100644 --- a/runtime/integration-tests/Cargo.toml +++ b/runtime/integration-tests/Cargo.toml @@ -88,12 +88,14 @@ cfg-traits = { path = "../../libs/traits" } cfg-types = { path = "../../libs/types" } cfg-utils = { path = "../../libs/utils" } +ethabi = { version = "16.0", default-features = false } ethereum = { version = "0.14.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" } pallet-evm-chain-id = { git = "https://github.com/PureStake/frontier", default-features = false, branch = "moonbeam-polkadot-v0.9.38" } +axelar-gateway-precompile = { path = "../../pallets/liquidity-pools-gateway/axelar-gateway-precompile" } liquidity-pools-gateway-routers = { path = "../../pallets/liquidity-pools-gateway/routers" } pallet-block-rewards = { path = "../../pallets/block-rewards" } pallet-ethereum-transaction = { path = "../../pallets/ethereum-transaction" } @@ -166,6 +168,7 @@ std = [ "pallet-collective/std", "pallet-democracy/std", "pallet-preimage/std", + "ethabi/std", ] runtime-benchmarks = [ diff --git a/runtime/integration-tests/src/ethereum_transaction/pallet.rs b/runtime/integration-tests/src/evm/ethereum_transaction.rs similarity index 99% rename from runtime/integration-tests/src/ethereum_transaction/pallet.rs rename to runtime/integration-tests/src/evm/ethereum_transaction.rs index 92713adfac..b29f0a5681 100644 --- a/runtime/integration-tests/src/ethereum_transaction/pallet.rs +++ b/runtime/integration-tests/src/evm/ethereum_transaction.rs @@ -25,7 +25,6 @@ use crate::{ AccountId, CouncilCollective, FastTrackVotingPeriod, MinimumDeposit, Runtime, RuntimeCall, RuntimeEvent, PARA_ID, }, - ethereum_transaction::pallet, utils::{ env, env::{ChainState, EventRange, TestEnv}, diff --git a/runtime/integration-tests/src/ethereum_transaction/mod.rs b/runtime/integration-tests/src/evm/mod.rs similarity index 93% rename from runtime/integration-tests/src/ethereum_transaction/mod.rs rename to runtime/integration-tests/src/evm/mod.rs index 0ca88fbd73..99172b9fdb 100644 --- a/runtime/integration-tests/src/ethereum_transaction/mod.rs +++ b/runtime/integration-tests/src/evm/mod.rs @@ -10,4 +10,5 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -mod pallet; +mod ethereum_transaction; +mod precompile; diff --git a/runtime/integration-tests/src/evm/precompile.rs b/runtime/integration-tests/src/evm/precompile.rs new file mode 100644 index 0000000000..2abb953735 --- /dev/null +++ b/runtime/integration-tests/src/evm/precompile.rs @@ -0,0 +1,232 @@ +// Copyright 2023 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 std::collections::BTreeMap; + +use axelar_gateway_precompile::SourceConverter; +use cfg_primitives::{Balance, PoolId, TrancheId, CFG}; +use cfg_traits::{ethereum::EthereumTransactor, liquidity_pools::Codec}; +use cfg_types::{ + domain_address::{Domain, DomainAddress}, + fixed_point::Rate, + tokens::{CurrencyId, CustomMetadata, GeneralCurrencyIndex}, +}; +use codec::Encode; +use ethabi::{Contract, Function, Param, ParamType, Token}; +use ethereum::{LegacyTransaction, TransactionAction, TransactionSignature, TransactionV2}; +use frame_support::{assert_err, assert_ok, dispatch::RawOrigin}; +use fudge::primitives::Chain; +use hex::ToHex; +use orml_traits::{asset_registry::AssetMetadata, MultiCurrency}; +use pallet_evm::{AddressMapping, FeeCalculator}; +use pallet_liquidity_pools::Message; +use runtime_common::{account_conversion::AccountConverter, evm::precompile::LP_AXELAR_GATEWAY}; +use sp_core::{Get, H160, H256, U256}; +use sp_runtime::traits::{BlakeTwo256, Hash}; +use tokio::runtime::Handle; +use xcm::{v3::MultiLocation, VersionedMultiLocation}; + +use crate::{ + chain::centrifuge::{ + AccountId, CouncilCollective, FastTrackVotingPeriod, MinimumDeposit, Runtime, RuntimeCall, + RuntimeEvent, RuntimeOrigin, PARA_ID, + }, + evm::ethereum_transaction::TEST_CONTRACT_CODE, + utils::{ + env, + env::{ChainState, EventRange, TestEnv}, + evm::{deploy_contract, mint_balance_into_derived_account}, + }, +}; + +#[tokio::test] +async fn axelar_precompile_execute() { + let mut env = env::test_env_default(Handle::current()); + + env.evolve().unwrap(); + + let currency_id = CurrencyId::ForeignAsset(123456); + + let sender_address = H160::from_low_u64_be(1_000_002); + + mint_balance_into_derived_account(&mut env, sender_address, 1_000_000 * CFG); + + let derived_sender_account = env + .with_state(Chain::Para(PARA_ID), || { + <Runtime as pallet_evm::Config>::AddressMapping::into_account_id(sender_address) + }) + .unwrap(); + + let receiver_address = H160::from_low_u64_be(1_000_003); + + let derived_receiver_account = env + .with_state(Chain::Para(PARA_ID), || { + <Runtime as pallet_evm::Config>::AddressMapping::into_account_id(receiver_address) + }) + .unwrap(); + + env.with_state(Chain::Para(PARA_ID), || { + let derived_receiver_balance = + orml_tokens::Pallet::<Runtime>::free_balance(currency_id, &derived_receiver_account); + + assert_eq!(derived_receiver_balance, 0) + }) + .unwrap(); + + let source_address = H160::from_low_u64_be(1111); + let evm_chain_name = String::from("Ethereum"); + let evm_chain_id = 0; + + let currency_metadata = AssetMetadata { + decimals: 18, + name: "Test".into(), + symbol: "TST".into(), + existential_deposit: 1_000_000, + location: Some(VersionedMultiLocation::V3(MultiLocation::here())), + additional: CustomMetadata { + transferability: Default::default(), + mintable: true, + permissioned: false, + pool_currency: false, + }, + }; + + env.with_mut_state(Chain::Para(PARA_ID), || { + orml_asset_registry::Pallet::<Runtime>::register_asset( + RuntimeOrigin::root(), + currency_metadata, + Some(currency_id), + ) + .unwrap(); + + orml_tokens::Pallet::<Runtime>::deposit( + currency_id, + &derived_sender_account, + 1_000_000_000_000 * 10u128.saturating_pow(18), + ) + .unwrap(); + }) + .unwrap(); + + let general_currency_id = env + .with_state(Chain::Para(PARA_ID), || { + pallet_liquidity_pools::Pallet::<Runtime>::try_get_general_index(currency_id).unwrap() + }) + .unwrap(); + + let transfer_amount = 100; + let msg = Message::<Domain, PoolId, TrancheId, Balance, Rate>::Transfer { + currency: general_currency_id, + sender: derived_sender_account.clone().into(), + receiver: derived_receiver_account.clone().into(), + amount: transfer_amount, + }; + + env.with_mut_state(Chain::Para(PARA_ID), || { + axelar_gateway_precompile::Pallet::<Runtime>::set_gateway( + RuntimeOrigin::root(), + sender_address, + ) + .unwrap(); + + axelar_gateway_precompile::Pallet::<Runtime>::set_converter( + RuntimeOrigin::root(), + BlakeTwo256::hash(evm_chain_name.as_bytes()), + SourceConverter { + domain: Domain::EVM(evm_chain_id), + }, + ) + .unwrap(); + + pallet_liquidity_pools_gateway::Pallet::<Runtime>::add_instance( + RuntimeOrigin::root(), + DomainAddress::EVM(evm_chain_id, source_address.0), + ) + .unwrap(); + }); + + let command_id = H256::from_low_u64_be(5678); + + let test_input = Contract { + constructor: None, + functions: BTreeMap::<String, Vec<Function>>::from([( + "execute".into(), + vec![Function { + name: "execute".into(), + inputs: vec![ + Param { + name: "commandId".into(), + kind: ParamType::FixedBytes(32), + internal_type: None, + }, + Param { + name: "sourceChain".into(), + kind: ParamType::String, + internal_type: None, + }, + Param { + name: "sourceAddress".into(), + kind: ParamType::String, + internal_type: None, + }, + Param { + name: "payload".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("execute".into()) + .map_err(|_| "cannot retrieve test contract function") + .unwrap() + .encode_input(&[ + Token::FixedBytes(command_id.0.to_vec()), + Token::String(evm_chain_name), + Token::String(String::from_utf8(source_address.as_fixed_bytes().to_vec()).unwrap()), + Token::Bytes(msg.serialize()), + ]) + .map_err(|_| "cannot encode input for test contract function") + .unwrap(); + + env.with_mut_state(Chain::Para(PARA_ID), || { + assert_ok!(pallet_evm::Pallet::<Runtime>::call( + RawOrigin::Signed(derived_sender_account.clone()).into(), + sender_address, + LP_AXELAR_GATEWAY.into(), + test_input.to_vec(), + U256::from(0), + 0x100000, + U256::from(1_000_000_000), + None, + Some(U256::from(0)), + Vec::new(), + )); + }) + .unwrap(); + + env.with_state(Chain::Para(PARA_ID), || { + let derived_receiver_balance = + orml_tokens::Pallet::<Runtime>::free_balance(currency_id, &derived_receiver_account); + + assert_eq!(derived_receiver_balance, transfer_amount) + }) + .unwrap(); +} diff --git a/runtime/integration-tests/src/lib.rs b/runtime/integration-tests/src/lib.rs index c2842655c9..25636495d4 100644 --- a/runtime/integration-tests/src/lib.rs +++ b/runtime/integration-tests/src/lib.rs @@ -14,7 +14,7 @@ #![cfg(test)] #![allow(unused)] -mod ethereum_transaction; +mod evm; mod liquidity_pools; mod pools; mod rewards;