From 66e707ab0bb60b215079624241420b0dc3220b6b Mon Sep 17 00:00:00 2001 From: David Salami Date: Thu, 3 Oct 2024 15:05:29 +0000 Subject: [PATCH 01/15] pallet-token-gateway: send and receive assets from EVM chains --- Cargo.lock | 23 + Cargo.toml | 1 + modules/ismp/pallets/token-gateway/Cargo.toml | 50 ++ .../ismp/pallets/token-gateway/src/impls.rs | 26 + modules/ismp/pallets/token-gateway/src/lib.rs | 498 ++++++++++++++++++ .../ismp/pallets/token-gateway/src/types.rs | 53 ++ .../ismp/pallets/token-governor/src/lib.rs | 59 ++- .../ismp/pallets/token-governor/src/types.rs | 14 + 8 files changed, 718 insertions(+), 6 deletions(-) create mode 100644 modules/ismp/pallets/token-gateway/Cargo.toml create mode 100644 modules/ismp/pallets/token-gateway/src/impls.rs create mode 100644 modules/ismp/pallets/token-gateway/src/lib.rs create mode 100644 modules/ismp/pallets/token-gateway/src/types.rs diff --git a/Cargo.lock b/Cargo.lock index aec0fd2d4..264a6c94a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12936,6 +12936,29 @@ dependencies = [ "sp-runtime 39.0.0", ] +[[package]] +name = "pallet-token-gateway" +version = "0.1.0" +dependencies = [ + "alloy-primitives", + "alloy-sol-macro", + "alloy-sol-types", + "anyhow", + "frame-support 37.0.0", + "frame-system", + "ismp", + "log", + "pallet-asset-gateway", + "pallet-ismp", + "pallet-token-governor", + "parity-scale-codec", + "primitive-types", + "scale-info", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.0", +] + [[package]] name = "pallet-token-governor" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 27c9c2e1e..6c498ede0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ members = [ "modules/ismp/pallets/call-decompressor", "modules/ismp/pallets/asset-gateway", "modules/ismp/pallets/token-governor", + "modules/ismp/pallets/token-gateway", "modules/ismp/pallets/hyperbridge", "modules/ismp/pallets/state-coprocessor", "modules/ismp/testsuite", diff --git a/modules/ismp/pallets/token-gateway/Cargo.toml b/modules/ismp/pallets/token-gateway/Cargo.toml new file mode 100644 index 000000000..69e20c39d --- /dev/null +++ b/modules/ismp/pallets/token-gateway/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "pallet-token-gateway" +version = "0.1.0" +edition = "2021" +description = "The token gateway is a susbtrate implementation of the token gateway protocol" +authors = ["Polytope Labs "] +publish = false + +[dependencies] +frame-support = { workspace = true } +frame-system = { workspace = true } +sp-runtime = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } +primitive-types = { workspace = true } + +ismp = { workspace = true } +pallet-ismp = { workspace = true } + +log = { workspace = true } +codec = { workspace = true } +scale-info = { workspace = true } +anyhow = { workspace = true } + +alloy-primitives = { workspace = true } +alloy-sol-macro = { workspace = true } +alloy-sol-types = { workspace = true } + +pallet-asset-gateway = { workspace = true } +pallet-token-governor = { workspace = true } + +[features] +default = ["std"] +std = [ + "frame-support/std", + "frame-system/std", + "sp-runtime/std", + "sp-core/std", + "sp-io/std", + "primitive-types/std", + "ismp/std", + "pallet-ismp/std", + "log/std", + "scale-info/std", + "anyhow/std", + "alloy-primitives/std", + "pallet-asset-gateway/std", + "pallet-token-governor/std" +] +try-runtime = [] diff --git a/modules/ismp/pallets/token-gateway/src/impls.rs b/modules/ismp/pallets/token-gateway/src/impls.rs new file mode 100644 index 000000000..7a0493c27 --- /dev/null +++ b/modules/ismp/pallets/token-gateway/src/impls.rs @@ -0,0 +1,26 @@ +// Copyright (C) Polytope Labs Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Pallet Implementations + +use sp_runtime::traits::AccountIdConversion; + +use crate::{Config, Pallet, PALLET_ID}; + +impl Pallet { + pub fn pallet_account() -> T::AccountId { + PALLET_ID.into_account_truncating() + } +} diff --git a/modules/ismp/pallets/token-gateway/src/lib.rs b/modules/ismp/pallets/token-gateway/src/lib.rs new file mode 100644 index 000000000..45efba9ce --- /dev/null +++ b/modules/ismp/pallets/token-gateway/src/lib.rs @@ -0,0 +1,498 @@ +// Copyright (C) Polytope Labs Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! The token governor handles asset registration as well as tracks the metadata of multi-chain +//! native tokens across all connected chains +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +mod impls; +mod types; +use alloy_sol_types::SolValue; +use codec::Decode; +use frame_support::{ + ensure, + pallet_prelude::Weight, + traits::{ + fungibles::{self, Mutate}, + tokens::Preservation, + Currency, ExistenceRequirement, + }, + PalletId, +}; +use ismp::{ + events::Meta, + router::{PostRequest, Request, Response, Timeout}, +}; +use pallet_asset_gateway::{convert_to_balance, Body}; +use pallet_token_governor::TokenGatewayAddressResponse; +use sp_core::{Get, U256}; +pub use types::*; + +use alloc::vec; +use ismp::module::IsmpModule; +use primitive_types::{H160, H256}; + +// Re-export pallet items so that they can be accessed from the crate namespace. +pub use pallet::*; + +/// The module id for this pallet +pub const PALLET_ID: PalletId = PalletId(*b"tokengtw"); + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::{ + pallet_prelude::*, + traits::{tokens::Preservation, Currency, ExistenceRequirement}, + }; + use frame_system::pallet_prelude::*; + use ismp::{ + dispatcher::{DispatchPost, DispatchRequest, FeeMetadata, IsmpDispatcher}, + host::StateMachine, + }; + use pallet_asset_gateway::{convert_to_erc20, Body}; + use pallet_token_governor::TokenGatewayAddressRequest; + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(_); + + /// The pallet's configuration trait. + #[pallet::config] + pub trait Config: frame_system::Config + pallet_ismp::Config { + /// The overarching runtime event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The [`IsmpDispatcher`] for dispatching cross-chain requests + type Dispatcher: IsmpDispatcher; + + /// A currency implementation for interacting with the native asset + type Currency: Currency; + + /// Fungible asset implementation + type Assets: fungibles::Mutate + fungibles::Inspect; + + /// The native asset ID as registered on hyperbridge + type NativeAssetId: Get>; + } + + /// Assets supported by this instance of token gateway + /// A map of the local asset id to the token gateway asset id + #[pallet::storage] + pub type SupportedAssets = StorageMap<_, Identity, AssetId, H256, OptionQuery>; + + /// Assets supported by this instance of token gateway + /// A map of the local asset id to the token gateway asset id + #[pallet::storage] + pub type LocalAssets = StorageMap<_, Identity, H256, AssetId, OptionQuery>; + + /// The token gateway adresses on different chains + #[pallet::storage] + pub type TokenGatewayAddresses = + StorageMap<_, Identity, StateMachine, H160, OptionQuery>; + + /// Pallet events that functions in this pallet can emit. + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// An asset has been teleported + AssetTeleported { + /// Source account on the relaychain + from: T::AccountId, + /// beneficiary account on destination + to: H160, + /// Amount transferred + amount: <::Currency as Currency>::Balance, + /// Destination chain + dest: StateMachine, + /// Request commitment + commitment: H256, + }, + + /// An asset has been received and transferred to the beneficiary's account + AssetReceived { + /// beneficiary account on relaychain + beneficiary: T::AccountId, + /// Amount transferred + amount: <::Currency as Currency>::Balance, + /// Destination chain + source: StateMachine, + }, + + /// An asset has been refunded and transferred to the beneficiary's account + AssetRefunded { + /// beneficiary account on relaychain + beneficiary: T::AccountId, + /// Amount transferred + amount: <::Currency as Currency>::Balance, + /// Destination chain + source: StateMachine, + }, + /// Token Gateway address enquiry dispatched + AddressEnquiryDispatched { commitment: H256 }, + } + + /// Errors that can be returned by this pallet. + #[pallet::error] + pub enum Error { + /// A asset that has not been registered + UnregisteredAsset, + /// A state machine does not have the token gateway address registered + UnregisteredDestinationChain, + /// Error while teleporting asset + AssetTeleportError, + /// Coprocessor was not configured in the runtime + CoprocessorNotConfigured, + /// A request to query the token gateway addresses failed to dispatch + AddressEnquiryDispatchFailed, + } + + #[pallet::call] + impl Pallet + where + ::AccountId: From<[u8; 32]>, + u128: From<<::Currency as Currency>::Balance>, + <::Assets as fungibles::Inspect>::Balance: + From<<::Currency as Currency>::Balance>, + [u8; 32]: From<::AccountId>, + { + /// Teleports a registered asset + /// locks the asset and dispatches a request to token gateway on the destination + #[pallet::call_index(0)] + #[pallet::weight(weight())] + pub fn teleport( + origin: OriginFor, + params: TeleportParams< + AssetId, + <::Currency as Currency>::Balance, + >, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + let dispatcher = ::Dispatcher::default(); + let asset_id = SupportedAssets::::get(params.asset_id.clone()) + .ok_or_else(|| Error::::UnregisteredAsset)?; + if params.asset_id == T::NativeAssetId::get() { + // Custody funds in pallet + ::Currency::transfer( + &who, + &Self::pallet_account(), + params.amount, + ExistenceRequirement::KeepAlive, + )?; + } else { + ::Assets::transfer( + params.asset_id, + &who, + &Self::pallet_account(), + params.amount.into(), + Preservation::Protect, + )?; + } + + // Dispatch Ismp request + + let mut to = [0u8; 32]; + to[12..].copy_from_slice(¶ms.recepient.0); + let from: [u8; 32] = who.clone().into(); + + let body = Body { + amount: { + let amount: u128 = params.amount.into(); + let mut bytes = [0u8; 32]; + convert_to_erc20(amount).to_big_endian(&mut bytes); + alloy_primitives::U256::from_be_bytes(bytes) + }, + asset_id: asset_id.0.into(), + redeem: false, + from: from.into(), + to: to.into(), + }; + + let token_gateway_address = TokenGatewayAddresses::::get(params.destination) + .ok_or_else(|| Error::::UnregisteredDestinationChain)?; + + let dispatch_post = DispatchPost { + dest: params.destination, + from: token_gateway_address.0.to_vec(), + to: token_gateway_address.0.to_vec(), + timeout: params.timeout, + body: { + // Prefix with the handleIncomingAsset enum variant + let mut encoded = vec![0]; + encoded.extend_from_slice(&Body::abi_encode(&body)); + encoded + }, + }; + + let metadata = FeeMetadata { payer: who.clone(), fee: Default::default() }; + let commitment = dispatcher + .dispatch_request(DispatchRequest::Post(dispatch_post), metadata) + .map_err(|_| Error::::AssetTeleportError)?; + + Self::deposit_event(Event::::AssetTeleported { + from: who, + to: params.recepient, + dest: params.destination, + amount: params.amount, + commitment, + }); + Ok(()) + } + + /// Request the token gateway address from Hyperbridge for specified chains + #[pallet::call_index(1)] + #[pallet::weight(weight())] + pub fn request_token_gateway_address( + origin: OriginFor, + chains: BoundedVec>, + ) -> DispatchResult { + T::AdminOrigin::ensure_origin(origin)?; + let request = TokenGatewayAddressRequest { chains }; + let dispatcher = ::Dispatcher::default(); + let dispatch_post = DispatchPost { + dest: T::Coprocessor::get().ok_or_else(|| Error::::CoprocessorNotConfigured)?, + from: PALLET_ID.0.to_vec(), + to: pallet_token_governor::PALLET_ID.to_vec(), + timeout: 0, + body: request.encode(), + }; + + let metadata = FeeMetadata { payer: [0u8; 32].into(), fee: Default::default() }; + let commitment = dispatcher + .dispatch_request(DispatchRequest::Post(dispatch_post), metadata) + .map_err(|_| Error::::AddressEnquiryDispatchFailed)?; + Self::deposit_event(Event::::AddressEnquiryDispatched { commitment }); + Ok(()) + } + + /// Map some local assets to their token gateway asset ids + #[pallet::call_index(2)] + #[pallet::weight(weight())] + pub fn register_assets( + origin: OriginFor, + assets: AssetRegistration>, + ) -> DispatchResult { + T::AdminOrigin::ensure_origin(origin)?; + for asset_map in assets.assets { + SupportedAssets::::insert( + asset_map.local_id.clone(), + asset_map.token_gateway_asset_id.clone(), + ); + LocalAssets::::insert(asset_map.token_gateway_asset_id, asset_map.local_id); + } + Ok(()) + } + } + + // Hack for implementing the [`Default`] bound needed for + // [`IsmpDispatcher`](ismp::dispatcher::IsmpDispatcher) and + // [`IsmpModule`](ismp::module::IsmpModule) + impl Default for Pallet { + fn default() -> Self { + Self(PhantomData) + } + } +} + +impl IsmpModule for Pallet +where + ::AccountId: From<[u8; 32]>, + <::Currency as Currency>::Balance: From, + <::Assets as fungibles::Inspect>::Balance: From, +{ + fn on_accept( + &self, + PostRequest { body, from, source, dest, nonce, .. }: PostRequest, + ) -> Result<(), ismp::error::Error> { + ensure!( + from == TokenGatewayAddresses::::get(source).unwrap_or_default().0.to_vec(), + ismp::error::Error::ModuleDispatchError { + msg: "Token Gateway: Unknown source contract address".to_string(), + meta: Meta { source, dest, nonce }, + } + ); + + let body = Body::abi_decode(&mut &body[1..], true).map_err(|_| { + ismp::error::Error::ModuleDispatchError { + msg: "Token Gateway: Failed to decode request body".to_string(), + meta: Meta { source, dest, nonce }, + } + })?; + let amount = convert_to_balance(U256::from_big_endian(&body.amount.to_be_bytes::<32>())) + .map_err(|_| ismp::error::Error::ModuleDispatchError { + msg: "Token Gateway: Trying to withdraw Invalid amount".to_string(), + meta: Meta { source, dest, nonce }, + })?; + + let local_asset_id = + LocalAssets::::get(H256::from(body.asset_id.0)).ok_or_else(|| { + ismp::error::Error::ModuleDispatchError { + msg: "Token Gateway: Unknown asset".to_string(), + meta: Meta { source, dest, nonce }, + } + })?; + let beneficiary: T::AccountId = body.to.0.into(); + if local_asset_id == T::NativeAssetId::get() { + ::Currency::transfer( + &Pallet::::pallet_account(), + &beneficiary, + amount.into(), + ExistenceRequirement::AllowDeath, + ) + .map_err(|_| ismp::error::Error::ModuleDispatchError { + msg: "Token Gateway: Failed to complete asset transfer".to_string(), + meta: Meta { source, dest, nonce }, + })?; + } else { + ::Assets::transfer( + local_asset_id, + &Pallet::::pallet_account(), + &beneficiary, + amount.into(), + Preservation::Protect, + ) + .map_err(|_| ismp::error::Error::ModuleDispatchError { + msg: "Token Gateway: Failed to complete asset transfer".to_string(), + meta: Meta { source, dest, nonce }, + })?; + } + + Self::deposit_event(Event::::AssetReceived { + beneficiary, + amount: amount.into(), + source, + }); + Ok(()) + } + + fn on_response(&self, response: Response) -> Result<(), ismp::error::Error> { + let data = response.response().ok_or_else(|| ismp::error::Error::ModuleDispatchError { + msg: "Token Gateway: Response has no body".to_string(), + meta: Meta { + source: response.source_chain(), + dest: response.dest_chain(), + nonce: response.nonce(), + }, + })?; + let resp = TokenGatewayAddressResponse::decode(&mut &*data).map_err(|_| { + ismp::error::Error::ModuleDispatchError { + msg: "Token Gateway: Failed to decode response body".to_string(), + meta: Meta { + source: response.source_chain(), + dest: response.dest_chain(), + nonce: response.nonce(), + }, + } + })?; + for (state_machine, addr) in resp.addresses { + TokenGatewayAddresses::::insert(state_machine, addr) + } + Ok(()) + } + + fn on_timeout(&self, request: Timeout) -> Result<(), ismp::error::Error> { + match request { + Timeout::Request(Request::Post(PostRequest { + body, + from, + source, + dest, + nonce, + .. + })) => { + ensure!( + from == TokenGatewayAddresses::::get(source).unwrap_or_default().0.to_vec(), + ismp::error::Error::ModuleDispatchError { + msg: "Token Gateway: Unknown source contract address".to_string(), + meta: Meta { source, dest, nonce }, + } + ); + + let body = Body::abi_decode(&mut &body[1..], true).map_err(|_| { + ismp::error::Error::ModuleDispatchError { + msg: "Token Gateway: Failed to decode request body".to_string(), + meta: Meta { source, dest, nonce }, + } + })?; + let beneficiary = body.from.0.into(); + + let amount = + convert_to_balance(U256::from_big_endian(&body.amount.to_be_bytes::<32>())) + .map_err(|_| ismp::error::Error::ModuleDispatchError { + msg: "Token Gateway: Trying to withdraw Invalid amount".to_string(), + meta: Meta { source, dest, nonce }, + })?; + let local_asset_id = LocalAssets::::get(H256::from(body.asset_id.0)) + .ok_or_else(|| ismp::error::Error::ModuleDispatchError { + msg: "Token Gateway: Unknown asset".to_string(), + meta: Meta { source, dest, nonce }, + })?; + + if local_asset_id == T::NativeAssetId::get() { + ::Currency::transfer( + &Pallet::::pallet_account(), + &beneficiary, + amount.into(), + ExistenceRequirement::AllowDeath, + ) + .map_err(|_| ismp::error::Error::ModuleDispatchError { + msg: "Token Gateway: Failed to complete asset transfer".to_string(), + meta: Meta { source, dest, nonce }, + })?; + } else { + ::Assets::transfer( + local_asset_id, + &Pallet::::pallet_account(), + &beneficiary, + amount.into(), + Preservation::Protect, + ) + .map_err(|_| ismp::error::Error::ModuleDispatchError { + msg: "Token Gateway: Failed to complete asset transfer".to_string(), + meta: Meta { source, dest, nonce }, + })?; + } + + Pallet::::deposit_event(Event::::AssetRefunded { + beneficiary, + amount: amount.into(), + source: dest, + }); + }, + Timeout::Request(Request::Get(get)) => Err(ismp::error::Error::ModuleDispatchError { + msg: "Tried to timeout unsupported request type".to_string(), + meta: Meta { source: get.source, dest: get.dest, nonce: get.nonce }, + })?, + + Timeout::Response(response) => Err(ismp::error::Error::ModuleDispatchError { + msg: "Tried to timeout unsupported request type".to_string(), + meta: Meta { + source: response.source_chain(), + dest: response.dest_chain(), + nonce: response.nonce(), + }, + })?, + } + Ok(()) + } +} + +/// Static weights because benchmarks suck, and we'll be getting PolkaVM soon anyways +fn weight() -> Weight { + Weight::from_parts(300_000_000, 0) +} diff --git a/modules/ismp/pallets/token-gateway/src/types.rs b/modules/ismp/pallets/token-gateway/src/types.rs new file mode 100644 index 000000000..1a5f3f365 --- /dev/null +++ b/modules/ismp/pallets/token-gateway/src/types.rs @@ -0,0 +1,53 @@ +// Copyright (C) Polytope Labs Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Pallet types + +use frame_support::{pallet_prelude::*, traits::fungibles}; +use ismp::host::StateMachine; +use primitive_types::H256; +use sp_core::H160; + +use crate::Config; + +pub type AssetId = + <::Assets as fungibles::Inspect<::AccountId>>::AssetId; + +/// Asset teleportation parameters +#[derive(Debug, Clone, Encode, Decode, scale_info::TypeInfo, PartialEq, Eq)] +pub struct TeleportParams { + /// Asset Id registered on Hyperbridge + pub asset_id: AssetId, + /// Destination state machine + pub destination: StateMachine, + /// Receiving account on destination + pub recepient: H160, + /// Amount to be sent + pub amount: Balance, + /// Request timeout + pub timeout: u64, +} + +#[derive(Clone, Encode, Decode, scale_info::TypeInfo, PartialEq, Eq, RuntimeDebug)] +pub struct AssetMap { + pub local_id: AssetId, + pub token_gateway_asset_id: H256, +} + +#[derive(Clone, Encode, Decode, scale_info::TypeInfo, PartialEq, Eq, RuntimeDebug)] +#[scale_info(skip_type_params(T))] +pub struct AssetRegistration { + pub assets: BoundedVec, ConstU32<5>>, +} diff --git a/modules/ismp/pallets/token-governor/src/lib.rs b/modules/ismp/pallets/token-governor/src/lib.rs index 6041bdd88..93fc5286a 100644 --- a/modules/ismp/pallets/token-governor/src/lib.rs +++ b/modules/ismp/pallets/token-governor/src/lib.rs @@ -23,10 +23,16 @@ mod impls; mod types; use alloy_sol_types::SolValue; use frame_support::pallet_prelude::Weight; -use ismp::router::{PostRequest, Response, Timeout}; +use ismp::{ + dispatcher::{FeeMetadata, IsmpDispatcher}, + router::{PostRequest, PostResponse, Response, Timeout}, +}; +use sp_core::ConstU32; +use sp_runtime::BoundedVec; pub use types::*; use alloc::{format, vec}; +use codec::{Decode, Encode}; use ismp::module::IsmpModule; use primitive_types::{H160, H256}; @@ -144,6 +150,14 @@ pub mod pallet { /// The new parameters new: Params<::Balance>, }, + + /// Response dispatched + ResponseDispatched { + /// Destination state machine + dest: StateMachine, + /// Response commitment + commitment: H256, + }, } /// Errors that can be returned by this pallet. @@ -366,11 +380,44 @@ pub mod pallet { } } -impl IsmpModule for Pallet { - fn on_accept( - &self, - PostRequest { body: data, from, source, .. }: PostRequest, - ) -> Result<(), ismp::error::Error> { +impl IsmpModule for Pallet +where + ::AccountId: From<[u8; 32]>, +{ + fn on_accept(&self, post: PostRequest) -> Result<(), ismp::error::Error> { + let PostRequest { body: data, from, source, .. } = post.clone(); + // We only accept messages from substrate chains requesting for Token Gateway Addresses + if source.is_substrate() { + let req = TokenGatewayAddressRequest::decode(&mut &*data) + .map_err(|err| ismp::error::Error::Custom(format!("Decode error: {err}")))?; + let mut addresses = BoundedVec::<_, ConstU32<5>>::new(); + for state_machine in req.chains { + if let Some(params) = TokenGatewayParams::::get(&state_machine) { + addresses.try_push((state_machine, params.address)).map_err(|err| { + ismp::error::Error::Custom(alloc::format!( + "Maximum of 5 state machines can be requested: {err:?}" + )) + })?; + } + } + + if !addresses.is_empty() { + let response = TokenGatewayAddressResponse { addresses }; + let dispatcher = ::Dispatcher::default(); + + let post_response = + PostResponse { post, response: response.encode(), timeout_timestamp: 0 }; + + let commitment = dispatcher.dispatch_response( + post_response, + FeeMetadata { payer: [0u8; 32].into(), fee: Default::default() }, + )?; + + Self::deposit_event(Event::::ResponseDispatched { dest: source, commitment }); + } + return Ok(()) + } + let RegistrarParams { address, .. } = TokenRegistrarParams::::get(&source) .ok_or_else(|| ismp::error::Error::Custom(format!("Pallet is not initialized")))?; if from != address.as_bytes().to_vec() { diff --git a/modules/ismp/pallets/token-governor/src/types.rs b/modules/ismp/pallets/token-governor/src/types.rs index daad26a9c..ef3034650 100644 --- a/modules/ismp/pallets/token-governor/src/types.rs +++ b/modules/ismp/pallets/token-governor/src/types.rs @@ -410,3 +410,17 @@ impl TokenGatewayRequest for SolContractInstance { [variant, encoded].concat() } } + +/// Struct for requesting the token gateway address for some state machines +#[derive(Debug, Clone, Encode, Decode, scale_info::TypeInfo, PartialEq, Eq, Default)] +pub struct TokenGatewayAddressRequest { + /// The chains whose token gateway addresses are being requested + pub chains: BoundedVec>, +} + +/// Struct for responding to token gateway address requests +#[derive(Debug, Clone, Encode, Decode, scale_info::TypeInfo, PartialEq, Eq, Default)] +pub struct TokenGatewayAddressResponse { + /// The token gateway address on diffirent chains + pub addresses: BoundedVec<(StateMachine, H160), ConstU32<5>>, +} From 48591a37fd45e5b327a52c7738df080e0586d7a3 Mon Sep 17 00:00:00 2001 From: David Salami Date: Fri, 4 Oct 2024 10:30:24 +0000 Subject: [PATCH 02/15] some refactors --- Cargo.lock | 3 +- Cargo.toml | 1 + modules/ismp/pallets/asset-gateway/Cargo.toml | 4 +- modules/ismp/pallets/asset-gateway/src/lib.rs | 80 +--------- modules/ismp/pallets/testsuite/Cargo.toml | 1 + .../src/tests/pallet_asset_gateway.rs | 3 +- .../src/tests/xcm_integration_test.rs | 3 +- modules/ismp/pallets/token-gateway/Cargo.toml | 2 - .../ismp/pallets/token-gateway/src/impls.rs | 149 +++++++++++++++++- modules/ismp/pallets/token-gateway/src/lib.rs | 44 ++---- .../ismp/pallets/token-gateway/src/types.rs | 32 ++++ .../ismp/pallets/token-governor/src/lib.rs | 87 ++++------ .../ismp/pallets/token-governor/src/types.rs | 14 -- 13 files changed, 243 insertions(+), 180 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 264a6c94a..63f60a48f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11615,6 +11615,7 @@ dependencies = [ "frame-system", "ismp", "pallet-ismp", + "pallet-token-gateway", "pallet-token-governor", "pallet-xcm", "parity-scale-codec", @@ -12350,6 +12351,7 @@ dependencies = [ "pallet-mmr 0.1.1", "pallet-sudo", "pallet-timestamp", + "pallet-token-gateway", "pallet-token-governor", "pallet-xcm", "parachains-common", @@ -12948,7 +12950,6 @@ dependencies = [ "frame-system", "ismp", "log", - "pallet-asset-gateway", "pallet-ismp", "pallet-token-governor", "parity-scale-codec", diff --git a/Cargo.toml b/Cargo.toml index 6c498ede0..af59e9d87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -305,6 +305,7 @@ pallet-asset-gateway = { path = "modules/ismp/pallets/asset-gateway", default-fe pallet-token-governor = { path = "modules/ismp/pallets/token-governor", default-features = false } pallet-state-coprocessor = { path = "modules/ismp/pallets/state-coprocessor", default-features = false } pallet-mmr = { path = "modules/trees/mmr/pallet", default-features = false } +pallet-token-gateway = { path = "modules/ismp/pallets/token-gateway", default-features = false } # merkle trees pallet-mmr-runtime-api = { path = "modules/trees/mmr/pallet/runtime-api", default-features = false } diff --git a/modules/ismp/pallets/asset-gateway/Cargo.toml b/modules/ismp/pallets/asset-gateway/Cargo.toml index 49f60f2f5..b540a1726 100644 --- a/modules/ismp/pallets/asset-gateway/Cargo.toml +++ b/modules/ismp/pallets/asset-gateway/Cargo.toml @@ -35,6 +35,7 @@ pallet-xcm = { workspace = true } staging-xcm = { workspace = true } staging-xcm-builder = { workspace = true } staging-xcm-executor = { workspace = true } +pallet-token-gateway = { workspace = true } [features] default = ["std"] @@ -56,6 +57,7 @@ std = [ "pallet-token-governor/std", "alloy-sol-types/std", "alloy-primitives/std", - "anyhow/std" + "anyhow/std", + "pallet-token-gateway/std" ] try-runtime = [] diff --git a/modules/ismp/pallets/asset-gateway/src/lib.rs b/modules/ismp/pallets/asset-gateway/src/lib.rs index 8f4ea9c83..9734ab8cc 100644 --- a/modules/ismp/pallets/asset-gateway/src/lib.rs +++ b/modules/ismp/pallets/asset-gateway/src/lib.rs @@ -20,6 +20,10 @@ extern crate alloc; use alloc::{boxed::Box, string::ToString, vec}; use alloy_sol_types::SolType; use core::marker::PhantomData; +use pallet_token_gateway::{ + impls::{convert_to_balance, convert_to_erc20}, + types::Body, +}; use pallet_token_governor::TokenGatewayParams; use frame_support::{ @@ -273,22 +277,6 @@ where } } -alloy_sol_macro::sol! { - #![sol(all_derives)] - struct Body { - // Amount of the asset to be sent - uint256 amount; - // The asset identifier - bytes32 asset_id; - // Flag to redeem the erc20 asset on the destination - bool redeem; - // Sender address - bytes32 from; - // Recipient address - bytes32 to; - } -} - #[derive(Clone)] pub struct Module(PhantomData); @@ -514,63 +502,3 @@ where } } } - -/// Converts an ERC20 U256 to a DOT u128 -pub fn convert_to_balance(value: U256) -> Result { - let dec_str = (value / U256::from(100_000_000u128)).to_string(); - dec_str.parse().map_err(|e| anyhow::anyhow!("{e:?}")) -} - -/// Converts a DOT u128 to an Erc20 denomination -pub fn convert_to_erc20(value: u128) -> U256 { - U256::from(value) * U256::from(100_000_000u128) -} - -#[cfg(test)] -mod tests { - use sp_core::U256; - use sp_runtime::Permill; - use std::ops::Mul; - - use crate::{convert_to_balance, convert_to_erc20}; - #[test] - fn test_per_mill() { - let per_mill = Permill::from_parts(1_000); - - println!("{}", per_mill.mul(20_000_000u128)); - } - - #[test] - fn balance_conversions() { - let supposedly_small_u256 = U256::from_dec_str("1000000000000000000").unwrap(); - // convert erc20 value to dot value - let converted_balance = convert_to_balance(supposedly_small_u256).unwrap(); - println!("{}", converted_balance); - - let dot = 10_000_000_000u128; - - assert_eq!(converted_balance, dot); - - // Convert 1 dot to erc20 - - let dot = 10_000_000_000u128; - let erc_20_val = convert_to_erc20(dot); - assert_eq!(erc_20_val, U256::from_dec_str("1000000000000000000").unwrap()); - } - - #[test] - fn max_value_check() { - let max = U256::MAX; - - let converted_balance = convert_to_balance(max); - assert!(converted_balance.is_err()) - } - - #[test] - fn min_value_check() { - let min = U256::from(1u128); - - let converted_balance = convert_to_balance(min).unwrap(); - assert_eq!(converted_balance, 0); - } -} diff --git a/modules/ismp/pallets/testsuite/Cargo.toml b/modules/ismp/pallets/testsuite/Cargo.toml index 1ea103cc9..a177018ff 100644 --- a/modules/ismp/pallets/testsuite/Cargo.toml +++ b/modules/ismp/pallets/testsuite/Cargo.toml @@ -45,6 +45,7 @@ pallet-ismp-relayer = { workspace = true, default-features = true } pallet-fishermen = { workspace = true, default-features = true } pallet-call-decompressor = { workspace = true, default-features = true } pallet-asset-gateway = { workspace = true, default-features = true } +pallet-token-gateway = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } mmr-primitives = { workspace = true, default-features = true } pallet-mmr = { workspace = true, default-features = true } diff --git a/modules/ismp/pallets/testsuite/src/tests/pallet_asset_gateway.rs b/modules/ismp/pallets/testsuite/src/tests/pallet_asset_gateway.rs index 53f66bf89..bbccfb101 100644 --- a/modules/ismp/pallets/testsuite/src/tests/pallet_asset_gateway.rs +++ b/modules/ismp/pallets/testsuite/src/tests/pallet_asset_gateway.rs @@ -14,7 +14,8 @@ use ismp::{ module::IsmpModule, router::{PostRequest, Request, Timeout}, }; -use pallet_asset_gateway::{convert_to_erc20, Body, Module}; +use pallet_asset_gateway::Module; +use pallet_token_gateway::{impls::convert_to_erc20, types::Body}; use sp_core::{ByteArray, H160}; use staging_xcm::v4::{Junction, Junctions, Location, NetworkId, WeightLimit}; use xcm_simulator::TestExt; diff --git a/modules/ismp/pallets/testsuite/src/tests/xcm_integration_test.rs b/modules/ismp/pallets/testsuite/src/tests/xcm_integration_test.rs index edd698c48..bcdf1068f 100644 --- a/modules/ismp/pallets/testsuite/src/tests/xcm_integration_test.rs +++ b/modules/ismp/pallets/testsuite/src/tests/xcm_integration_test.rs @@ -128,7 +128,8 @@ async fn should_dispatch_ismp_request_when_xcm_is_received() -> anyhow::Result<( _ => None, }) { let body = - pallet_asset_gateway::Body::abi_decode(&mut &post.body[1..], true).unwrap(); + pallet_token_gateway::types::Body::abi_decode(&mut &post.body[1..], true) + .unwrap(); let to = alloy_primitives::FixedBytes::<32>::from_slice( &vec![vec![0u8; 12], vec![1u8; 20]].concat(), ); diff --git a/modules/ismp/pallets/token-gateway/Cargo.toml b/modules/ismp/pallets/token-gateway/Cargo.toml index 69e20c39d..3eb363fd4 100644 --- a/modules/ismp/pallets/token-gateway/Cargo.toml +++ b/modules/ismp/pallets/token-gateway/Cargo.toml @@ -26,7 +26,6 @@ alloy-primitives = { workspace = true } alloy-sol-macro = { workspace = true } alloy-sol-types = { workspace = true } -pallet-asset-gateway = { workspace = true } pallet-token-governor = { workspace = true } [features] @@ -44,7 +43,6 @@ std = [ "scale-info/std", "anyhow/std", "alloy-primitives/std", - "pallet-asset-gateway/std", "pallet-token-governor/std" ] try-runtime = [] diff --git a/modules/ismp/pallets/token-gateway/src/impls.rs b/modules/ismp/pallets/token-gateway/src/impls.rs index 7a0493c27..ce42272e8 100644 --- a/modules/ismp/pallets/token-gateway/src/impls.rs +++ b/modules/ismp/pallets/token-gateway/src/impls.rs @@ -15,12 +15,157 @@ // Pallet Implementations -use sp_runtime::traits::AccountIdConversion; +use alloc::string::ToString; +use codec::{Decode, Encode}; +use frame_support::ensure; +use ismp::{ + dispatcher::{FeeMetadata, IsmpDispatcher}, + events::Meta, + module::IsmpModule, + router::{PostRequest, PostResponse}, +}; +use pallet_token_governor::TokenGatewayParams; +use sp_core::{ConstU32, U256}; +use sp_runtime::{traits::AccountIdConversion, BoundedVec}; -use crate::{Config, Pallet, PALLET_ID}; +use crate::{ + Config, Event, Pallet, TokenGatewayAddressRequest, TokenGatewayAddressResponse, PALLET_ID, +}; impl Pallet { pub fn pallet_account() -> T::AccountId { PALLET_ID.into_account_truncating() } } + +/// Converts an ERC20 U256 to a DOT u128 +pub fn convert_to_balance(value: U256) -> Result { + let dec_str = (value / U256::from(100_000_000u128)).to_string(); + dec_str.parse().map_err(|e| anyhow::anyhow!("{e:?}")) +} + +/// Converts a DOT u128 to an Erc20 denomination +pub fn convert_to_erc20(value: u128) -> U256 { + U256::from(value) * U256::from(100_000_000u128) +} + +pub struct TokenGatewayAddressModule(core::marker::PhantomData); + +impl Default for TokenGatewayAddressModule { + fn default() -> Self { + Self(core::marker::PhantomData) + } +} + +impl IsmpModule for TokenGatewayAddressModule +where + ::AccountId: From<[u8; 32]>, +{ + fn on_accept(&self, post: PostRequest) -> Result<(), ismp::Error> { + let PostRequest { body: data, from, source, dest, nonce, .. } = post.clone(); + // Check that source module is equal to the known token gateway deployment address + ensure!( + from == PALLET_ID.0.to_vec(), + ismp::error::Error::ModuleDispatchError { + msg: "Token Gateway: Unknown source contract address".to_string(), + meta: Meta { source, dest, nonce }, + } + ); + + ensure!( + source.is_substrate(), + ismp::error::Error::ModuleDispatchError { + msg: "Token Gateway: Illegal source chain".to_string(), + meta: Meta { source, dest, nonce }, + } + ); + + let req = TokenGatewayAddressRequest::decode(&mut &*data).map_err(|err| { + ismp::error::Error::Custom(format!("Invalid request from a substrate chain: {err}")) + })?; + let mut addresses = BoundedVec::<_, ConstU32<5>>::new(); + for state_machine in req.chains { + if let Some(params) = TokenGatewayParams::::get(&state_machine) { + addresses.try_push((state_machine, params.address)).map_err(|err| { + ismp::error::Error::Custom(alloc::format!( + "Maximum of 5 state machines can be requested: {err:?}" + )) + })?; + } + } + + if !addresses.is_empty() { + let response = TokenGatewayAddressResponse { addresses }; + let dispatcher = ::Dispatcher::default(); + + let post_response = + PostResponse { post, response: response.encode(), timeout_timestamp: 0 }; + + let commitment = dispatcher.dispatch_response( + post_response, + FeeMetadata { payer: [0u8; 32].into(), fee: Default::default() }, + )?; + + Pallet::::deposit_event(Event::::ResponseDispatched { dest: source, commitment }); + } + return Ok(()) + } + + fn on_response(&self, _response: ismp::router::Response) -> Result<(), ismp::Error> { + Err(ismp::error::Error::Custom("Module does not accept responses".to_string())) + } + + fn on_timeout(&self, _request: ismp::router::Timeout) -> Result<(), ismp::Error> { + Err(ismp::error::Error::Custom("Module does not accept timeouts".to_string())) + } +} + +#[cfg(test)] +mod tests { + use sp_core::U256; + use sp_runtime::Permill; + use std::ops::Mul; + + use super::{convert_to_balance, convert_to_erc20}; + + #[test] + fn test_per_mill() { + let per_mill = Permill::from_parts(1_000); + + println!("{}", per_mill.mul(20_000_000u128)); + } + + #[test] + fn balance_conversions() { + let supposedly_small_u256 = U256::from_dec_str("1000000000000000000").unwrap(); + // convert erc20 value to dot value + let converted_balance = convert_to_balance(supposedly_small_u256).unwrap(); + println!("{}", converted_balance); + + let dot = 10_000_000_000u128; + + assert_eq!(converted_balance, dot); + + // Convert 1 dot to erc20 + + let dot = 10_000_000_000u128; + let erc_20_val = convert_to_erc20(dot); + assert_eq!(erc_20_val, U256::from_dec_str("1000000000000000000").unwrap()); + } + + #[test] + fn max_value_check() { + let max = U256::MAX; + + let converted_balance = convert_to_balance(max); + assert!(converted_balance.is_err()) + } + + #[test] + fn min_value_check() { + let min = U256::from(1u128); + + let converted_balance = convert_to_balance(min).unwrap(); + assert_eq!(converted_balance, 0); + } +} diff --git a/modules/ismp/pallets/token-gateway/src/lib.rs b/modules/ismp/pallets/token-gateway/src/lib.rs index 45efba9ce..967b38b03 100644 --- a/modules/ismp/pallets/token-gateway/src/lib.rs +++ b/modules/ismp/pallets/token-gateway/src/lib.rs @@ -13,14 +13,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! The token governor handles asset registration as well as tracks the metadata of multi-chain -//! native tokens across all connected chains +//! The token gateway enables asset transfers to EVM instances of token gateway #![cfg_attr(not(feature = "std"), no_std)] extern crate alloc; -mod impls; -mod types; +pub mod impls; +pub mod types; +use crate::impls::{convert_to_balance, convert_to_erc20}; use alloy_sol_types::SolValue; use codec::Decode; use frame_support::{ @@ -37,8 +37,6 @@ use ismp::{ events::Meta, router::{PostRequest, Request, Response, Timeout}, }; -use pallet_asset_gateway::{convert_to_balance, Body}; -use pallet_token_governor::TokenGatewayAddressResponse; use sp_core::{Get, U256}; pub use types::*; @@ -64,8 +62,6 @@ pub mod pallet { dispatcher::{DispatchPost, DispatchRequest, FeeMetadata, IsmpDispatcher}, host::StateMachine, }; - use pallet_asset_gateway::{convert_to_erc20, Body}; - use pallet_token_governor::TokenGatewayAddressRequest; #[pallet::pallet] #[pallet::without_storage_info] @@ -86,7 +82,7 @@ pub mod pallet { /// Fungible asset implementation type Assets: fungibles::Mutate + fungibles::Inspect; - /// The native asset ID as registered on hyperbridge + /// The native asset ID type NativeAssetId: Get>; } @@ -96,7 +92,7 @@ pub mod pallet { pub type SupportedAssets = StorageMap<_, Identity, AssetId, H256, OptionQuery>; /// Assets supported by this instance of token gateway - /// A map of the local asset id to the token gateway asset id + /// A map of the token gateway asset id to the local asset id #[pallet::storage] pub type LocalAssets = StorageMap<_, Identity, H256, AssetId, OptionQuery>; @@ -144,6 +140,13 @@ pub mod pallet { }, /// Token Gateway address enquiry dispatched AddressEnquiryDispatched { commitment: H256 }, + /// Response dispatched + ResponseDispatched { + /// Destination state machine + dest: StateMachine, + /// Response commitment + commitment: H256, + }, } /// Errors that can be returned by this pallet. @@ -205,7 +208,7 @@ pub mod pallet { } // Dispatch Ismp request - + // Token gateway expected abi encoded address let mut to = [0u8; 32]; to[12..].copy_from_slice(¶ms.recepient.0); let from: [u8; 32] = who.clone().into(); @@ -267,7 +270,7 @@ pub mod pallet { let dispatch_post = DispatchPost { dest: T::Coprocessor::get().ok_or_else(|| Error::::CoprocessorNotConfigured)?, from: PALLET_ID.0.to_vec(), - to: pallet_token_governor::PALLET_ID.to_vec(), + to: PALLET_ID.0.to_vec(), timeout: 0, body: request.encode(), }; @@ -407,22 +410,7 @@ where fn on_timeout(&self, request: Timeout) -> Result<(), ismp::error::Error> { match request { - Timeout::Request(Request::Post(PostRequest { - body, - from, - source, - dest, - nonce, - .. - })) => { - ensure!( - from == TokenGatewayAddresses::::get(source).unwrap_or_default().0.to_vec(), - ismp::error::Error::ModuleDispatchError { - msg: "Token Gateway: Unknown source contract address".to_string(), - meta: Meta { source, dest, nonce }, - } - ); - + Timeout::Request(Request::Post(PostRequest { body, source, dest, nonce, .. })) => { let body = Body::abi_decode(&mut &body[1..], true).map_err(|_| { ismp::error::Error::ModuleDispatchError { msg: "Token Gateway: Failed to decode request body".to_string(), diff --git a/modules/ismp/pallets/token-gateway/src/types.rs b/modules/ismp/pallets/token-gateway/src/types.rs index 1a5f3f365..3c31d7653 100644 --- a/modules/ismp/pallets/token-gateway/src/types.rs +++ b/modules/ismp/pallets/token-gateway/src/types.rs @@ -40,14 +40,46 @@ pub struct TeleportParams { pub timeout: u64, } +/// Local asset Id and its corresponding token gateway asset id #[derive(Clone, Encode, Decode, scale_info::TypeInfo, PartialEq, Eq, RuntimeDebug)] pub struct AssetMap { pub local_id: AssetId, pub token_gateway_asset_id: H256, } +/// A struct for registering some assets #[derive(Clone, Encode, Decode, scale_info::TypeInfo, PartialEq, Eq, RuntimeDebug)] #[scale_info(skip_type_params(T))] pub struct AssetRegistration { pub assets: BoundedVec, ConstU32<5>>, } + +alloy_sol_macro::sol! { + #![sol(all_derives)] + struct Body { + // Amount of the asset to be sent + uint256 amount; + // The asset identifier + bytes32 asset_id; + // Flag to redeem the erc20 asset on the destination + bool redeem; + // Sender address + bytes32 from; + // Recipient address + bytes32 to; + } +} + +/// Struct for requesting the token gateway address for some state machines +#[derive(Debug, Clone, Encode, Decode, scale_info::TypeInfo, PartialEq, Eq, Default)] +pub struct TokenGatewayAddressRequest { + /// The chains whose token gateway addresses are being requested + pub chains: BoundedVec>, +} + +/// Struct for responding to token gateway address requests +#[derive(Debug, Clone, Encode, Decode, scale_info::TypeInfo, PartialEq, Eq, Default)] +pub struct TokenGatewayAddressResponse { + /// The token gateway address on diffirent chains + pub addresses: BoundedVec<(StateMachine, H160), ConstU32<5>>, +} diff --git a/modules/ismp/pallets/token-governor/src/lib.rs b/modules/ismp/pallets/token-governor/src/lib.rs index 93fc5286a..822e03c78 100644 --- a/modules/ismp/pallets/token-governor/src/lib.rs +++ b/modules/ismp/pallets/token-governor/src/lib.rs @@ -23,18 +23,13 @@ mod impls; mod types; use alloy_sol_types::SolValue; use frame_support::pallet_prelude::Weight; -use ismp::{ - dispatcher::{FeeMetadata, IsmpDispatcher}, - router::{PostRequest, PostResponse, Response, Timeout}, -}; -use sp_core::ConstU32; -use sp_runtime::BoundedVec; +use ismp::router::{PostRequest, Response, Timeout}; + pub use types::*; use alloc::{format, vec}; -use codec::{Decode, Encode}; use ismp::module::IsmpModule; -use primitive_types::{H160, H256}; +use primitive_types::{H160, H256, U256}; // Re-export pallet items so that they can be accessed from the crate namespace. pub use pallet::*; @@ -109,6 +104,15 @@ pub mod pallet { pub type TokenGatewayParams = StorageMap<_, Twox64Concat, StateMachine, GatewayParams, OptionQuery>; + /// Native asset ids for standalone chains connected to token gateway. + #[pallet::storage] + pub type StandaloneChainAssets = + StorageMap<_, Twox64Concat, StateMachine, H256, OptionQuery>; + + /// Balances for net inflow of non native assets into a standalone chain + #[pallet::storage] + pub type InflowBalances = StorageMap<_, Twox64Concat, H256, U256, OptionQuery>; + /// Pallet events that functions in this pallet can emit. #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] @@ -150,14 +154,6 @@ pub mod pallet { /// The new parameters new: Params<::Balance>, }, - - /// Response dispatched - ResponseDispatched { - /// Destination state machine - dest: StateMachine, - /// Response commitment - commitment: H256, - }, } /// Errors that can be returned by this pallet. @@ -324,6 +320,22 @@ pub mod pallet { Ok(()) } + + /// Register the token native asset ids for standalone chains + #[pallet::call_index(8)] + #[pallet::weight(weight())] + pub fn register_standalone_chain_native_assets( + origin: OriginFor, + assets: BTreeMap, + ) -> DispatchResult { + T::AdminOrigin::ensure_origin(origin)?; + + for (state_machine, asset_id) in assets { + StandaloneChainAssets::::insert(state_machine, asset_id); + } + + Ok(()) + } } /// This allows users to create assets from any chain using the TokenRegistrar. @@ -380,44 +392,11 @@ pub mod pallet { } } -impl IsmpModule for Pallet -where - ::AccountId: From<[u8; 32]>, -{ - fn on_accept(&self, post: PostRequest) -> Result<(), ismp::error::Error> { - let PostRequest { body: data, from, source, .. } = post.clone(); - // We only accept messages from substrate chains requesting for Token Gateway Addresses - if source.is_substrate() { - let req = TokenGatewayAddressRequest::decode(&mut &*data) - .map_err(|err| ismp::error::Error::Custom(format!("Decode error: {err}")))?; - let mut addresses = BoundedVec::<_, ConstU32<5>>::new(); - for state_machine in req.chains { - if let Some(params) = TokenGatewayParams::::get(&state_machine) { - addresses.try_push((state_machine, params.address)).map_err(|err| { - ismp::error::Error::Custom(alloc::format!( - "Maximum of 5 state machines can be requested: {err:?}" - )) - })?; - } - } - - if !addresses.is_empty() { - let response = TokenGatewayAddressResponse { addresses }; - let dispatcher = ::Dispatcher::default(); - - let post_response = - PostResponse { post, response: response.encode(), timeout_timestamp: 0 }; - - let commitment = dispatcher.dispatch_response( - post_response, - FeeMetadata { payer: [0u8; 32].into(), fee: Default::default() }, - )?; - - Self::deposit_event(Event::::ResponseDispatched { dest: source, commitment }); - } - return Ok(()) - } - +impl IsmpModule for Pallet { + fn on_accept( + &self, + PostRequest { body: data, from, source, .. }: PostRequest, + ) -> Result<(), ismp::error::Error> { let RegistrarParams { address, .. } = TokenRegistrarParams::::get(&source) .ok_or_else(|| ismp::error::Error::Custom(format!("Pallet is not initialized")))?; if from != address.as_bytes().to_vec() { diff --git a/modules/ismp/pallets/token-governor/src/types.rs b/modules/ismp/pallets/token-governor/src/types.rs index ef3034650..daad26a9c 100644 --- a/modules/ismp/pallets/token-governor/src/types.rs +++ b/modules/ismp/pallets/token-governor/src/types.rs @@ -410,17 +410,3 @@ impl TokenGatewayRequest for SolContractInstance { [variant, encoded].concat() } } - -/// Struct for requesting the token gateway address for some state machines -#[derive(Debug, Clone, Encode, Decode, scale_info::TypeInfo, PartialEq, Eq, Default)] -pub struct TokenGatewayAddressRequest { - /// The chains whose token gateway addresses are being requested - pub chains: BoundedVec>, -} - -/// Struct for responding to token gateway address requests -#[derive(Debug, Clone, Encode, Decode, scale_info::TypeInfo, PartialEq, Eq, Default)] -pub struct TokenGatewayAddressResponse { - /// The token gateway address on diffirent chains - pub addresses: BoundedVec<(StateMachine, H160), ConstU32<5>>, -} From b07228d942c6ed8fe8d96a21b2f505ae66c5c2a5 Mon Sep 17 00:00:00 2001 From: David Salami Date: Fri, 4 Oct 2024 14:53:34 +0000 Subject: [PATCH 03/15] implement token gateway inspector --- Cargo.lock | 24 ++ Cargo.toml | 2 + modules/ismp/pallets/testsuite/Cargo.toml | 1 + modules/ismp/pallets/testsuite/src/runtime.rs | 47 +++- .../ismp/pallets/testsuite/src/tests/mod.rs | 2 + .../src/tests/pallet_token_gateway.rs | 266 ++++++++++++++++++ .../token-gateway-inspector/Cargo.toml | 50 ++++ .../token-gateway-inspector/src/lib.rs | 240 ++++++++++++++++ .../ismp/pallets/token-gateway/src/impls.rs | 2 + .../ismp/pallets/token-governor/src/lib.rs | 27 +- 10 files changed, 633 insertions(+), 28 deletions(-) create mode 100644 modules/ismp/pallets/testsuite/src/tests/pallet_token_gateway.rs create mode 100644 modules/ismp/pallets/token-gateway-inspector/Cargo.toml create mode 100644 modules/ismp/pallets/token-gateway-inspector/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 63f60a48f..f0a23921d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12352,6 +12352,7 @@ dependencies = [ "pallet-sudo", "pallet-timestamp", "pallet-token-gateway", + "pallet-token-gateway-inspector", "pallet-token-governor", "pallet-xcm", "parachains-common", @@ -12960,6 +12961,29 @@ dependencies = [ "sp-runtime 39.0.0", ] +[[package]] +name = "pallet-token-gateway-inspector" +version = "0.1.0" +dependencies = [ + "alloy-primitives", + "alloy-sol-macro", + "alloy-sol-types", + "anyhow", + "frame-support 37.0.0", + "frame-system", + "ismp", + "log", + "pallet-ismp", + "pallet-token-gateway", + "pallet-token-governor", + "parity-scale-codec", + "primitive-types", + "scale-info", + "sp-core 34.0.0", + "sp-io 38.0.0", + "sp-runtime 39.0.0", +] + [[package]] name = "pallet-token-governor" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index af59e9d87..6410f30ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ members = [ "modules/ismp/pallets/asset-gateway", "modules/ismp/pallets/token-governor", "modules/ismp/pallets/token-gateway", + "modules/ismp/pallets/token-gateway-inspector", "modules/ismp/pallets/hyperbridge", "modules/ismp/pallets/state-coprocessor", "modules/ismp/testsuite", @@ -306,6 +307,7 @@ pallet-token-governor = { path = "modules/ismp/pallets/token-governor", default- pallet-state-coprocessor = { path = "modules/ismp/pallets/state-coprocessor", default-features = false } pallet-mmr = { path = "modules/trees/mmr/pallet", default-features = false } pallet-token-gateway = { path = "modules/ismp/pallets/token-gateway", default-features = false } +pallet-token-gateway-inspector = { path = "modules/ismp/pallets/token-gateway-inspector", default-features = false } # merkle trees pallet-mmr-runtime-api = { path = "modules/trees/mmr/pallet/runtime-api", default-features = false } diff --git a/modules/ismp/pallets/testsuite/Cargo.toml b/modules/ismp/pallets/testsuite/Cargo.toml index a177018ff..c0f0cab58 100644 --- a/modules/ismp/pallets/testsuite/Cargo.toml +++ b/modules/ismp/pallets/testsuite/Cargo.toml @@ -50,6 +50,7 @@ sp-state-machine = { workspace = true, default-features = true } mmr-primitives = { workspace = true, default-features = true } pallet-mmr = { workspace = true, default-features = true } pallet-token-governor = { workspace = true, default-features = true } +pallet-token-gateway-inspector = { workspace = true, default-features = true } # Polkadot pallet-xcm = { workspace = true, default-features = true } diff --git a/modules/ismp/pallets/testsuite/src/runtime.rs b/modules/ismp/pallets/testsuite/src/runtime.rs index 068c1f0d4..1ff0ce420 100644 --- a/modules/ismp/pallets/testsuite/src/runtime.rs +++ b/modules/ismp/pallets/testsuite/src/runtime.rs @@ -39,16 +39,21 @@ use ismp::{ }; use ismp_sync_committee::constants::sepolia::Sepolia; use pallet_ismp::{mmr::Leaf, ModuleId}; +use pallet_token_governor::GatewayParams; use sp_core::{ crypto::AccountId32, offchain::{testing::TestOffchainExt, OffchainDbExt, OffchainWorkerExt}, - H256, + H160, H256, }; use sp_runtime::{ traits::{IdentityLookup, Keccak256}, BuildStorage, }; +use staging_xcm::prelude::Location; use substrate_state_machine::SubstrateStateMachine; +use xcm_simulator_example::ALICE; + +pub const INITIAL_BALANCE: u128 = 1_000_000_000_000_000_000; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -75,6 +80,8 @@ frame_support::construct_runtime!( TokenGovernor: pallet_token_governor, Sudo: pallet_sudo, IsmpSyncCommittee: ismp_sync_committee::pallet, + TokenGateway: pallet_token_gateway, + TokenGatewayInspector: pallet_token_gateway_inspector, } ); @@ -203,6 +210,22 @@ impl pallet_hyperbridge::Config for Test { type IsmpHost = Ismp; } +parameter_types! { + pub const NativeAssetId: Location = Location::here(); +} + +impl pallet_token_gateway::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Dispatcher = Ismp; + type Assets = Assets; + type Currency = Balances; + type NativeAssetId = NativeAssetId; +} + +impl pallet_token_gateway_inspector::Config for Test { + type RuntimeEvent = RuntimeEvent; +} + impl ismp_sync_committee::pallet::Config for Test { type AdminOrigin = EnsureRoot; type IsmpHost = Ismp; @@ -424,7 +447,10 @@ where pub fn new_test_ext() -> sp_io::TestExternalities { let _ = env_logger::builder().is_test(true).try_init(); - let storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); + let mut storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); + pallet_balances::GenesisConfig:: { balances: vec![(ALICE, INITIAL_BALANCE)] } + .assimilate_storage(&mut storage) + .unwrap(); let mut ext = sp_io::TestExternalities::new(storage); register_offchain_ext(&mut ext); @@ -435,6 +461,23 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pallet_token_governor::Params:: { registration_fee: Default::default() }; pallet_token_governor::ProtocolParams::::put(protocol_params); + pallet_token_gateway::SupportedAssets::::insert(Location::here(), H256::zero()); + pallet_token_gateway::LocalAssets::::insert(H256::zero(), Location::here()); + pallet_token_gateway::TokenGatewayAddresses::::insert( + StateMachine::Evm(1), + H160::zero(), + ); + pallet_token_gateway_inspector::StandaloneChainAssets::::insert( + StateMachine::Kusama(100), + H256::zero(), + ); + + let params = GatewayParams { + address: H160::zero(), + host: H160::zero(), + call_dispatcher: H160::random(), + }; + pallet_token_governor::TokenGatewayParams::::insert(StateMachine::Evm(1), params); }); ext } diff --git a/modules/ismp/pallets/testsuite/src/tests/mod.rs b/modules/ismp/pallets/testsuite/src/tests/mod.rs index cf467107c..15b9530b0 100644 --- a/modules/ismp/pallets/testsuite/src/tests/mod.rs +++ b/modules/ismp/pallets/testsuite/src/tests/mod.rs @@ -8,3 +8,5 @@ mod pallet_ismp_host_executive; mod pallet_ismp_relayer; mod xcm_integration_test; + +mod pallet_token_gateway; diff --git a/modules/ismp/pallets/testsuite/src/tests/pallet_token_gateway.rs b/modules/ismp/pallets/testsuite/src/tests/pallet_token_gateway.rs new file mode 100644 index 000000000..ed1fd5afe --- /dev/null +++ b/modules/ismp/pallets/testsuite/src/tests/pallet_token_gateway.rs @@ -0,0 +1,266 @@ +use alloy_sol_types::SolValue; +use ismp::{ + host::StateMachine, + router::{PostRequest, Request, Timeout}, +}; +use pallet_token_gateway::{impls::convert_to_erc20, Body, TeleportParams}; +use sp_core::{ByteArray, H160, H256, U256}; +use staging_xcm::prelude::Location; +use xcm_simulator_example::ALICE; + +use crate::runtime::{ + new_test_ext, RuntimeOrigin, Test, TokenGateway, TokenGatewayInspector, INITIAL_BALANCE, +}; +use ismp::module::IsmpModule; + +const SEND_AMOUNT: u128 = 1000_000_000_0000; +#[test] +fn should_teleport_asset_correctly() { + new_test_ext().execute_with(|| { + let params = TeleportParams { + asset_id: Location::here(), + destination: StateMachine::Evm(1), + recepient: H160::random(), + timeout: 0, + amount: SEND_AMOUNT, + }; + + TokenGateway::teleport(RuntimeOrigin::signed(ALICE), params).unwrap(); + + let new_balance = pallet_balances::Pallet::::free_balance(ALICE); + + assert_eq!(new_balance, INITIAL_BALANCE - SEND_AMOUNT); + }) +} + +#[test] +fn should_receive_asset_correctly() { + new_test_ext().execute_with(|| { + let params = TeleportParams { + asset_id: Location::here(), + destination: StateMachine::Evm(1), + recepient: H160::random(), + timeout: 0, + amount: SEND_AMOUNT, + }; + + TokenGateway::teleport(RuntimeOrigin::signed(ALICE), params).unwrap(); + + let new_balance = pallet_balances::Pallet::::free_balance(ALICE); + + assert_eq!(new_balance, INITIAL_BALANCE - SEND_AMOUNT); + + let module = TokenGateway::default(); + let post = PostRequest { + source: StateMachine::Evm(1), + dest: StateMachine::Kusama(100), + nonce: 0, + from: H160::zero().0.to_vec(), + to: H160::zero().0.to_vec(), + timeout_timestamp: 1000, + body: { + let body = Body { + amount: { + let mut bytes = [0u8; 32]; + // Module callback will convert to ten decimals + convert_to_erc20(SEND_AMOUNT).to_big_endian(&mut bytes); + alloy_primitives::U256::from_be_bytes(bytes) + }, + asset_id: H256::zero().0.into(), + redeem: false, + from: alloy_primitives::B256::from_slice(ALICE.as_slice()), + to: alloy_primitives::B256::from_slice(ALICE.as_slice()), + }; + + let encoded = vec![vec![0], Body::abi_encode(&body)].concat(); + encoded + }, + }; + + module.on_accept(post).unwrap(); + let new_balance = pallet_balances::Pallet::::free_balance(ALICE); + + assert_eq!(new_balance, INITIAL_BALANCE); + }); +} + +#[test] +fn should_timeout_request_correctly() { + new_test_ext().execute_with(|| { + let params = TeleportParams { + asset_id: Location::here(), + destination: StateMachine::Evm(1), + recepient: H160::random(), + timeout: 0, + amount: SEND_AMOUNT, + }; + + TokenGateway::teleport(RuntimeOrigin::signed(ALICE), params).unwrap(); + + let new_balance = pallet_balances::Pallet::::free_balance(ALICE); + + assert_eq!(new_balance, INITIAL_BALANCE - SEND_AMOUNT); + + let module = TokenGateway::default(); + let post = PostRequest { + source: StateMachine::Evm(1), + dest: StateMachine::Kusama(100), + nonce: 0, + from: H160::zero().0.to_vec(), + to: H160::zero().0.to_vec(), + timeout_timestamp: 1000, + body: { + let body = Body { + amount: { + let mut bytes = [0u8; 32]; + // Module callback will convert to ten decimals + convert_to_erc20(SEND_AMOUNT).to_big_endian(&mut bytes); + alloy_primitives::U256::from_be_bytes(bytes) + }, + asset_id: H256::zero().0.into(), + redeem: false, + from: alloy_primitives::B256::from_slice(ALICE.as_slice()), + to: alloy_primitives::B256::from_slice(ALICE.as_slice()), + }; + + let encoded = vec![vec![0], Body::abi_encode(&body)].concat(); + encoded + }, + }; + + module.on_timeout(Timeout::Request(Request::Post(post))).unwrap(); + let new_balance = pallet_balances::Pallet::::free_balance(ALICE); + + assert_eq!(new_balance, INITIAL_BALANCE); + }); +} + +#[test] +fn inspector_should_intercept_illegal_request() { + new_test_ext().execute_with(|| { + let asset_id: H256 = [1u8; 32].into(); + let post = PostRequest { + source: StateMachine::Kusama(100), + dest: StateMachine::Evm(1), + nonce: 0, + from: H160::zero().0.to_vec(), + to: H160::zero().0.to_vec(), + timeout_timestamp: 1000, + body: { + let body = Body { + amount: { + let mut bytes = [0u8; 32]; + // Module callback will convert to ten decimals + convert_to_erc20(SEND_AMOUNT).to_big_endian(&mut bytes); + alloy_primitives::U256::from_be_bytes(bytes) + }, + asset_id: asset_id.0.into(), + redeem: false, + from: alloy_primitives::B256::from_slice(ALICE.as_slice()), + to: alloy_primitives::B256::from_slice(ALICE.as_slice()), + }; + + let encoded = vec![vec![0], Body::abi_encode(&body)].concat(); + encoded + }, + }; + + let result = TokenGatewayInspector::inspect_request(&post); + println!("{result:?}"); + assert!(result.is_err()); + + pallet_token_gateway_inspector::InflowBalances::::insert( + asset_id, + convert_to_erc20(SEND_AMOUNT), + ); + + let result = TokenGatewayInspector::inspect_request(&post); + assert!(result.is_ok()); + let inflow = pallet_token_gateway_inspector::InflowBalances::::get(asset_id); + assert_eq!(inflow, U256::zero()); + }); +} + +#[test] +fn inspector_should_record_non_native_asset_inflow() { + new_test_ext().execute_with(|| { + let asset_id: H256 = [1u8; 32].into(); + let post = PostRequest { + source: StateMachine::Evm(1), + dest: StateMachine::Kusama(100), + nonce: 0, + from: H160::zero().0.to_vec(), + to: H160::zero().0.to_vec(), + timeout_timestamp: 1000, + body: { + let body = Body { + amount: { + let mut bytes = [0u8; 32]; + // Module callback will convert to ten decimals + convert_to_erc20(SEND_AMOUNT).to_big_endian(&mut bytes); + alloy_primitives::U256::from_be_bytes(bytes) + }, + asset_id: asset_id.0.into(), + redeem: false, + from: alloy_primitives::B256::from_slice(ALICE.as_slice()), + to: alloy_primitives::B256::from_slice(ALICE.as_slice()), + }; + + let encoded = vec![vec![0], Body::abi_encode(&body)].concat(); + encoded + }, + }; + + let result = TokenGatewayInspector::inspect_request(&post); + println!("{result:?}"); + assert!(result.is_ok()); + + let inflow = pallet_token_gateway_inspector::InflowBalances::::get(asset_id); + + assert_eq!(convert_to_erc20(SEND_AMOUNT), inflow); + }); +} + +#[test] +fn inspector_should_handle_timeout_correctly() { + new_test_ext().execute_with(|| { + let asset_id: H256 = [1u8; 32].into(); + let post = PostRequest { + source: StateMachine::Kusama(100), + dest: StateMachine::Evm(1), + nonce: 0, + from: H160::zero().0.to_vec(), + to: H160::zero().0.to_vec(), + timeout_timestamp: 1000, + body: { + let body = Body { + amount: { + let mut bytes = [0u8; 32]; + // Module callback will convert to ten decimals + convert_to_erc20(SEND_AMOUNT).to_big_endian(&mut bytes); + alloy_primitives::U256::from_be_bytes(bytes) + }, + asset_id: asset_id.0.into(), + redeem: false, + from: alloy_primitives::B256::from_slice(ALICE.as_slice()), + to: alloy_primitives::B256::from_slice(ALICE.as_slice()), + }; + + let encoded = vec![vec![0], Body::abi_encode(&body)].concat(); + encoded + }, + }; + + let inflow = pallet_token_gateway_inspector::InflowBalances::::get(asset_id); + + assert_eq!(inflow, U256::zero()); + + let result = TokenGatewayInspector::handle_timeout(&post); + println!("{result:?}"); + assert!(result.is_ok()); + + let inflow = pallet_token_gateway_inspector::InflowBalances::::get(asset_id); + + assert_eq!(convert_to_erc20(SEND_AMOUNT), inflow); + }); +} diff --git a/modules/ismp/pallets/token-gateway-inspector/Cargo.toml b/modules/ismp/pallets/token-gateway-inspector/Cargo.toml new file mode 100644 index 000000000..c4249530e --- /dev/null +++ b/modules/ismp/pallets/token-gateway-inspector/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "pallet-token-gateway-inspector" +version = "0.1.0" +edition = "2021" +description = "The token gateway inspector ensures the validity of token gateway messages coming from standalone chains" +authors = ["Polytope Labs "] +publish = false + +[dependencies] +frame-support = { workspace = true } +frame-system = { workspace = true } +sp-runtime = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } +primitive-types = { workspace = true } + +ismp = { workspace = true } +pallet-ismp = { workspace = true } + +log = { workspace = true } +codec = { workspace = true } +scale-info = { workspace = true } +anyhow = { workspace = true } + +alloy-primitives = { workspace = true } +alloy-sol-macro = { workspace = true } +alloy-sol-types = { workspace = true } + +pallet-token-gateway = { workspace = true } +pallet-token-governor = { workspace = true } + +[features] +default = ["std"] +std = [ + "frame-support/std", + "frame-system/std", + "sp-runtime/std", + "sp-core/std", + "sp-io/std", + "primitive-types/std", + "ismp/std", + "pallet-ismp/std", + "log/std", + "scale-info/std", + "anyhow/std", + "alloy-primitives/std", + "pallet-token-gateway/std", + "pallet-token-governor/std", +] +try-runtime = [] diff --git a/modules/ismp/pallets/token-gateway-inspector/src/lib.rs b/modules/ismp/pallets/token-gateway-inspector/src/lib.rs new file mode 100644 index 000000000..f7a95a08b --- /dev/null +++ b/modules/ismp/pallets/token-gateway-inspector/src/lib.rs @@ -0,0 +1,240 @@ +// Copyright (C) Polytope Labs Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! The token governor handles asset registration as well as tracks the metadata of multi-chain +//! native tokens across all connected chains +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +use alloy_sol_types::SolValue; +use frame_support::pallet_prelude::Weight; +use ismp::router::PostRequest; + +use alloc::{format, vec}; +use primitive_types::{H256, U256}; + +// Re-export pallet items so that they can be accessed from the crate namespace. +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use alloc::collections::BTreeMap; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use ismp::{events::Meta, host::StateMachine}; + use pallet_token_gateway::Body; + use pallet_token_governor::TokenGatewayParams; + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(_); + + /// The pallet's configuration trait. + #[pallet::config] + pub trait Config: + frame_system::Config + pallet_ismp::Config + pallet_token_governor::Config + { + /// The overarching runtime event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + + /// Native asset ids for standalone chains connected to token gateway. + #[pallet::storage] + pub type StandaloneChainAssets = + StorageMap<_, Twox64Concat, StateMachine, H256, OptionQuery>; + + /// Balances for net inflow of non native assets into a standalone chain + #[pallet::storage] + pub type InflowBalances = StorageMap<_, Twox64Concat, H256, U256, ValueQuery>; + + /// Pallet events that functions in this pallet can emit. + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Illegal request has been intercepted + IllegalRequest { source: StateMachine }, + /// Native asset IDs have been registered + NativeAssetsRegistered { assets: BTreeMap }, + } + + /// Errors that can be returned by this pallet. + #[pallet::error] + pub enum Error {} + + #[pallet::call] + impl Pallet + where + ::AccountId: From<[u8; 32]>, + ::Balance: Default, + { + /// Register the token native asset ids for standalone chains + #[pallet::call_index(0)] + #[pallet::weight(weight())] + pub fn register_standalone_chain_native_assets( + origin: OriginFor, + assets: BTreeMap, + ) -> DispatchResult { + T::AdminOrigin::ensure_origin(origin)?; + + for (state_machine, asset_id) in assets.clone() { + StandaloneChainAssets::::insert(state_machine, asset_id); + } + + Self::deposit_event(Event::::NativeAssetsRegistered { assets }); + + Ok(()) + } + } + + // Hack for implementing the [`Default`] bound needed for + // [`IsmpDispatcher`](ismp::dispatcher::IsmpDispatcher) and + // [`IsmpModule`](ismp::module::IsmpModule) + impl Default for Pallet { + fn default() -> Self { + Self(PhantomData) + } + } + + impl Pallet { + pub fn inspect_request(post: &PostRequest) -> Result<(), ismp::Error> { + let PostRequest { body, from, to, source, dest, nonce, .. } = post.clone(); + // Case #1: if the source is EVM and dest is substrate then we want to record the inflow + // amount if it's not the native asset + + if let Some(token_gateway_address) = TokenGatewayParams::::get(source) { + if token_gateway_address.address.0.to_vec() == from && + source.is_evm() && dest.is_substrate() + { + let body = Body::abi_decode(&mut &body[1..], true).map_err(|_| { + ismp::error::Error::ModuleDispatchError { + msg: "Token Gateway: Failed to decode request body".to_string(), + meta: Meta { source, dest, nonce }, + } + })?; + + let native_asset_id = + StandaloneChainAssets::::get(dest).ok_or_else(|| { + ismp::Error::Custom(format!( + "Native asset id not registered for {dest}" + )) + })?; + + if native_asset_id.0 != body.asset_id.0 { + InflowBalances::::try_mutate(H256::from(body.asset_id.0), |val| { + let amount = U256::from_big_endian(&body.amount.to_be_bytes::<32>()); + *val += amount; + Ok::<_, ()>(()) + }) + .map_err(|_| { + ismp::Error::Custom(format!( + "Failed to record inflow while inspecting packet" + )) + })?; + } + } + } + + // Case #2: If the source is substrate and dest is EVM we want to ensure the amount is + // <= the total infow amount for that asset We also update the balance for that asset + + if let Some(token_gateway_address) = TokenGatewayParams::::get(dest) { + if token_gateway_address.address.0.to_vec() == to && + source.is_substrate() && + dest.is_evm() + { + let body = Body::abi_decode(&mut &body[1..], true).map_err(|_| { + ismp::error::Error::ModuleDispatchError { + msg: "Token Gateway: Failed to decode request body".to_string(), + meta: Meta { source, dest, nonce }, + } + })?; + + let native_asset_id = + StandaloneChainAssets::::get(source).ok_or_else(|| { + ismp::Error::Custom(format!( + "Native asset id not registered for {source}" + )) + })?; + if native_asset_id.0 != body.asset_id.0 { + let balance = InflowBalances::::get(H256::from(body.asset_id.0)); + let amount = U256::from_big_endian(&body.amount.to_be_bytes::<32>()); + if amount > balance { + Err(ismp::Error::Custom(format!("Illegal Token Gateway request")))?; + Pallet::::deposit_event(Event::::IllegalRequest { source }) + } + + InflowBalances::::try_mutate(H256::from(body.asset_id.0), |val| { + let amount = U256::from_big_endian(&body.amount.to_be_bytes::<32>()); + *val -= amount; + Ok::<_, ()>(()) + }) + .map_err(|_| { + ismp::Error::Custom(format!( + "Failed to record inflow while inspecting packet" + )) + })?; + } + } + } + + Ok(()) + } + + pub fn handle_timeout(post: &PostRequest) -> Result<(), ismp::Error> { + let PostRequest { body, to, source, dest, nonce, .. } = post.clone(); + if let Some(token_gateway_address) = TokenGatewayParams::::get(dest) { + if token_gateway_address.address.0.to_vec() == to && + source.is_substrate() && + dest.is_evm() + { + let body = Body::abi_decode(&mut &body[1..], true).map_err(|_| { + ismp::error::Error::ModuleDispatchError { + msg: "Token Gateway: Failed to decode request body".to_string(), + meta: Meta { source, dest, nonce }, + } + })?; + + let native_asset_id = + StandaloneChainAssets::::get(source).ok_or_else(|| { + ismp::Error::Custom(format!( + "Native asset id not registered for {source}" + )) + })?; + if native_asset_id.0 != body.asset_id.0 { + InflowBalances::::try_mutate(H256::from(body.asset_id.0), |val| { + let amount = U256::from_big_endian(&body.amount.to_be_bytes::<32>()); + *val += amount; + Ok::<_, ()>(()) + }) + .map_err(|_| { + ismp::Error::Custom(format!( + "Failed to record inflow while inspecting packet" + )) + })?; + } + } + } + + Ok(()) + } + } +} + +/// Static weights because benchmarks suck, and we'll be getting PolkaVM soon anyways +fn weight() -> Weight { + Weight::from_parts(300_000_000, 0) +} diff --git a/modules/ismp/pallets/token-gateway/src/impls.rs b/modules/ismp/pallets/token-gateway/src/impls.rs index ce42272e8..da9e00ac9 100644 --- a/modules/ismp/pallets/token-gateway/src/impls.rs +++ b/modules/ismp/pallets/token-gateway/src/impls.rs @@ -49,6 +49,8 @@ pub fn convert_to_erc20(value: u128) -> U256 { U256::from(value) * U256::from(100_000_000u128) } +/// An Ismp Module that receives requests from substrate chains requesting for the token gateway +/// addresses on EVM chains pub struct TokenGatewayAddressModule(core::marker::PhantomData); impl Default for TokenGatewayAddressModule { diff --git a/modules/ismp/pallets/token-governor/src/lib.rs b/modules/ismp/pallets/token-governor/src/lib.rs index 822e03c78..d72e38fd7 100644 --- a/modules/ismp/pallets/token-governor/src/lib.rs +++ b/modules/ismp/pallets/token-governor/src/lib.rs @@ -29,7 +29,7 @@ pub use types::*; use alloc::{format, vec}; use ismp::module::IsmpModule; -use primitive_types::{H160, H256, U256}; +use primitive_types::{H160, H256}; // Re-export pallet items so that they can be accessed from the crate namespace. pub use pallet::*; @@ -104,15 +104,6 @@ pub mod pallet { pub type TokenGatewayParams = StorageMap<_, Twox64Concat, StateMachine, GatewayParams, OptionQuery>; - /// Native asset ids for standalone chains connected to token gateway. - #[pallet::storage] - pub type StandaloneChainAssets = - StorageMap<_, Twox64Concat, StateMachine, H256, OptionQuery>; - - /// Balances for net inflow of non native assets into a standalone chain - #[pallet::storage] - pub type InflowBalances = StorageMap<_, Twox64Concat, H256, U256, OptionQuery>; - /// Pallet events that functions in this pallet can emit. #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] @@ -320,22 +311,6 @@ pub mod pallet { Ok(()) } - - /// Register the token native asset ids for standalone chains - #[pallet::call_index(8)] - #[pallet::weight(weight())] - pub fn register_standalone_chain_native_assets( - origin: OriginFor, - assets: BTreeMap, - ) -> DispatchResult { - T::AdminOrigin::ensure_origin(origin)?; - - for (state_machine, asset_id) in assets { - StandaloneChainAssets::::insert(state_machine, asset_id); - } - - Ok(()) - } } /// This allows users to create assets from any chain using the TokenRegistrar. From f8dfd5489959a4e792560d0a7435234ccf8f50a2 Mon Sep 17 00:00:00 2001 From: David Salami Date: Fri, 4 Oct 2024 15:08:14 +0000 Subject: [PATCH 04/15] integrate token gateway inspector --- Cargo.lock | 1 + .../pallets/token-gateway-inspector/src/lib.rs | 2 +- .../ismp/pallets/token-gateway/src/impls.rs | 2 +- modules/ismp/pallets/token-gateway/src/lib.rs | 2 +- parachain/runtimes/gargantua/Cargo.toml | 2 ++ parachain/runtimes/gargantua/src/ismp.rs | 18 ++++++++++++------ parachain/runtimes/gargantua/src/lib.rs | 3 ++- 7 files changed, 20 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f0a23921d..9324f27e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6115,6 +6115,7 @@ dependencies = [ "pallet-state-coprocessor", "pallet-sudo", "pallet-timestamp", + "pallet-token-gateway-inspector", "pallet-token-governor", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", diff --git a/modules/ismp/pallets/token-gateway-inspector/src/lib.rs b/modules/ismp/pallets/token-gateway-inspector/src/lib.rs index f7a95a08b..2a5028af5 100644 --- a/modules/ismp/pallets/token-gateway-inspector/src/lib.rs +++ b/modules/ismp/pallets/token-gateway-inspector/src/lib.rs @@ -23,7 +23,7 @@ use alloy_sol_types::SolValue; use frame_support::pallet_prelude::Weight; use ismp::router::PostRequest; -use alloc::{format, vec}; +use alloc::{format, string::ToString, vec}; use primitive_types::{H256, U256}; // Re-export pallet items so that they can be accessed from the crate namespace. diff --git a/modules/ismp/pallets/token-gateway/src/impls.rs b/modules/ismp/pallets/token-gateway/src/impls.rs index da9e00ac9..6dfb10b57 100644 --- a/modules/ismp/pallets/token-gateway/src/impls.rs +++ b/modules/ismp/pallets/token-gateway/src/impls.rs @@ -15,7 +15,7 @@ // Pallet Implementations -use alloc::string::ToString; +use alloc::{format, string::ToString}; use codec::{Decode, Encode}; use frame_support::ensure; use ismp::{ diff --git a/modules/ismp/pallets/token-gateway/src/lib.rs b/modules/ismp/pallets/token-gateway/src/lib.rs index 967b38b03..bf841eb9e 100644 --- a/modules/ismp/pallets/token-gateway/src/lib.rs +++ b/modules/ismp/pallets/token-gateway/src/lib.rs @@ -40,7 +40,7 @@ use ismp::{ use sp_core::{Get, U256}; pub use types::*; -use alloc::vec; +use alloc::{string::ToString, vec}; use ismp::module::IsmpModule; use primitive_types::{H160, H256}; diff --git a/parachain/runtimes/gargantua/Cargo.toml b/parachain/runtimes/gargantua/Cargo.toml index fa30f2144..c422c8305 100644 --- a/parachain/runtimes/gargantua/Cargo.toml +++ b/parachain/runtimes/gargantua/Cargo.toml @@ -101,6 +101,7 @@ pallet-mmr-runtime-api = { workspace = true } sp-mmr-primitives = { workspace = true } simnode-runtime-api = { workspace = true } hyperbridge-client-machine = { workspace = true } +pallet-token-gateway-inspector = { workspace = true } [features] default = [ @@ -173,6 +174,7 @@ std = [ "pallet-call-decompressor/std", "pallet-state-coprocessor/std", "pallet-asset-gateway/std", + "pallet-token-gateway-inspector/std", "pallet-token-governor/std", "pallet-assets/std", "pallet-mmr/std", diff --git a/parachain/runtimes/gargantua/src/ismp.rs b/parachain/runtimes/gargantua/src/ismp.rs index f3986b950..eec9769f0 100644 --- a/parachain/runtimes/gargantua/src/ismp.rs +++ b/parachain/runtimes/gargantua/src/ismp.rs @@ -16,7 +16,7 @@ use crate::{ alloc::{boxed::Box, string::ToString}, weights, AccountId, Assets, Balance, Balances, Gateway, Ismp, IsmpParachain, Mmr, - ParachainInfo, Runtime, RuntimeEvent, Timestamp, EXISTENTIAL_DEPOSIT, + ParachainInfo, Runtime, RuntimeEvent, Timestamp, TokenGatewayInspector, EXISTENTIAL_DEPOSIT, }; use frame_support::{ pallet_prelude::{ConstU32, Get}, @@ -154,6 +154,10 @@ impl pallet_asset_gateway::Config for Runtime { type IsmpHost = Ismp; } +impl pallet_token_gateway_inspector::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} + #[cfg(feature = "runtime-benchmarks")] pub struct XcmBenchmarkHelper; #[cfg(feature = "runtime-benchmarks")] @@ -198,10 +202,7 @@ impl pallet_assets::Config for Runtime { impl IsmpModule for ProxyModule { fn on_accept(&self, request: PostRequest) -> Result<(), Error> { if request.dest != HostStateMachine::get() { - let token_gateway = Gateway::token_gateway_address(&request.dest); - if request.source.is_substrate() && request.from == token_gateway.0.to_vec() { - Err(Error::Custom("Illegal request!".into()))? - } + TokenGatewayInspector::inspect_request(&request)?; Ismp::dispatch_request( Request::Post(request), @@ -250,7 +251,12 @@ impl IsmpModule for ProxyModule { fn on_timeout(&self, timeout: Timeout) -> Result<(), Error> { let (from, source) = match &timeout { - Timeout::Request(Request::Post(post)) => (&post.from, &post.source), + Timeout::Request(Request::Post(post)) => { + if post.source != HostStateMachine::get() { + TokenGatewayInspector::handle_timeout(post)?; + } + (&post.from, &post.source) + }, Timeout::Request(Request::Get(get)) => (&get.from, &get.source), Timeout::Response(res) => (&res.post.to, &res.post.dest), }; diff --git a/parachain/runtimes/gargantua/src/lib.rs b/parachain/runtimes/gargantua/src/lib.rs index 49e3bad05..f7403f566 100644 --- a/parachain/runtimes/gargantua/src/lib.rs +++ b/parachain/runtimes/gargantua/src/lib.rs @@ -232,7 +232,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("gargantua"), impl_name: create_runtime_str!("gargantua"), authoring_version: 1, - spec_version: 1130, + spec_version: 1140, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -708,6 +708,7 @@ construct_runtime!( TokenGovernor: pallet_token_governor = 59, StateCoprocessor: pallet_state_coprocessor = 60, Fishermen: pallet_fishermen = 61, + TokenGatewayInspector: pallet_token_gateway_inspector = 62, // Governance TechnicalCollective: pallet_collective = 80, From b60e8515d3a34c224c78f79074bf360d665d8756 Mon Sep 17 00:00:00 2001 From: David Salami Date: Fri, 4 Oct 2024 15:23:30 +0000 Subject: [PATCH 05/15] add token gateway address enquiry module to gargantua runtime --- Cargo.lock | 1 + modules/ismp/pallets/token-gateway/src/impls.rs | 12 ++++-------- modules/ismp/pallets/token-gateway/src/lib.rs | 7 ------- parachain/runtimes/gargantua/Cargo.toml | 2 ++ parachain/runtimes/gargantua/src/ismp.rs | 3 +++ 5 files changed, 10 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9324f27e3..77458e109 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6115,6 +6115,7 @@ dependencies = [ "pallet-state-coprocessor", "pallet-sudo", "pallet-timestamp", + "pallet-token-gateway", "pallet-token-gateway-inspector", "pallet-token-governor", "pallet-transaction-payment", diff --git a/modules/ismp/pallets/token-gateway/src/impls.rs b/modules/ismp/pallets/token-gateway/src/impls.rs index 6dfb10b57..130ce8772 100644 --- a/modules/ismp/pallets/token-gateway/src/impls.rs +++ b/modules/ismp/pallets/token-gateway/src/impls.rs @@ -28,9 +28,7 @@ use pallet_token_governor::TokenGatewayParams; use sp_core::{ConstU32, U256}; use sp_runtime::{traits::AccountIdConversion, BoundedVec}; -use crate::{ - Config, Event, Pallet, TokenGatewayAddressRequest, TokenGatewayAddressResponse, PALLET_ID, -}; +use crate::{Config, Pallet, TokenGatewayAddressRequest, TokenGatewayAddressResponse, PALLET_ID}; impl Pallet { pub fn pallet_account() -> T::AccountId { @@ -59,7 +57,7 @@ impl Default for TokenGatewayAddressModule { } } -impl IsmpModule for TokenGatewayAddressModule +impl IsmpModule for TokenGatewayAddressModule where ::AccountId: From<[u8; 32]>, { @@ -98,17 +96,15 @@ where if !addresses.is_empty() { let response = TokenGatewayAddressResponse { addresses }; - let dispatcher = ::Dispatcher::default(); + let dispatcher = ::Dispatcher::default(); let post_response = PostResponse { post, response: response.encode(), timeout_timestamp: 0 }; - let commitment = dispatcher.dispatch_response( + let _ = dispatcher.dispatch_response( post_response, FeeMetadata { payer: [0u8; 32].into(), fee: Default::default() }, )?; - - Pallet::::deposit_event(Event::::ResponseDispatched { dest: source, commitment }); } return Ok(()) } diff --git a/modules/ismp/pallets/token-gateway/src/lib.rs b/modules/ismp/pallets/token-gateway/src/lib.rs index bf841eb9e..e1d7caba7 100644 --- a/modules/ismp/pallets/token-gateway/src/lib.rs +++ b/modules/ismp/pallets/token-gateway/src/lib.rs @@ -140,13 +140,6 @@ pub mod pallet { }, /// Token Gateway address enquiry dispatched AddressEnquiryDispatched { commitment: H256 }, - /// Response dispatched - ResponseDispatched { - /// Destination state machine - dest: StateMachine, - /// Response commitment - commitment: H256, - }, } /// Errors that can be returned by this pallet. diff --git a/parachain/runtimes/gargantua/Cargo.toml b/parachain/runtimes/gargantua/Cargo.toml index c422c8305..695ee1c78 100644 --- a/parachain/runtimes/gargantua/Cargo.toml +++ b/parachain/runtimes/gargantua/Cargo.toml @@ -102,6 +102,7 @@ sp-mmr-primitives = { workspace = true } simnode-runtime-api = { workspace = true } hyperbridge-client-machine = { workspace = true } pallet-token-gateway-inspector = { workspace = true } +pallet-token-gateway = { workspace = true } [features] default = [ @@ -175,6 +176,7 @@ std = [ "pallet-state-coprocessor/std", "pallet-asset-gateway/std", "pallet-token-gateway-inspector/std", + "pallet-token-gateway/std", "pallet-token-governor/std", "pallet-assets/std", "pallet-mmr/std", diff --git a/parachain/runtimes/gargantua/src/ismp.rs b/parachain/runtimes/gargantua/src/ismp.rs index eec9769f0..36b3808fe 100644 --- a/parachain/runtimes/gargantua/src/ismp.rs +++ b/parachain/runtimes/gargantua/src/ismp.rs @@ -221,6 +221,9 @@ impl IsmpModule for ProxyModule { pallet_ismp_demo::IsmpModuleCallback::::default().on_accept(request), id if id == token_gateway => pallet_asset_gateway::Module::::default().on_accept(request), + id if id == ModuleId::Pallet(pallet_token_gateway::PALLET_ID) => + pallet_token_gateway::impls::TokenGatewayAddressModule::::default() + .on_accept(request), _ => Err(Error::Custom("Destination module not found".to_string())), } } From 8f6b89e7b9397b92abf39c72c02246029f661777 Mon Sep 17 00:00:00 2001 From: David Salami Date: Mon, 7 Oct 2024 09:04:54 +0000 Subject: [PATCH 06/15] add pallet token gateway version --- Cargo.lock | 2 +- Cargo.toml | 2 +- modules/ismp/pallets/token-gateway/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 77458e109..60696745a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12943,7 +12943,7 @@ dependencies = [ [[package]] name = "pallet-token-gateway" -version = "0.1.0" +version = "1.15.0" dependencies = [ "alloy-primitives", "alloy-sol-macro", diff --git a/Cargo.toml b/Cargo.toml index 6410f30ff..eab715883 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -306,7 +306,7 @@ pallet-asset-gateway = { path = "modules/ismp/pallets/asset-gateway", default-fe pallet-token-governor = { path = "modules/ismp/pallets/token-governor", default-features = false } pallet-state-coprocessor = { path = "modules/ismp/pallets/state-coprocessor", default-features = false } pallet-mmr = { path = "modules/trees/mmr/pallet", default-features = false } -pallet-token-gateway = { path = "modules/ismp/pallets/token-gateway", default-features = false } +pallet-token-gateway = { version = "1.15.0", path = "modules/ismp/pallets/token-gateway", default-features = false } pallet-token-gateway-inspector = { path = "modules/ismp/pallets/token-gateway-inspector", default-features = false } # merkle trees diff --git a/modules/ismp/pallets/token-gateway/Cargo.toml b/modules/ismp/pallets/token-gateway/Cargo.toml index 3eb363fd4..bbf5bd6fa 100644 --- a/modules/ismp/pallets/token-gateway/Cargo.toml +++ b/modules/ismp/pallets/token-gateway/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-token-gateway" -version = "0.1.0" +version = "1.15.0" edition = "2021" description = "The token gateway is a susbtrate implementation of the token gateway protocol" authors = ["Polytope Labs "] From 5ba6b1ebfcbdef5dfa9313db07adc567218e6825 Mon Sep 17 00:00:00 2001 From: David Salami Date: Mon, 7 Oct 2024 09:56:14 +0000 Subject: [PATCH 07/15] add token gateway readme --- .../src/tests/pallet_token_gateway.rs | 1 + modules/ismp/pallets/token-gateway/README.md | 75 +++++++++++++++++++ .../ismp/pallets/token-gateway/src/impls.rs | 63 ++++++++++++++-- modules/ismp/pallets/token-gateway/src/lib.rs | 31 ++------ parachain/runtimes/gargantua/src/ismp.rs | 2 +- 5 files changed, 141 insertions(+), 31 deletions(-) create mode 100644 modules/ismp/pallets/token-gateway/README.md diff --git a/modules/ismp/pallets/testsuite/src/tests/pallet_token_gateway.rs b/modules/ismp/pallets/testsuite/src/tests/pallet_token_gateway.rs index ed1fd5afe..f3deb6aac 100644 --- a/modules/ismp/pallets/testsuite/src/tests/pallet_token_gateway.rs +++ b/modules/ismp/pallets/testsuite/src/tests/pallet_token_gateway.rs @@ -14,6 +14,7 @@ use crate::runtime::{ use ismp::module::IsmpModule; const SEND_AMOUNT: u128 = 1000_000_000_0000; + #[test] fn should_teleport_asset_correctly() { new_test_ext().execute_with(|| { diff --git a/modules/ismp/pallets/token-gateway/README.md b/modules/ismp/pallets/token-gateway/README.md new file mode 100644 index 000000000..56951e9d8 --- /dev/null +++ b/modules/ismp/pallets/token-gateway/README.md @@ -0,0 +1,75 @@ +# Pallet Token Gateway + +This allows standalone chains or parachains make asset transfers to and from EVM token gateway deployments. + + +## Overview + +The Pallet allows the [`AdminOrigin`](https://docs.rs/pallet-ismp/latest/pallet_ismp/pallet/trait.Config.html#associatedtype.AdminOrigin) configured in [`pallet-ismp`](https://docs.rs/pallet-ismp/latest/pallet_ismp) to dispatch calls for registering asset Ids +and also requesting the token gateway addresses from Hyperbridge. + +## Adding to Runtime + +The first step is to implement the pallet config for the runtime. + +```rust,ignore +use frame_support::parameter_types; +use ismp::Error; +use ismp::host::StateMachine; +use ismp::module::IsmpModule; +use ismp::router::{IsmpRouter, Post, Response, Timeout}; + +parameter_types! { + // The Native asset Id for the native currency, for parachains this would be the XCM location for the parachain + // For standalone chains, any constant of your choosing + pub const NativeAssetId: StateMachine = Location::here(); +} + +impl pallet_ismp::Config for Runtime { + // configure the runtime event + type RuntimeEvent = RuntimeEvent; + // Pallet Ismp + type Dispatcher = Ismp; + // Pallet Assets + type Assets = Assets; + // Pallet balances + type Currency = Balances; + // The Native asset Id + type NativeAssetId = NativeAssetId; +} + +#[derive(Default)] +struct Router; +impl IsmpRouter for Router { + fn module_for_id(&self, id: Vec) -> Result, Error> { + if TokenGateway::is_token_gateway(id.clone()) { + Box::new(TokenGateway::default()) + }; + + let module = match id.as_slice() { + // This is for receiving responses about token gateway addresses from Hyperbridge + id if id == &pallet_token_gateway::PALLET_ID.0 => Box::new(pallet_token_gateway::impls::AddressResponseModule::default()), + _ => Err(Error::ModuleNotFound(id))? + }; + Ok(module) + } +} +``` + +## Setting up + +The pallet requires some setting up before the teleport function is available for use in the runtime. + +1. Request token gateway addresses for the EVM chains of interest by dispatching the `request_token_gateway_address` extrinsic. +2. Register a map of local asset Ids to their token gateway equivalents by dispatching `register_assets` extrinsic. + Note: This registration must be done for the native asset also. + +## Dispatchable Functions + +- `teleport` - This function is used to bridge assets to EVM chains through Hyperbridge. +- `request_token_gateway_address` - This call allows the `AdminOrigin` origin to request the token gateway addresses from Hyperbridge. +- `register_assets` - This call allows the configured `AdminOrigin` to register a map of local asset ids to their equivalent asset ids on token gateway. + +## License + +This library is licensed under the Apache 2.0 License, Copyright (c) 2024 Polytope Labs. diff --git a/modules/ismp/pallets/token-gateway/src/impls.rs b/modules/ismp/pallets/token-gateway/src/impls.rs index 130ce8772..7c4765317 100644 --- a/modules/ismp/pallets/token-gateway/src/impls.rs +++ b/modules/ismp/pallets/token-gateway/src/impls.rs @@ -15,25 +15,32 @@ // Pallet Implementations -use alloc::{format, string::ToString}; +use alloc::{format, string::ToString, vec::Vec}; use codec::{Decode, Encode}; use frame_support::ensure; use ismp::{ dispatcher::{FeeMetadata, IsmpDispatcher}, events::Meta, module::IsmpModule, - router::{PostRequest, PostResponse}, + router::{PostRequest, PostResponse, Response}, }; use pallet_token_governor::TokenGatewayParams; use sp_core::{ConstU32, U256}; use sp_runtime::{traits::AccountIdConversion, BoundedVec}; -use crate::{Config, Pallet, TokenGatewayAddressRequest, TokenGatewayAddressResponse, PALLET_ID}; +use crate::{ + Config, Pallet, TokenGatewayAddressRequest, TokenGatewayAddressResponse, TokenGatewayAddresses, + TokenGatewayReverseMap, PALLET_ID, +}; impl Pallet { pub fn pallet_account() -> T::AccountId { PALLET_ID.into_account_truncating() } + + pub fn is_token_gateway(id: Vec) -> bool { + TokenGatewayReverseMap::::contains_key(id) + } } /// Converts an ERC20 U256 to a DOT u128 @@ -49,15 +56,15 @@ pub fn convert_to_erc20(value: u128) -> U256 { /// An Ismp Module that receives requests from substrate chains requesting for the token gateway /// addresses on EVM chains -pub struct TokenGatewayAddressModule(core::marker::PhantomData); +pub struct AddressRequestModule(core::marker::PhantomData); -impl Default for TokenGatewayAddressModule { +impl Default for AddressRequestModule { fn default() -> Self { Self(core::marker::PhantomData) } } -impl IsmpModule for TokenGatewayAddressModule +impl IsmpModule for AddressRequestModule where ::AccountId: From<[u8; 32]>, { @@ -118,6 +125,50 @@ where } } +pub struct AddressResponseModule(core::marker::PhantomData); + +impl Default for AddressResponseModule { + fn default() -> Self { + Self(core::marker::PhantomData) + } +} + +impl IsmpModule for AddressResponseModule { + fn on_accept(&self, _post: PostRequest) -> Result<(), ismp::Error> { + Err(ismp::error::Error::Custom("Module does not accept requests".to_string())) + } + + fn on_response(&self, response: Response) -> Result<(), ismp::error::Error> { + let data = response.response().ok_or_else(|| ismp::error::Error::ModuleDispatchError { + msg: "AddressResponseModule: Response has no body".to_string(), + meta: Meta { + source: response.source_chain(), + dest: response.dest_chain(), + nonce: response.nonce(), + }, + })?; + let resp = TokenGatewayAddressResponse::decode(&mut &*data).map_err(|_| { + ismp::error::Error::ModuleDispatchError { + msg: "AddressResponseModule: Failed to decode response body".to_string(), + meta: Meta { + source: response.source_chain(), + dest: response.dest_chain(), + nonce: response.nonce(), + }, + } + })?; + for (state_machine, addr) in resp.addresses { + TokenGatewayAddresses::::insert(state_machine, addr); + TokenGatewayReverseMap::::insert(addr.0.to_vec(), state_machine) + } + Ok(()) + } + + fn on_timeout(&self, _request: ismp::router::Timeout) -> Result<(), ismp::Error> { + Err(ismp::error::Error::Custom("Module does not accept timeouts".to_string())) + } +} + #[cfg(test)] mod tests { use sp_core::U256; diff --git a/modules/ismp/pallets/token-gateway/src/lib.rs b/modules/ismp/pallets/token-gateway/src/lib.rs index e1d7caba7..c95013fbd 100644 --- a/modules/ismp/pallets/token-gateway/src/lib.rs +++ b/modules/ismp/pallets/token-gateway/src/lib.rs @@ -22,7 +22,6 @@ pub mod impls; pub mod types; use crate::impls::{convert_to_balance, convert_to_erc20}; use alloy_sol_types::SolValue; -use codec::Decode; use frame_support::{ ensure, pallet_prelude::Weight, @@ -101,6 +100,11 @@ pub mod pallet { pub type TokenGatewayAddresses = StorageMap<_, Identity, StateMachine, H160, OptionQuery>; + /// The token gateway adresses on different chains + #[pallet::storage] + pub type TokenGatewayReverseMap = + StorageMap<_, Identity, Vec, StateMachine, OptionQuery>; + /// Pallet events that functions in this pallet can emit. #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] @@ -376,29 +380,8 @@ where Ok(()) } - fn on_response(&self, response: Response) -> Result<(), ismp::error::Error> { - let data = response.response().ok_or_else(|| ismp::error::Error::ModuleDispatchError { - msg: "Token Gateway: Response has no body".to_string(), - meta: Meta { - source: response.source_chain(), - dest: response.dest_chain(), - nonce: response.nonce(), - }, - })?; - let resp = TokenGatewayAddressResponse::decode(&mut &*data).map_err(|_| { - ismp::error::Error::ModuleDispatchError { - msg: "Token Gateway: Failed to decode response body".to_string(), - meta: Meta { - source: response.source_chain(), - dest: response.dest_chain(), - nonce: response.nonce(), - }, - } - })?; - for (state_machine, addr) in resp.addresses { - TokenGatewayAddresses::::insert(state_machine, addr) - } - Ok(()) + fn on_response(&self, _response: Response) -> Result<(), ismp::error::Error> { + Err(ismp::error::Error::Custom("Module does not accept responses".to_string())) } fn on_timeout(&self, request: Timeout) -> Result<(), ismp::error::Error> { diff --git a/parachain/runtimes/gargantua/src/ismp.rs b/parachain/runtimes/gargantua/src/ismp.rs index 36b3808fe..42929bcf0 100644 --- a/parachain/runtimes/gargantua/src/ismp.rs +++ b/parachain/runtimes/gargantua/src/ismp.rs @@ -222,7 +222,7 @@ impl IsmpModule for ProxyModule { id if id == token_gateway => pallet_asset_gateway::Module::::default().on_accept(request), id if id == ModuleId::Pallet(pallet_token_gateway::PALLET_ID) => - pallet_token_gateway::impls::TokenGatewayAddressModule::::default() + pallet_token_gateway::impls::AddressRequestModule::::default() .on_accept(request), _ => Err(Error::Custom("Destination module not found".to_string())), } From 07badffc95de2e75533ee455ebe5216c823f1dae Mon Sep 17 00:00:00 2001 From: David Salami Date: Mon, 7 Oct 2024 10:01:29 +0000 Subject: [PATCH 08/15] nit --- modules/ismp/pallets/token-gateway/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/ismp/pallets/token-gateway/README.md b/modules/ismp/pallets/token-gateway/README.md index 56951e9d8..b9ff2717e 100644 --- a/modules/ismp/pallets/token-gateway/README.md +++ b/modules/ismp/pallets/token-gateway/README.md @@ -60,9 +60,11 @@ impl IsmpRouter for Router { The pallet requires some setting up before the teleport function is available for use in the runtime. -1. Request token gateway addresses for the EVM chains of interest by dispatching the `request_token_gateway_address` extrinsic. +1. Register your native asset directly on `Hyperbridge` by dispatching `TokenGovernor::create_erc6160_asset`. 2. Register a map of local asset Ids to their token gateway equivalents by dispatching `register_assets` extrinsic. - Note: This registration must be done for the native asset also. + Note: This registration must be done for your native asset also. +3. Request token gateway addresses for the EVM chains of interest by dispatching the `request_token_gateway_address` extrinsic. + ## Dispatchable Functions From 551f5dc9d658ed4d864af0a5ebdb19756a219fdf Mon Sep 17 00:00:00 2001 From: David Salami Date: Mon, 7 Oct 2024 10:10:51 +0000 Subject: [PATCH 09/15] fix no-std --- modules/ismp/pallets/token-gateway/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ismp/pallets/token-gateway/src/lib.rs b/modules/ismp/pallets/token-gateway/src/lib.rs index c95013fbd..1bb403c11 100644 --- a/modules/ismp/pallets/token-gateway/src/lib.rs +++ b/modules/ismp/pallets/token-gateway/src/lib.rs @@ -39,7 +39,7 @@ use ismp::{ use sp_core::{Get, U256}; pub use types::*; -use alloc::{string::ToString, vec}; +use alloc::{string::ToString, vec, vec::Vec}; use ismp::module::IsmpModule; use primitive_types::{H160, H256}; From fe686f9ae3cdb367baf7343c7ef3aa634ab99e53 Mon Sep 17 00:00:00 2001 From: David Salami Date: Mon, 7 Oct 2024 15:29:03 +0000 Subject: [PATCH 10/15] monitor all token gateway requests between non-evm chains --- modules/ismp/pallets/testsuite/src/runtime.rs | 6 +- .../src/tests/pallet_token_gateway.rs | 50 +++- .../token-gateway-inspector/src/lib.rs | 239 ++++++++++-------- modules/ismp/pallets/token-gateway/README.md | 7 +- .../ismp/pallets/token-gateway/src/impls.rs | 147 +---------- modules/ismp/pallets/token-gateway/src/lib.rs | 58 ++--- .../ismp/pallets/token-gateway/src/types.rs | 22 +- parachain/runtimes/gargantua/src/ismp.rs | 3 - 8 files changed, 213 insertions(+), 319 deletions(-) diff --git a/modules/ismp/pallets/testsuite/src/runtime.rs b/modules/ismp/pallets/testsuite/src/runtime.rs index 1ff0ce420..95d112fdd 100644 --- a/modules/ismp/pallets/testsuite/src/runtime.rs +++ b/modules/ismp/pallets/testsuite/src/runtime.rs @@ -16,6 +16,8 @@ extern crate alloc; +use std::collections::BTreeSet; + use alloc::collections::BTreeMap; use cumulus_pallet_parachain_system::ParachainSetCode; use frame_support::{ @@ -465,11 +467,11 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pallet_token_gateway::LocalAssets::::insert(H256::zero(), Location::here()); pallet_token_gateway::TokenGatewayAddresses::::insert( StateMachine::Evm(1), - H160::zero(), + H160::zero().0.to_vec(), ); pallet_token_gateway_inspector::StandaloneChainAssets::::insert( StateMachine::Kusama(100), - H256::zero(), + vec![H256::zero()].into_iter().collect::>(), ); let params = GatewayParams { diff --git a/modules/ismp/pallets/testsuite/src/tests/pallet_token_gateway.rs b/modules/ismp/pallets/testsuite/src/tests/pallet_token_gateway.rs index f3deb6aac..425d35eea 100644 --- a/modules/ismp/pallets/testsuite/src/tests/pallet_token_gateway.rs +++ b/modules/ismp/pallets/testsuite/src/tests/pallet_token_gateway.rs @@ -3,7 +3,10 @@ use ismp::{ host::StateMachine, router::{PostRequest, Request, Timeout}, }; -use pallet_token_gateway::{impls::convert_to_erc20, Body, TeleportParams}; +use pallet_token_gateway::{ + impls::{convert_to_erc20, module_id}, + Body, TeleportParams, +}; use sp_core::{ByteArray, H160, H256, U256}; use staging_xcm::prelude::Location; use xcm_simulator_example::ALICE; @@ -21,9 +24,11 @@ fn should_teleport_asset_correctly() { let params = TeleportParams { asset_id: Location::here(), destination: StateMachine::Evm(1), - recepient: H160::random(), + recepient: H256::random(), timeout: 0, amount: SEND_AMOUNT, + token_gateway: H160::zero().0.to_vec(), + relayer_fee: Default::default(), }; TokenGateway::teleport(RuntimeOrigin::signed(ALICE), params).unwrap(); @@ -40,9 +45,11 @@ fn should_receive_asset_correctly() { let params = TeleportParams { asset_id: Location::here(), destination: StateMachine::Evm(1), - recepient: H160::random(), + recepient: H256::random(), timeout: 0, amount: SEND_AMOUNT, + token_gateway: H160::zero().0.to_vec(), + relayer_fee: Default::default(), }; TokenGateway::teleport(RuntimeOrigin::signed(ALICE), params).unwrap(); @@ -91,9 +98,11 @@ fn should_timeout_request_correctly() { let params = TeleportParams { asset_id: Location::here(), destination: StateMachine::Evm(1), - recepient: H160::random(), + recepient: H256::random(), timeout: 0, amount: SEND_AMOUNT, + token_gateway: H160::zero().0.to_vec(), + relayer_fee: Default::default(), }; TokenGateway::teleport(RuntimeOrigin::signed(ALICE), params).unwrap(); @@ -144,7 +153,7 @@ fn inspector_should_intercept_illegal_request() { source: StateMachine::Kusama(100), dest: StateMachine::Evm(1), nonce: 0, - from: H160::zero().0.to_vec(), + from: module_id().0.to_vec(), to: H160::zero().0.to_vec(), timeout_timestamp: 1000, body: { @@ -171,19 +180,23 @@ fn inspector_should_intercept_illegal_request() { assert!(result.is_err()); pallet_token_gateway_inspector::InflowBalances::::insert( + StateMachine::Kusama(100), asset_id, convert_to_erc20(SEND_AMOUNT), ); let result = TokenGatewayInspector::inspect_request(&post); assert!(result.is_ok()); - let inflow = pallet_token_gateway_inspector::InflowBalances::::get(asset_id); + let inflow = pallet_token_gateway_inspector::InflowBalances::::get( + StateMachine::Kusama(100), + asset_id, + ); assert_eq!(inflow, U256::zero()); }); } #[test] -fn inspector_should_record_non_native_asset_inflow() { +fn inspector_should_record_asset_inflow() { new_test_ext().execute_with(|| { let asset_id: H256 = [1u8; 32].into(); let post = PostRequest { @@ -216,7 +229,10 @@ fn inspector_should_record_non_native_asset_inflow() { println!("{result:?}"); assert!(result.is_ok()); - let inflow = pallet_token_gateway_inspector::InflowBalances::::get(asset_id); + let inflow = pallet_token_gateway_inspector::InflowBalances::::get( + StateMachine::Kusama(100), + asset_id, + ); assert_eq!(convert_to_erc20(SEND_AMOUNT), inflow); }); @@ -230,7 +246,7 @@ fn inspector_should_handle_timeout_correctly() { source: StateMachine::Kusama(100), dest: StateMachine::Evm(1), nonce: 0, - from: H160::zero().0.to_vec(), + from: module_id().0.to_vec(), to: H160::zero().0.to_vec(), timeout_timestamp: 1000, body: { @@ -252,15 +268,27 @@ fn inspector_should_handle_timeout_correctly() { }, }; - let inflow = pallet_token_gateway_inspector::InflowBalances::::get(asset_id); + let inflow = pallet_token_gateway_inspector::InflowBalances::::get( + StateMachine::Kusama(100), + asset_id, + ); assert_eq!(inflow, U256::zero()); + pallet_token_gateway_inspector::InflowBalances::::insert( + StateMachine::Evm(1), + asset_id, + convert_to_erc20(SEND_AMOUNT), + ); + let result = TokenGatewayInspector::handle_timeout(&post); println!("{result:?}"); assert!(result.is_ok()); - let inflow = pallet_token_gateway_inspector::InflowBalances::::get(asset_id); + let inflow = pallet_token_gateway_inspector::InflowBalances::::get( + StateMachine::Kusama(100), + asset_id, + ); assert_eq!(convert_to_erc20(SEND_AMOUNT), inflow); }); diff --git a/modules/ismp/pallets/token-gateway-inspector/src/lib.rs b/modules/ismp/pallets/token-gateway-inspector/src/lib.rs index 2a5028af5..e0e6445d7 100644 --- a/modules/ismp/pallets/token-gateway-inspector/src/lib.rs +++ b/modules/ismp/pallets/token-gateway-inspector/src/lib.rs @@ -32,8 +32,8 @@ pub use pallet::*; #[frame_support::pallet] pub mod pallet { use super::*; - use alloc::collections::BTreeMap; - use frame_support::pallet_prelude::*; + use alloc::collections::{BTreeMap, BTreeSet}; + use frame_support::{pallet_prelude::*, Blake2_128Concat}; use frame_system::pallet_prelude::*; use ismp::{events::Meta, host::StateMachine}; use pallet_token_gateway::Body; @@ -55,11 +55,12 @@ pub mod pallet { /// Native asset ids for standalone chains connected to token gateway. #[pallet::storage] pub type StandaloneChainAssets = - StorageMap<_, Twox64Concat, StateMachine, H256, OptionQuery>; + StorageMap<_, Twox64Concat, StateMachine, BTreeSet, OptionQuery>; /// Balances for net inflow of non native assets into a standalone chain #[pallet::storage] - pub type InflowBalances = StorageMap<_, Twox64Concat, H256, U256, ValueQuery>; + pub type InflowBalances = + StorageDoubleMap<_, Blake2_128Concat, StateMachine, Twox64Concat, H256, U256, ValueQuery>; /// Pallet events that functions in this pallet can emit. #[pallet::event] @@ -68,7 +69,9 @@ pub mod pallet { /// Illegal request has been intercepted IllegalRequest { source: StateMachine }, /// Native asset IDs have been registered - NativeAssetsRegistered { assets: BTreeMap }, + NativeAssetsRegistered { assets: BTreeMap> }, + /// Native asset IDs have been deregistered + NativeAssetsDeregistered { assets: BTreeMap> }, } /// Errors that can be returned by this pallet. @@ -81,23 +84,59 @@ pub mod pallet { ::AccountId: From<[u8; 32]>, ::Balance: Default, { - /// Register the token native asset ids for standalone chains + /// Register the native token asset ids for standalone chains #[pallet::call_index(0)] #[pallet::weight(weight())] pub fn register_standalone_chain_native_assets( origin: OriginFor, - assets: BTreeMap, + assets: BTreeMap>, ) -> DispatchResult { T::AdminOrigin::ensure_origin(origin)?; - for (state_machine, asset_id) in assets.clone() { - StandaloneChainAssets::::insert(state_machine, asset_id); + for (state_machine, mut new_asset_ids) in assets.clone() { + let _ = StandaloneChainAssets::::try_mutate(state_machine, |asset_ids| { + if let Some(set) = asset_ids { + set.append(&mut new_asset_ids); + } else { + *asset_ids = Some(new_asset_ids); + }; + + Ok::<(), ()>(()) + }); } Self::deposit_event(Event::::NativeAssetsRegistered { assets }); Ok(()) } + + /// Deregister the native token asset ids for standalone chains + #[pallet::call_index(1)] + #[pallet::weight(weight())] + pub fn deregister_standalone_chain_native_assets( + origin: OriginFor, + assets: BTreeMap>, + ) -> DispatchResult { + T::AdminOrigin::ensure_origin(origin)?; + + for (state_machine, new_asset_ids) in assets.clone() { + let _ = StandaloneChainAssets::::try_mutate(state_machine, |asset_ids| { + if let Some(set) = asset_ids { + for id in new_asset_ids { + set.remove(&id); + } + if set.is_empty() { + *asset_ids = None; + } + } + Ok::<(), ()>(()) + }); + } + + Self::deposit_event(Event::::NativeAssetsDeregistered { assets }); + + Ok(()) + } } // Hack for implementing the [`Default`] bound needed for @@ -111,83 +150,55 @@ pub mod pallet { impl Pallet { pub fn inspect_request(post: &PostRequest) -> Result<(), ismp::Error> { - let PostRequest { body, from, to, source, dest, nonce, .. } = post.clone(); - // Case #1: if the source is EVM and dest is substrate then we want to record the inflow - // amount if it's not the native asset - - if let Some(token_gateway_address) = TokenGatewayParams::::get(source) { - if token_gateway_address.address.0.to_vec() == from && - source.is_evm() && dest.is_substrate() - { - let body = Body::abi_decode(&mut &body[1..], true).map_err(|_| { - ismp::error::Error::ModuleDispatchError { - msg: "Token Gateway: Failed to decode request body".to_string(), - meta: Meta { source, dest, nonce }, - } - })?; + let PostRequest { body, from, source, dest, nonce, .. } = post.clone(); - let native_asset_id = - StandaloneChainAssets::::get(dest).ok_or_else(|| { - ismp::Error::Custom(format!( - "Native asset id not registered for {dest}" - )) - })?; - - if native_asset_id.0 != body.asset_id.0 { - InflowBalances::::try_mutate(H256::from(body.asset_id.0), |val| { - let amount = U256::from_big_endian(&body.amount.to_be_bytes::<32>()); - *val += amount; - Ok::<_, ()>(()) - }) - .map_err(|_| { - ismp::Error::Custom(format!( - "Failed to record inflow while inspecting packet" - )) - })?; - } - } + if source.is_evm() && dest.is_evm() { + return Ok(()) } - // Case #2: If the source is substrate and dest is EVM we want to ensure the amount is - // <= the total infow amount for that asset We also update the balance for that asset - - if let Some(token_gateway_address) = TokenGatewayParams::::get(dest) { - if token_gateway_address.address.0.to_vec() == to && - source.is_substrate() && - dest.is_evm() - { - let body = Body::abi_decode(&mut &body[1..], true).map_err(|_| { - ismp::error::Error::ModuleDispatchError { - msg: "Token Gateway: Failed to decode request body".to_string(), - meta: Meta { source, dest, nonce }, - } - })?; + if from == pallet_token_gateway::impls::module_id().0.to_vec() || + TokenGatewayParams::::get(source) + .map(|params| params.address.0.to_vec() == from) + .unwrap_or_default() + { + let body = Body::abi_decode(&mut &body[1..], true).map_err(|_| { + ismp::error::Error::ModuleDispatchError { + msg: "Token Gateway: Failed to decode request body".to_string(), + meta: Meta { source, dest, nonce }, + } + })?; - let native_asset_id = - StandaloneChainAssets::::get(source).ok_or_else(|| { - ismp::Error::Custom(format!( - "Native asset id not registered for {source}" - )) - })?; - if native_asset_id.0 != body.asset_id.0 { - let balance = InflowBalances::::get(H256::from(body.asset_id.0)); + if !dest.is_evm() { + InflowBalances::::try_mutate(dest, H256::from(body.asset_id.0), |val| { let amount = U256::from_big_endian(&body.amount.to_be_bytes::<32>()); - if amount > balance { - Err(ismp::Error::Custom(format!("Illegal Token Gateway request")))?; - Pallet::::deposit_event(Event::::IllegalRequest { source }) - } + *val += amount; + Ok::<_, ()>(()) + }) + .map_err(|_| { + ismp::Error::Custom(format!( + "Failed to record inflow while inspecting packet" + )) + })?; + } - InflowBalances::::try_mutate(H256::from(body.asset_id.0), |val| { - let amount = U256::from_big_endian(&body.amount.to_be_bytes::<32>()); - *val -= amount; - Ok::<_, ()>(()) - }) - .map_err(|_| { - ismp::Error::Custom(format!( - "Failed to record inflow while inspecting packet" - )) - })?; + let native_asset_ids = StandaloneChainAssets::::get(source).unwrap_or_default(); + if !native_asset_ids.contains(&H256::from(body.asset_id.0)) && !source.is_evm() { + let balance = InflowBalances::::get(source, H256::from(body.asset_id.0)); + let amount = U256::from_big_endian(&body.amount.to_be_bytes::<32>()); + if amount > balance { + Err(ismp::Error::Custom(format!("Illegal Token Gateway request")))?; + Pallet::::deposit_event(Event::::IllegalRequest { source }) } + + InflowBalances::::try_mutate(source, H256::from(body.asset_id.0), |val| { + *val -= amount; + Ok::<_, ()>(()) + }) + .map_err(|_| { + ismp::Error::Custom(format!( + "Failed to record inflow while inspecting packet" + )) + })?; } } @@ -195,40 +206,50 @@ pub mod pallet { } pub fn handle_timeout(post: &PostRequest) -> Result<(), ismp::Error> { - let PostRequest { body, to, source, dest, nonce, .. } = post.clone(); - if let Some(token_gateway_address) = TokenGatewayParams::::get(dest) { - if token_gateway_address.address.0.to_vec() == to && - source.is_substrate() && - dest.is_evm() - { - let body = Body::abi_decode(&mut &body[1..], true).map_err(|_| { - ismp::error::Error::ModuleDispatchError { - msg: "Token Gateway: Failed to decode request body".to_string(), - meta: Meta { source, dest, nonce }, - } - })?; + let PostRequest { body, from, source, dest, nonce, .. } = post.clone(); + if source.is_evm() && dest.is_evm() { + return Ok(()) + } - let native_asset_id = - StandaloneChainAssets::::get(source).ok_or_else(|| { - ismp::Error::Custom(format!( - "Native asset id not registered for {source}" - )) - })?; - if native_asset_id.0 != body.asset_id.0 { - InflowBalances::::try_mutate(H256::from(body.asset_id.0), |val| { - let amount = U256::from_big_endian(&body.amount.to_be_bytes::<32>()); - *val += amount; - Ok::<_, ()>(()) - }) - .map_err(|_| { - ismp::Error::Custom(format!( - "Failed to record inflow while inspecting packet" - )) - })?; + if from == pallet_token_gateway::impls::module_id().0.to_vec() || + TokenGatewayParams::::get(source) + .map(|params| params.address.0.to_vec() == from) + .unwrap_or_default() + { + let body = Body::abi_decode(&mut &body[1..], true).map_err(|_| { + ismp::error::Error::ModuleDispatchError { + msg: "Token Gateway: Failed to decode request body".to_string(), + meta: Meta { source, dest, nonce }, } + })?; + + let native_asset_ids = StandaloneChainAssets::::get(source).unwrap_or_default(); + if !native_asset_ids.contains(&H256::from(body.asset_id.0)) && !source.is_evm() { + InflowBalances::::try_mutate(source, H256::from(body.asset_id.0), |val| { + let amount = U256::from_big_endian(&body.amount.to_be_bytes::<32>()); + *val += amount; + Ok::<_, ()>(()) + }) + .map_err(|_| { + ismp::Error::Custom(format!( + "Failed to record inflow while inspecting packet" + )) + })?; } - } + if !dest.is_evm() { + InflowBalances::::try_mutate(dest, H256::from(body.asset_id.0), |val| { + let amount = U256::from_big_endian(&body.amount.to_be_bytes::<32>()); + *val -= amount; + Ok::<_, ()>(()) + }) + .map_err(|_| { + ismp::Error::Custom(format!( + "Failed to record inflow while inspecting packet" + )) + })?; + } + } Ok(()) } } diff --git a/modules/ismp/pallets/token-gateway/README.md b/modules/ismp/pallets/token-gateway/README.md index b9ff2717e..53fffac85 100644 --- a/modules/ismp/pallets/token-gateway/README.md +++ b/modules/ismp/pallets/token-gateway/README.md @@ -42,13 +42,8 @@ impl pallet_ismp::Config for Runtime { struct Router; impl IsmpRouter for Router { fn module_for_id(&self, id: Vec) -> Result, Error> { - if TokenGateway::is_token_gateway(id.clone()) { - Box::new(TokenGateway::default()) - }; - let module = match id.as_slice() { - // This is for receiving responses about token gateway addresses from Hyperbridge - id if id == &pallet_token_gateway::PALLET_ID.0 => Box::new(pallet_token_gateway::impls::AddressResponseModule::default()), + id if TokenGateway::is_token_gateway(&id) => Box::new(TokenGateway::default()), _ => Err(Error::ModuleNotFound(id))? }; Ok(module) diff --git a/modules/ismp/pallets/token-gateway/src/impls.rs b/modules/ismp/pallets/token-gateway/src/impls.rs index 7c4765317..d2ec37e21 100644 --- a/modules/ismp/pallets/token-gateway/src/impls.rs +++ b/modules/ismp/pallets/token-gateway/src/impls.rs @@ -15,34 +15,28 @@ // Pallet Implementations -use alloc::{format, string::ToString, vec::Vec}; -use codec::{Decode, Encode}; -use frame_support::ensure; -use ismp::{ - dispatcher::{FeeMetadata, IsmpDispatcher}, - events::Meta, - module::IsmpModule, - router::{PostRequest, PostResponse, Response}, -}; -use pallet_token_governor::TokenGatewayParams; -use sp_core::{ConstU32, U256}; -use sp_runtime::{traits::AccountIdConversion, BoundedVec}; - -use crate::{ - Config, Pallet, TokenGatewayAddressRequest, TokenGatewayAddressResponse, TokenGatewayAddresses, - TokenGatewayReverseMap, PALLET_ID, -}; +use alloc::string::ToString; +use sp_core::{H160, U256}; +use sp_runtime::traits::AccountIdConversion; + +use crate::{Config, Pallet, PALLET_ID}; impl Pallet { pub fn pallet_account() -> T::AccountId { PALLET_ID.into_account_truncating() } - pub fn is_token_gateway(id: Vec) -> bool { - TokenGatewayReverseMap::::contains_key(id) + pub fn is_token_gateway(id: &[u8]) -> bool { + id == &module_id().0 } } +/// Module Id is the last 20 bytes of the keccak hash of the pallet id +pub fn module_id() -> H160 { + let hash = sp_io::hashing::keccak_256(&PALLET_ID.0); + H160::from_slice(&hash[12..32]) +} + /// Converts an ERC20 U256 to a DOT u128 pub fn convert_to_balance(value: U256) -> Result { let dec_str = (value / U256::from(100_000_000u128)).to_string(); @@ -54,121 +48,6 @@ pub fn convert_to_erc20(value: u128) -> U256 { U256::from(value) * U256::from(100_000_000u128) } -/// An Ismp Module that receives requests from substrate chains requesting for the token gateway -/// addresses on EVM chains -pub struct AddressRequestModule(core::marker::PhantomData); - -impl Default for AddressRequestModule { - fn default() -> Self { - Self(core::marker::PhantomData) - } -} - -impl IsmpModule for AddressRequestModule -where - ::AccountId: From<[u8; 32]>, -{ - fn on_accept(&self, post: PostRequest) -> Result<(), ismp::Error> { - let PostRequest { body: data, from, source, dest, nonce, .. } = post.clone(); - // Check that source module is equal to the known token gateway deployment address - ensure!( - from == PALLET_ID.0.to_vec(), - ismp::error::Error::ModuleDispatchError { - msg: "Token Gateway: Unknown source contract address".to_string(), - meta: Meta { source, dest, nonce }, - } - ); - - ensure!( - source.is_substrate(), - ismp::error::Error::ModuleDispatchError { - msg: "Token Gateway: Illegal source chain".to_string(), - meta: Meta { source, dest, nonce }, - } - ); - - let req = TokenGatewayAddressRequest::decode(&mut &*data).map_err(|err| { - ismp::error::Error::Custom(format!("Invalid request from a substrate chain: {err}")) - })?; - let mut addresses = BoundedVec::<_, ConstU32<5>>::new(); - for state_machine in req.chains { - if let Some(params) = TokenGatewayParams::::get(&state_machine) { - addresses.try_push((state_machine, params.address)).map_err(|err| { - ismp::error::Error::Custom(alloc::format!( - "Maximum of 5 state machines can be requested: {err:?}" - )) - })?; - } - } - - if !addresses.is_empty() { - let response = TokenGatewayAddressResponse { addresses }; - let dispatcher = ::Dispatcher::default(); - - let post_response = - PostResponse { post, response: response.encode(), timeout_timestamp: 0 }; - - let _ = dispatcher.dispatch_response( - post_response, - FeeMetadata { payer: [0u8; 32].into(), fee: Default::default() }, - )?; - } - return Ok(()) - } - - fn on_response(&self, _response: ismp::router::Response) -> Result<(), ismp::Error> { - Err(ismp::error::Error::Custom("Module does not accept responses".to_string())) - } - - fn on_timeout(&self, _request: ismp::router::Timeout) -> Result<(), ismp::Error> { - Err(ismp::error::Error::Custom("Module does not accept timeouts".to_string())) - } -} - -pub struct AddressResponseModule(core::marker::PhantomData); - -impl Default for AddressResponseModule { - fn default() -> Self { - Self(core::marker::PhantomData) - } -} - -impl IsmpModule for AddressResponseModule { - fn on_accept(&self, _post: PostRequest) -> Result<(), ismp::Error> { - Err(ismp::error::Error::Custom("Module does not accept requests".to_string())) - } - - fn on_response(&self, response: Response) -> Result<(), ismp::error::Error> { - let data = response.response().ok_or_else(|| ismp::error::Error::ModuleDispatchError { - msg: "AddressResponseModule: Response has no body".to_string(), - meta: Meta { - source: response.source_chain(), - dest: response.dest_chain(), - nonce: response.nonce(), - }, - })?; - let resp = TokenGatewayAddressResponse::decode(&mut &*data).map_err(|_| { - ismp::error::Error::ModuleDispatchError { - msg: "AddressResponseModule: Failed to decode response body".to_string(), - meta: Meta { - source: response.source_chain(), - dest: response.dest_chain(), - nonce: response.nonce(), - }, - } - })?; - for (state_machine, addr) in resp.addresses { - TokenGatewayAddresses::::insert(state_machine, addr); - TokenGatewayReverseMap::::insert(addr.0.to_vec(), state_machine) - } - Ok(()) - } - - fn on_timeout(&self, _request: ismp::router::Timeout) -> Result<(), ismp::Error> { - Err(ismp::error::Error::Custom("Module does not accept timeouts".to_string())) - } -} - #[cfg(test)] mod tests { use sp_core::U256; diff --git a/modules/ismp/pallets/token-gateway/src/lib.rs b/modules/ismp/pallets/token-gateway/src/lib.rs index 1bb403c11..f836c2bbb 100644 --- a/modules/ismp/pallets/token-gateway/src/lib.rs +++ b/modules/ismp/pallets/token-gateway/src/lib.rs @@ -20,7 +20,7 @@ extern crate alloc; pub mod impls; pub mod types; -use crate::impls::{convert_to_balance, convert_to_erc20}; +use crate::impls::{convert_to_balance, convert_to_erc20, module_id}; use alloy_sol_types::SolValue; use frame_support::{ ensure, @@ -41,7 +41,7 @@ pub use types::*; use alloc::{string::ToString, vec, vec::Vec}; use ismp::module::IsmpModule; -use primitive_types::{H160, H256}; +use primitive_types::H256; // Re-export pallet items so that they can be accessed from the crate namespace. pub use pallet::*; @@ -51,6 +51,8 @@ pub const PALLET_ID: PalletId = PalletId(*b"tokengtw"); #[frame_support::pallet] pub mod pallet { + use alloc::collections::BTreeMap; + use super::*; use frame_support::{ pallet_prelude::*, @@ -98,12 +100,7 @@ pub mod pallet { /// The token gateway adresses on different chains #[pallet::storage] pub type TokenGatewayAddresses = - StorageMap<_, Identity, StateMachine, H160, OptionQuery>; - - /// The token gateway adresses on different chains - #[pallet::storage] - pub type TokenGatewayReverseMap = - StorageMap<_, Identity, Vec, StateMachine, OptionQuery>; + StorageMap<_, Identity, StateMachine, Vec, OptionQuery>; /// Pallet events that functions in this pallet can emit. #[pallet::event] @@ -114,7 +111,7 @@ pub mod pallet { /// Source account on the relaychain from: T::AccountId, /// beneficiary account on destination - to: H160, + to: H256, /// Amount transferred amount: <::Currency as Currency>::Balance, /// Destination chain @@ -142,8 +139,6 @@ pub mod pallet { /// Destination chain source: StateMachine, }, - /// Token Gateway address enquiry dispatched - AddressEnquiryDispatched { commitment: H256 }, } /// Errors that can be returned by this pallet. @@ -166,6 +161,8 @@ pub mod pallet { where ::AccountId: From<[u8; 32]>, u128: From<<::Currency as Currency>::Balance>, + ::Balance: + From<<::Currency as Currency>::Balance>, <::Assets as fungibles::Inspect>::Balance: From<<::Currency as Currency>::Balance>, [u8; 32]: From<::AccountId>, @@ -206,8 +203,7 @@ pub mod pallet { // Dispatch Ismp request // Token gateway expected abi encoded address - let mut to = [0u8; 32]; - to[12..].copy_from_slice(¶ms.recepient.0); + let to = params.recepient.0; let from: [u8; 32] = who.clone().into(); let body = Body { @@ -223,13 +219,10 @@ pub mod pallet { to: to.into(), }; - let token_gateway_address = TokenGatewayAddresses::::get(params.destination) - .ok_or_else(|| Error::::UnregisteredDestinationChain)?; - let dispatch_post = DispatchPost { dest: params.destination, - from: token_gateway_address.0.to_vec(), - to: token_gateway_address.0.to_vec(), + from: module_id().0.to_vec(), + to: params.token_gateway, timeout: params.timeout, body: { // Prefix with the handleIncomingAsset enum variant @@ -239,7 +232,7 @@ pub mod pallet { }, }; - let metadata = FeeMetadata { payer: who.clone(), fee: Default::default() }; + let metadata = FeeMetadata { payer: who.clone(), fee: params.relayer_fee.into() }; let commitment = dispatcher .dispatch_request(DispatchRequest::Post(dispatch_post), metadata) .map_err(|_| Error::::AssetTeleportError)?; @@ -254,29 +247,17 @@ pub mod pallet { Ok(()) } - /// Request the token gateway address from Hyperbridge for specified chains + /// Set the token gateway address for specified chains #[pallet::call_index(1)] #[pallet::weight(weight())] - pub fn request_token_gateway_address( + pub fn set_token_gateway_addresses( origin: OriginFor, - chains: BoundedVec>, + addresses: BTreeMap>, ) -> DispatchResult { T::AdminOrigin::ensure_origin(origin)?; - let request = TokenGatewayAddressRequest { chains }; - let dispatcher = ::Dispatcher::default(); - let dispatch_post = DispatchPost { - dest: T::Coprocessor::get().ok_or_else(|| Error::::CoprocessorNotConfigured)?, - from: PALLET_ID.0.to_vec(), - to: PALLET_ID.0.to_vec(), - timeout: 0, - body: request.encode(), - }; - - let metadata = FeeMetadata { payer: [0u8; 32].into(), fee: Default::default() }; - let commitment = dispatcher - .dispatch_request(DispatchRequest::Post(dispatch_post), metadata) - .map_err(|_| Error::::AddressEnquiryDispatchFailed)?; - Self::deposit_event(Event::::AddressEnquiryDispatched { commitment }); + for (chain, address) in addresses { + TokenGatewayAddresses::::insert(chain, address.clone()); + } Ok(()) } @@ -320,7 +301,8 @@ where PostRequest { body, from, source, dest, nonce, .. }: PostRequest, ) -> Result<(), ismp::error::Error> { ensure!( - from == TokenGatewayAddresses::::get(source).unwrap_or_default().0.to_vec(), + from == TokenGatewayAddresses::::get(source).unwrap_or_default().to_vec() || + from == module_id().0.to_vec(), ismp::error::Error::ModuleDispatchError { msg: "Token Gateway: Unknown source contract address".to_string(), meta: Meta { source, dest, nonce }, diff --git a/modules/ismp/pallets/token-gateway/src/types.rs b/modules/ismp/pallets/token-gateway/src/types.rs index 3c31d7653..746592ead 100644 --- a/modules/ismp/pallets/token-gateway/src/types.rs +++ b/modules/ismp/pallets/token-gateway/src/types.rs @@ -15,10 +15,10 @@ //! Pallet types +use alloc::vec::Vec; use frame_support::{pallet_prelude::*, traits::fungibles}; use ismp::host::StateMachine; use primitive_types::H256; -use sp_core::H160; use crate::Config; @@ -33,11 +33,15 @@ pub struct TeleportParams { /// Destination state machine pub destination: StateMachine, /// Receiving account on destination - pub recepient: H160, + pub recepient: H256, /// Amount to be sent pub amount: Balance, /// Request timeout pub timeout: u64, + /// Token gateway address + pub token_gateway: Vec, + /// Relayer fee + pub relayer_fee: Balance, } /// Local asset Id and its corresponding token gateway asset id @@ -69,17 +73,3 @@ alloy_sol_macro::sol! { bytes32 to; } } - -/// Struct for requesting the token gateway address for some state machines -#[derive(Debug, Clone, Encode, Decode, scale_info::TypeInfo, PartialEq, Eq, Default)] -pub struct TokenGatewayAddressRequest { - /// The chains whose token gateway addresses are being requested - pub chains: BoundedVec>, -} - -/// Struct for responding to token gateway address requests -#[derive(Debug, Clone, Encode, Decode, scale_info::TypeInfo, PartialEq, Eq, Default)] -pub struct TokenGatewayAddressResponse { - /// The token gateway address on diffirent chains - pub addresses: BoundedVec<(StateMachine, H160), ConstU32<5>>, -} diff --git a/parachain/runtimes/gargantua/src/ismp.rs b/parachain/runtimes/gargantua/src/ismp.rs index 42929bcf0..eec9769f0 100644 --- a/parachain/runtimes/gargantua/src/ismp.rs +++ b/parachain/runtimes/gargantua/src/ismp.rs @@ -221,9 +221,6 @@ impl IsmpModule for ProxyModule { pallet_ismp_demo::IsmpModuleCallback::::default().on_accept(request), id if id == token_gateway => pallet_asset_gateway::Module::::default().on_accept(request), - id if id == ModuleId::Pallet(pallet_token_gateway::PALLET_ID) => - pallet_token_gateway::impls::AddressRequestModule::::default() - .on_accept(request), _ => Err(Error::Custom("Destination module not found".to_string())), } } From b1aa20c03ae9d43344f6a623471327f6790bec13 Mon Sep 17 00:00:00 2001 From: David Salami Date: Mon, 7 Oct 2024 16:50:36 +0000 Subject: [PATCH 11/15] allow remote creation of MNTs from substrate chains --- .../src/tests/pallet_token_gateway.rs | 2 + modules/ismp/pallets/token-gateway/README.md | 11 ++--- modules/ismp/pallets/token-gateway/src/lib.rs | 43 +++++++++++++++++-- .../ismp/pallets/token-governor/src/impls.rs | 8 ++-- .../ismp/pallets/token-governor/src/lib.rs | 21 ++++++++- .../ismp/pallets/token-governor/src/types.rs | 9 ++++ 6 files changed, 80 insertions(+), 14 deletions(-) diff --git a/modules/ismp/pallets/testsuite/src/tests/pallet_token_gateway.rs b/modules/ismp/pallets/testsuite/src/tests/pallet_token_gateway.rs index 425d35eea..eaa5d3729 100644 --- a/modules/ismp/pallets/testsuite/src/tests/pallet_token_gateway.rs +++ b/modules/ismp/pallets/testsuite/src/tests/pallet_token_gateway.rs @@ -1,3 +1,5 @@ +#![cfg(test)] + use alloy_sol_types::SolValue; use ismp::{ host::StateMachine, diff --git a/modules/ismp/pallets/token-gateway/README.md b/modules/ismp/pallets/token-gateway/README.md index 53fffac85..d88dca124 100644 --- a/modules/ismp/pallets/token-gateway/README.md +++ b/modules/ismp/pallets/token-gateway/README.md @@ -55,18 +55,19 @@ impl IsmpRouter for Router { The pallet requires some setting up before the teleport function is available for use in the runtime. -1. Register your native asset directly on `Hyperbridge` by dispatching `TokenGovernor::create_erc6160_asset`. -2. Register a map of local asset Ids to their token gateway equivalents by dispatching `register_assets` extrinsic. +1. Register your native assets directly on `Hyperbridge` by dispatching `create_erc6160_asset`. +2. Register a map of all local asset Ids to their token gateway equivalents by dispatching `register_assets` extrinsic. Note: This registration must be done for your native asset also. -3. Request token gateway addresses for the EVM chains of interest by dispatching the `request_token_gateway_address` extrinsic. +3. Set token gateway addresses for the EVM chains of interest by dispatching the `set_token_gateway_addresses` extrinsic. + This allows us validate incoming requests. ## Dispatchable Functions - `teleport` - This function is used to bridge assets to EVM chains through Hyperbridge. -- `request_token_gateway_address` - This call allows the `AdminOrigin` origin to request the token gateway addresses from Hyperbridge. +- `set_token_gateway_addresses` - This call allows the `AdminOrigin` origin to set the token gateway address for EVM chains. - `register_assets` - This call allows the configured `AdminOrigin` to register a map of local asset ids to their equivalent asset ids on token gateway. - +- `create_erc6160_asset` - This call dispatches a request to Hyperbridge to create multi chain native assets on token gateway deployments ## License This library is licensed under the Apache 2.0 License, Copyright (c) 2024 Polytope Labs. diff --git a/modules/ismp/pallets/token-gateway/src/lib.rs b/modules/ismp/pallets/token-gateway/src/lib.rs index f836c2bbb..e1993bfe0 100644 --- a/modules/ismp/pallets/token-gateway/src/lib.rs +++ b/modules/ismp/pallets/token-gateway/src/lib.rs @@ -63,6 +63,7 @@ pub mod pallet { dispatcher::{DispatchPost, DispatchRequest, FeeMetadata, IsmpDispatcher}, host::StateMachine, }; + use pallet_token_governor::{ERC6160AssetRegistration, RemoteERC6160AssetRegistration}; #[pallet::pallet] #[pallet::without_storage_info] @@ -139,6 +140,12 @@ pub mod pallet { /// Destination chain source: StateMachine, }, + + /// ERC6160 asset creation request dispatched to hyperbridge + ERC6160AssetRegistrationDispatched { + /// Request commitment + commitment: H256, + }, } /// Errors that can be returned by this pallet. @@ -146,14 +153,10 @@ pub mod pallet { pub enum Error { /// A asset that has not been registered UnregisteredAsset, - /// A state machine does not have the token gateway address registered - UnregisteredDestinationChain, /// Error while teleporting asset AssetTeleportError, /// Coprocessor was not configured in the runtime CoprocessorNotConfigured, - /// A request to query the token gateway addresses failed to dispatch - AddressEnquiryDispatchFailed, } #[pallet::call] @@ -278,6 +281,38 @@ pub mod pallet { } Ok(()) } + + /// Registers a multi-chain ERC6160 asset. The asset should not already exist. + /// + /// This works by dispatching a request to the TokenGateway module on each requested chain + /// to create the asset. + #[pallet::call_index(3)] + #[pallet::weight(weight())] + pub fn create_erc6160_asset( + origin: OriginFor, + owner: T::AccountId, + assets: Vec, + ) -> DispatchResult { + T::AdminOrigin::ensure_origin(origin)?; + + let dispatcher = ::Dispatcher::default(); + let dispatch_post = DispatchPost { + dest: T::Coprocessor::get().ok_or_else(|| Error::::CoprocessorNotConfigured)?, + from: module_id().0.to_vec(), + to: pallet_token_governor::PALLET_ID.to_vec(), + timeout: 0, + body: { RemoteERC6160AssetRegistration { assets, owner: owner.clone() }.encode() }, + }; + + let metadata = FeeMetadata { payer: owner.into(), fee: Default::default() }; + + let commitment = dispatcher + .dispatch_request(DispatchRequest::Post(dispatch_post), metadata) + .map_err(|_| Error::::AssetTeleportError)?; + Self::deposit_event(Event::::ERC6160AssetRegistrationDispatched { commitment }); + + Ok(()) + } } // Hack for implementing the [`Default`] bound needed for diff --git a/modules/ismp/pallets/token-governor/src/impls.rs b/modules/ismp/pallets/token-governor/src/impls.rs index 99a8e4bea..212c6d732 100644 --- a/modules/ismp/pallets/token-governor/src/impls.rs +++ b/modules/ismp/pallets/token-governor/src/impls.rs @@ -85,7 +85,7 @@ where .ok_or_else(|| Error::::UnknownTokenGateway)?; let dispatcher = T::Dispatcher::default(); - dispatcher + let commitment = dispatcher .dispatch_request( DispatchRequest::Post(DispatchPost { dest: chain.clone(), @@ -99,11 +99,11 @@ where .map_err(|_| Error::::DispatchFailed)?; // tracks which chains the asset is deployed on SupportedChains::::insert(asset_id, chain, true); + Self::deposit_event(Event::::AssetRegistered { asset_id, commitment, dest: chain }); } AssetMetadatas::::insert(asset_id, metadata); AssetOwners::::insert(asset_id, who); - Self::deposit_event(Event::::AssetRegistered { asset_id }); Ok(()) } @@ -388,7 +388,7 @@ where .ok_or_else(|| Error::::UnknownTokenGateway)?; let dispatcher = T::Dispatcher::default(); - dispatcher + let commitment = dispatcher .dispatch_request( DispatchRequest::Post(DispatchPost { dest: chain.clone(), @@ -402,13 +402,13 @@ where .map_err(|_| Error::::DispatchFailed)?; // tracks which chains the asset is deployed on SupportedChains::::insert(asset_id, chain, true); + Self::deposit_event(Event::::AssetRegistered { asset_id, commitment, dest: chain }); } AssetMetadatas::::insert(asset_id, metadata); let who: T::AccountId = PalletId(PALLET_ID).into_account_truncating(); AssetOwners::::insert(asset_id, who); - Self::deposit_event(Event::::AssetRegistered { asset_id }); Ok(()) } } diff --git a/modules/ismp/pallets/token-governor/src/lib.rs b/modules/ismp/pallets/token-governor/src/lib.rs index d72e38fd7..3ff651e19 100644 --- a/modules/ismp/pallets/token-governor/src/lib.rs +++ b/modules/ismp/pallets/token-governor/src/lib.rs @@ -112,6 +112,10 @@ pub mod pallet { AssetRegistered { /// The asset identifier asset_id: H256, + /// Request commitment + commitment: H256, + /// Destination chain + dest: StateMachine, }, /// A new pending asset has been registered NewPendingAsset { @@ -367,11 +371,26 @@ pub mod pallet { } } -impl IsmpModule for Pallet { +impl IsmpModule for Pallet +where + T::AccountId: From<[u8; 32]>, +{ fn on_accept( &self, PostRequest { body: data, from, source, .. }: PostRequest, ) -> Result<(), ismp::error::Error> { + // Only substrate chains are allowed to fully register assets remotely + if source.is_substrate() { + let remote_reg: RemoteERC6160AssetRegistration = + codec::Decode::decode(&mut &*data) + .map_err(|_| ismp::error::Error::Custom(format!("Failed to decode data")))?; + for asset in remote_reg.assets { + Pallet::::register_asset(asset, remote_reg.owner.clone()).map_err(|e| { + ismp::error::Error::Custom(format!("Failed create asset {e:?}")) + })?; + } + return Ok(()) + } let RegistrarParams { address, .. } = TokenRegistrarParams::::get(&source) .ok_or_else(|| ismp::error::Error::Custom(format!("Pallet is not initialized")))?; if from != address.as_bytes().to_vec() { diff --git a/modules/ismp/pallets/token-governor/src/types.rs b/modules/ismp/pallets/token-governor/src/types.rs index daad26a9c..bfeea058d 100644 --- a/modules/ismp/pallets/token-governor/src/types.rs +++ b/modules/ismp/pallets/token-governor/src/types.rs @@ -101,6 +101,15 @@ pub struct ERC6160AssetRegistration { pub chains: Vec, } +/// Holds data required for multi-chain native asset registration +#[derive(Debug, Clone, Encode, Decode, scale_info::TypeInfo, PartialEq, Eq)] +pub struct RemoteERC6160AssetRegistration { + /// Owner of these assets + pub owner: AccountId, + /// Assets + pub assets: Vec, +} + /// Holds data required for multi-chain native asset registration (unsigned) #[derive(Debug, Clone, Encode, Decode, scale_info::TypeInfo, PartialEq, Eq)] pub struct UnsignedERC6160AssetRegistration { From 850eebd4b9eef5108adfa33480884cac1906461b Mon Sep 17 00:00:00 2001 From: David Salami Date: Mon, 7 Oct 2024 17:33:48 +0000 Subject: [PATCH 12/15] register assets when creating MNTs --- modules/ismp/pallets/token-gateway/README.md | 3 -- modules/ismp/pallets/token-gateway/src/lib.rs | 39 ++++++++----------- .../ismp/pallets/token-gateway/src/types.rs | 7 +++- 3 files changed, 22 insertions(+), 27 deletions(-) diff --git a/modules/ismp/pallets/token-gateway/README.md b/modules/ismp/pallets/token-gateway/README.md index d88dca124..5ad0ebb83 100644 --- a/modules/ismp/pallets/token-gateway/README.md +++ b/modules/ismp/pallets/token-gateway/README.md @@ -56,8 +56,6 @@ impl IsmpRouter for Router { The pallet requires some setting up before the teleport function is available for use in the runtime. 1. Register your native assets directly on `Hyperbridge` by dispatching `create_erc6160_asset`. -2. Register a map of all local asset Ids to their token gateway equivalents by dispatching `register_assets` extrinsic. - Note: This registration must be done for your native asset also. 3. Set token gateway addresses for the EVM chains of interest by dispatching the `set_token_gateway_addresses` extrinsic. This allows us validate incoming requests. @@ -66,7 +64,6 @@ The pallet requires some setting up before the teleport function is available fo - `teleport` - This function is used to bridge assets to EVM chains through Hyperbridge. - `set_token_gateway_addresses` - This call allows the `AdminOrigin` origin to set the token gateway address for EVM chains. -- `register_assets` - This call allows the configured `AdminOrigin` to register a map of local asset ids to their equivalent asset ids on token gateway. - `create_erc6160_asset` - This call dispatches a request to Hyperbridge to create multi chain native assets on token gateway deployments ## License diff --git a/modules/ismp/pallets/token-gateway/src/lib.rs b/modules/ismp/pallets/token-gateway/src/lib.rs index e1993bfe0..fc449d190 100644 --- a/modules/ismp/pallets/token-gateway/src/lib.rs +++ b/modules/ismp/pallets/token-gateway/src/lib.rs @@ -63,7 +63,7 @@ pub mod pallet { dispatcher::{DispatchPost, DispatchRequest, FeeMetadata, IsmpDispatcher}, host::StateMachine, }; - use pallet_token_governor::{ERC6160AssetRegistration, RemoteERC6160AssetRegistration}; + use pallet_token_governor::RemoteERC6160AssetRegistration; #[pallet::pallet] #[pallet::without_storage_info] @@ -264,44 +264,39 @@ pub mod pallet { Ok(()) } - /// Map some local assets to their token gateway asset ids - #[pallet::call_index(2)] - #[pallet::weight(weight())] - pub fn register_assets( - origin: OriginFor, - assets: AssetRegistration>, - ) -> DispatchResult { - T::AdminOrigin::ensure_origin(origin)?; - for asset_map in assets.assets { - SupportedAssets::::insert( - asset_map.local_id.clone(), - asset_map.token_gateway_asset_id.clone(), - ); - LocalAssets::::insert(asset_map.token_gateway_asset_id, asset_map.local_id); - } - Ok(()) - } - /// Registers a multi-chain ERC6160 asset. The asset should not already exist. /// /// This works by dispatching a request to the TokenGateway module on each requested chain /// to create the asset. - #[pallet::call_index(3)] + #[pallet::call_index(2)] #[pallet::weight(weight())] pub fn create_erc6160_asset( origin: OriginFor, owner: T::AccountId, - assets: Vec, + assets: AssetRegistration>, ) -> DispatchResult { T::AdminOrigin::ensure_origin(origin)?; + for asset_map in assets.assets.clone() { + let asset_id: H256 = + sp_io::hashing::keccak_256(asset_map.reg.symbol.as_ref()).into(); + SupportedAssets::::insert(asset_map.local_id.clone(), asset_id.clone()); + LocalAssets::::insert(asset_id, asset_map.local_id); + } + let dispatcher = ::Dispatcher::default(); let dispatch_post = DispatchPost { dest: T::Coprocessor::get().ok_or_else(|| Error::::CoprocessorNotConfigured)?, from: module_id().0.to_vec(), to: pallet_token_governor::PALLET_ID.to_vec(), timeout: 0, - body: { RemoteERC6160AssetRegistration { assets, owner: owner.clone() }.encode() }, + body: { + RemoteERC6160AssetRegistration { + assets: assets.assets.into_iter().map(|asset_map| asset_map.reg).collect(), + owner: owner.clone(), + } + .encode() + }, }; let metadata = FeeMetadata { payer: owner.into(), fee: Default::default() }; diff --git a/modules/ismp/pallets/token-gateway/src/types.rs b/modules/ismp/pallets/token-gateway/src/types.rs index 746592ead..1da61b68a 100644 --- a/modules/ismp/pallets/token-gateway/src/types.rs +++ b/modules/ismp/pallets/token-gateway/src/types.rs @@ -18,6 +18,7 @@ use alloc::vec::Vec; use frame_support::{pallet_prelude::*, traits::fungibles}; use ismp::host::StateMachine; +use pallet_token_governor::ERC6160AssetRegistration; use primitive_types::H256; use crate::Config; @@ -47,15 +48,17 @@ pub struct TeleportParams { /// Local asset Id and its corresponding token gateway asset id #[derive(Clone, Encode, Decode, scale_info::TypeInfo, PartialEq, Eq, RuntimeDebug)] pub struct AssetMap { + /// Local Asset Id pub local_id: AssetId, - pub token_gateway_asset_id: H256, + /// MNT Asset registration details + pub reg: ERC6160AssetRegistration, } /// A struct for registering some assets #[derive(Clone, Encode, Decode, scale_info::TypeInfo, PartialEq, Eq, RuntimeDebug)] #[scale_info(skip_type_params(T))] pub struct AssetRegistration { - pub assets: BoundedVec, ConstU32<5>>, + pub assets: BoundedVec, ConstU32<10>>, } alloy_sol_macro::sol! { From 72bbdc4b651cec4d14e289c412ea8879d05aba21 Mon Sep 17 00:00:00 2001 From: David Salami Date: Tue, 8 Oct 2024 08:42:11 +0000 Subject: [PATCH 13/15] improve token gateway request detection in gateway inspector --- .../token-gateway-inspector/src/lib.rs | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/modules/ismp/pallets/token-gateway-inspector/src/lib.rs b/modules/ismp/pallets/token-gateway-inspector/src/lib.rs index e0e6445d7..ee6f477f5 100644 --- a/modules/ismp/pallets/token-gateway-inspector/src/lib.rs +++ b/modules/ismp/pallets/token-gateway-inspector/src/lib.rs @@ -149,18 +149,30 @@ pub mod pallet { } impl Pallet { + pub fn is_token_gateway_request( + from: Vec, + to: Vec, + source: StateMachine, + dest: StateMachine, + ) -> bool { + from == pallet_token_gateway::impls::module_id().0.to_vec() || + TokenGatewayParams::::get(source) + .map(|params| params.address.0.to_vec() == from) + .unwrap_or_default() || + to == pallet_token_gateway::impls::module_id().0.to_vec() || + TokenGatewayParams::::get(dest) + .map(|params| params.address.0.to_vec() == to) + .unwrap_or_default() + } + pub fn inspect_request(post: &PostRequest) -> Result<(), ismp::Error> { - let PostRequest { body, from, source, dest, nonce, .. } = post.clone(); + let PostRequest { body, from, to, source, dest, nonce, .. } = post.clone(); if source.is_evm() && dest.is_evm() { return Ok(()) } - if from == pallet_token_gateway::impls::module_id().0.to_vec() || - TokenGatewayParams::::get(source) - .map(|params| params.address.0.to_vec() == from) - .unwrap_or_default() - { + if Self::is_token_gateway_request(from.clone(), to.clone(), source, dest) { let body = Body::abi_decode(&mut &body[1..], true).map_err(|_| { ismp::error::Error::ModuleDispatchError { msg: "Token Gateway: Failed to decode request body".to_string(), @@ -206,16 +218,12 @@ pub mod pallet { } pub fn handle_timeout(post: &PostRequest) -> Result<(), ismp::Error> { - let PostRequest { body, from, source, dest, nonce, .. } = post.clone(); + let PostRequest { body, from, to, source, dest, nonce, .. } = post.clone(); if source.is_evm() && dest.is_evm() { return Ok(()) } - if from == pallet_token_gateway::impls::module_id().0.to_vec() || - TokenGatewayParams::::get(source) - .map(|params| params.address.0.to_vec() == from) - .unwrap_or_default() - { + if Self::is_token_gateway_request(from.clone(), to.clone(), source, dest) { let body = Body::abi_decode(&mut &body[1..], true).map_err(|_| { ismp::error::Error::ModuleDispatchError { msg: "Token Gateway: Failed to decode request body".to_string(), From a3d85d8f21cb0b69b6528f1f5173e1a25f770948 Mon Sep 17 00:00:00 2001 From: David Salami Date: Tue, 8 Oct 2024 08:48:20 +0000 Subject: [PATCH 14/15] add some comments --- modules/ismp/pallets/token-gateway-inspector/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/ismp/pallets/token-gateway-inspector/src/lib.rs b/modules/ismp/pallets/token-gateway-inspector/src/lib.rs index ee6f477f5..39bdb03b4 100644 --- a/modules/ismp/pallets/token-gateway-inspector/src/lib.rs +++ b/modules/ismp/pallets/token-gateway-inspector/src/lib.rs @@ -168,6 +168,9 @@ pub mod pallet { pub fn inspect_request(post: &PostRequest) -> Result<(), ismp::Error> { let PostRequest { body, from, to, source, dest, nonce, .. } = post.clone(); + // Token Gateway contracts on EVM chains are immutable and non upgradeable + // As long as the initial deployment is valid + // it's impossible to send malicious requests if source.is_evm() && dest.is_evm() { return Ok(()) } @@ -219,6 +222,9 @@ pub mod pallet { pub fn handle_timeout(post: &PostRequest) -> Result<(), ismp::Error> { let PostRequest { body, from, to, source, dest, nonce, .. } = post.clone(); + // Token Gateway contracts on EVM chains are immutable and non upgradeable + // As long as the initial deployment is valid + // it's impossible to send malicious requests if source.is_evm() && dest.is_evm() { return Ok(()) } From 15b8886843426f90dd89e38a8d12ad4b9d7fdca1 Mon Sep 17 00:00:00 2001 From: David Salami Date: Tue, 8 Oct 2024 09:21:04 +0000 Subject: [PATCH 15/15] fix no-std --- modules/ismp/pallets/token-gateway-inspector/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ismp/pallets/token-gateway-inspector/src/lib.rs b/modules/ismp/pallets/token-gateway-inspector/src/lib.rs index 39bdb03b4..1ec040e85 100644 --- a/modules/ismp/pallets/token-gateway-inspector/src/lib.rs +++ b/modules/ismp/pallets/token-gateway-inspector/src/lib.rs @@ -23,7 +23,7 @@ use alloy_sol_types::SolValue; use frame_support::pallet_prelude::Weight; use ismp::router::PostRequest; -use alloc::{format, string::ToString, vec}; +use alloc::{format, string::ToString, vec, vec::Vec}; use primitive_types::{H256, U256}; // Re-export pallet items so that they can be accessed from the crate namespace.