diff --git a/libs/mocks/src/ethereum_transactor.rs b/libs/mocks/src/ethereum_transactor.rs new file mode 100644 index 0000000000..b92418019b --- /dev/null +++ b/libs/mocks/src/ethereum_transactor.rs @@ -0,0 +1,37 @@ +#[frame_support::pallet(dev_mode)] +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] + pub struct Pallet(_); + + #[pallet::storage] + type CallIds = StorageMap<_, _, String, mock_builder::CallId>; + + impl Pallet { + pub fn mock_call( + func: impl Fn(H160, H160, &[u8], U256, U256, U256) -> DispatchResult + 'static, + ) { + register_call!(move |(a, b, c, d, e, f)| func(a, b, c, d, e, f)); + } + } + + impl EthereumTransactor for Pallet { + fn call( + a: H160, + b: H160, + c: &[u8], + d: U256, + e: U256, + f: U256, + ) -> DispatchResultWithPostInfo { + execute_call!((a, b, c, d, e, f)) + } + } +} diff --git a/libs/mocks/src/lib.rs b/libs/mocks/src/lib.rs index 0e1a86c8f3..f1b7fc56ee 100644 --- a/libs/mocks/src/lib.rs +++ b/libs/mocks/src/lib.rs @@ -3,6 +3,7 @@ pub mod change_guard; pub mod converter; pub mod currency_conversion; pub mod data; +pub mod ethereum_transactor; pub mod fees; pub mod foreign_investment; pub mod foreign_investment_hooks; @@ -11,6 +12,7 @@ pub mod liquidity_pools; pub mod liquidity_pools_gateway; pub mod liquidity_pools_gateway_queue; pub mod liquidity_pools_gateway_routers; +pub mod message_receiver; pub mod pay_fee; pub mod permissions; pub mod pools; diff --git a/libs/mocks/src/message_receiver.rs b/libs/mocks/src/message_receiver.rs new file mode 100644 index 0000000000..b862579a7a --- /dev/null +++ b/libs/mocks/src/message_receiver.rs @@ -0,0 +1,35 @@ +#[frame_support::pallet(dev_mode)] +pub mod pallet { + use cfg_traits::liquidity_pools::MessageReceiver; + use frame_support::pallet_prelude::*; + use mock_builder::{execute_call, register_call}; + + #[pallet::config] + pub trait Config: frame_system::Config { + type Middleware; + type Origin; + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::storage] + type CallIds = StorageMap<_, _, String, mock_builder::CallId>; + + impl Pallet { + pub fn mock_receive( + f: impl Fn(T::Middleware, T::Origin, Vec) -> DispatchResult + 'static, + ) { + register_call!(move |(a, b, c)| f(a, b, c)); + } + } + + impl MessageReceiver for Pallet { + type Middleware = T::Middleware; + type Origin = T::Origin; + + fn receive(a: Self::Middleware, b: Self::Origin, c: Vec) -> DispatchResult { + execute_call!((a, b, c)) + } + } +} diff --git a/libs/traits/src/liquidity_pools.rs b/libs/traits/src/liquidity_pools.rs index 24febe10c9..fddba2a5ba 100644 --- a/libs/traits/src/liquidity_pools.rs +++ b/libs/traits/src/liquidity_pools.rs @@ -13,9 +13,7 @@ use frame_support::{ dispatch::{DispatchResult, DispatchResultWithPostInfo}, - traits::Get, weights::Weight, - BoundedVec, }; use sp_runtime::DispatchError; use sp_std::vec::Vec; @@ -80,14 +78,11 @@ pub trait MessageReceiver { /// The originator of the received message type Origin; - /// The maximum lenght for a message the implementor is able to receive. - type MaxEncodedLen: Get; - /// Sends a message for origin to destination fn receive( middleware: Self::Middleware, origin: Self::Origin, - message: BoundedVec, + message: Vec, ) -> DispatchResult; } diff --git a/pallets/axelar-router/src/lib.rs b/pallets/axelar-router/src/lib.rs index 0cccc73ac5..fe39a19246 100644 --- a/pallets/axelar-router/src/lib.rs +++ b/pallets/axelar-router/src/lib.rs @@ -5,7 +5,7 @@ use cfg_traits::{ }; use cfg_types::{domain_address::DomainAddress, EVMChainId}; use ethabi::{Contract, Function, Param, ParamType, Token}; -use fp_evm::{ExitError, PrecompileFailure, PrecompileHandle}; +use fp_evm::{ExitError, PrecompileHandle}; use frame_support::{ pallet_prelude::*, weights::{constants::RocksDbWeight, Weight}, @@ -17,6 +17,12 @@ use precompile_utils::prelude::*; use sp_core::{H160, H256, U256}; use sp_std::collections::btree_map::BTreeMap; +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + /// Maximum size allowed for a byte representation of an Axelar EVM chain /// string, as found below: /// @@ -86,77 +92,6 @@ pub struct FeeValues { pub gas_limit: U256, } -/// Encodes the provided message into the format required for submitting it -/// to the Axelar contract which in turn calls the LiquidityPools -/// contract with the serialized LP message as `payload`. -/// -/// Axelar contract call: -/// -/// -/// LiquidityPools contract call: -/// -fn wrap_into_axelar_msg( - serialized_msg: Vec, - target_chain: Vec, - target_contract: H160, -) -> Result, &'static str> { - 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"; - - #[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: Some(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( - String::from_utf8(target_chain).map_err(|_| "target chain conversion error")?, - ), - // 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(serialized_msg), - ]) - .map_err(|_| "cannot encode input for Axelar contract function")?; - - Ok(encoded_axelar_contract) -} - #[frame_support::pallet] pub mod pallet { use super::*; @@ -173,7 +108,7 @@ pub mod pallet { type AdminOrigin: EnsureOrigin; /// The target of the messages comming from other chains - type Gateway: MessageReceiver; + type Receiver: MessageReceiver; /// Middleware used by the gateway type Middleware: From; @@ -277,9 +212,6 @@ pub mod pallet { // // _execute(sourceChain, sourceAddress, payload); // } - // - // Note: The _execute logic in this case will forward all calls to the - // liquidity-pools-gateway with a special runtime local origin #[precompile::public("execute(bytes32,string,string,bytes)")] fn execute( handle: &mut impl PrecompileHandle, @@ -302,13 +234,6 @@ pub mod pallet { ExitError::Other("gateway contract address mismatch".into()), ); - let msg = BoundedVec::::MaxEncodedLen>::try_from( - payload.as_bytes().to_vec(), - ) - .map_err(|_| PrecompileFailure::Error { - exit_status: ExitError::Other("payload conversion".into()), - })?; - match config.domain { DomainConfig::Evm(EvmConfig { chain_id, .. }) => { let source_address_bytes = @@ -316,8 +241,9 @@ pub mod pallet { .ok_or(ExitError::Other("invalid source address".into()))?; let origin = DomainAddress::EVM(chain_id, source_address_bytes); + let message = payload.as_bytes().to_vec(); - T::Gateway::receive(AxelarId::Evm(chain_id).into(), origin, msg) + T::Receiver::receive(AxelarId::Evm(chain_id).into(), origin, message) .map_err(|e| TryDispatchError::Substrate(e).into()) } } @@ -389,3 +315,74 @@ pub mod pallet { } } } + +/// Encodes the provided message into the format required for submitting it +/// to the Axelar contract which in turn calls the LiquidityPools +/// contract with the serialized LP message as `payload`. +/// +/// Axelar contract call: +/// +/// +/// LiquidityPools contract call: +/// +fn wrap_into_axelar_msg( + serialized_msg: Vec, + target_chain: Vec, + target_contract: H160, +) -> Result, &'static str> { + 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"; + + #[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: Some(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( + String::from_utf8(target_chain).map_err(|_| "target chain conversion error")?, + ), + // 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(serialized_msg), + ]) + .map_err(|_| "cannot encode input for Axelar contract function")?; + + Ok(encoded_axelar_contract) +} diff --git a/pallets/axelar-router/src/mock.rs b/pallets/axelar-router/src/mock.rs new file mode 100644 index 0000000000..9024350667 --- /dev/null +++ b/pallets/axelar-router/src/mock.rs @@ -0,0 +1,57 @@ +use cfg_types::domain_address::DomainAddress; +use frame_support::{derive_impl, traits::EitherOfDiverse}; +use frame_system::{EnsureRoot, EnsureSigned}; +use sp_core::{H160, H256}; +use sp_io::TestExternalities; + +use crate::{pallet as pallet_axelar_router, AxelarId}; + +type AccountId = u64; + +pub struct Middleware(AxelarId); + +impl From for Middleware { + fn from(id: AxelarId) -> Self { + Middleware(id) + } +} + +frame_support::construct_runtime!( + pub enum Runtime { + System: frame_system, + Receiver: cfg_mocks::message_receiver::pallet, + Transactor: cfg_mocks::ethereum_transactor::pallet, + AccountCodeChecker: cfg_mocks::pre_conditions::pallet, + AxelarRouter: pallet_axelar_router, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type Block = frame_system::mocking::MockBlock; +} + +impl cfg_mocks::message_receiver::pallet::Config for Runtime { + type Middleware = Middleware; + type Origin = DomainAddress; +} + +impl cfg_mocks::ethereum_transactor::pallet::Config for Runtime {} + +impl cfg_mocks::pre_conditions::pallet::Config for Runtime { + type Conditions = (H160, H256); + type Result = bool; +} + +impl pallet_axelar_router::Config for Runtime { + type AdminOrigin = EitherOfDiverse, EnsureSigned>; + type EvmAccountCodeChecker = AccountCodeChecker; + type Middleware = Middleware; + type Receiver = Receiver; + type RuntimeEvent = RuntimeEvent; + type Transactor = Transactor; +} + +pub fn new_test_ext() -> TestExternalities { + System::externalities() +} diff --git a/pallets/axelar-router/src/tests.rs b/pallets/axelar-router/src/tests.rs new file mode 100644 index 0000000000..09dceaa092 --- /dev/null +++ b/pallets/axelar-router/src/tests.rs @@ -0,0 +1,8 @@ +use crate::mock::*; + +#[test] +fn configure_router() { + new_test_ext().execute_with(|| { + //TODO + }); +}