From a1109d8b42023b09a3205383f5bf82d2993098d0 Mon Sep 17 00:00:00 2001 From: William Freudenberger Date: Sun, 18 Aug 2024 16:10:18 +0200 Subject: [PATCH] feat: LP V2 message forwarding (#1971) * refactor: LpMessage * feat: extend LpMessage with forwarding * wip: forwarding pallet * chore: apply domainaddress refactor * refactor: combine traits, cleanup, add docs * chore: compile runtimes * tests: add UTs * fix: remove RouterProvider from Forwarder * feat: move serialization to runtime type * chore: apply suggestions from @cdamian's review * fix: release build * refactor: unite Receiver|SenderMessage types in mock * refactor: remove trait namespaces in forwarder test * fix: re-add RouterProvider to forwarder pallet * fix: use source domain * fix: use source domain fix: clippy * feat: instantiate mock_router * refactor: add MessageDeserializer to Gateway config * Revert "refactor: add MessageDeserializer to Gateway config" This reverts commit 128c35842646924a2489abbdd5e66a9dfecbfaf1. * refactor: remove receive_message gateway extrinsic * refactor: remove artifacts * taplo: fmt * fix: ensure multi-router-setup validity This reverts commit cd72182e2e3303f364ae6a673c5d232189cafda0. --- Cargo.lock | 22 + Cargo.toml | 2 + libs/mocks/src/router_message.rs | 33 +- libs/traits/src/liquidity_pools.rs | 33 +- libs/types/src/domain_address.rs | 7 + pallets/axelar-router/src/lib.rs | 17 +- pallets/axelar-router/src/mock.rs | 1 + pallets/liquidity-pools-forwarder/Cargo.toml | 65 +++ pallets/liquidity-pools-forwarder/src/lib.rs | 266 +++++++++++ pallets/liquidity-pools-forwarder/src/mock.rs | 136 ++++++ .../liquidity-pools-forwarder/src/tests.rs | 419 ++++++++++++++++++ pallets/liquidity-pools-gateway/src/lib.rs | 41 +- .../src/message_processing.rs | 2 +- pallets/liquidity-pools-gateway/src/mock.rs | 19 +- pallets/liquidity-pools-gateway/src/tests.rs | 250 +++++------ pallets/liquidity-pools/src/message.rs | 100 ++++- runtime/altair/Cargo.toml | 4 + runtime/altair/src/lib.rs | 19 +- runtime/centrifuge/Cargo.toml | 4 + runtime/centrifuge/src/lib.rs | 19 +- runtime/common/src/evm/mod.rs | 15 +- runtime/common/src/routing.rs | 43 +- runtime/development/Cargo.toml | 4 + runtime/development/src/lib.rs | 19 +- .../integration-tests/src/cases/routers.rs | 2 +- 25 files changed, 1315 insertions(+), 227 deletions(-) create mode 100644 pallets/liquidity-pools-forwarder/Cargo.toml create mode 100644 pallets/liquidity-pools-forwarder/src/lib.rs create mode 100644 pallets/liquidity-pools-forwarder/src/mock.rs create mode 100644 pallets/liquidity-pools-forwarder/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 8f8b2d2768..3fef637a1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -180,6 +180,7 @@ dependencies = [ "pallet-investments", "pallet-keystore", "pallet-liquidity-pools", + "pallet-liquidity-pools-forwarder", "pallet-liquidity-pools-gateway", "pallet-liquidity-pools-gateway-queue", "pallet-liquidity-rewards", @@ -1502,6 +1503,7 @@ dependencies = [ "pallet-investments", "pallet-keystore", "pallet-liquidity-pools", + "pallet-liquidity-pools-forwarder", "pallet-liquidity-pools-gateway", "pallet-liquidity-pools-gateway-queue", "pallet-liquidity-rewards", @@ -3115,6 +3117,7 @@ dependencies = [ "pallet-investments", "pallet-keystore", "pallet-liquidity-pools", + "pallet-liquidity-pools-forwarder", "pallet-liquidity-pools-gateway", "pallet-liquidity-pools-gateway-queue", "pallet-liquidity-rewards", @@ -8229,6 +8232,25 @@ dependencies = [ "staging-xcm", ] +[[package]] +name = "pallet-liquidity-pools-forwarder" +version = "0.0.1" +dependencies = [ + "cfg-mocks", + "cfg-primitives", + "cfg-traits", + "cfg-types", + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=release-polkadot-v1.7.2)", +] + [[package]] name = "pallet-liquidity-pools-gateway" version = "0.0.1" diff --git a/Cargo.toml b/Cargo.toml index 9e6599e871..7dfca2b3d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ members = [ "pallets/liquidity-pools", "pallets/liquidity-pools-gateway", "pallets/liquidity-pools-gateway/queue", + "pallets/liquidity-pools-forwarder", "pallets/liquidity-rewards", "pallets/loans", "pallets/oracle-feed", @@ -236,6 +237,7 @@ pallet-keystore = { path = "pallets/keystore", default-features = false } pallet-liquidity-pools = { path = "pallets/liquidity-pools", default-features = false } pallet-liquidity-pools-gateway = { path = "pallets/liquidity-pools-gateway", default-features = false } pallet-liquidity-pools-gateway-queue = { path = "pallets/liquidity-pools-gateway/queue", default-features = false } +pallet-liquidity-pools-forwarder = { path = "pallets/liquidity-pools-forwarder", default-features = false } pallet-liquidity-rewards = { path = "pallets/liquidity-rewards", default-features = false } pallet-loans = { path = "pallets/loans", default-features = false } pallet-oracle-feed = { path = "pallets/oracle-feed", default-features = false } diff --git a/libs/mocks/src/router_message.rs b/libs/mocks/src/router_message.rs index 58e7166d38..075b881d3c 100644 --- a/libs/mocks/src/router_message.rs +++ b/libs/mocks/src/router_message.rs @@ -2,49 +2,52 @@ pub mod pallet { use cfg_traits::liquidity_pools::{MessageReceiver, MessageSender}; use frame_support::pallet_prelude::*; - use mock_builder::{execute_call, register_call, CallHandler}; + use mock_builder::{execute_call_instance, register_call_instance, CallHandler}; #[pallet::config] - pub trait Config: frame_system::Config { + pub trait Config: frame_system::Config { type Middleware; type Origin; + type Message; } #[pallet::pallet] - pub struct Pallet(_); + pub struct Pallet(_); #[pallet::storage] - type CallIds = StorageMap<_, _, String, mock_builder::CallId>; + type CallIds, I: 'static = ()> = StorageMap<_, _, String, mock_builder::CallId>; - impl Pallet { + impl, I: 'static> Pallet { pub fn mock_receive( - f: impl Fn(T::Middleware, T::Origin, Vec) -> DispatchResult + 'static, + f: impl Fn(T::Middleware, T::Origin, T::Message) -> DispatchResult + 'static, ) { - register_call!(move |(a, b, c)| f(a, b, c)); + register_call_instance!(move |(a, b, c)| f(a, b, c)); } pub fn mock_send( - f: impl Fn(T::Middleware, T::Origin, Vec) -> DispatchResult + 'static, + f: impl Fn(T::Middleware, T::Origin, T::Message) -> DispatchResult + 'static, ) -> CallHandler { - register_call!(move |(a, b, c)| f(a, b, c)) + register_call_instance!(move |(a, b, c)| f(a, b, c)) } } - impl MessageReceiver for Pallet { + impl, I: 'static> MessageReceiver for Pallet { + type Message = T::Message; type Middleware = T::Middleware; type Origin = T::Origin; - fn receive(a: Self::Middleware, b: Self::Origin, c: Vec) -> DispatchResult { - execute_call!((a, b, c)) + fn receive(a: Self::Middleware, b: Self::Origin, c: Self::Message) -> DispatchResult { + execute_call_instance!((a, b, c)) } } - impl MessageSender for Pallet { + impl, I: 'static> MessageSender for Pallet { + type Message = T::Message; type Middleware = T::Middleware; type Origin = T::Origin; - fn send(a: Self::Middleware, b: Self::Origin, c: Vec) -> DispatchResult { - execute_call!((a, b, c)) + fn send(a: Self::Middleware, b: Self::Origin, c: Self::Message) -> DispatchResult { + execute_call_instance!((a, b, c)) } } } diff --git a/libs/traits/src/liquidity_pools.rs b/libs/traits/src/liquidity_pools.rs index 5b274599b3..7c5dddd69d 100644 --- a/libs/traits/src/liquidity_pools.rs +++ b/libs/traits/src/liquidity_pools.rs @@ -12,7 +12,7 @@ // GNU General Public License for more details. use frame_support::{dispatch::DispatchResult, weights::Weight}; -use sp_runtime::DispatchError; +use sp_runtime::{app_crypto::sp_core::H160, DispatchError}; use sp_std::vec::Vec; /// Type that represents the hash of an LP message. @@ -20,7 +20,9 @@ pub type MessageHash = [u8; 32]; /// An encoding & decoding trait for the purpose of meeting the /// LiquidityPools General Message Passing Format -pub trait LPMessage: Sized { +pub trait LpMessage: Sized { + type Domain; + fn serialize(&self) -> Vec; fn deserialize(input: &[u8]) -> Result; @@ -55,6 +57,19 @@ pub trait LPMessage: Sized { /// Hash - hash of the message that should be disputed. /// Router - the address of the recovery router. fn dispute_recovery_message(hash: [u8; 32], router: [u8; 32]) -> Self; + + /// Checks whether a message is a forwarded one. + fn is_forwarded(&self) -> bool; + + /// Unwraps a forwarded message. + fn unwrap_forwarded(self) -> Option<(Self::Domain, H160, Self)>; + + /// Attempts to wrap into a forwarded message. + fn try_wrap_forward( + domain: Self::Domain, + forwarding_contract: H160, + message: Self, + ) -> Result; } pub trait RouterProvider: Sized { @@ -73,9 +88,15 @@ pub trait MessageSender { /// The originator of the message to be sent type Origin; + /// The type of the message + type Message; + /// Sends a message for origin to destination - fn send(middleware: Self::Middleware, origin: Self::Origin, message: Vec) - -> DispatchResult; + fn send( + middleware: Self::Middleware, + origin: Self::Origin, + message: Self::Message, + ) -> DispatchResult; } /// The behavior of an entity that can receive messages @@ -86,11 +107,13 @@ pub trait MessageReceiver { /// The originator of the received message type Origin; + type Message; + /// Sends a message for origin to destination fn receive( middleware: Self::Middleware, origin: Self::Origin, - message: Vec, + message: Self::Message, ) -> DispatchResult; } diff --git a/libs/types/src/domain_address.rs b/libs/types/src/domain_address.rs index 8f400f57b8..04f48b708c 100644 --- a/libs/types/src/domain_address.rs +++ b/libs/types/src/domain_address.rs @@ -64,6 +64,13 @@ impl Domain { pub fn into_account(&self) -> AccountId { self.into_account_truncating() } + + pub fn get_evm_chain_id(&self) -> Option { + match self { + Domain::Centrifuge => None, + Domain::Evm(id) => Some(*id), + } + } } #[derive(Encode, Decode, Clone, Eq, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] diff --git a/pallets/axelar-router/src/lib.rs b/pallets/axelar-router/src/lib.rs index 6d68016b01..23b1bed796 100644 --- a/pallets/axelar-router/src/lib.rs +++ b/pallets/axelar-router/src/lib.rs @@ -131,13 +131,17 @@ pub mod pallet { /// messages from type AdminOrigin: EnsureOrigin; - /// The target of the messages comming from other chains - type Receiver: MessageReceiver; + /// The target of the messages coming from other chains + type Receiver: MessageReceiver< + Middleware = Self::Middleware, + Origin = DomainAddress, + Message = Vec, + >; /// Middleware used by the gateway type Middleware: From; - /// The target of the messages comming from this chain + /// The target of the messages coming from this chain type Transactor: EthereumTransactor; /// Checker to ensure an evm account code is registered @@ -319,10 +323,15 @@ pub mod pallet { } impl MessageSender for Pallet { + type Message = Vec; type Middleware = AxelarId; type Origin = DomainAddress; - fn send(axelar_id: AxelarId, origin: Self::Origin, message: Vec) -> DispatchResult { + fn send( + axelar_id: AxelarId, + origin: Self::Origin, + message: Self::Message, + ) -> DispatchResult { let chain_name = ChainNameById::::get(axelar_id) .ok_or(Error::::RouterConfigurationNotFound)?; let config = Configuration::::get(&chain_name) diff --git a/pallets/axelar-router/src/mock.rs b/pallets/axelar-router/src/mock.rs index 958889f340..2244f00d10 100644 --- a/pallets/axelar-router/src/mock.rs +++ b/pallets/axelar-router/src/mock.rs @@ -33,6 +33,7 @@ impl frame_system::Config for Runtime { } impl cfg_mocks::router_message::pallet::Config for Runtime { + type Message = Vec; type Middleware = Middleware; type Origin = DomainAddress; } diff --git a/pallets/liquidity-pools-forwarder/Cargo.toml b/pallets/liquidity-pools-forwarder/Cargo.toml new file mode 100644 index 0000000000..3e63e2f4b6 --- /dev/null +++ b/pallets/liquidity-pools-forwarder/Cargo.toml @@ -0,0 +1,65 @@ +[package] +name = "pallet-liquidity-pools-forwarder" +description = "Centrifuge Liquidity Pools Relayer Pallet" +version = "0.0.1" +authors.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +documentation.workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +frame-support = { workspace = true } +frame-system = { workspace = true } +parity-scale-codec = { workspace = true } +scale-info = { workspace = true } +sp-core = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } + +# Benchmarking +frame-benchmarking = { workspace = true, optional = true } + +# Our custom pallets +cfg-traits = { workspace = true } +cfg-types = { workspace = true } + +[dev-dependencies] +cfg-mocks = { workspace = true, default-features = true } +cfg-primitives = { workspace = true } +sp-io = { workspace = true, default-features = true } + +[features] +default = ["std"] +std = [ + "parity-scale-codec/std", + "cfg-types/std", + "frame-support/std", + "frame-system/std", + "frame-benchmarking/std", + "sp-std/std", + "sp-core/std", + "sp-runtime/std", + "scale-info/std", +] +try-runtime = [ + "cfg-traits/try-runtime", + "cfg-types/try-runtime", + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", + "cfg-mocks/try-runtime", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "cfg-traits/runtime-benchmarks", + "cfg-types/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "cfg-mocks/runtime-benchmarks", +] diff --git a/pallets/liquidity-pools-forwarder/src/lib.rs b/pallets/liquidity-pools-forwarder/src/lib.rs new file mode 100644 index 0000000000..05e4800ee8 --- /dev/null +++ b/pallets/liquidity-pools-forwarder/src/lib.rs @@ -0,0 +1,266 @@ +// Copyright 2024 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. +// +//! # Liquidity Pools Forwarder Pallet. +//! +//! The Forwarder pallet acts as middleware for incoming and outgoing Liquidity +//! Pools messages by wrapping them, if they are forwarded ones. +//! +//! For incoming messages, it extracts the payload from forwarded messages. +//! +//! For outgoing messages, it wraps the payload based on the configured router +//! info. +//! +//! Assumptions: +//! * The EVM side ensures that incoming forwarded messages are valid. +//! * Nesting forwarded messages is not allowed, e.g. messages from A are +//! forwarded exactly via one intermediary domain B to recipient C + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +use core::fmt::Debug; + +use cfg_traits::liquidity_pools::{LpMessage as LpMessageT, MessageReceiver, MessageSender}; +use cfg_types::domain_address::{Domain, DomainAddress}; +use frame_support::{dispatch::DispatchResult, pallet_prelude::*}; +use frame_system::pallet_prelude::OriginFor; +pub use pallet::*; +use parity_scale_codec::FullCodec; +use sp_core::H160; +use sp_std::convert::TryInto; + +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct ForwardInfo { + /// Refers to the chain from which the message originates. + /// + /// Example: Assume a three-hop A -> B -> C, then this refers to the domain + /// of A. + pub(crate) source_domain: Domain, + /// Refers to contract on forwarding chain. + /// + /// Example: Assume A -> B -> C, then this refers to the forwarding + /// contract address on B. + pub(crate) contract: H160, +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// Required origin for configuring domain forwarding. + type AdminOrigin: EnsureOrigin; + + /// The event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The Liquidity Pools message type. + type Message: LpMessageT + + Clone + + Debug + + PartialEq + + Eq + + MaxEncodedLen + + TypeInfo + + FullCodec; + + /// The entity of the messages coming from this chain. + type MessageSender: MessageSender< + Middleware = Self::RouterId, + Origin = DomainAddress, + Message = Self::Message, + >; + + /// The entity which acts on unwrapped messages. + type MessageReceiver: MessageReceiver< + Middleware = Self::RouterId, + Origin = DomainAddress, + Message = Self::Message, + >; + + /// An identification of a router. + type RouterId: Parameter + MaxEncodedLen; + } + + #[pallet::event] + #[pallet::generate_deposit(pub (super) fn deposit_event)] + pub enum Event { + /// Forwarding info was set + ForwarderSet { + router_id: T::RouterId, + source_domain: Domain, + forwarding_contract: H160, + }, + /// Forwarding info was removed + ForwarderRemoved { + router_id: T::RouterId, + source_domain: Domain, + forwarding_contract: H160, + }, + } + + /// Maps a router id to its forwarding info. + /// + /// Can only be mutated via admin origin. + #[pallet::storage] + pub type RouterForwarding = + StorageMap<_, Blake2_128Concat, T::RouterId, ForwardInfo, OptionQuery>; + + #[pallet::error] + pub enum Error { + /// The router id does not have any forwarder info stored + ForwardInfoNotFound, + /// Failed to unwrap a message which should be a forwarded one + UnwrappingFailed, + /// Received a forwarded message from source domain `A` which contradics + /// the corresponding stored forwarding info that expects source domain + /// `B` + /// + /// NOTE: Should never occur because we can assume EVM ensures message + /// validity + SourceDomainMismatch, + } + + #[pallet::call] + impl Pallet { + /// Set forwarding info for the given router id. + /// + /// Origin: Admin. + /// + /// NOTE: Simple weight due to origin requirement. + #[pallet::weight(T::DbWeight::get().writes(1))] + #[pallet::call_index(0)] + pub fn set_forwarder( + origin: OriginFor, + router_id: T::RouterId, + source_domain: Domain, + forwarding_contract: H160, + ) -> DispatchResult { + T::AdminOrigin::ensure_origin(origin)?; + + RouterForwarding::::insert( + &router_id, + ForwardInfo { + source_domain, + contract: forwarding_contract, + }, + ); + + Self::deposit_event(Event::::ForwarderSet { + router_id, + source_domain, + forwarding_contract, + }); + + Ok(()) + } + + /// Remove the forwarding info for the given router id. + /// + /// Origin: Admin. + /// + /// NOTE: Simple weight due to origin requirement. + #[pallet::weight(T::DbWeight::get().writes(1))] + #[pallet::call_index(1)] + pub fn remove_forwarder(origin: OriginFor, router_id: T::RouterId) -> DispatchResult { + T::AdminOrigin::ensure_origin(origin)?; + + RouterForwarding::::take(&router_id) + .map(|info| { + Self::deposit_event(Event::::ForwarderRemoved { + router_id, + source_domain: info.source_domain, + forwarding_contract: info.contract, + }); + }) + .ok_or(Error::::ForwardInfoNotFound.into()) + } + } + + impl MessageSender for Pallet { + type Message = T::Message; + type Middleware = T::RouterId; + type Origin = DomainAddress; + + fn send( + router_id: T::RouterId, + origin: DomainAddress, + message: T::Message, + ) -> DispatchResult { + let msg = RouterForwarding::::get(&router_id) + .map(|info| { + T::Message::try_wrap_forward(info.source_domain, info.contract, message.clone()) + }) + .unwrap_or_else(|| { + ensure!(!message.is_forwarded(), Error::::ForwardInfoNotFound); + Ok(message) + })?; + + T::MessageSender::send(router_id, origin, msg) + } + } + + impl MessageReceiver for Pallet { + type Message = T::Message; + type Middleware = T::RouterId; + type Origin = DomainAddress; + + fn receive( + router_id: T::RouterId, + forwarding_domain_address: DomainAddress, + message: T::Message, + ) -> DispatchResult { + // Message can be unwrapped iff it was forwarded + // + // NOTE: Contract address irrelevant here because it is only necessary for + // outbound forwarded messages + let (lp_message, domain_address) = match ( + RouterForwarding::::get(&router_id), + message.clone().unwrap_forwarded(), + ) { + (Some(info), Some((source_domain, _contract, lp_message))) => { + ensure!( + info.source_domain == source_domain, + Error::::SourceDomainMismatch + ); + + let domain_address = DomainAddress::Evm( + info.source_domain + .get_evm_chain_id() + .expect("Domain not Centrifuge; qed"), + info.contract, + ); + Ok((lp_message, domain_address)) + } + (Some(_), None) => Err(Error::::UnwrappingFailed), + (None, None) => Ok((message, forwarding_domain_address)), + (None, Some((_, _, _))) => Err(Error::::ForwardInfoNotFound), + } + .map_err(|e: Error| e)?; + + T::MessageReceiver::receive(router_id, domain_address, lp_message) + } + } +} diff --git a/pallets/liquidity-pools-forwarder/src/mock.rs b/pallets/liquidity-pools-forwarder/src/mock.rs new file mode 100644 index 0000000000..24cc25f73d --- /dev/null +++ b/pallets/liquidity-pools-forwarder/src/mock.rs @@ -0,0 +1,136 @@ +use cfg_traits::liquidity_pools::{LpMessage, MessageHash}; +use cfg_types::domain_address::{Domain, DomainAddress}; +use frame_support::{ + derive_impl, + dispatch::DispatchResult, + pallet_prelude::{Decode, Encode, MaxEncodedLen, TypeInfo}, + weights::constants::RocksDbWeight, +}; +use frame_system::EnsureRoot; +use sp_core::{crypto::AccountId32, H160}; +use sp_runtime::{traits::IdentityLookup, DispatchError}; + +use crate::pallet as pallet_liquidity_pools_forwarder; + +pub type RouterId = u32; + +const SOURCE_CHAIN_ID: u64 = 1; +const FORWARDER_CHAIN_ID: u64 = 42; +pub const SOURCE_DOMAIN: Domain = Domain::Evm(SOURCE_CHAIN_ID); +pub const FORWARDER_DOMAIN: Domain = Domain::Evm(FORWARDER_CHAIN_ID); +const FORWARDER_ADAPTER_ADDRESS: H160 = H160::repeat_byte(1); +pub const FORWARDER_DOMAIN_ADDRESS: DomainAddress = + DomainAddress::Evm(FORWARDER_CHAIN_ID, FORWARDER_ADAPTER_ADDRESS); +pub const SOURCE_DOMAIN_ADDRESS: DomainAddress = + DomainAddress::Evm(SOURCE_CHAIN_ID, FORWARD_CONTRACT); +pub const FORWARD_CONTRACT: H160 = H160::repeat_byte(2); +pub const ROUTER_ID: RouterId = 1u32; +const FORWARD_SERIALIZED_MESSAGE_BYTES: [u8; 1] = [0x42]; +const NON_FORWARD_SERIALIZED_MESSAGE_BYTES: [u8; 1] = [0x43]; +pub const ERROR_NESTING: DispatchError = DispatchError::Other("Nesting forward msg not allowed"); + +#[derive(Eq, PartialEq, Debug, Clone, Encode, Decode, TypeInfo, MaxEncodedLen, Hash)] +pub enum Message { + NonForward, + Forward, +} + +impl LpMessage for Message { + type Domain = Domain; + + fn serialize(&self) -> Vec { + match self { + Message::NonForward => NON_FORWARD_SERIALIZED_MESSAGE_BYTES.to_vec(), + Message::Forward => FORWARD_SERIALIZED_MESSAGE_BYTES.to_vec(), + } + } + + fn deserialize(input: &[u8]) -> Result { + match input { + x if x == &NON_FORWARD_SERIALIZED_MESSAGE_BYTES[..] => Ok(Self::NonForward), + x if x == &FORWARD_SERIALIZED_MESSAGE_BYTES[..] => Ok(Self::Forward), + _ => unimplemented!(), + } + } + + fn pack_with(&mut self, _: Self) -> DispatchResult { + unimplemented!("out of scope") + } + + fn submessages(&self) -> Vec { + unimplemented!("out of scope") + } + + fn empty() -> Self { + unimplemented!("out of scope") + } + + fn to_proof_message(&self) -> Self { + unimplemented!("out of scope") + } + + fn is_proof_message(&self) -> bool { + unimplemented!("out of scope") + } + + fn get_message_hash(&self) -> MessageHash { + unimplemented!("out of scope") + } + + fn initiate_recovery_message(_: [u8; 32], _: [u8; 32]) -> Self { + unimplemented!("out of scope") + } + + fn dispute_recovery_message(_: [u8; 32], _: [u8; 32]) -> Self { + unimplemented!("out of scope") + } + + fn is_forwarded(&self) -> bool { + matches!(self, Self::Forward) + } + + fn unwrap_forwarded(self) -> Option<(Self::Domain, H160, Self)> { + match self { + Self::NonForward => None, + Self::Forward => Some((SOURCE_DOMAIN, FORWARD_CONTRACT, Self::NonForward)), + } + } + + fn try_wrap_forward(_: Self::Domain, _: H160, message: Self) -> Result { + match message { + Self::Forward => Err(ERROR_NESTING), + Self::NonForward => Ok(Self::Forward), + } + } +} + +frame_support::construct_runtime!( + pub enum Runtime { + System: frame_system, + MockSenderReceiver: cfg_mocks::router_message::pallet, + LiquidityPoolsForwarder: pallet_liquidity_pools_forwarder, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type AccountId = AccountId32; + type Block = frame_system::mocking::MockBlock; + type DbWeight = RocksDbWeight; + type Lookup = IdentityLookup; +} + +impl cfg_mocks::router_message::pallet::Config for Runtime { + type Message = Message; + type Middleware = RouterId; + type Origin = DomainAddress; +} + +impl pallet_liquidity_pools_forwarder::Config for Runtime { + type AdminOrigin = EnsureRoot; + type Message = Message; + type MessageReceiver = MockSenderReceiver; + type MessageSender = MockSenderReceiver; + type RouterId = RouterId; + type RuntimeEvent = RuntimeEvent; +} diff --git a/pallets/liquidity-pools-forwarder/src/tests.rs b/pallets/liquidity-pools-forwarder/src/tests.rs new file mode 100644 index 0000000000..9c5ba4a686 --- /dev/null +++ b/pallets/liquidity-pools-forwarder/src/tests.rs @@ -0,0 +1,419 @@ +use frame_support::{assert_noop, assert_ok}; +use sp_core::crypto::AccountId32; + +use crate::{mock::*, pallet::RouterForwarding, ForwardInfo}; + +mod set_forwarder { + use sp_runtime::DispatchError; + + use super::*; + + #[test] + fn success() { + System::externalities().execute_with(|| { + assert_ok!(LiquidityPoolsForwarder::set_forwarder( + RuntimeOrigin::root(), + ROUTER_ID, + SOURCE_DOMAIN, + FORWARD_CONTRACT + )); + + assert_eq!( + RouterForwarding::::get(ROUTER_ID), + Some(ForwardInfo { + contract: FORWARD_CONTRACT, + source_domain: SOURCE_DOMAIN + }) + ); + + System::assert_last_event(RuntimeEvent::LiquidityPoolsForwarder( + crate::Event::ForwarderSet { + router_id: ROUTER_ID, + source_domain: SOURCE_DOMAIN, + forwarding_contract: FORWARD_CONTRACT, + }, + )); + }) + } + + #[test] + fn erroring_out_with_bad_origin() { + System::externalities().execute_with(|| { + assert_noop!( + LiquidityPoolsForwarder::set_forwarder( + RuntimeOrigin::signed(AccountId32::new([1u8; 32])), + ROUTER_ID, + SOURCE_DOMAIN, + FORWARD_CONTRACT + ), + DispatchError::BadOrigin + ); + + assert!(RouterForwarding::::get(ROUTER_ID).is_none()); + }) + } +} + +mod remove_forwarder { + use frame_support::assert_noop; + use sp_runtime::DispatchError; + + use super::*; + use crate::Error; + + #[test] + fn success() { + System::externalities().execute_with(|| { + assert_ok!(LiquidityPoolsForwarder::set_forwarder( + RuntimeOrigin::root(), + ROUTER_ID, + SOURCE_DOMAIN, + FORWARD_CONTRACT + )); + + assert_ok!(LiquidityPoolsForwarder::remove_forwarder( + RuntimeOrigin::root(), + ROUTER_ID, + )); + + assert!(RouterForwarding::::get(ROUTER_ID).is_none()); + + System::assert_last_event(RuntimeEvent::LiquidityPoolsForwarder( + crate::Event::ForwarderRemoved { + router_id: ROUTER_ID, + source_domain: SOURCE_DOMAIN, + forwarding_contract: FORWARD_CONTRACT, + }, + )); + }) + } + + #[test] + fn erroring_out_with_not_found() { + System::externalities().execute_with(|| { + assert!(RouterForwarding::::get(ROUTER_ID).is_none()); + assert_noop!( + LiquidityPoolsForwarder::remove_forwarder(RuntimeOrigin::root(), ROUTER_ID,), + Error::::ForwardInfoNotFound + ); + }) + } + + #[test] + fn erroring_out_with_bad_origin() { + System::externalities().execute_with(|| { + assert_noop!( + LiquidityPoolsForwarder::set_forwarder( + RuntimeOrigin::signed(AccountId32::new([1u8; 32])), + ROUTER_ID, + SOURCE_DOMAIN, + FORWARD_CONTRACT + ), + DispatchError::BadOrigin + ); + }) + } +} + +mod send_message { + use cfg_traits::liquidity_pools::MessageSender; + + use super::*; + + fn config_mocks(msg: Message, set_forwarding_info: bool) { + MockSenderReceiver::mock_send(move |router_id, sender, message| { + assert_eq!(router_id, ROUTER_ID); + assert_eq!(sender, FORWARDER_DOMAIN_ADDRESS); + assert_eq!(message, msg); + + Ok(()) + }); + + if set_forwarding_info { + assert_ok!(LiquidityPoolsForwarder::set_forwarder( + RuntimeOrigin::root(), + ROUTER_ID, + SOURCE_DOMAIN, + FORWARD_CONTRACT + )); + } + } + + mod success { + use super::*; + + #[test] + fn with_forwarding() { + System::externalities().execute_with(|| { + config_mocks(Message::Forward, true); + + assert_ok!(LiquidityPoolsForwarder::send( + ROUTER_ID, + FORWARDER_DOMAIN_ADDRESS, + Message::NonForward + )); + }); + } + + #[test] + fn without_forwarding() { + System::externalities().execute_with(|| { + config_mocks(Message::NonForward, false); + + assert_ok!(LiquidityPoolsForwarder::send( + ROUTER_ID, + FORWARDER_DOMAIN_ADDRESS, + Message::NonForward + )); + }); + } + } + + mod erroring_out { + use sp_runtime::DispatchError; + + use super::*; + use crate::Error; + + const ERROR: DispatchError = DispatchError::Other("Send failed on purpose"); + + #[test] + /// Attempting to send forwarded message with missing forward info + /// panics in mock because `Message::NonForward` serialization is + /// expected + fn with_missing_forward_info() { + System::externalities().execute_with(|| { + config_mocks(Message::Forward, false); + + assert_noop!( + LiquidityPoolsForwarder::send( + ROUTER_ID, + FORWARDER_DOMAIN_ADDRESS, + Message::Forward + ), + Error::::ForwardInfoNotFound + ); + }); + } + + #[test] + #[should_panic] + /// Attempting to send forwarded message panics here in mock + /// because `Message::NonForward` serialization is expected + fn with_expected_non_forward_serialization() { + System::externalities().execute_with(|| { + config_mocks(Message::NonForward, true); + + assert_ok!(LiquidityPoolsForwarder::send( + ROUTER_ID, + FORWARDER_DOMAIN_ADDRESS, + Message::NonForward + )); + }); + } + + #[test] + #[should_panic] + /// Attempting to send non-forwarded message panics here in mock + /// because `Message::Forward` serialization is expected + fn with_expected_forward_serialization() { + System::externalities().execute_with(|| { + config_mocks(Message::Forward, false); + + assert_ok!(LiquidityPoolsForwarder::send( + ROUTER_ID, + FORWARDER_DOMAIN_ADDRESS, + Message::NonForward + )); + }); + } + + #[test] + fn with_nesting() { + System::externalities().execute_with(|| { + config_mocks(Message::Forward, true); + + assert_noop!( + LiquidityPoolsForwarder::send( + ROUTER_ID, + FORWARDER_DOMAIN_ADDRESS, + Message::Forward + ), + ERROR_NESTING + ); + }); + } + + #[test] + fn non_forward_with_message_receiver_err() { + System::externalities().execute_with(|| { + config_mocks(Message::Forward, true); + MockSenderReceiver::mock_send(|_, _, _| Err(ERROR)); + + assert_noop!( + LiquidityPoolsForwarder::send( + ROUTER_ID, + FORWARDER_DOMAIN_ADDRESS, + Message::NonForward + ), + ERROR + ); + }); + } + } +} + +mod receive_message { + use cfg_traits::liquidity_pools::MessageReceiver; + + use super::*; + + fn config_mocks(was_forwarded: bool, set_forwarding_info: bool) { + MockSenderReceiver::mock_receive(move |middleware, origin, message| { + assert_eq!(middleware, ROUTER_ID); + if was_forwarded { + assert_eq!(origin, SOURCE_DOMAIN_ADDRESS); + } else { + assert_eq!(origin, FORWARDER_DOMAIN_ADDRESS); + } + assert_eq!(message, Message::NonForward); + Ok(()) + }); + + if set_forwarding_info { + assert_ok!(LiquidityPoolsForwarder::set_forwarder( + RuntimeOrigin::root(), + ROUTER_ID, + SOURCE_DOMAIN, + FORWARD_CONTRACT + )); + } + } + + mod success { + use cfg_traits::liquidity_pools::MessageReceiver; + + use super::*; + + #[test] + fn with_forwarding() { + System::externalities().execute_with(|| { + config_mocks(true, true); + + assert_ok!(LiquidityPoolsForwarder::receive( + ROUTER_ID, + FORWARDER_DOMAIN_ADDRESS, + Message::Forward + )); + }); + } + + #[test] + fn without_forwarding() { + System::externalities().execute_with(|| { + config_mocks(false, false); + + assert_ok!(LiquidityPoolsForwarder::receive( + ROUTER_ID, + FORWARDER_DOMAIN_ADDRESS, + Message::NonForward + )); + }); + } + } + + mod erroring_out { + use sp_runtime::DispatchError; + + use super::*; + use crate::Error; + + const ERROR: DispatchError = DispatchError::Other("Receive failed on purpose"); + + #[test] + fn with_missing_forward_info() { + System::externalities().execute_with(|| { + config_mocks(true, false); + + assert_noop!( + LiquidityPoolsForwarder::receive( + ROUTER_ID, + FORWARDER_DOMAIN_ADDRESS, + Message::Forward + ), + Error::::ForwardInfoNotFound + ); + }); + } + + #[test] + fn with_source_domain_mismatch() { + System::externalities().execute_with(|| { + config_mocks(true, false); + assert_ok!(LiquidityPoolsForwarder::set_forwarder( + RuntimeOrigin::root(), + ROUTER_ID, + FORWARDER_DOMAIN, + FORWARD_CONTRACT + )); + + assert_noop!( + LiquidityPoolsForwarder::receive( + ROUTER_ID, + FORWARDER_DOMAIN_ADDRESS, + Message::Forward + ), + Error::::SourceDomainMismatch + ); + }); + } + + #[test] + fn with_failed_unwrapping() { + System::externalities().execute_with(|| { + config_mocks(true, true); + + assert_noop!( + LiquidityPoolsForwarder::receive( + ROUTER_ID, + FORWARDER_DOMAIN_ADDRESS, + Message::NonForward + ), + Error::::UnwrappingFailed + ); + }); + } + + #[test] + fn forward_with_message_receiver_err() { + System::externalities().execute_with(|| { + config_mocks(true, true); + MockSenderReceiver::mock_receive(|_, _, _| Err(ERROR)); + + assert_noop!( + LiquidityPoolsForwarder::receive( + ROUTER_ID, + FORWARDER_DOMAIN_ADDRESS, + Message::Forward + ), + ERROR + ); + }); + } + #[test] + fn non_forward_with_message_receiver_err() { + System::externalities().execute_with(|| { + MockSenderReceiver::mock_receive(|_, _, _| Err(ERROR)); + + assert_noop!( + LiquidityPoolsForwarder::receive( + ROUTER_ID, + FORWARDER_DOMAIN_ADDRESS, + Message::NonForward + ), + ERROR + ); + }); + } + } +} diff --git a/pallets/liquidity-pools-gateway/src/lib.rs b/pallets/liquidity-pools-gateway/src/lib.rs index 2fd6e75c2f..8e22be8714 100644 --- a/pallets/liquidity-pools-gateway/src/lib.rs +++ b/pallets/liquidity-pools-gateway/src/lib.rs @@ -30,7 +30,7 @@ use core::fmt::Debug; use cfg_primitives::LP_DEFENSIVE_WEIGHT; use cfg_traits::liquidity_pools::{ - InboundMessageHandler, LPMessage, MessageHash, MessageProcessor, MessageQueue, MessageReceiver, + InboundMessageHandler, LpMessage, MessageHash, MessageProcessor, MessageQueue, MessageReceiver, MessageSender, OutboundMessageHandler, RouterProvider, }; use cfg_types::domain_address::{Domain, DomainAddress}; @@ -42,7 +42,7 @@ pub use pallet::*; use parity_scale_codec::FullCodec; use sp_arithmetic::traits::{BaseArithmetic, EnsureAddAssign, One}; use sp_runtime::SaturatedConversion; -use sp_std::{convert::TryInto, vec::Vec}; +use sp_std::convert::TryInto; use crate::{ message_processing::{InboundEntry, ProofEntry}, @@ -97,7 +97,7 @@ pub mod pallet { type AdminOrigin: EnsureOrigin<::RuntimeOrigin>; /// The Liquidity Pools message type. - type Message: LPMessage + type Message: LpMessage + Clone + Debug + PartialEq @@ -107,7 +107,11 @@ pub mod pallet { + FullCodec; /// The target of the messages coming from this chain - type MessageSender: MessageSender; + type MessageSender: MessageSender< + Middleware = Self::RouterId, + Origin = DomainAddress, + Message = Self::Message, + >; /// An identification of a router type RouterId: Parameter + MaxEncodedLen; @@ -398,22 +402,9 @@ pub mod pallet { Ok(()) } - /// Process an inbound message. - #[pallet::weight(T::WeightInfo::receive_message())] - #[pallet::call_index(5)] - pub fn receive_message( - origin: OriginFor, - router_id: T::RouterId, - msg: BoundedVec, - ) -> DispatchResult { - let GatewayOrigin::Domain(origin_address) = T::LocalEVMOrigin::ensure_origin(origin)?; - - if let DomainAddress::Centrifuge(_) = origin_address { - return Err(Error::::InvalidMessageOrigin.into()); - } - - Self::receive(router_id, origin_address, msg.into()) - } + // Deprecated: receive_message with call_index(5) + // + // NOTE: If required, should be exposed by router. /// Set the address of the domain hook /// @@ -604,7 +595,7 @@ pub mod pallet { Error::::MessagingRouterNotFound ); - T::MessageSender::send(messaging_router, T::Sender::get(), message.serialize()) + T::MessageSender::send(messaging_router, T::Sender::get(), message) } } @@ -663,8 +654,7 @@ pub mod pallet { (res, weight) } GatewayMessage::Outbound { message, router_id } => { - let res = - T::MessageSender::send(router_id, T::Sender::get(), message.serialize()); + let res = T::MessageSender::send(router_id, T::Sender::get(), message); (res, LP_DEFENSIVE_WEIGHT) } @@ -684,13 +674,14 @@ pub mod pallet { } impl MessageReceiver for Pallet { + type Message = T::Message; type Middleware = T::RouterId; type Origin = DomainAddress; fn receive( router_id: T::RouterId, origin_address: DomainAddress, - message: Vec, + message: T::Message, ) -> DispatchResult { ensure!( Allowlist::::contains_key(origin_address.domain(), origin_address.clone()), @@ -699,7 +690,7 @@ pub mod pallet { let gateway_message = GatewayMessage::::Inbound { domain_address: origin_address, - message: T::Message::deserialize(&message)?, + message, router_id, }; diff --git a/pallets/liquidity-pools-gateway/src/message_processing.rs b/pallets/liquidity-pools-gateway/src/message_processing.rs index 377b6b7a9c..720cd923e9 100644 --- a/pallets/liquidity-pools-gateway/src/message_processing.rs +++ b/pallets/liquidity-pools-gateway/src/message_processing.rs @@ -1,5 +1,5 @@ use cfg_traits::liquidity_pools::{ - InboundMessageHandler, LPMessage, MessageHash, MessageQueue, RouterProvider, + InboundMessageHandler, LpMessage, MessageHash, MessageQueue, RouterProvider, }; use cfg_types::domain_address::{Domain, DomainAddress}; use frame_support::{ diff --git a/pallets/liquidity-pools-gateway/src/mock.rs b/pallets/liquidity-pools-gateway/src/mock.rs index acd3d052ce..8b86553d31 100644 --- a/pallets/liquidity-pools-gateway/src/mock.rs +++ b/pallets/liquidity-pools-gateway/src/mock.rs @@ -1,7 +1,7 @@ use std::fmt::{Debug, Formatter}; use cfg_mocks::{pallet_mock_liquidity_pools, pallet_mock_liquidity_pools_gateway_queue}; -use cfg_traits::liquidity_pools::{LPMessage, MessageHash, RouterProvider}; +use cfg_traits::liquidity_pools::{LpMessage, MessageHash, RouterProvider}; use cfg_types::{ domain_address::{Domain, DomainAddress}, EVMChainId, @@ -59,7 +59,9 @@ impl MaxEncodedLen for Message { } } -impl LPMessage for Message { +impl LpMessage for Message { + type Domain = Domain; + fn serialize(&self) -> Vec { match self { Self::Pack(list) => list.iter().map(|_| 0x42).collect(), @@ -124,6 +126,18 @@ impl LPMessage for Message { fn dispute_recovery_message(hash: [u8; 32], router: [u8; 32]) -> Self { Self::DisputeMessageRecovery((hash, router)) } + + fn is_forwarded(&self) -> bool { + unimplemented!("out of scope") + } + + fn unwrap_forwarded(self) -> Option<(Self::Domain, H160, Self)> { + unimplemented!("out of scope") + } + + fn try_wrap_forward(_: Self::Domain, _: H160, _: Self) -> Result { + unimplemented!("out of scope") + } } #[derive(Default, Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen, Hash)] @@ -170,6 +184,7 @@ impl pallet_mock_liquidity_pools_gateway_queue::Config for Runtime { } impl cfg_mocks::router_message::pallet::Config for Runtime { + type Message = Message; type Middleware = RouterId; type Origin = DomainAddress; } diff --git a/pallets/liquidity-pools-gateway/src/tests.rs b/pallets/liquidity-pools-gateway/src/tests.rs index b8ca112b5e..805e77c251 100644 --- a/pallets/liquidity-pools-gateway/src/tests.rs +++ b/pallets/liquidity-pools-gateway/src/tests.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use cfg_primitives::LP_DEFENSIVE_WEIGHT; -use cfg_traits::liquidity_pools::{LPMessage, MessageProcessor, OutboundMessageHandler}; +use cfg_traits::liquidity_pools::{LpMessage, MessageProcessor, OutboundMessageHandler}; use cfg_types::domain_address::*; use frame_support::{assert_err, assert_noop, assert_ok}; use itertools::Itertools; @@ -19,7 +19,6 @@ use sp_std::sync::{ use super::{ mock::{RuntimeEvent as MockEvent, *}, - origin::*, pallet::*, }; use crate::{ @@ -280,142 +279,6 @@ mod extrinsics { } } - mod receive_message { - use super::*; - - #[test] - fn success() { - new_test_ext().execute_with(|| { - let address = H160::from_slice(&get_test_account_id().as_slice()[..20]); - let domain_address = DomainAddress::Evm(0, address); - let message = Message::Simple; - - let router_id = ROUTER_ID_1; - - assert_ok!(LiquidityPoolsGateway::add_instance( - RuntimeOrigin::root(), - domain_address.clone(), - )); - - let encoded_msg = message.serialize(); - - let gateway_message = GatewayMessage::Inbound { - domain_address: domain_address.clone(), - message: message.clone(), - router_id: router_id.clone(), - }; - - let handler = MockLiquidityPoolsGatewayQueue::mock_submit(move |mock_message| { - assert_eq!(mock_message, gateway_message); - Ok(()) - }); - - assert_ok!(LiquidityPoolsGateway::receive_message( - GatewayOrigin::Domain(domain_address).into(), - router_id, - BoundedVec::::try_from(encoded_msg).unwrap() - )); - - assert_eq!(handler.times(), 1); - }); - } - - #[test] - fn bad_origin() { - new_test_ext().execute_with(|| { - let encoded_msg = Message::Simple.serialize(); - - let router_id = ROUTER_ID_1; - - assert_noop!( - LiquidityPoolsGateway::receive_message( - RuntimeOrigin::signed(AccountId32::new([0u8; 32])), - router_id, - BoundedVec::::try_from(encoded_msg).unwrap() - ), - BadOrigin, - ); - }); - } - - #[test] - fn invalid_message_origin() { - new_test_ext().execute_with(|| { - let domain_address = DomainAddress::Centrifuge(get_test_account_id().into()); - let encoded_msg = Message::Simple.serialize(); - let router_id = ROUTER_ID_1; - - assert_noop!( - LiquidityPoolsGateway::receive_message( - GatewayOrigin::Domain(domain_address).into(), - router_id, - BoundedVec::::try_from(encoded_msg).unwrap() - ), - Error::::InvalidMessageOrigin, - ); - }); - } - - #[test] - fn unknown_instance() { - new_test_ext().execute_with(|| { - let address = H160::from_slice(&get_test_account_id().as_slice()[..20]); - let domain_address = DomainAddress::Evm(0, address); - let encoded_msg = Message::Simple.serialize(); - let router_id = ROUTER_ID_1; - - assert_noop!( - LiquidityPoolsGateway::receive_message( - GatewayOrigin::Domain(domain_address).into(), - router_id, - BoundedVec::::try_from(encoded_msg).unwrap() - ), - Error::::UnknownInstance, - ); - }); - } - - #[test] - fn message_queue_error() { - new_test_ext().execute_with(|| { - let address = H160::from_slice(&get_test_account_id().as_slice()[..20]); - let domain_address = DomainAddress::Evm(0, address); - let message = Message::Simple; - - let router_id = ROUTER_ID_1; - - assert_ok!(LiquidityPoolsGateway::add_instance( - RuntimeOrigin::root(), - domain_address.clone(), - )); - - let encoded_msg = message.serialize(); - - let err = sp_runtime::DispatchError::from("liquidity_pools error"); - - let gateway_message = GatewayMessage::Inbound { - domain_address: domain_address.clone(), - message: message.clone(), - router_id: router_id.clone(), - }; - - MockLiquidityPoolsGatewayQueue::mock_submit(move |mock_message| { - assert_eq!(mock_message, gateway_message); - Err(err) - }); - - assert_noop!( - LiquidityPoolsGateway::receive_message( - GatewayOrigin::Domain(domain_address).into(), - router_id, - BoundedVec::::try_from(encoded_msg).unwrap() - ), - err, - ); - }); - } - } - mod set_domain_hook { use super::*; @@ -885,7 +748,6 @@ mod extrinsics { assert_eq!( mock_message, Message::InitiateMessageRecovery((MESSAGE_HASH, recovery_router)) - .serialize() ); Ok(()) @@ -957,7 +819,6 @@ mod extrinsics { assert_eq!( mock_message, Message::InitiateMessageRecovery((MESSAGE_HASH, recovery_router)) - .serialize() ); Ok(()) @@ -991,7 +852,6 @@ mod extrinsics { assert_eq!( mock_message, Message::InitiateMessageRecovery((MESSAGE_HASH, recovery_router)) - .serialize() ); Err(err) @@ -1027,7 +887,6 @@ mod extrinsics { assert_eq!( mock_message, Message::DisputeMessageRecovery((MESSAGE_HASH, recovery_router)) - .serialize() ); Ok(()) @@ -1099,7 +958,6 @@ mod extrinsics { assert_eq!( mock_message, Message::DisputeMessageRecovery((MESSAGE_HASH, recovery_router)) - .serialize() ); Ok(()) @@ -1133,7 +991,6 @@ mod extrinsics { assert_eq!( mock_message, Message::DisputeMessageRecovery((MESSAGE_HASH, recovery_router)) - .serialize() ); Err(err) @@ -3451,7 +3308,7 @@ mod implementations { move |mock_router_id, mock_sender, mock_message| { assert_eq!(mock_router_id, ROUTER_ID_1); assert_eq!(mock_sender, ::Sender::get()); - assert_eq!(mock_message, message.serialize()); + assert_eq!(mock_message, message); Ok(()) }, @@ -3480,7 +3337,7 @@ mod implementations { move |mock_router_id, mock_sender, mock_message| { assert_eq!(mock_router_id, ROUTER_ID_1); assert_eq!(mock_sender, ::Sender::get()); - assert_eq!(mock_message, message.serialize()); + assert_eq!(mock_message, message); Err(router_err) }, @@ -3494,6 +3351,107 @@ mod implementations { } } + mod receive { + use cfg_traits::liquidity_pools::MessageReceiver; + + use super::*; + #[test] + fn success() { + new_test_ext().execute_with(|| { + let address = H160::from_slice(&get_test_account_id().as_slice()[..20]); + let domain_address = DomainAddress::Evm(0, address); + let message = Message::Simple; + + let router_id = ROUTER_ID_1; + + assert_ok!(LiquidityPoolsGateway::add_instance( + RuntimeOrigin::root(), + domain_address.clone(), + )); + + let gateway_message = GatewayMessage::Inbound { + domain_address: domain_address.clone(), + message: message.clone(), + router_id: router_id.clone(), + }; + + let handler = MockLiquidityPoolsGatewayQueue::mock_submit(move |mock_message| { + assert_eq!(mock_message, gateway_message); + Ok(()) + }); + + assert_ok!(LiquidityPoolsGateway::receive( + router_id, + domain_address, + message + )); + + assert_eq!(handler.times(), 1); + }); + } + + #[test] + fn unknown_instance_centrifuge() { + new_test_ext().execute_with(|| { + let domain_address = DomainAddress::Centrifuge(get_test_account_id().into()); + let router_id = ROUTER_ID_1; + + assert_noop!( + LiquidityPoolsGateway::receive(router_id, domain_address, Message::Simple), + Error::::UnknownInstance, + ); + }); + } + + #[test] + fn unknown_instance_evm() { + new_test_ext().execute_with(|| { + let address = H160::from_slice(&get_test_account_id().as_slice()[..20]); + let domain_address = DomainAddress::Evm(0, address); + let router_id = ROUTER_ID_1; + + assert_noop!( + LiquidityPoolsGateway::receive(router_id, domain_address, Message::Simple), + Error::::UnknownInstance, + ); + }); + } + + #[test] + fn message_queue_error() { + new_test_ext().execute_with(|| { + let address = H160::from_slice(&get_test_account_id().as_slice()[..20]); + let domain_address = DomainAddress::Evm(0, address); + let message = Message::Simple; + + let router_id = ROUTER_ID_1; + + assert_ok!(LiquidityPoolsGateway::add_instance( + RuntimeOrigin::root(), + domain_address.clone(), + )); + + let err = sp_runtime::DispatchError::from("liquidity_pools error"); + + let gateway_message = GatewayMessage::Inbound { + domain_address: domain_address.clone(), + message: message.clone(), + router_id: router_id.clone(), + }; + + MockLiquidityPoolsGatewayQueue::mock_submit(move |mock_message| { + assert_eq!(mock_message, gateway_message); + Err(err) + }); + + assert_noop!( + LiquidityPoolsGateway::receive(router_id, domain_address, Message::Simple), + err, + ); + }); + } + } + mod pallet { use super::*; diff --git a/pallets/liquidity-pools/src/message.rs b/pallets/liquidity-pools/src/message.rs index 1b52ca3e23..8609a9eea7 100644 --- a/pallets/liquidity-pools/src/message.rs +++ b/pallets/liquidity-pools/src/message.rs @@ -6,7 +6,7 @@ //! representation for each message variant. use cfg_traits::{ - liquidity_pools::{LPMessage, MessageHash}, + liquidity_pools::{LpMessage, MessageHash}, Seconds, }; use cfg_types::domain_address::Domain; @@ -18,9 +18,10 @@ use serde::{ ser::{Error as _, SerializeTuple}, Deserialize, Serialize, Serializer, }; +use sp_core::H160; use sp_io::hashing::keccak_256; use sp_runtime::{traits::ConstU32, DispatchError, DispatchResult}; -use sp_std::{vec, vec::Vec}; +use sp_std::{boxed::Box, vec, vec::Vec}; use crate::gmpf; // Generic Message Passing Format @@ -79,9 +80,9 @@ impl TryInto for SerializableDomain { /// A message type that can not be a batch. #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] -pub struct NoBatchMessage(Message); +pub struct NonBatchMessage(Message); -impl TryFrom for NoBatchMessage { +impl TryFrom for NonBatchMessage { type Error = DispatchError; fn try_from(message: Message) -> Result { @@ -92,16 +93,16 @@ impl TryFrom for NoBatchMessage { } } -impl MaxEncodedLen for NoBatchMessage { +impl MaxEncodedLen for NonBatchMessage { fn max_encoded_len() -> usize { - // This message use a non batch message version to obtain the encoded - // len to avoid an infite recursion: message -> batch -> message -> batch ... + // This message uses a non-batch message version to obtain the encoded + // len to avoid an infinite recursion: message -> batch -> message -> batch ... Message::<()>::max_encoded_len() } } #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen, Default)] -pub struct BatchMessages(BoundedVec>); +pub struct BatchMessages(BoundedVec>); impl Serialize for BatchMessages { fn serialize(&self, serializer: S) -> Result { @@ -192,6 +193,38 @@ impl BatchMessages { } } +/// A message type that cannot be forwarded. +#[derive(Encode, Decode, Serialize, Deserialize, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct NonForwardMessage(Box); + +impl TryFrom for NonForwardMessage { + type Error = DispatchError; + + fn try_from(message: Message) -> Result { + match message { + Message::Forwarded { .. } => Err(DispatchError::Other( + "A submessage can not be a forwarded one", + )), + _ => Ok(Self(message.into())), + } + } +} + +impl From for Message { + fn from(value: NonForwardMessage) -> Self { + *value.0 + } +} + +impl MaxEncodedLen for NonForwardMessage { + fn max_encoded_len() -> usize { + // This message uses a non-forwarded message version to obtain the encoded + // len to avoid an infinite recursion: message -> forward -> message -> forward + // ... + Message::<()>::max_encoded_len() + } +} + /// A LiquidityPools Message #[derive( Encode, @@ -206,7 +239,7 @@ impl BatchMessages { MaxEncodedLen, Default, )] -pub enum Message { +pub enum Message { #[default] Invalid, // --- Gateway --- @@ -530,9 +563,20 @@ pub enum Message { /// The amount of tranche tokens which should be redeemed. amount: u128, }, + /// A wrapped Message which was forwarded from the source domain via the + /// contract. + /// + /// Directionality: Centrifuge <-> EVM Domain. + Forwarded { + source_domain: SerializableDomain, + forwarding_contract: H160, + message: ForwardContent, + }, } -impl LPMessage for Message { +impl LpMessage for Message { + type Domain = Domain; + fn serialize(&self) -> Vec { gmpf::to_vec(self).unwrap_or_default() } @@ -567,7 +611,7 @@ impl LPMessage for Message { } fn get_message_hash(&self) -> MessageHash { - keccak_256(&LPMessage::serialize(self)) + keccak_256(&LpMessage::serialize(self)) } fn to_proof_message(&self) -> Self { @@ -583,6 +627,40 @@ impl LPMessage for Message { fn dispute_recovery_message(hash: [u8; 32], router: [u8; 32]) -> Self { Message::DisputeMessageRecovery { hash, router } } + + fn is_forwarded(&self) -> bool { + matches!(self, Message::Forwarded { .. }) + } + + fn unwrap_forwarded(self) -> Option<(Domain, H160, Self)> { + match self { + Self::Forwarded { + source_domain, + forwarding_contract, + message, + } => source_domain + .try_into() + .ok() + .map(|domain| (domain, forwarding_contract, message.into())), + _ => None, + } + } + + fn try_wrap_forward( + source_domain: Domain, + forwarding_contract: H160, + message: Self, + ) -> Result { + Ok(Self::Forwarded { + source_domain: source_domain.into(), + forwarding_contract, + message: message.try_into().map_err(|_| { + DispatchError::Other( + "Failed to convert LpMessage {message:?} into NonForwardMessage", + ) + })?, + }) + } } /// A Liquidity Pool message for updating restrictions on foreign domains. diff --git a/runtime/altair/Cargo.toml b/runtime/altair/Cargo.toml index ea5009dd4e..9393852b18 100644 --- a/runtime/altair/Cargo.toml +++ b/runtime/altair/Cargo.toml @@ -105,6 +105,7 @@ pallet-interest-accrual = { workspace = true } pallet-investments = { workspace = true } pallet-keystore = { workspace = true } pallet-liquidity-pools = { workspace = true } +pallet-liquidity-pools-forwarder = { workspace = true } pallet-liquidity-pools-gateway = { workspace = true } pallet-liquidity-pools-gateway-queue = { workspace = true } pallet-liquidity-rewards = { workspace = true } @@ -234,6 +235,7 @@ std = [ "pallet-investments/std", "pallet-keystore/std", "pallet-liquidity-pools/std", + "pallet-liquidity-pools-forwarder/std", "pallet-liquidity-pools-gateway/std", "pallet-liquidity-rewards/std", "pallet-loans/std", @@ -321,6 +323,7 @@ runtime-benchmarks = [ "pallet-investments/runtime-benchmarks", "pallet-keystore/runtime-benchmarks", "pallet-liquidity-pools/runtime-benchmarks", + "pallet-liquidity-pools-forwarder/runtime-benchmarks", "pallet-liquidity-pools-gateway/runtime-benchmarks", "pallet-liquidity-rewards/runtime-benchmarks", "pallet-loans/runtime-benchmarks", @@ -407,6 +410,7 @@ try-runtime = [ "pallet-investments/try-runtime", "pallet-keystore/try-runtime", "pallet-liquidity-pools/try-runtime", + "pallet-liquidity-pools-forwarder/try-runtime", "pallet-liquidity-pools-gateway/try-runtime", "pallet-liquidity-rewards/try-runtime", "pallet-loans/try-runtime", diff --git a/runtime/altair/src/lib.rs b/runtime/altair/src/lib.rs index 4c809605ef..88056e59c9 100644 --- a/runtime/altair/src/lib.rs +++ b/runtime/altair/src/lib.rs @@ -117,7 +117,10 @@ use runtime_common::{ permissions::{IsUnfrozenTrancheInvestor, PoolAdminCheck}, remarks::Remark, rewards::SingleCurrencyMovement, - routing::{EvmAccountCodeChecker, LPGatewayRouterProvider, RouterDispatcher, RouterId}, + routing::{ + EvmAccountCodeChecker, LPGatewayRouterProvider, MessageSerializer, RouterDispatcher, + RouterId, + }, transfer_filter::{PreLpTransfer, PreNativeTransfer}, xcm::AccountIdToLocation, xcm_transactor, AllowanceDeposit, CurrencyED, @@ -1754,6 +1757,15 @@ impl pallet_liquidity_pools::Config for Runtime { type WeightInfo = (); } +impl pallet_liquidity_pools_forwarder::Config for Runtime { + type AdminOrigin = EnsureRootOr; + type Message = pallet_liquidity_pools::Message; + type MessageReceiver = LiquidityPoolsGateway; + type MessageSender = MessageSerializer, LiquidityPoolsForwarder>; + type RouterId = RouterId; + type RuntimeEvent = RuntimeEvent; +} + parameter_types! { pub Sender: DomainAddress = gateway::get_gateway_domain_address::(); pub const MaxIncomingMessageSize: u32 = 1024; @@ -1768,7 +1780,7 @@ impl pallet_liquidity_pools_gateway::Config for Runtime { type MaxRouterCount = MaxRouterCount; type Message = pallet_liquidity_pools::Message; type MessageQueue = LiquidityPoolsGatewayQueue; - type MessageSender = RouterDispatcher; + type MessageSender = LiquidityPoolsForwarder; type RouterId = RouterId; type RouterProvider = LPGatewayRouterProvider; type RuntimeEvent = RuntimeEvent; @@ -1893,7 +1905,7 @@ impl pallet_axelar_router::Config for Runtime { type AdminOrigin = EnsureRoot; type EvmAccountCodeChecker = EvmAccountCodeChecker; type Middleware = RouterId; - type Receiver = LiquidityPoolsGateway; + type Receiver = MessageSerializer, LiquidityPoolsForwarder>; type RuntimeEvent = RuntimeEvent; type Transactor = EthereumTransaction; } @@ -2124,6 +2136,7 @@ construct_runtime!( // Removed: Swaps = 200 TokenMux: pallet_token_mux::{Pallet, Call, Storage, Event} = 201, LiquidityPoolsGatewayQueue: pallet_liquidity_pools_gateway_queue::{Pallet, Call, Storage, Event} = 202, + LiquidityPoolsForwarder: pallet_liquidity_pools_forwarder::{Pallet, Call, Storage, Event} = 203, } ); diff --git a/runtime/centrifuge/Cargo.toml b/runtime/centrifuge/Cargo.toml index 0a4f83b50f..4fb42616de 100644 --- a/runtime/centrifuge/Cargo.toml +++ b/runtime/centrifuge/Cargo.toml @@ -102,6 +102,7 @@ pallet-interest-accrual = { workspace = true } pallet-investments = { workspace = true } pallet-keystore = { workspace = true } pallet-liquidity-pools = { workspace = true } +pallet-liquidity-pools-forwarder = { workspace = true } pallet-liquidity-pools-gateway = { workspace = true } pallet-liquidity-pools-gateway-queue = { workspace = true } pallet-liquidity-rewards = { workspace = true } @@ -226,6 +227,7 @@ std = [ "pallet-investments/std", "pallet-keystore/std", "pallet-liquidity-pools/std", + "pallet-liquidity-pools-forwarder/std", "pallet-liquidity-pools-gateway/std", "pallet-liquidity-rewards/std", "pallet-loans/std", @@ -309,6 +311,7 @@ runtime-benchmarks = [ "pallet-investments/runtime-benchmarks", "pallet-keystore/runtime-benchmarks", "pallet-liquidity-pools/runtime-benchmarks", + "pallet-liquidity-pools-forwarder/runtime-benchmarks", "pallet-liquidity-pools-gateway/runtime-benchmarks", "pallet-liquidity-rewards/runtime-benchmarks", "pallet-loans/runtime-benchmarks", @@ -391,6 +394,7 @@ try-runtime = [ "pallet-investments/try-runtime", "pallet-keystore/try-runtime", "pallet-liquidity-pools/try-runtime", + "pallet-liquidity-pools-forwarder/try-runtime", "pallet-liquidity-pools-gateway/try-runtime", "pallet-liquidity-rewards/try-runtime", "pallet-loans/try-runtime", diff --git a/runtime/centrifuge/src/lib.rs b/runtime/centrifuge/src/lib.rs index 7aaf756c4d..5d76da74fc 100644 --- a/runtime/centrifuge/src/lib.rs +++ b/runtime/centrifuge/src/lib.rs @@ -117,7 +117,10 @@ use runtime_common::{ }, permissions::{IsUnfrozenTrancheInvestor, PoolAdminCheck}, rewards::SingleCurrencyMovement, - routing::{EvmAccountCodeChecker, LPGatewayRouterProvider, RouterDispatcher, RouterId}, + routing::{ + EvmAccountCodeChecker, LPGatewayRouterProvider, MessageSerializer, RouterDispatcher, + RouterId, + }, transfer_filter::{PreLpTransfer, PreNativeTransfer}, xcm::AccountIdToLocation, xcm_transactor, AllowanceDeposit, CurrencyED, @@ -1843,6 +1846,15 @@ parameter_types! { pub const MaxRouterCount: u32 = 8; } +impl pallet_liquidity_pools_forwarder::Config for Runtime { + type AdminOrigin = EnsureAccountOrRootOr; + type Message = pallet_liquidity_pools::Message; + type MessageReceiver = LiquidityPoolsGateway; + type MessageSender = MessageSerializer, LiquidityPoolsForwarder>; + type RouterId = RouterId; + type RuntimeEvent = RuntimeEvent; +} + parameter_types! { // A temporary admin account for the LP logic // This is a multi-sig controlled pure proxy on mainnet @@ -1867,7 +1879,7 @@ impl pallet_liquidity_pools_gateway::Config for Runtime { type MaxRouterCount = MaxRouterCount; type Message = pallet_liquidity_pools::Message; type MessageQueue = LiquidityPoolsGatewayQueue; - type MessageSender = RouterDispatcher; + type MessageSender = LiquidityPoolsForwarder; type RouterId = RouterId; type RouterProvider = LPGatewayRouterProvider; type RuntimeEvent = RuntimeEvent; @@ -1992,7 +2004,7 @@ impl pallet_axelar_router::Config for Runtime { type AdminOrigin = EnsureAccountOrRootOr; type EvmAccountCodeChecker = EvmAccountCodeChecker; type Middleware = RouterId; - type Receiver = LiquidityPoolsGateway; + type Receiver = MessageSerializer, LiquidityPoolsForwarder>; type RuntimeEvent = RuntimeEvent; type Transactor = EthereumTransaction; } @@ -2096,6 +2108,7 @@ construct_runtime!( Remarks: pallet_remarks::{Pallet, Call, Event} = 113, PoolFees: pallet_pool_fees::{Pallet, Call, Storage, Event} = 114, LiquidityPoolsGatewayQueue: pallet_liquidity_pools_gateway_queue::{Pallet, Call, Storage, Event} = 115, + LiquidityPoolsForwarder: pallet_liquidity_pools_forwarder::{Pallet, Call, Storage, Event} = 116, // XCM XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event} = 120, diff --git a/runtime/common/src/evm/mod.rs b/runtime/common/src/evm/mod.rs index 477b66371c..1dc311c452 100644 --- a/runtime/common/src/evm/mod.rs +++ b/runtime/common/src/evm/mod.rs @@ -14,8 +14,10 @@ use cfg_primitives::AuraId; use frame_support::{traits::FindAuthor, weights::constants::WEIGHT_REF_TIME_PER_SECOND}; use pallet_ethereum::{Transaction, TransactionAction}; #[cfg(feature = "std")] +use sp_core::Hasher; +#[cfg(feature = "std")] use sp_core::KeccakHasher; -use sp_core::{crypto::ByteArray, Hasher, H160}; +use sp_core::{crypto::ByteArray, H160}; use sp_runtime::{ConsensusEngineId, Permill}; use sp_std::marker::PhantomData; @@ -115,20 +117,21 @@ impl> FindAuthor for FindAuth // NOTE: If the above file changes, this code needs to be adapted as follows: // 1. Update the `liquidity-pools` submodule to the latest desired state // 2. Build with `forge-build` -// 3. Go to `./out/PassthroughAdapter.sol` and copy-paste the -// `deployedBytecode` here. +// 3. Go to `./out/PassthroughAdapter.sol` and copy-paste the `deployedBytecode` here. // 4. Run tests and update mismatching hashes. -// 5. On Development chain, you might also have to update the -// `evm.accountCodes` storage via raw writing. +// 5. On Development chain, you might also have to update the `evm.accountCodes` storage via raw +// writing. // // Blake256 hash of the deployed passthrough router contract code as // Encoded::encode(Vec): // `0x283d01c648e109952e3120e8928a19614c5c694477c780920ac29a748f96babf` +#[cfg(feature = "std")] pub const PASSTHROUGH_ROUTER_ACCOUNT_CODES: [u8; 3665] = hex_literal::hex!("6080604052600436106100e4575f3560e01c806342f1de1411610087578063b0fa844411610057578063b0fa844414610263578063bf353dbb14610277578063d4e8be83146102a2578063f8a8fd6d146102c1575f80fd5b806342f1de14146101e757806365fae35e146102065780636d90d4ad146102255780639c52a7f114610244575f80fd5b80631c6ffa46116100c25780631c6ffa46146101755780631c92115f146101965780632bb1ae7c146101b55780632d0c7583146101d4575f80fd5b8063097ac46e146100e85780630bfb963b14610109578063116191b61461013e575b5f80fd5b3480156100f3575f80fd5b506101076101023660046108d5565b6102cc565b005b348015610114575f80fd5b5061012b61012336600461091d565b5f9392505050565b6040519081526020015b60405180910390f35b348015610149575f80fd5b5060015461015d906001600160a01b031681565b6040516001600160a01b039091168152602001610135565b348015610180575f80fd5b5061018961040b565b6040516101359190610965565b3480156101a1575f80fd5b506101076101b036600461099a565b610497565b3480156101c0575f80fd5b506101076101cf366004610a39565b6104e0565b6101076101e2366004610a93565b505050565b3480156101f2575f80fd5b5061010761020136600461099a565b610523565b348015610211575f80fd5b50610107610220366004610ae3565b6105bb565b348015610230575f80fd5b5061010761023f36600461099a565b610653565b34801561024f575f80fd5b5061010761025e366004610ae3565b61071d565b34801561026e575f80fd5b506101896107b4565b348015610282575f80fd5b5061012b610291366004610ae3565b5f6020819052908152604090205481565b3480156102ad575f80fd5b506101076102bc366004610b03565b6107c1565b348015610107575f80fd5b335f908152602081905260409020546001146103255760405162461bcd60e51b8152602060048201526013602482015272105d5d1a0bdb9bdd0b585d5d1a1bdc9a5e9959606a1b60448201526064015b60405180910390fd5b826a39b7bab931b2a1b430b4b760a91b0361034d576002610347828483610bc4565b506103cc565b826c736f757263654164647265737360981b03610371576003610347828483610bc4565b60405162461bcd60e51b815260206004820152602a60248201527f506173737468726f756768416461707465722f66696c652d756e7265636f676e604482015269697a65642d706172616d60b01b606482015260840161031c565b827fe42e0b9a029dc87ccb1029c632e6359090acd0eb032b2b59c811e3ec70160dc683836040516103fe929190610ca6565b60405180910390a2505050565b6002805461041890610b41565b80601f016020809104026020016040519081016040528092919081815260200182805461044490610b41565b801561048f5780601f106104665761010080835404028352916020019161048f565b820191905f5260205f20905b81548152906001019060200180831161047257829003601f168201915b505050505081565b7ffabee705da75429b35b4ca6585fef97dc7a96c1aaeca74c480eeefe2f140c27e8686868686866040516104d096959493929190610cc1565b60405180910390a1505050505050565b7ffabee705da75429b35b4ca6585fef97dc7a96c1aaeca74c480eeefe2f140c27e6002600384846040516105179493929190610d88565b60405180910390a15050565b600154604051635fa45e5b60e11b81526001600160a01b039091169063bf48bcb6906105559085908590600401610ca6565b5f604051808303815f87803b15801561056c575f80fd5b505af115801561057e573d5f803e3d5ffd5b505050507f0352e36764157a0a91a3565aca47fd498d8a1eff81976b83ff9b179a8ad61e418686868686866040516104d096959493929190610cc1565b335f9081526020819052604090205460011461060f5760405162461bcd60e51b8152602060048201526013602482015272105d5d1a0bdb9bdd0b585d5d1a1bdc9a5e9959606a1b604482015260640161031c565b6001600160a01b0381165f8181526020819052604080822060019055517fdd0e34038ac38b2a1ce960229778ac48a8719bc900b6c4f8d0475c6e8b385a609190a250565b604051630922c0cb60e31b81526108009081906349160658906106a6907f8505b897b40f92d6c56f2c1cd87ce4ab0da8b445d7453a51231ff9874ad45e26908b908b908b908b908b908b90600401610dcc565b5f604051808303815f87803b1580156106bd575f80fd5b505af11580156106cf573d5f803e3d5ffd5b505050507f80bd9fe4a5709d9803f037c9c5601c8a67ea987a0f35a2767de92bdb0363f49887878787878760405161070c96959493929190610cc1565b60405180910390a150505050505050565b335f908152602081905260409020546001146107715760405162461bcd60e51b8152602060048201526013602482015272105d5d1a0bdb9bdd0b585d5d1a1bdc9a5e9959606a1b604482015260640161031c565b6001600160a01b0381165f81815260208190526040808220829055517f184450df2e323acec0ed3b5c7531b81f9b4cdef7914dfd4c0a4317416bb5251b9190a250565b6003805461041890610b41565b335f908152602081905260409020546001146108155760405162461bcd60e51b8152602060048201526013602482015272105d5d1a0bdb9bdd0b585d5d1a1bdc9a5e9959606a1b604482015260640161031c565b81666761746577617960c81b03610371576001805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383161790556040516001600160a01b038216815282907f8fef588b5fc1afbf5b2f06c1a435d513f208da2e6704c3d8f0e0ec91167066ba9060200160405180910390a25050565b5f8083601f8401126108a0575f80fd5b50813567ffffffffffffffff8111156108b7575f80fd5b6020830191508360208285010111156108ce575f80fd5b9250929050565b5f805f604084860312156108e7575f80fd5b83359250602084013567ffffffffffffffff811115610904575f80fd5b61091086828701610890565b9497909650939450505050565b5f805f6040848603121561092f575f80fd5b833567ffffffffffffffff811115610945575f80fd5b61095186828701610890565b909790965060209590950135949350505050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b5f805f805f80606087890312156109af575f80fd5b863567ffffffffffffffff8111156109c5575f80fd5b6109d189828a01610890565b909750955050602087013567ffffffffffffffff8111156109f0575f80fd5b6109fc89828a01610890565b909550935050604087013567ffffffffffffffff811115610a1b575f80fd5b610a2789828a01610890565b979a9699509497509295939492505050565b5f8060208385031215610a4a575f80fd5b823567ffffffffffffffff811115610a60575f80fd5b610a6c85828601610890565b90969095509350505050565b80356001600160a01b0381168114610a8e575f80fd5b919050565b5f805f60408486031215610aa5575f80fd5b833567ffffffffffffffff811115610abb575f80fd5b610ac786828701610890565b9094509250610ada905060208501610a78565b90509250925092565b5f60208284031215610af3575f80fd5b610afc82610a78565b9392505050565b5f8060408385031215610b14575f80fd5b82359150610b2460208401610a78565b90509250929050565b634e487b7160e01b5f52604160045260245ffd5b600181811c90821680610b5557607f821691505b602082108103610b7357634e487b7160e01b5f52602260045260245ffd5b50919050565b601f8211156101e257805f5260205f20601f840160051c81016020851015610b9e5750805b601f840160051c820191505b81811015610bbd575f8155600101610baa565b5050505050565b67ffffffffffffffff831115610bdc57610bdc610b2d565b610bf083610bea8354610b41565b83610b79565b5f601f841160018114610c21575f8515610c0a5750838201355b5f19600387901b1c1916600186901b178355610bbd565b5f83815260208120601f198716915b82811015610c505786850135825560209485019460019092019101610c30565b5086821015610c6c575f1960f88860031b161c19848701351681555b505060018560011b0183555050505050565b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b602081525f610cb9602083018486610c7e565b949350505050565b606081525f610cd460608301888a610c7e565b8281036020840152610ce7818789610c7e565b90508281036040840152610cfc818587610c7e565b9998505050505050505050565b5f8154610d1581610b41565b808552600182168015610d2f5760018114610d4b57610d7f565b60ff1983166020870152602082151560051b8701019350610d7f565b845f5260205f205f5b83811015610d765781546020828a010152600182019150602081019050610d54565b87016020019450505b50505092915050565b606081525f610d9a6060830187610d09565b8281036020840152610dac8187610d09565b90508281036040840152610dc1818587610c7e565b979650505050505050565b878152608060208201525f610de560808301888a610c7e565b8281036040840152610df8818789610c7e565b90508281036060840152610e0d818587610c7e565b9a995050505050505050505056fea2646970667358221220887d4d2af8c9d806029e96166ff134446439fbaad5ab5e545e48a94ed37b42d964736f6c634300081a0033"); /// Input for the KeccakHasher to derive a random `H160` where the passthrough /// router is always located at. Refers to address: /// `0x283d01c648e109952e3120e8928a19614c5c694477c780920ac29a748f96babf` +#[cfg(feature = "std")] const PASSTHROUGH_ROUTER_ACCOUNT_CODES_ACCOUNT_LOCATION_SALT: &[u8] = b"PASSTHROUGH_ROUTER_ACCOUNT_CODES_ACCOUNT_LOCATION_SALT"; @@ -180,13 +183,13 @@ mod tests { } } +#[cfg(feature = "std")] pub mod utils { use sp_core::H160; use sp_std::collections::btree_map::BTreeMap; use crate::evm::precompile::H160Addresses; - #[cfg(feature = "std")] pub fn account_genesis() -> BTreeMap { let mut precompiles = diff --git a/runtime/common/src/routing.rs b/runtime/common/src/routing.rs index ab20fba17e..7b804923f3 100644 --- a/runtime/common/src/routing.rs +++ b/runtime/common/src/routing.rs @@ -1,5 +1,5 @@ use cfg_traits::{ - liquidity_pools::{MessageSender, RouterProvider}, + liquidity_pools::{LpMessage, MessageReceiver, MessageSender, RouterProvider}, PreConditions, }; use cfg_types::domain_address::{Domain, DomainAddress}; @@ -8,6 +8,7 @@ use frame_support::{ pallet_prelude::{Decode, Encode, MaxEncodedLen, TypeInfo}, }; pub use pallet_axelar_router::AxelarId; +use pallet_liquidity_pools::Message; use sp_core::{H160, H256}; use sp_runtime::traits::{BlakeTwo256, Hash}; use sp_std::{marker::PhantomData, vec, vec::Vec}; @@ -57,10 +58,11 @@ impl MessageSender for RouterDispatcher where Routers: pallet_axelar_router::Config, { + type Message = Vec; type Middleware = RouterId; type Origin = DomainAddress; - fn send(router_id: RouterId, origin: Self::Origin, message: Vec) -> DispatchResult { + fn send(router_id: RouterId, origin: Self::Origin, message: Self::Message) -> DispatchResult { match router_id { RouterId::Axelar(axelar_id) => { pallet_axelar_router::Pallet::::send(axelar_id, origin, message) @@ -78,3 +80,40 @@ impl PreConditions<(H160, H256)> for EvmAccountCode BlakeTwo256::hash_of(&code) == contract_hash } } + +pub struct MessageSerializer(PhantomData<(Sender, Receiver)>); + +impl MessageSender for MessageSerializer +where + Sender: MessageSender, Middleware = RouterId, Origin = DomainAddress>, +{ + type Message = Message; + type Middleware = RouterId; + type Origin = DomainAddress; + + fn send( + middleware: Self::Middleware, + origin: Self::Origin, + message: Self::Message, + ) -> DispatchResult { + Sender::send(middleware, origin, message.serialize()) + } +} + +impl MessageReceiver for MessageSerializer +where + Receiver: MessageReceiver, +{ + type Message = Vec; + type Middleware = RouterId; + type Origin = DomainAddress; + + fn receive( + middleware: Self::Middleware, + origin: Self::Origin, + payload: Self::Message, + ) -> DispatchResult { + let message = Message::deserialize(&payload)?; + Receiver::receive(middleware, origin, message) + } +} diff --git a/runtime/development/Cargo.toml b/runtime/development/Cargo.toml index ac92c94e31..8391926851 100644 --- a/runtime/development/Cargo.toml +++ b/runtime/development/Cargo.toml @@ -106,6 +106,7 @@ pallet-interest-accrual = { workspace = true } pallet-investments = { workspace = true } pallet-keystore = { workspace = true } pallet-liquidity-pools = { workspace = true } +pallet-liquidity-pools-forwarder = { workspace = true } pallet-liquidity-pools-gateway = { workspace = true } pallet-liquidity-pools-gateway-queue = { workspace = true } pallet-liquidity-rewards = { workspace = true } @@ -236,6 +237,7 @@ std = [ "pallet-investments/std", "pallet-keystore/std", "pallet-liquidity-pools/std", + "pallet-liquidity-pools-forwarder/std", "pallet-liquidity-pools-gateway/std", "pallet-liquidity-rewards/std", "pallet-loans/std", @@ -323,6 +325,7 @@ runtime-benchmarks = [ "pallet-investments/runtime-benchmarks", "pallet-keystore/runtime-benchmarks", "pallet-liquidity-pools/runtime-benchmarks", + "pallet-liquidity-pools-forwarder/runtime-benchmarks", "pallet-liquidity-pools-gateway/runtime-benchmarks", "pallet-liquidity-rewards/runtime-benchmarks", "pallet-loans/runtime-benchmarks", @@ -410,6 +413,7 @@ try-runtime = [ "pallet-investments/try-runtime", "pallet-keystore/try-runtime", "pallet-liquidity-pools/try-runtime", + "pallet-liquidity-pools-forwarder/try-runtime", "pallet-liquidity-pools-gateway/try-runtime", "pallet-liquidity-rewards/try-runtime", "pallet-loans/try-runtime", diff --git a/runtime/development/src/lib.rs b/runtime/development/src/lib.rs index ed499ffe89..2727b103b9 100644 --- a/runtime/development/src/lib.rs +++ b/runtime/development/src/lib.rs @@ -125,7 +125,10 @@ use runtime_common::{ permissions::{IsUnfrozenTrancheInvestor, PoolAdminCheck}, remarks::Remark, rewards::SingleCurrencyMovement, - routing::{EvmAccountCodeChecker, LPGatewayRouterProvider, RouterDispatcher, RouterId}, + routing::{ + EvmAccountCodeChecker, LPGatewayRouterProvider, MessageSerializer, RouterDispatcher, + RouterId, + }, transfer_filter::{PreLpTransfer, PreNativeTransfer}, xcm::AccountIdToLocation, xcm_transactor, AllowanceDeposit, CurrencyED, @@ -1859,6 +1862,15 @@ impl pallet_liquidity_pools::Config for Runtime { type WeightInfo = (); } +impl pallet_liquidity_pools_forwarder::Config for Runtime { + type AdminOrigin = EnsureRootOr; + type Message = pallet_liquidity_pools::Message; + type MessageReceiver = LiquidityPoolsGateway; + type MessageSender = MessageSerializer, LiquidityPoolsForwarder>; + type RouterId = RouterId; + type RuntimeEvent = RuntimeEvent; +} + parameter_types! { pub Sender: DomainAddress = gateway::get_gateway_domain_address::(); pub const MaxIncomingMessageSize: u32 = 1024; @@ -1873,7 +1885,7 @@ impl pallet_liquidity_pools_gateway::Config for Runtime { type MaxRouterCount = MaxRouterCount; type Message = pallet_liquidity_pools::Message; type MessageQueue = LiquidityPoolsGatewayQueue; - type MessageSender = RouterDispatcher; + type MessageSender = LiquidityPoolsForwarder; type RouterId = RouterId; type RouterProvider = LPGatewayRouterProvider; type RuntimeEvent = RuntimeEvent; @@ -1999,7 +2011,7 @@ impl pallet_axelar_router::Config for Runtime { type AdminOrigin = EnsureRoot; type EvmAccountCodeChecker = EvmAccountCodeChecker; type Middleware = RouterId; - type Receiver = LiquidityPoolsGateway; + type Receiver = MessageSerializer, LiquidityPoolsForwarder>; type RuntimeEvent = RuntimeEvent; type Transactor = EthereumTransaction; } @@ -2204,6 +2216,7 @@ construct_runtime!( // our pallets part 2 AnchorsV2: pallet_anchors_v2::{Pallet, Call, Storage, Event} = 130, LiquidityPoolsGatewayQueue: pallet_liquidity_pools_gateway_queue::{Pallet, Call, Storage, Event} = 131, + LiquidityPoolsForwarder: pallet_liquidity_pools_forwarder::{Pallet, Call, Storage, Event} = 132, // XCM XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event} = 120, diff --git a/runtime/integration-tests/src/cases/routers.rs b/runtime/integration-tests/src/cases/routers.rs index 9394b18b1b..bd4a7bce0c 100644 --- a/runtime/integration-tests/src/cases/routers.rs +++ b/runtime/integration-tests/src/cases/routers.rs @@ -1,5 +1,5 @@ use cfg_primitives::Balance; -use cfg_traits::liquidity_pools::{LPMessage, MessageProcessor}; +use cfg_traits::liquidity_pools::{LpMessage, MessageProcessor}; use cfg_types::{ domain_address::{Domain, DomainAddress}, EVMChainId,