diff --git a/Cargo.toml b/Cargo.toml index 02bc6aede8669..66e69f71a7b08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -157,6 +157,7 @@ members = [ "frame/utility", "frame/vesting", "frame/whitelist", + "frame/ibc", "primitives/api", "primitives/api/proc-macro", "primitives/api/test", diff --git a/frame/ibc/Cargo.toml b/frame/ibc/Cargo.toml new file mode 100644 index 0000000000000..15b11038342d5 --- /dev/null +++ b/frame/ibc/Cargo.toml @@ -0,0 +1,84 @@ +[package] +name = 'pallet-ibc' +version = "3.0.0-pre.0" +authors = ['Octopus Network '] +edition = '2021' +license = "Apache-2.0" +homepage = "https://docs.substrate.io/" +repository = "https://github.com/octopus-network/substrate-ibc/" +description = "An IBC implementation on Substrate." +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.2", default-features = false, features = ["derive"] } +log = { version = "0.4.0", default-features = false } +prost-types = { version = "0.11", default-features = false } +prost = { version = "0.11", default-features = false } +serde_json = { version = "1.0", default-features = false } +serde = { version = "1.0", default-features = false } +flex-error = { version = "0.4.4", default-features = false } +hex = {version = "0.4.0", default-features = false } + +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +sp-core = { version = "6.0.0", path = "../../primitives/core" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-tracing = { version = "5.0.0", default-features = false, path = "../../primitives/tracing" } +time = { version = "0.3.11", features = ["macros","parsing"], default-features = false} + +ibc = { version = "0.18.0", default-features = false } +ibc-proto = { version = "0.20.0", default-features = false } +tendermint-proto = { version = "=0.23.9", default-features = false } + +frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } + +[dev-dependencies] +hex = '0.4.0' +sha2 = '0.10.2' +serde = { version = "1.0" } +ibc = { version = "0.18.0", features = ["mocks"] } +sp-io = { version = "6.0.0", path = "../../primitives/io" } +sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", path = "../../primitives/std" } +sp-keyring = { version = "6.0.0", path = "../../primitives/keyring" } +frame-support = { version = "4.0.0-dev", path = "../support" } +pallet-assets = { version = "4.0.0-dev", path = "../assets" } +pallet-balances = { version = "4.0.0-dev", path = "../balances" } +pallet-timestamp = { version = "4.0.0-dev", path = "../timestamp" } +pallet-babe = { version = "4.0.0-dev", path = "../babe" } +sp-version = { version = "5.0.0", path = "../../primitives/version" } +sp-core = { version = "6.0.0", path = "../../primitives/core" } +chrono = "0.4.19" + +[features] +default = ['std'] +std = [ + 'codec/std', + 'log/std', + "scale-info/std", + 'frame-benchmarking/std', + 'frame-support/std', + 'frame-system/std', + 'sp-core/std', + 'sp-runtime/std', + 'sp-std/std', + 'sp-io/std', + 'sp-tracing/std', + 'prost-types/std', + 'prost/std', + 'ibc/std', + 'ibc-proto/std', + 'serde_json/std', + 'serde/std', + 'flex-error/std', + 'hex/std', + 'time/std', +] +runtime-benchmarks = ["frame-benchmarking"] +try-runtime = ["frame-support/try-runtime"] \ No newline at end of file diff --git a/frame/ibc/README.md b/frame/ibc/README.md new file mode 100644 index 0000000000000..2bb71b0b97cd6 --- /dev/null +++ b/frame/ibc/README.md @@ -0,0 +1,17 @@ +# Substrate IBC Pallet (work in progress) + +This project is [funded by Interchain Foundation](https://interchain-io.medium.com/ibc-on-substrate-with-cdot-a7025e521028). + +## Overview + +This pallet implements the standard [IBC protocol](https://github.com/cosmos/ics). + +The goal of this pallet is to allow the blockchains built on Substrate to gain the ability to interact with other chains in a trustless way via IBC protocol. + +The pallet implements the chain specific logic of [ICS spec](https://github.com/cosmos/ibc/tree/51f0c9e8d8ebcbe6f7f023a8b80f65a8fab705e3/spec), and is integrated with [ibc-rs](https://github.com/informalsystems/ibc-rs), which implements the generic cross-chain logic in [ICS spec](https://github.com/cosmos/ibc/tree/51f0c9e8d8ebcbe6f7f023a8b80f65a8fab705e3/spec). + +## Interface + +### Dispatchable Functions +- `deliver` - This function acts as an entry for most of the IBC request. I.e., create clients, update clients, handshakes to create channels, ...etc +- `raw_transfer` - ICS20 fungible token transfer, Handling transfer request as sending chain or receiving chain. \ No newline at end of file diff --git a/frame/ibc/src/context.rs b/frame/ibc/src/context.rs new file mode 100644 index 0000000000000..8508e5e65c650 --- /dev/null +++ b/frame/ibc/src/context.rs @@ -0,0 +1,67 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) 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. + +use crate::*; +use alloc::{ + borrow::{Borrow, Cow, ToOwned}, + collections::BTreeMap, + sync::Arc, +}; +use scale_info::TypeInfo; + +use crate::module::applications::transfer::transfer_handle_callback::TransferModule; +use ibc::{ + applications::transfer::{context::Ics20Context, error::Error as ICS20Error, MODULE_ID_STR}, + core::{ + ics04_channel::{ + channel::{Counterparty, Order}, + error::Error as Ics04Error, + Version, + }, + ics24_host::identifier::{ChannelId, ConnectionId, PortId}, + ics26_routing::context::{ + Ics26Context, Module, ModuleId, ModuleOutputBuilder, RouterBuilder, + }, + }, +}; + +use crate::module::core::ics26_routing::{Router, SubRouterBuilder}; + +/// A struct capturing all the functional dependencies (i.e., context) +/// which the ICS26 module requires to be able to dispatch and process IBC messages. +#[derive(Clone, Debug)] +pub struct Context { + pub _pd: PhantomData, + pub router: Router, +} + +impl Context { + pub fn new() -> Self { + let r = SubRouterBuilder::default() + .add_route(MODULE_ID_STR.parse().unwrap(), TransferModule(PhantomData::)) // register transfer Module + .unwrap() + .build(); + + Self { _pd: PhantomData::default(), router: r } + } +} + +impl Default for Context { + fn default() -> Self { + Self::new() + } +} diff --git a/frame/ibc/src/events.rs b/frame/ibc/src/events.rs new file mode 100644 index 0000000000000..92db60337f2d3 --- /dev/null +++ b/frame/ibc/src/events.rs @@ -0,0 +1,356 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) 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. + +use crate::*; +use core::borrow::Borrow; +use ibc::{core::ics26_routing, events::IbcEvent as RawIbcEvent}; + +/// ibc-rs' `ModuleEvent` representation in substrate +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct ModuleEvent { + pub kind: Vec, + pub module_name: ModuleId, + pub attributes: Vec, +} + +impl From for ModuleEvent { + fn from(module_event: ibc::events::ModuleEvent) -> Self { + Self { + kind: module_event.kind.as_bytes().to_vec(), + module_name: module_event.module_name.into(), + attributes: module_event.attributes.into_iter().map(|event| event.into()).collect(), + } + } +} + +impl From for ibc::events::ModuleEvent { + fn from(module_event: ModuleEvent) -> Self { + Self { + kind: String::from_utf8(module_event.kind).expect("never failed"), + module_name: module_event.module_name.into(), + attributes: module_event.attributes.into_iter().map(|event| event.into()).collect(), + } + } +} + +/// ibc-rs' `ModuleId` representation in substrate +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct ModuleId(pub Vec); + +impl From for ModuleId { + fn from(module_id: ics26_routing::context::ModuleId) -> Self { + Self(format!("{}", module_id).as_bytes().to_vec()) + } +} + +impl From for ics26_routing::context::ModuleId { + fn from(module_id: ModuleId) -> Self { + ics26_routing::context::ModuleId::from_str( + &String::from_utf8(module_id.0).expect("Convert From UTF8 Never Faild"), + ) + .expect("should never fiaild") + } +} + +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct ModuleEventAttribute { + pub key: Vec, + pub value: Vec, +} + +impl From for ModuleEventAttribute { + fn from(module_event_attribute: ibc::events::ModuleEventAttribute) -> Self { + Self { + key: module_event_attribute.key.as_bytes().to_vec(), + value: module_event_attribute.value.as_bytes().to_vec(), + } + } +} + +impl From for ibc::events::ModuleEventAttribute { + fn from(module_event_attribute: ModuleEventAttribute) -> Self { + Self { + key: String::from_utf8(module_event_attribute.key).expect("should not be faild"), + value: String::from_utf8(module_event_attribute.value).expect("should not be faild"), + } + } +} + +impl From for Event { + fn from(value: RawIbcEvent) -> Self { + match value { + RawIbcEvent::NewBlock(value) => Event::::NewBlock { height: value.height.into() }, + RawIbcEvent::CreateClient(value) => { + let height = value.0.height; + let client_id = value.0.client_id; + let client_type = value.0.client_type; + let consensus_height = value.0.consensus_height; + Event::::CreateClient { + height: height.into(), + client_id: client_id.into(), + client_type: client_type.into(), + consensus_height: consensus_height.into(), + } + }, + RawIbcEvent::UpdateClient(value) => { + let height = value.common.height; + let client_id = value.common.client_id; + let client_type = value.common.client_type; + let consensus_height = value.common.consensus_height; + Event::::UpdateClient { + height: height.into(), + client_id: client_id.into(), + client_type: client_type.into(), + consensus_height: consensus_height.into(), + } + }, + // Upgrade client events are not currently being used + RawIbcEvent::UpgradeClient(value) => { + let height = value.0.height; + let client_id = value.0.client_id; + let client_type = value.0.client_type; + let consensus_height = value.0.consensus_height; + Event::::UpgradeClient { + height: height.into(), + client_id: client_id.into(), + client_type: client_type.into(), + consensus_height: consensus_height.into(), + } + }, + RawIbcEvent::ClientMisbehaviour(value) => { + let height = value.0.height; + let client_id = value.0.client_id; + let client_type = value.0.client_type; + let consensus_height = value.0.consensus_height; + Event::::ClientMisbehaviour { + height: height.into(), + client_id: client_id.into(), + client_type: client_type.into(), + consensus_height: consensus_height.into(), + } + }, + RawIbcEvent::OpenInitConnection(value) => { + let height = value.attributes().height; + let connection_id: Option = + value.attributes().connection_id.clone().map(|val| val.into()); + let client_id = value.attributes().client_id.clone(); + let counterparty_connection_id: Option = + value.attributes().counterparty_connection_id.clone().map(|val| val.into()); + + let counterparty_client_id = value.attributes().counterparty_client_id.clone(); + Event::::OpenInitConnection { + height: height.into(), + connection_id, + client_id: client_id.into(), + counterparty_connection_id, + counterparty_client_id: counterparty_client_id.into(), + } + }, + RawIbcEvent::OpenTryConnection(value) => { + let height = value.attributes().height; + let connection_id: Option = + value.attributes().connection_id.clone().map(|val| val.into()); + let client_id = value.attributes().client_id.clone(); + let counterparty_connection_id: Option = + value.attributes().counterparty_connection_id.clone().map(|val| val.into()); + + let counterparty_client_id = value.attributes().counterparty_client_id.clone(); + Event::::OpenTryConnection { + height: height.into(), + connection_id, + client_id: client_id.into(), + counterparty_connection_id, + counterparty_client_id: counterparty_client_id.into(), + } + }, + RawIbcEvent::OpenAckConnection(value) => { + let height = value.attributes().height; + let connection_id: Option = + value.attributes().connection_id.clone().map(|val| val.into()); + let client_id = value.attributes().client_id.clone(); + let counterparty_connection_id: Option = + value.attributes().counterparty_connection_id.clone().map(|val| val.into()); + + let counterparty_client_id = value.attributes().counterparty_client_id.clone(); + Event::::OpenAckConnection { + height: height.into(), + connection_id, + client_id: client_id.into(), + counterparty_connection_id, + counterparty_client_id: counterparty_client_id.into(), + } + }, + RawIbcEvent::OpenConfirmConnection(value) => { + let height = value.attributes().height; + let connection_id: Option = + value.attributes().connection_id.clone().map(|val| val.into()); + let client_id = value.attributes().client_id.clone(); + let counterparty_connection_id: Option = + value.attributes().counterparty_connection_id.clone().map(|val| val.into()); + + let counterparty_client_id = value.attributes().counterparty_client_id.clone(); + Event::::OpenConfirmConnection { + height: height.into(), + connection_id, + client_id: client_id.into(), + counterparty_connection_id, + counterparty_client_id: counterparty_client_id.into(), + } + }, + RawIbcEvent::OpenInitChannel(value) => { + let height = value.height; + let port_id = value.port_id.clone(); + let channel_id: Option = value.channel_id.clone().map(|val| val.into()); + let connection_id = value.connection_id.clone(); + let counterparty_port_id = value.counterparty_port_id.clone(); + let counterparty_channel_id: Option = + value.channel_id.map(|val| val.into()); + Event::::OpenInitChannel { + height: height.into(), + port_id: port_id.into(), + channel_id, + connection_id: connection_id.into(), + counterparty_port_id: counterparty_port_id.into(), + counterparty_channel_id, + } + }, + RawIbcEvent::OpenTryChannel(value) => { + let height = value.height; + let port_id = value.port_id.clone(); + let channel_id: Option = value.channel_id.clone().map(|val| val.into()); + let connection_id = value.connection_id.clone(); + let counterparty_port_id = value.counterparty_port_id.clone(); + let counterparty_channel_id: Option = + value.channel_id.map(|val| val.into()); + Event::::OpenTryChannel { + height: height.into(), + port_id: port_id.into(), + channel_id, + connection_id: connection_id.into(), + counterparty_port_id: counterparty_port_id.into(), + counterparty_channel_id, + } + }, + RawIbcEvent::OpenAckChannel(value) => { + let height = value.height; + let port_id = value.port_id.clone(); + let channel_id: Option = value.channel_id.clone().map(|val| val.into()); + let connection_id = value.connection_id.clone(); + let counterparty_port_id = value.counterparty_port_id.clone(); + let counterparty_channel_id: Option = + value.channel_id.map(|val| val.into()); + Event::::OpenAckChannel { + height: height.into(), + port_id: port_id.into(), + channel_id, + connection_id: connection_id.into(), + counterparty_port_id: counterparty_port_id.into(), + counterparty_channel_id, + } + }, + RawIbcEvent::OpenConfirmChannel(value) => { + let height = value.height; + let port_id = value.port_id.clone(); + let channel_id: Option = value.channel_id.clone().map(|val| val.into()); + let connection_id = value.connection_id.clone(); + let counterparty_port_id = value.counterparty_port_id; + let counterparty_channel_id: Option = + value.channel_id.map(|val| val.into()); + Event::::OpenConfirmChannel { + height: height.into(), + port_id: port_id.into(), + channel_id, + connection_id: connection_id.into(), + counterparty_port_id: counterparty_port_id.into(), + counterparty_channel_id, + } + }, + RawIbcEvent::CloseInitChannel(value) => { + let height = value.height; + let port_id = value.port_id.clone(); + let channel_id: Option = Some(value.channel_id.into()); + let connection_id = value.connection_id.clone(); + let counterparty_port_id = value.counterparty_port_id; + let counterparty_channel_id: Option = + value.counterparty_channel_id.map(|val| val.into()); + Event::::CloseInitChannel { + height: height.into(), + port_id: port_id.into(), + channel_id, + connection_id: connection_id.into(), + counterparty_port_id: counterparty_port_id.into(), + counterparty_channel_id, + } + }, + RawIbcEvent::CloseConfirmChannel(value) => { + let height = value.height; + let port_id = value.port_id.clone(); + let channel_id: Option = value.channel_id.clone().map(|val| val.into()); + let connection_id = value.connection_id.clone(); + let counterparty_port_id = value.counterparty_port_id.clone(); + let counterparty_channel_id: Option = + value.channel_id.map(|val| val.into()); + Event::::CloseConfirmChannel { + height: height.into(), + port_id: port_id.into(), + channel_id, + connection_id: connection_id.into(), + counterparty_port_id: counterparty_port_id.into(), + counterparty_channel_id, + } + }, + RawIbcEvent::SendPacket(value) => { + let height = value.height; + let packet = value.packet; + Event::::SendPacket { height: height.into(), packet: packet.into() } + }, + RawIbcEvent::ReceivePacket(value) => { + let height = value.height; + let packet = value.packet; + Event::::ReceivePacket { height: height.into(), packet: packet.into() } + }, + RawIbcEvent::WriteAcknowledgement(value) => { + let height = value.height; + let packet = value.packet; + let ack = value.ack; + + Event::::WriteAcknowledgement { + height: height.into(), + packet: packet.into(), + ack, + } + }, + RawIbcEvent::AcknowledgePacket(value) => { + let height = value.height; + let packet = value.packet; + Event::::AcknowledgePacket { height: height.into(), packet: packet.into() } + }, + RawIbcEvent::TimeoutPacket(value) => { + let height = value.height; + let packet = value.packet; + Event::::TimeoutPacket { height: height.into(), packet: packet.into() } + }, + RawIbcEvent::TimeoutOnClosePacket(value) => { + let height = value.height; + let packet = value.packet; + Event::::TimeoutOnClosePacket { height: height.into(), packet: packet.into() } + }, + RawIbcEvent::AppModule(value) => Event::::AppModule(value.into()), + RawIbcEvent::ChainError(value) => Event::::ChainError(value.as_bytes().to_vec()), + } + } +} diff --git a/frame/ibc/src/lib.rs b/frame/ibc/src/lib.rs new file mode 100644 index 0000000000000..867e0e7917542 --- /dev/null +++ b/frame/ibc/src/lib.rs @@ -0,0 +1,672 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) 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. + +//! # Substrate IBC Pallet (work in progress) +//! +//! This project is [funded by Interchain Foundation](https://interchain-io.medium.com/ibc-on-substrate-with-cdot-a7025e521028). +//! +//! ## Overview +//! +//! This pallet implements the standard [IBC protocol](https://github.com/cosmos/ics). +//! +//! The goal of this pallet is to allow the blockchains built on Substrate to gain the ability to +//! interact with other chains in a trustless way via IBC protocol. +//! +//! The pallet implements the chain specific logic of [ICS spec](https://github.com/cosmos/ibc/tree/51f0c9e8d8ebcbe6f7f023a8b80f65a8fab705e3/spec), and is integrated with [ibc-rs](https://github.com/informalsystems/ibc-rs), which implements the generic cross-chain logic in [ICS spec](https://github.com/cosmos/ibc/tree/51f0c9e8d8ebcbe6f7f023a8b80f65a8fab705e3/spec). +//! +//! ## Interface +//! +//! ### Dispatchable Functions +//! - `deliver` - This function acts as an entry for most of the IBC request. I.e., create clients, +//! update clients, handshakes to create channels, ...etc +//! - `raw_transfer` - ICS20 fungible token transfer, Handling transfer request as sending chain or +//! receiving chain. + +#![cfg_attr(not(feature = "std"), no_std)] +// todo need in future to remove +#![allow(unreachable_code)] +#![allow(unreachable_patterns)] +#![allow(clippy::type_complexity)] +#![allow(non_camel_case_types)] +#![allow(dead_code)] +#![allow(unused_assignments)] +#![allow(unused_imports)] +#![allow(unused_variables)] +#![allow(clippy::too_many_arguments)] + +extern crate alloc; +extern crate core; + +pub use pallet::*; + +use alloc::{ + format, + string::{String, ToString}, +}; +use core::{marker::PhantomData, str::FromStr}; +use scale_info::{prelude::vec, TypeInfo}; +use serde::{Deserialize, Serialize}; + +use codec::{Codec, Decode, Encode}; + +use frame_support::{ + sp_runtime::traits::{AtLeast32BitUnsigned, CheckedConversion}, + sp_std::fmt::Debug, + traits::{tokens::fungibles, Currency, ExistenceRequirement::AllowDeath}, + PalletId, +}; +use frame_system::ensure_signed; +use sp_runtime::{traits::AccountIdConversion, DispatchError, RuntimeDebug, TypeId}; +use sp_std::prelude::*; + +use ibc::{ + applications::transfer::msgs::transfer::MsgTransfer, + core::{ + ics02_client::{client_state::AnyClientState, height}, + ics04_channel::timeout::TimeoutHeight, + ics24_host::identifier::{self, ChainId as ICS24ChainId, ChannelId as IbcChannelId}, + ics26_routing::msgs::Ics26Envelope, + }, + timestamp, + tx_msg::Msg, +}; + +use tendermint_proto::Protobuf; + +pub mod context; +pub mod events; +pub mod module; +pub mod traits; +pub mod utils; + +use crate::{context::Context, traits::AssetIdAndNameProvider}; + +use crate::module::core::ics24_host::{ + ChannelId, ClientId, ClientType, ConnectionId, Height, Packet, PortId, Timestamp, +}; + +pub const REVISION_NUMBER: u64 = 8888; + +type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + +/// A struct corresponds to `Any` in crate "prost-types", used in ibc-rs. +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct Any { + pub type_url: Vec, + pub value: Vec, +} + +impl From for Any { + fn from(any: ibc_proto::google::protobuf::Any) -> Self { + Self { type_url: any.type_url.as_bytes().to_vec(), value: any.value } + } +} + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use crate::{ + events::ModuleEvent, + module::{ + applications::transfer::transfer_handle_callback::TransferModule, + core::ics24_host::{ + ChannelId, ClientId, ClientType, ConnectionId, Height, Packet, PortId, Sequence, + Timestamp, + }, + }, + }; + use frame_support::{ + dispatch::DispatchResult, + pallet_prelude::*, + traits::{ + fungibles::{Inspect, Mutate, Transfer}, + UnixTime, + }, + }; + use frame_system::pallet_prelude::*; + use ibc::{ + applications::transfer::context::Ics20Context, + core::{ + ics02_client::client_consensus::AnyConsensusState, + ics04_channel::{ + channel::{Counterparty, Order}, + context::ChannelKeeper, + events::WriteAcknowledgement, + Version, + }, + ics24_host::{ + identifier::{ChannelId as IbcChannelId, PortId as IbcPortId}, + path::{ClientConsensusStatePath, ClientStatePath}, + }, + ics26_routing::error::Error as Ics26Error, + }, + events::IbcEvent, + handler::{HandlerOutput, HandlerOutputBuilder}, + signer::Signer, + }; + use sp_runtime::traits::IdentifyAccount; + + /// Configure the pallet by specifying the parameters and types on which it depends. + #[pallet::config] + pub trait Config: frame_system::Config + Sync + Send + Debug { + /// The overarching event type. + type Event: From> + IsType<::Event>; + + /// The provider providing timestamp of host chain + type TimeProvider: UnixTime; + + /// The currency type of the runtime + type Currency: Currency; + + /// Identifier for the class of asset. + type AssetId: Member + + Parameter + + AtLeast32BitUnsigned + + Codec + + Copy + + Debug + + Default + + MaybeSerializeDeserialize; + + /// The units in which we record balances. + type AssetBalance: Parameter + + Member + + AtLeast32BitUnsigned + + Codec + + Default + + From + + Into + + Copy + + MaybeSerializeDeserialize + + Debug; + + /// Expose customizable associated type of asset transfer, lock and unlock + type Assets: Transfer + + Mutate + + Inspect; + + /// Map of cross-chain asset ID & name + type AssetIdByName: AssetIdAndNameProvider; + + /// Account Id Conversion from SS58 string or hex string + type AccountIdConversion: TryFrom + + IdentifyAccount + + Clone + + PartialEq + + Debug; + + // The native token name + const NATIVE_TOKEN_NAME: &'static [u8]; + } + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + #[pallet::without_storage_info] + pub struct Pallet(_); + + #[pallet::storage] + /// ClientStatePath(client_id) => ClientState + pub type ClientStates = + StorageMap<_, Blake2_128Concat, Vec, Vec, ValueQuery>; + + #[pallet::storage] + /// (client_id, height) => timestamp + pub type ClientProcessedTimes = StorageDoubleMap< + _, + Blake2_128Concat, + Vec, + Blake2_128Concat, + Vec, + Vec, + ValueQuery, + >; + + #[pallet::storage] + /// (client_id, height) => host_height + pub type ClientProcessedHeights = StorageDoubleMap< + _, + Blake2_128Concat, + Vec, + Blake2_128Concat, + Vec, + Vec, + ValueQuery, + >; + + #[pallet::storage] + /// ClientConsensusStatePath(client_id, Height) => ConsensusState + pub type ConsensusStates = + StorageMap<_, Blake2_128Concat, Vec, Vec, ValueQuery>; + + #[pallet::storage] + /// ConnectionsPath(connection_id) => ConnectionEnd + pub type Connections = StorageMap<_, Blake2_128Concat, Vec, Vec, ValueQuery>; + + #[pallet::storage] + /// ChannelEndPath(port_id, channel_id) => ChannelEnd + pub type Channels = StorageMap<_, Blake2_128Concat, Vec, Vec, ValueQuery>; + + #[pallet::storage] + /// ConnectionsPath(connection_id) => Vec + pub type ChannelsConnection = + StorageMap<_, Blake2_128Concat, Vec, Vec>, ValueQuery>; + + #[pallet::storage] + /// SeqSendsPath(port_id, channel_id) => sequence + pub type NextSequenceSend = + StorageMap<_, Blake2_128Concat, Vec, u64, ValueQuery>; + + #[pallet::storage] + /// SeqRecvsPath(port_id, channel_id) => sequence + pub type NextSequenceRecv = + StorageMap<_, Blake2_128Concat, Vec, u64, ValueQuery>; + + #[pallet::storage] + /// SeqAcksPath(port_id, channel_id) => sequence + pub type NextSequenceAck = StorageMap<_, Blake2_128Concat, Vec, u64, ValueQuery>; + + #[pallet::storage] + /// AcksPath(port_id, channel_id, sequence) => hash of acknowledgement + pub type Acknowledgements = + StorageMap<_, Blake2_128Concat, Vec, Vec, ValueQuery>; + + #[pallet::storage] + /// ClientTypePath(client_id) => client_type + pub type Clients = StorageMap<_, Blake2_128Concat, Vec, Vec, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn client_counter)] + /// client counter + pub type ClientCounter = StorageValue<_, u64, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn connection_counter)] + /// connection counter + pub type ConnectionCounter = StorageValue<_, u64, ValueQuery>; + + #[pallet::storage] + /// channel counter + pub type ChannelCounter = StorageValue<_, u64, ValueQuery>; + + #[pallet::storage] + /// ClientConnectionsPath(client_id) => connection_id + pub type ConnectionClient = + StorageMap<_, Blake2_128Concat, Vec, Vec, ValueQuery>; + + #[pallet::storage] + /// ReceiptsPath(port_id, channel_id, sequence) => receipt + pub type PacketReceipt = + StorageMap<_, Blake2_128Concat, Vec, Vec, ValueQuery>; + + #[pallet::storage] + /// CommitmentsPath(port_id, channel_id, sequence) => hash of (timestamp, height, packet) + pub type PacketCommitment = + StorageMap<_, Blake2_128Concat, Vec, Vec, ValueQuery>; + + #[pallet::storage] + /// (height, port_id, channel_id, sequence) => send-packet event + pub type SendPacketEvent = StorageNMap< + _, + ( + NMapKey>, + NMapKey>, + NMapKey, + ), + Vec, + ValueQuery, + >; + + #[pallet::storage] + /// (port_id, channel_id, sequence) => writ ack event + pub type WriteAckPacketEvent = StorageNMap< + _, + ( + NMapKey>, + NMapKey>, + NMapKey, + ), + Vec, + ValueQuery, + >; + + #[pallet::storage] + /// Latest height + pub type LatestHeight = StorageValue<_, Vec, ValueQuery>; + + #[pallet::storage] + /// Previous host block height + pub type OldHeight = StorageValue<_, u64, ValueQuery>; + + #[pallet::storage] + /// (asset name) => asset id + pub type AssetIdByName = + StorageMap<_, Twox64Concat, Vec, T::AssetId, ValueQuery>; + + #[pallet::genesis_config] + pub struct GenesisConfig { + pub asset_id_by_name: Vec<(String, T::AssetId)>, + } + + #[cfg(feature = "std")] + impl Default for GenesisConfig { + fn default() -> Self { + Self { asset_id_by_name: Vec::new() } + } + } + + #[pallet::genesis_build] + impl GenesisBuild for GenesisConfig { + fn build(&self) { + for (token_id, id) in self.asset_id_by_name.iter() { + >::insert(token_id.as_bytes(), id); + } + } + } + + /// Substrate IBC event list + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// New block + NewBlock { height: Height }, + /// Client Create + CreateClient { + height: Height, + client_id: ClientId, + client_type: ClientType, + consensus_height: Height, + }, + /// Client update + UpdateClient { + height: Height, + client_id: ClientId, + client_type: ClientType, + consensus_height: Height, + }, + /// Client upgraded + UpgradeClient { + height: Height, + client_id: ClientId, + client_type: ClientType, + consensus_height: Height, + }, + /// Client misbehaviour + ClientMisbehaviour { + height: Height, + client_id: ClientId, + client_type: ClientType, + consensus_height: Height, + }, + /// Connection open init + OpenInitConnection { + height: Height, + connection_id: Option, + client_id: ClientId, + counterparty_connection_id: Option, + counterparty_client_id: ClientId, + }, + /// Connection open try + OpenTryConnection { + height: Height, + connection_id: Option, + client_id: ClientId, + counterparty_connection_id: Option, + counterparty_client_id: ClientId, + }, + /// Connection open ack + OpenAckConnection { + height: Height, + connection_id: Option, + client_id: ClientId, + counterparty_connection_id: Option, + counterparty_client_id: ClientId, + }, + /// Connection open confirm + OpenConfirmConnection { + height: Height, + connection_id: Option, + client_id: ClientId, + counterparty_connection_id: Option, + counterparty_client_id: ClientId, + }, + /// Channel open init + OpenInitChannel { + height: Height, + port_id: PortId, + channel_id: Option, + connection_id: ConnectionId, + counterparty_port_id: PortId, + counterparty_channel_id: Option, + }, + /// Channel open try + OpenTryChannel { + height: Height, + port_id: PortId, + channel_id: Option, + connection_id: ConnectionId, + counterparty_port_id: PortId, + counterparty_channel_id: Option, + }, + /// Channel open ack + OpenAckChannel { + height: Height, + port_id: PortId, + channel_id: Option, + connection_id: ConnectionId, + counterparty_port_id: PortId, + counterparty_channel_id: Option, + }, + /// Channel open confirm + OpenConfirmChannel { + height: Height, + port_id: PortId, + channel_id: Option, + connection_id: ConnectionId, + counterparty_port_id: PortId, + counterparty_channel_id: Option, + }, + /// Channel close init + CloseInitChannel { + height: Height, + port_id: PortId, + channel_id: Option, + connection_id: ConnectionId, + counterparty_port_id: PortId, + counterparty_channel_id: Option, + }, + /// Channel close confirm + CloseConfirmChannel { + height: Height, + port_id: PortId, + channel_id: Option, + connection_id: ConnectionId, + counterparty_port_id: PortId, + counterparty_channel_id: Option, + }, + /// Send packet + SendPacket { height: Height, packet: Packet }, + /// Receive packet + ReceivePacket { height: Height, packet: Packet }, + /// WriteAcknowledgement packet + WriteAcknowledgement { height: Height, packet: Packet, ack: Vec }, + /// Acknowledgements packet + AcknowledgePacket { height: Height, packet: Packet }, + /// Timeout packet + TimeoutPacket { height: Height, packet: Packet }, + /// TimoutOnClose packet + TimeoutOnClosePacket { height: Height, packet: Packet }, + /// Chain error + ChainError(Vec), + /// App Module + AppModule(ModuleEvent), + /// Transfer native token + TransferNativeToken(T::AccountIdConversion, T::AccountIdConversion, BalanceOf), + /// Transfer non-native token + TransferNoNativeToken( + T::AccountIdConversion, + T::AccountIdConversion, + ::AssetBalance, + ), + /// Burn cross chain token + BurnToken(T::AssetId, T::AccountIdConversion, T::AssetBalance), + /// Mint chairperson token + MintToken(T::AssetId, T::AccountIdConversion, T::AssetBalance), + } + + /// Errors in MMR verification informing users that something went wrong. + #[pallet::error] + pub enum Error { + /// Invalid token id + InvalidTokenId, + /// Wrong assert id + WrongAssetId, + // Parser Msg Transfer Error + ParserMsgTransferError, + } + + /// Dispatchable functions allows users to interact with the pallet and invoke state changes. + /// These functions materialize as "extrinsic", which are often compared to transactions. + /// Dispatch able functions must be annotated with a weight and must return a DispatchResult. + #[pallet::call] + impl Pallet { + /// This function acts as an entry for most of the IBC request. + /// I.e., create clients, update clients, handshakes to create channels, ...etc + /// + /// The origin must be Signed and the sender must have sufficient funds fee. + /// + /// Parameters: + /// - `messages`: The arbitrary ICS message's representation in Substrate, which contains an + /// URL and + /// a serialized protocol buffer message. The URL name that uniquely identifies the type of + /// the serialized protocol buffer message. + /// + /// The relevant events are emitted when successful. + #[pallet::weight(0)] + pub fn deliver(origin: OriginFor, messages: Vec) -> DispatchResultWithPostInfo { + let _sender = ensure_signed(origin)?; + let mut ctx = Context::::new(); + + let messages: Vec = messages + .into_iter() + .map(|message| ibc_proto::google::protobuf::Any { + type_url: String::from_utf8(message.type_url.clone()) + .expect("Convert From UTF8 Never Faild"), + value: message.value, + }) + .collect(); + + for (_, message) in messages.clone().into_iter().enumerate() { + match ibc::core::ics26_routing::handler::deliver(&mut ctx, message.clone()) { + Ok(ibc::core::ics26_routing::handler::MsgReceipt { events, log: _log }) => { + // deposit events about send packet event and ics20 transfer event + for event in events { + Self::deposit_event(event.clone().into()); + } + }, + Err(error) => { + log::error!("deliver error : {:?} ", error); + }, + }; + } + + Ok(().into()) + } + + /// ICS20 fungible token transfer. + /// Handling transfer request as sending chain or receiving chain. + /// + /// Parameters: + /// - `messages`: A serialized protocol buffer message containing the transfer request. + /// + /// The relevant events are emitted when successful. + #[pallet::weight(0)] + pub fn raw_transfer( + origin: OriginFor, + messages: Vec, + ) -> DispatchResultWithPostInfo { + let _sender = ensure_signed(origin)?; + let mut ctx = TransferModule(PhantomData::); + + let messages: Vec = messages + .into_iter() + .map(|message| ibc_proto::google::protobuf::Any { + type_url: String::from_utf8(message.type_url.clone()) + .expect("Convert From UTF8 Never Faild"), + value: message.value, + }) + .collect(); + for message in messages { + let mut handle_out = HandlerOutputBuilder::new(); + let msg_transfer = MsgTransfer::try_from(message) + .map_err(|_| Error::::ParserMsgTransferError)?; + let result = ibc::applications::transfer::relay::send_transfer::send_transfer( + &mut ctx, + &mut handle_out, + msg_transfer, + ); + match result { + Ok(_value) => { + log::trace!("raw_transfer Successful!"); + }, + Err(error) => { + log::trace!("raw_transfer Error : {:?} ", error); + }, + } + + let HandlerOutput::<()> { result, log, events } = handle_out.with_result(()); + + // deposit events about send packet event and ics20 transfer event + for event in events { + Self::deposit_event(event.clone().into()); + } + } + + Ok(().into()) + } + } +} + +impl AssetIdAndNameProvider for Pallet { + type Err = Error; + + fn try_get_asset_id(name: impl AsRef<[u8]>) -> Result<::AssetId, Self::Err> { + let asset_id = >::try_get(name.as_ref().to_vec()); + match asset_id { + Ok(id) => Ok(id), + _ => Err(Error::::InvalidTokenId), + } + } + + fn try_get_asset_name(asset_id: T::AssetId) -> Result, Self::Err> { + let token_id = >::iter().find(|p| p.1 == asset_id).map(|p| p.0); + match token_id { + Some(id) => Ok(id), + _ => Err(Error::::WrongAssetId), + } + } +} + +pub fn from_channel_id_to_vec(value: IbcChannelId) -> Vec { + value.to_string().as_bytes().to_vec() +} diff --git a/frame/ibc/src/mock.rs b/frame/ibc/src/mock.rs new file mode 100644 index 0000000000000..2ce8fa8c563c6 --- /dev/null +++ b/frame/ibc/src/mock.rs @@ -0,0 +1,235 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2021 Parity Technologies (UK) 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. + +//! Test utilities + +use crate as pallet_ibc; +pub use frame_support::{ + construct_runtime, parameter_types, + traits::{ + ConstU128, ConstU16, ConstU32, ConstU8, KeyOwnerProofSystem, Randomness, StorageInfo, + }, + weights::{ + constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND}, + DispatchClass, IdentityFee, Weight, + }, + StorageValue, +}; +use frame_system as system; +use frame_system::{ + limits::{BlockLength, BlockWeights}, + EnsureRoot, +}; +use sp_core::{crypto::KeyTypeId, OpaqueMetadata, H256}; +use sp_runtime::{ + create_runtime_str, + generic::{self, Era}, + testing::Header, + traits::{AccountIdLookup, BlakeTwo256, IdentifyAccount, IdentityLookup, Verify}, + MultiSignature, +}; +use sp_version::RuntimeVersion; +use std::time::{Duration, Instant}; + +pub type Signature = MultiSignature; +pub(crate) type AccountId = <::Signer as IdentifyAccount>::AccountId; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +// Configure a mock runtime to test the pallet. +construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Assets: pallet_assets::, + Balances: pallet_balances, + Ibc: pallet_ibc, + } +); + +/// A hash of some data used by the chain. +pub type Hash = sp_core::H256; + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const SS58Prefix: u8 = 42; +} + +/// Index of a transaction in the chain. +pub type Index = u32; +/// An index to a block. +pub type BlockNumber = u32; + +impl frame_system::Config for Test { + /// The basic call filter to use in dispatchable. + type BaseCallFilter = frame_support::traits::Everything; + /// Block & extrinsics weights: base values and limits. + type BlockWeights = (); + /// The maximum length of a block (in bytes). + type BlockLength = (); + /// The identifier used to distinguish between accounts. + type AccountId = AccountId; + /// The aggregated dispatch type that is available for extrinsics. + type Call = Call; + /// The lookup mechanism to get account ID from whatever is passed in dispatchers. + type Lookup = AccountIdLookup; + /// The index type for storing how many extrinsics an account has signed. + type Index = Index; + /// The index type for blocks. + type BlockNumber = BlockNumber; + /// The type for hashing blocks and tries. + type Hash = Hash; + /// The hashing algorithm used. + type Hashing = BlakeTwo256; + /// The header type. + type Header = generic::Header; + /// The ubiquitous event type. + type Event = Event; + /// The ubiquitous origin type. + type Origin = Origin; + /// Maximum number of block number to block hash mappings to keep (oldest pruned first). + type BlockHashCount = (); + /// The weight of database operations that the runtime can invoke. + type DbWeight = (); + /// Version of the runtime. + type Version = (); + /// Converts a module to the index of the module in `construct_runtime!`. + /// + /// This type is being generated by `construct_runtime!`. + type PalletInfo = PalletInfo; + /// What to do if a new account is created. + type OnNewAccount = (); + /// What to do if an account is fully reaped from the system. + type OnKilledAccount = (); + /// The data to be stored in an account. + type AccountData = pallet_balances::AccountData; + /// Weight information for the extrinsics of this pallet. + type SystemWeightInfo = (); + /// This is used as an identifier of the chain. 42 is the generic substrate prefix. + type SS58Prefix = ConstU16<42>; + /// The set code logic, just the default since we're not a parachain. + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +pub type Balance = u128; +/// Type used for expressing timestamp. +pub type Moment = u64; + +pub const MILLICENTS: Balance = 10_000_000_000_000; +pub const CENTS: Balance = 1_000 * MILLICENTS; // assume this is worth about a cent. +pub const DOLLARS: Balance = 100 * CENTS; + +parameter_types! { + pub const AssetDeposit: Balance = 100 * DOLLARS; + pub const ApprovalDeposit: Balance = 1 * DOLLARS; + pub const StringLimit: u32 = 50; + pub const MetadataDepositBase: Balance = 10 * DOLLARS; + pub const MetadataDepositPerByte: Balance = 1 * DOLLARS; +} + +impl pallet_assets::Config for Test { + type Event = Event; + type Balance = AssetBalance; + type AssetId = AssetId; + type Currency = Balances; + type ForceOrigin = EnsureRoot; + type AssetDeposit = AssetDeposit; + type AssetAccountDeposit = ConstU128; + type MetadataDepositBase = MetadataDepositBase; + type MetadataDepositPerByte = MetadataDepositPerByte; + type ApprovalDeposit = ApprovalDeposit; + type StringLimit = StringLimit; + type Freezer = (); + type Extra = (); + type WeightInfo = pallet_assets::weights::SubstrateWeight; +} + +parameter_types! { + pub const ExistentialDeposit: Balance = 1 * DOLLARS; + // For weight estimation, we assume that the most locks on an individual account will be 50. + // This number may need to be adjusted in the future if this assumption no longer holds true. + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; +} + +impl pallet_balances::Config for Test { + type MaxLocks = MaxLocks; + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; + /// The type for recording an account's balance. + type Balance = Balance; + /// The ubiquitous event type. + type Event = Event; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = frame_system::Pallet; + type WeightInfo = pallet_balances::weights::SubstrateWeight; +} + +parameter_types! { + pub const MinimumPeriod: Moment = SLOT_DURATION / 2; +} + +impl pallet_timestamp::Config for Test { + /// A timestamp: milliseconds since the unix epoch. + type Moment = Moment; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +parameter_types! { + pub const MaxAuthorities: u32 = 100; + pub const MaxKeys: u32 = 10_000; + pub const MaxPeerInHeartbeats: u32 = 10_000; + pub const MaxPeerDataEncodingSize: u32 = 1_000; +} + +pub const MILLISECS_PER_BLOCK: Moment = 6000; +pub const SECS_PER_BLOCK: Moment = MILLISECS_PER_BLOCK / 1000; + +// NOTE: Currently it is not possible to change the slot duration after the chain has started. +// Attempting to do so will brick block production. +pub const SLOT_DURATION: Moment = MILLISECS_PER_BLOCK; + +// 1 in 4 blocks (on average, not counting collisions) will be primary BABE blocks. +pub const PRIMARY_PROBABILITY: (u64, u64) = (1, 4); + +pub type AssetBalance = u128; +pub type AssetId = u32; + +impl super::pallet::Config for Test { + type Event = Event; + type TimeProvider = pallet_timestamp::Pallet; + type Currency = Balances; + type AssetId = AssetId; + type AssetBalance = AssetBalance; + type Assets = Assets; + type AssetIdByName = Ibc; + type AccountIdConversion = pallet_ibc::module::applications::transfer::IbcAccount; + const NATIVE_TOKEN_NAME: &'static [u8] = b"DEMO"; +} + +// Build genesis storage according to the mock runtime. +pub fn new_test_ext() -> sp_io::TestExternalities { + system::GenesisConfig::default().build_storage::().unwrap().into() +} diff --git a/frame/ibc/src/module/applications/mod.rs b/frame/ibc/src/module/applications/mod.rs new file mode 100644 index 0000000000000..dc8a5fa54cd3a --- /dev/null +++ b/frame/ibc/src/module/applications/mod.rs @@ -0,0 +1,18 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) 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. + +pub mod transfer; diff --git a/frame/ibc/src/module/applications/transfer/channel.rs b/frame/ibc/src/module/applications/transfer/channel.rs new file mode 100644 index 0000000000000..7d116fff5e906 --- /dev/null +++ b/frame/ibc/src/module/applications/transfer/channel.rs @@ -0,0 +1,302 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) 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. + +use super::transfer_handle_callback::TransferModule; +use crate::*; +use core::{str::FromStr, time::Duration}; +use log::{error, info, trace, warn}; + +use crate::context::Context; +use ibc::{ + core::{ + ics02_client::{ + client_consensus::AnyConsensusState, client_state::AnyClientState, + context::ClientReader, + }, + ics03_connection::{ + connection::ConnectionEnd, context::ConnectionReader, error::Error as ICS03Error, + }, + ics04_channel::{ + channel::ChannelEnd, + commitment::{ + AcknowledgementCommitment as IbcAcknowledgementCommitment, + PacketCommitment as IbcPacketCommitment, + }, + context::{ChannelKeeper, ChannelReader}, + error::Error as Ics04Error, + packet::{Receipt, Sequence}, + }, + ics05_port::{context::PortReader, error::Error as Ics05Error}, + ics24_host::{ + identifier::{ChannelId, ClientId, ConnectionId, PortId}, + path::{ + AcksPath, ChannelEndsPath, CommitmentsPath, ConnectionsPath, ReceiptsPath, + SeqAcksPath, SeqRecvsPath, SeqSendsPath, + }, + Path, + }, + ics26_routing::context::ModuleId, + }, + timestamp::Timestamp, + Height, +}; + +impl ChannelReader for TransferModule { + fn channel_end(&self, port_channel_id: &(PortId, ChannelId)) -> Result { + let connect = Context::::new(); + connect.channel_end(port_channel_id) + } + + fn connection_end(&self, connection_id: &ConnectionId) -> Result { + let connect = Context::::new(); + ChannelReader::connection_end(&connect, connection_id) + } + + /// Returns the `ChannelsConnection` for the given identifier `conn_id`. + fn connection_channels( + &self, + conn_id: &ConnectionId, + ) -> Result, Ics04Error> { + let connect = Context::::new(); + connect.connection_channels(conn_id) + } + + fn client_state(&self, client_id: &ClientId) -> Result { + let connect = Context::::new(); + + ChannelReader::client_state(&connect, client_id) + } + + fn client_consensus_state( + &self, + client_id: &ClientId, + height: Height, + ) -> Result { + let connect = Context::::new(); + ChannelReader::client_consensus_state(&connect, client_id, height) + } + + fn get_next_sequence_send( + &self, + port_channel_id: &(PortId, ChannelId), + ) -> Result { + let connect = Context::::new(); + connect.get_next_sequence_send(port_channel_id) + } + + fn get_next_sequence_recv( + &self, + port_channel_id: &(PortId, ChannelId), + ) -> Result { + let connect = Context::::new(); + connect.get_next_sequence_recv(port_channel_id) + } + + fn get_next_sequence_ack( + &self, + port_channel_id: &(PortId, ChannelId), + ) -> Result { + let connect = Context::::new(); + connect.get_next_sequence_ack(port_channel_id) + } + + /// Returns the `PacketCommitment` for the given identifier `(PortId, ChannelId, Sequence)`. + fn get_packet_commitment( + &self, + key: &(PortId, ChannelId, Sequence), + ) -> Result { + let connect = Context::::new(); + connect.get_packet_commitment(key) + } + + fn get_packet_receipt( + &self, + key: &(PortId, ChannelId, Sequence), + ) -> Result { + let connect = Context::::new(); + connect.get_packet_receipt(key) + } + + /// Returns the `Acknowledgements` for the given identifier `(PortId, ChannelId, Sequence)`. + fn get_packet_acknowledgement( + &self, + key: &(PortId, ChannelId, Sequence), + ) -> Result { + let connect = Context::::new(); + connect.get_packet_acknowledgement(key) + } + + /// A hashing function for packet commitments + fn hash(&self, value: Vec) -> Vec { + let connect = Context::::new(); + connect.hash(value) + } + + /// Returns the current height of the local chain. + fn host_height(&self) -> Height { + let connect = Context::::new(); + ChannelReader::host_height(&connect) + } + + /// Returns the current timestamp of the local chain. + fn host_timestamp(&self) -> Timestamp { + let connect = Context::::new(); + ChannelReader::host_timestamp(&connect) + } + + /// Returns the `AnyConsensusState` for the given identifier `height`. + fn host_consensus_state(&self, height: Height) -> Result { + let connect = Context::::new(); + ConnectionReader::host_consensus_state(&connect, height) + .map_err(Ics04Error::ics03_connection) + } + + fn pending_host_consensus_state(&self) -> Result { + let connect = Context::::new(); + ClientReader::pending_host_consensus_state(&connect) + .map_err(|e| Ics04Error::ics03_connection(ICS03Error::ics02_client(e))) + } + + /// Returns the `ClientProcessedTimes` for the given identifier `client_id` & `height`. + fn client_update_time( + &self, + client_id: &ClientId, + height: Height, + ) -> Result { + let connect = Context::::new(); + connect.client_update_time(client_id, height) + } + + fn client_update_height( + &self, + client_id: &ClientId, + height: Height, + ) -> Result { + let connect = Context::::new(); + connect.client_update_height(client_id, height) + } + + /// Returns a counter on the number of channel ids have been created thus far. + /// The value of this counter should increase only via method + /// `ChannelKeeper::increase_channel_counter`. + fn channel_counter(&self) -> Result { + let connect = Context::::new(); + connect.channel_counter() + } + + fn max_expected_time_per_block(&self) -> Duration { + let connect = Context::::new(); + connect.max_expected_time_per_block() + } +} + +impl ChannelKeeper for TransferModule { + fn store_packet_commitment( + &mut self, + key: (PortId, ChannelId, Sequence), + commitment: IbcPacketCommitment, + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.store_packet_commitment(key, commitment) + } + + fn delete_packet_commitment( + &mut self, + key: (PortId, ChannelId, Sequence), + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.delete_packet_commitment(key) + } + + fn store_packet_receipt( + &mut self, + key: (PortId, ChannelId, Sequence), + receipt: Receipt, + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.store_packet_receipt(key, receipt) + } + + fn store_packet_acknowledgement( + &mut self, + key: (PortId, ChannelId, Sequence), + ack_commitment: IbcAcknowledgementCommitment, + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + + connect.store_packet_acknowledgement(key, ack_commitment) + } + + fn delete_packet_acknowledgement( + &mut self, + key: (PortId, ChannelId, Sequence), + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.delete_packet_acknowledgement(key) + } + + fn store_connection_channels( + &mut self, + conn_id: ConnectionId, + port_channel_id: &(PortId, ChannelId), + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.store_connection_channels(conn_id, port_channel_id) + } + + /// Stores the given channel_end at a path associated with the port_id and channel_id. + fn store_channel( + &mut self, + port_channel_id: (PortId, ChannelId), + channel_end: &ChannelEnd, + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.store_channel(port_channel_id, channel_end) + } + + fn store_next_sequence_send( + &mut self, + port_channel_id: (PortId, ChannelId), + seq: Sequence, + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.store_next_sequence_send(port_channel_id, seq) + } + + fn store_next_sequence_recv( + &mut self, + port_channel_id: (PortId, ChannelId), + seq: Sequence, + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.store_next_sequence_recv(port_channel_id, seq) + } + + fn store_next_sequence_ack( + &mut self, + port_channel_id: (PortId, ChannelId), + seq: Sequence, + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.store_next_sequence_ack(port_channel_id, seq) + } + + fn increase_channel_counter(&mut self) { + let mut connect = Context::::new(); + connect.increase_channel_counter() + } +} diff --git a/frame/ibc/src/module/applications/transfer/mod.rs b/frame/ibc/src/module/applications/transfer/mod.rs new file mode 100644 index 0000000000000..2c638c7f9d54f --- /dev/null +++ b/frame/ibc/src/module/applications/transfer/mod.rs @@ -0,0 +1,288 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) 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. + +pub mod channel; +pub mod transfer_handle_callback; + +use crate::{context::Context, *}; +use frame_support::traits::{ + fungibles::{Mutate, Transfer}, + ExistenceRequirement::AllowDeath, +}; +use log::{error, trace}; + +use crate::utils::get_channel_escrow_address; +use ibc::{ + applications::transfer::{ + context::{BankKeeper, Ics20Context, Ics20Keeper, Ics20Reader}, + error::Error as Ics20Error, + PrefixedCoin, PORT_ID_STR, + }, + core::ics24_host::identifier::PortId, + signer::Signer, +}; +use sp_runtime::{ + traits::{CheckedConversion, IdentifyAccount, Verify}, + MultiSignature, +}; + +use transfer_handle_callback::TransferModule; + +impl Ics20Keeper for TransferModule { + type AccountId = ::AccountId; +} + +impl BankKeeper for TransferModule { + type AccountId = ::AccountId; + + fn send_coins( + &mut self, + from: &Self::AccountId, + to: &Self::AccountId, + amt: &PrefixedCoin, + ) -> Result<(), Ics20Error> { + // TODO(davirain): trace_path now is private + // let is_native_asset = amt.denom.trace_path().is_empty(); + let is_native_asset = true; + match is_native_asset { + // transfer native token + true => { + // TODO(davirain): amount now is private, and base_denom is private + // let amount = amt.amount.as_u256().low_u128().checked_into().expect("Convert MUST + // NOT Failed"); let ibc_token_name = amt.denom.base_denom().as_str().as_bytes(); + let amount = todo!(); + let ibc_token_name = &[1, 1, 2, 3]; + let native_token_name = T::NATIVE_TOKEN_NAME; + + // assert native token name equal want to send ibc token name + assert_eq!( + native_token_name, ibc_token_name, + "send ibc token name is not native token name" + ); + + >::transfer( + &from.clone().into_account(), + &to.clone().into_account(), + amount, + AllowDeath, + ) + .map_err(|error| { + error!("❌ [send_coins] : Error: ({:?})", error); + Ics20Error::invalid_token() + })?; + + // add emit transfer native token event + Pallet::::deposit_event(Event::::TransferNativeToken( + from.clone(), + to.clone(), + amount, + )) + }, + // transfer non-native token + false => { + // TODO(davirain): amount now is private, and base_denom is private + // let amount = amt.amount.as_u256().low_u128().into(); + // let denom = amt.denom.base_denom().as_str(); + let amount = todo!(); + let denom = &[1, 1, 2, 3]; + // look cross chain asset have register in host chain + match T::AssetIdByName::try_get_asset_id(denom) { + Ok(token_id) => { + >::transfer( + token_id.into(), + &from.clone().into_account(), + &to.clone().into_account(), + amount, + true, + ) + .map_err(|error| { + error!("❌ [send_coins] : Error: ({:?})", error); + Ics20Error::invalid_token() + })?; + + // add emit transfer no native token event + Pallet::::deposit_event(Event::::TransferNoNativeToken( + from.clone(), + to.clone(), + amount, + )); + }, + Err(_error) => { + error!("❌ [send_coins]: denom: ({:?})", denom); + return Err(Ics20Error::invalid_token()) + }, + } + }, + } + + Ok(()) + } + + fn mint_coins( + &mut self, + account: &Self::AccountId, + amt: &PrefixedCoin, + ) -> Result<(), Ics20Error> { + // TODO(davirain): amount now is private, and base_denom is private + // let amount = amt.amount.as_u256().low_u128().into(); + // let denom = amt.denom.base_denom().as_str(); + let amount = todo!(); + let denom = &[1, 1, 2, 3]; + // look cross chain asset have register in host chain + match T::AssetIdByName::try_get_asset_id(denom) { + Ok(token_id) => { + >::mint_into( + token_id.into(), + &account.clone().into_account(), + amount, + ) + .map_err(|error| { + error!("❌ [mint_coins] : Error: ({:?})", error); + Ics20Error::invalid_token() + })?; + + // add mint token event + Pallet::::deposit_event(Event::::MintToken( + token_id, + account.clone(), + amount, + )); + }, + Err(_error) => { + error!("❌ [mint_coins]: denom: ({:?})", denom); + return Err(Ics20Error::invalid_token()) + }, + } + Ok(()) + } + + fn burn_coins( + &mut self, + account: &Self::AccountId, + amt: &PrefixedCoin, + ) -> Result<(), Ics20Error> { + // TODO(davirain): amount now is private, and base_denom is private + // let amount = amt.amount.as_u256().low_u128().into(); + // let denom = amt.denom.base_denom().as_str(); + let amount = todo!(); + let denom = &[1, 1, 2, 3]; + // look cross chain asset have register in host chain + match T::AssetIdByName::try_get_asset_id(denom) { + Ok(token_id) => { + >::burn_from( + token_id.into(), + &account.clone().into_account(), + amount, + ) + .map_err(|error| { + error!("❌ [burn_coins] : Error: ({:?})", error); + Ics20Error::invalid_token() + })?; + + // add burn token event + Pallet::::deposit_event(Event::::BurnToken( + token_id, + account.clone(), + amount, + )); + }, + Err(_error) => { + error!("❌ [burn_coins]: denom: ({:?})", denom); + return Err(Ics20Error::invalid_token()) + }, + } + Ok(()) + } +} + +impl Ics20Reader for TransferModule { + type AccountId = ::AccountId; + + fn get_port(&self) -> Result { + PortId::from_str(PORT_ID_STR) + .map_err(|e| Ics20Error::invalid_port_id(PORT_ID_STR.to_string(), e)) + } + + fn get_channel_escrow_address( + &self, + port_id: &PortId, + channel_id: &IbcChannelId, + ) -> Result { + get_channel_escrow_address(port_id, channel_id)? + .try_into() + .map_err(|_| Ics20Error::parse_account_failure()) + } + + fn is_send_enabled(&self) -> bool { + // TODO(davirain), need according channelEnd def + true + } + + fn is_receive_enabled(&self) -> bool { + // TODO(davirain), need according channelEnd def + true + } +} + +impl Ics20Context for TransferModule { + type AccountId = ::AccountIdConversion; // Need Setting Account TODO(davirian) +} + +/// Alias to 512-bit hash when used in the context of a transaction signature on the chain. +pub type Signature = MultiSignature; + +/// Some way of identifying an account on the chain. We intentionally make it equivalent +/// to the public key of our transaction signing scheme. +pub type AccountId = <::Signer as IdentifyAccount>::AccountId; + +#[derive(Clone, Debug, PartialEq, TypeInfo, Encode, Decode)] +pub struct IbcAccount(AccountId); + +impl IdentifyAccount for IbcAccount { + type AccountId = AccountId; + fn into_account(self) -> Self::AccountId { + self.0 + } +} + +impl TryFrom for IbcAccount +where + AccountId: From<[u8; 32]>, +{ + type Error = &'static str; + + /// Convert a signer to an IBC account. + /// Only valid hex strings are supported for now. + fn try_from(signer: Signer) -> Result { + let acc_str = signer.as_ref(); + if acc_str.starts_with("0x") { + match acc_str.strip_prefix("0x") { + Some(hex_string) => TryInto::<[u8; 32]>::try_into( + hex::decode(hex_string).map_err(|_| "Error decoding invalid hex string")?, + ) + .map_err(|_| "Invalid account id hex string") + .map(|acc| Self(acc.into())), + _ => Err("Signer does not hold a valid hex string"), + } + } + // Do SS58 decoding instead + else { + error!("Convert Signer ❌ : Failed! "); + Err("invalid ibc address or substrate address") + } + } +} diff --git a/frame/ibc/src/module/applications/transfer/transfer_handle_callback.rs b/frame/ibc/src/module/applications/transfer/transfer_handle_callback.rs new file mode 100644 index 0000000000000..4abf0f625bba4 --- /dev/null +++ b/frame/ibc/src/module/applications/transfer/transfer_handle_callback.rs @@ -0,0 +1,178 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) 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. + +use crate::*; + +use crate::utils::host_height; +use ibc::{ + applications::transfer::{acknowledgement::Acknowledgement, error::Error as Ics20Error}, + core::{ + ics04_channel::{ + channel::{Counterparty, Order}, + context::ChannelKeeper, + error::Error as Ics04Error, + msgs::acknowledgement::Acknowledgement as GenericAcknowledgement, + packet::{Packet as IbcPacket, PacketResult}, + Version, + }, + ics24_host::identifier::{ChannelId as IbcChannelId, ConnectionId, PortId}, + ics26_routing::context::{Module, ModuleOutputBuilder, OnRecvPacketAck}, + }, + events::IbcEvent, + signer::Signer, +}; + +/// A structure handling ICS20 callback +#[derive(Debug)] +pub struct TransferModule(pub PhantomData); + +impl Module for TransferModule { + fn on_chan_open_init( + &mut self, + output: &mut ModuleOutputBuilder, + order: Order, + connection_hops: &[ConnectionId], + port_id: &PortId, + channel_id: &IbcChannelId, + counterparty: &Counterparty, + version: &Version, + ) -> Result<(), Ics04Error> { + ibc::applications::transfer::context::on_chan_open_init( + self, + output, + order, + connection_hops, + port_id, + channel_id, + counterparty, + version, + ) + .map_err(|value| Ics04Error::app_module(value.to_string())) + } + + fn on_chan_open_try( + &mut self, + output: &mut ModuleOutputBuilder, + order: Order, + connection_hops: &[ConnectionId], + port_id: &PortId, + channel_id: &IbcChannelId, + counterparty: &Counterparty, + version: &Version, + counterparty_version: &Version, + ) -> Result { + ibc::applications::transfer::context::on_chan_open_try( + self, + output, + order, + connection_hops, + port_id, + channel_id, + counterparty, + version, + counterparty_version, + ) + .map_err(|value| Ics04Error::app_module(value.to_string())) + } + + fn on_chan_open_ack( + &mut self, + output: &mut ModuleOutputBuilder, + port_id: &PortId, + channel_id: &IbcChannelId, + counterparty_version: &Version, + ) -> Result<(), Ics04Error> { + ibc::applications::transfer::context::on_chan_open_ack( + self, + output, + port_id, + channel_id, + counterparty_version, + ) + .map_err(|value| Ics04Error::app_module(value.to_string())) + } + + fn on_chan_open_confirm( + &mut self, + output: &mut ModuleOutputBuilder, + port_id: &PortId, + channel_id: &IbcChannelId, + ) -> Result<(), Ics04Error> { + ibc::applications::transfer::context::on_chan_open_confirm( + self, output, port_id, channel_id, + ) + .map_err(|value| Ics04Error::app_module(value.to_string())) + } + + fn on_chan_close_init( + &mut self, + output: &mut ModuleOutputBuilder, + port_id: &PortId, + channel_id: &IbcChannelId, + ) -> Result<(), Ics04Error> { + ibc::applications::transfer::context::on_chan_close_init(self, output, port_id, channel_id) + .map_err(|value| Ics04Error::app_module(value.to_string())) + } + + fn on_chan_close_confirm( + &mut self, + output: &mut ModuleOutputBuilder, + port_id: &PortId, + channel_id: &IbcChannelId, + ) -> Result<(), Ics04Error> { + ibc::applications::transfer::context::on_chan_close_confirm( + self, output, port_id, channel_id, + ) + .map_err(|value| Ics04Error::app_module(value.to_string())) + } + + fn on_recv_packet( + &self, + output: &mut ModuleOutputBuilder, + packet: &IbcPacket, + relayer: &Signer, + ) -> OnRecvPacketAck { + ibc::applications::transfer::context::on_recv_packet(self, output, packet, relayer) + } + + fn on_acknowledgement_packet( + &mut self, + output: &mut ModuleOutputBuilder, + packet: &IbcPacket, + acknowledgement: &GenericAcknowledgement, + relayer: &Signer, + ) -> Result<(), Ics04Error> { + ibc::applications::transfer::context::on_acknowledgement_packet( + self, + output, + packet, + acknowledgement, + relayer, + ) + .map_err(|value| Ics04Error::app_module(value.to_string())) + } + + fn on_timeout_packet( + &mut self, + output: &mut ModuleOutputBuilder, + packet: &IbcPacket, + relayer: &Signer, + ) -> Result<(), Ics04Error> { + ibc::applications::transfer::context::on_timeout_packet(self, output, packet, relayer) + .map_err(|value| Ics04Error::app_module(value.to_string())) + } +} diff --git a/frame/ibc/src/module/core/ics02_client.rs b/frame/ibc/src/module/core/ics02_client.rs new file mode 100644 index 0000000000000..e555dec03d738 --- /dev/null +++ b/frame/ibc/src/module/core/ics02_client.rs @@ -0,0 +1,252 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) 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. + +use crate::*; +use alloc::string::ToString; +use core::str::FromStr; +use log::{error, info, trace, warn}; + +use crate::context::Context; +use ibc::{ + core::{ + ics02_client::{ + client_consensus::AnyConsensusState, + client_state::AnyClientState, + client_type::ClientType, + context::{ClientKeeper, ClientReader}, + error::Error as Ics02Error, + }, + ics24_host::{ + identifier::ClientId, + path::{ClientConsensusStatePath, ClientStatePath, ClientTypePath}, + }, + }, + timestamp::Timestamp, + Height, +}; + +impl ClientReader for Context { + fn client_type(&self, client_id: &ClientId) -> Result { + let client_type_path = ClientTypePath(client_id.clone()).to_string().as_bytes().to_vec(); + if >::contains_key(client_type_path.clone()) { + let data = >::get(client_type_path); + let data = + String::from_utf8(data).map_err(|_| Ics02Error::implementation_specific())?; + ClientType::from_str(&data).map_err(|e| Ics02Error::unknown_client_type(e.to_string())) + } else { + Err(Ics02Error::client_not_found(client_id.clone())) + } + } + + fn client_state(&self, client_id: &ClientId) -> Result { + let client_state_path = ClientStatePath(client_id.clone()).to_string().as_bytes().to_vec(); + + if >::contains_key(&client_state_path) { + let data = >::get(&client_state_path); + AnyClientState::decode_vec(&*data).map_err(|_| Ics02Error::implementation_specific()) + } else { + Err(Ics02Error::client_not_found(client_id.clone())) + } + } + + fn consensus_state( + &self, + client_id: &ClientId, + height: Height, + ) -> Result { + // search key + let client_consensus_state_path = ClientConsensusStatePath { + client_id: client_id.clone(), + epoch: height.revision_number(), + height: height.revision_height(), + } + .to_string() + .as_bytes() + .to_vec(); + + if >::contains_key(client_consensus_state_path.clone()) { + let values = >::get(client_consensus_state_path.clone()); + AnyConsensusState::decode_vec(&*values) + .map_err(|_| Ics02Error::implementation_specific()) + } else { + Err(Ics02Error::consensus_state_not_found(client_id.clone(), height)) + } + } + + fn next_consensus_state( + &self, + client_id: &ClientId, + height: Height, + ) -> Result, Ics02Error> { + // search key + let client_consensus_state_path = ClientConsensusStatePath { + client_id: client_id.clone(), + epoch: height.revision_number(), + height: height.revision_height(), + } + .to_string() + .as_bytes() + .to_vec(); + + if >::contains_key(client_consensus_state_path.clone()) { + let values = >::get(client_consensus_state_path.clone()); + let any_consensus_state = AnyConsensusState::decode_vec(&*values) + .map_err(|_| Ics02Error::implementation_specific())?; + Ok(Some(any_consensus_state)) + } else { + Err(Ics02Error::consensus_state_not_found(client_id.clone(), height)) + } + } + + fn prev_consensus_state( + &self, + client_id: &ClientId, + height: Height, + ) -> Result, Ics02Error> { + // search key + let client_consensus_state_path = ClientConsensusStatePath { + client_id: client_id.clone(), + epoch: height.revision_number(), + height: height.revision_height(), + } + .to_string() + .as_bytes() + .to_vec(); + + if >::contains_key(client_consensus_state_path.clone()) { + let values = >::get(client_consensus_state_path.clone()); + let any_consensus_state = AnyConsensusState::decode_vec(&*values).unwrap(); + Ok(Some(any_consensus_state)) + } else { + Err(Ics02Error::consensus_state_not_found(client_id.clone(), height)) + } + } + + fn host_height(&self) -> Height { + let block_number = format!("{:?}", >::block_number()); + let current_height: u64 = block_number.parse().unwrap_or_default(); + Height::new(REVISION_NUMBER, current_height).expect("Contruct Heigjt Never failed") + } + + fn host_consensus_state(&self, _height: Height) -> Result { + todo!() + } + + fn pending_host_consensus_state(&self) -> Result { + todo!() + } + + fn client_counter(&self) -> Result { + Ok(>::get()) + } +} + +impl ClientKeeper for Context { + fn store_client_type( + &mut self, + client_id: ClientId, + client_type: ClientType, + ) -> Result<(), Ics02Error> { + let client_type_path = ClientTypePath(client_id.clone()).to_string().as_bytes().to_vec(); + let client_type = client_type.as_str().encode(); + >::insert(client_type_path, client_type); + Ok(()) + } + + fn store_client_state( + &mut self, + client_id: ClientId, + client_state: AnyClientState, + ) -> Result<(), Ics02Error> { + let client_state_path = ClientStatePath(client_id.clone()).to_string().as_bytes().to_vec(); + + let data = client_state.encode_vec().map_err(|_| Ics02Error::implementation_specific())?; + // store client states key-value + >::insert(client_state_path.clone(), data); + + Ok(()) + } + + fn store_consensus_state( + &mut self, + client_id: ClientId, + height: Height, + consensus_state: AnyConsensusState, + ) -> Result<(), Ics02Error> { + // store key + let client_consensus_state_path = ClientConsensusStatePath { + client_id: client_id.clone(), + epoch: height.revision_number(), + height: height.revision_height(), + } + .to_string() + .as_bytes() + .to_vec(); + + // store value + let consensus_state = consensus_state + .encode_vec() + .map_err(|_| Ics02Error::implementation_specific())?; + // store client_consensus_state path as key, consensus_state as value + >::insert(client_consensus_state_path, consensus_state); + + Ok(()) + } + + fn increase_client_counter(&mut self) { + let ret = >::try_mutate(|val| -> Result<(), Ics02Error> { + let new = val.checked_add(1).expect("Never Overflow"); + *val = new; + Ok(()) + }); + } + + fn store_update_time( + &mut self, + client_id: ClientId, + height: Height, + timestamp: Timestamp, + ) -> Result<(), Ics02Error> { + let encode_timestamp = serde_json::to_string(×tamp) + .map_err(|_| Ics02Error::implementation_specific())? + .as_bytes() + .to_vec(); + + >::insert( + client_id.as_bytes(), + height.encode_vec().map_err(|_| Ics02Error::implementation_specific())?, + encode_timestamp, + ); + + Ok(()) + } + + fn store_update_height( + &mut self, + client_id: ClientId, + height: Height, + host_height: Height, + ) -> Result<(), Ics02Error> { + >::insert( + client_id.as_bytes(), + height.encode_vec().map_err(|_| Ics02Error::implementation_specific())?, + host_height.encode_vec().map_err(|_| Ics02Error::implementation_specific())?, + ); + + Ok(()) + } +} diff --git a/frame/ibc/src/module/core/ics03_connection.rs b/frame/ibc/src/module/core/ics03_connection.rs new file mode 100644 index 0000000000000..82a9803cfa972 --- /dev/null +++ b/frame/ibc/src/module/core/ics03_connection.rs @@ -0,0 +1,128 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) 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. + +use crate::*; + +use crate::context::Context; +use log::{error, info, trace, warn}; + +use ibc::{ + core::{ + ics02_client::{ + client_consensus::AnyConsensusState, client_state::AnyClientState, + context::ClientReader, + }, + ics03_connection::{ + connection::ConnectionEnd, + context::{ConnectionKeeper, ConnectionReader}, + error::Error as Ics03Error, + }, + ics23_commitment::commitment::CommitmentPrefix, + ics24_host::{ + identifier::{ClientId, ConnectionId}, + path::{ClientConnectionsPath, ConnectionsPath}, + }, + }, + Height, +}; + +impl ConnectionReader for Context { + fn connection_end(&self, conn_id: &ConnectionId) -> Result { + let connections_path = ConnectionsPath(conn_id.clone()).to_string().as_bytes().to_vec(); + + if >::contains_key(&connections_path) { + let data = >::get(&connections_path); + ConnectionEnd::decode_vec(&*data).map_err(|_| Ics03Error::implementation_specific()) + } else { + Err(Ics03Error::connection_mismatch(conn_id.clone())) + } + } + + fn client_state(&self, client_id: &ClientId) -> Result { + ClientReader::client_state(self, client_id).map_err(Ics03Error::ics02_client) + } + + fn host_current_height(&self) -> Height { + let block_number = format!("{:?}", >::block_number()); + let current_height: u64 = block_number.parse().unwrap_or_default(); + >::put(current_height); + Height::new(REVISION_NUMBER, current_height).expect("Contruct Height Never faild") + } + + fn host_oldest_height(&self) -> Height { + let height = >::get(); + Height::new(REVISION_NUMBER, height).expect("get host oldest height Never faild") + } + + fn commitment_prefix(&self) -> CommitmentPrefix { + "ibc".as_bytes().to_vec().try_into().unwrap_or_default() + } + + fn client_consensus_state( + &self, + client_id: &ClientId, + height: Height, + ) -> Result { + ClientReader::consensus_state(self, client_id, height).map_err(Ics03Error::ics02_client) + } + + fn host_consensus_state(&self, _height: Height) -> Result { + todo!() + } + + fn connection_counter(&self) -> Result { + Ok(>::get()) + } +} + +impl ConnectionKeeper for Context { + fn store_connection( + &mut self, + connection_id: ConnectionId, + connection_end: &ConnectionEnd, + ) -> Result<(), Ics03Error> { + let connections_path = + ConnectionsPath(connection_id.clone()).to_string().as_bytes().to_vec(); + let data = + connection_end.encode_vec().map_err(|_| Ics03Error::implementation_specific())?; + + // store connection end + >::insert(connections_path, data); + + Ok(()) + } + + fn store_connection_to_client( + &mut self, + connection_id: ConnectionId, + client_id: &ClientId, + ) -> Result<(), Ics03Error> { + let client_connection_paths = + ClientConnectionsPath(client_id.clone()).to_string().as_bytes().to_vec(); + + >::insert(client_connection_paths, connection_id.as_bytes().to_vec()); + Ok(()) + } + + fn increase_connection_counter(&mut self) { + let ret = >::try_mutate(|val| -> Result<(), Ics03Error> { + let new = val.checked_add(1).expect("Never Overflow"); + *val = new; + Ok(()) + }); + } +} diff --git a/frame/ibc/src/module/core/ics04_channel.rs b/frame/ibc/src/module/core/ics04_channel.rs new file mode 100644 index 0000000000000..b17a4a48dd6c4 --- /dev/null +++ b/frame/ibc/src/module/core/ics04_channel.rs @@ -0,0 +1,542 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) 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. + +use crate::*; +use core::{str::FromStr, time::Duration}; +use log::{error, info, trace, warn}; + +use crate::context::Context; +use ibc::{ + core::{ + ics02_client::{ + client_consensus::AnyConsensusState, client_state::AnyClientState, + context::ClientReader, + }, + ics03_connection::{ + connection::ConnectionEnd, context::ConnectionReader, error::Error as ICS03Error, + }, + ics04_channel::{ + channel::ChannelEnd, + commitment::{ + AcknowledgementCommitment as IbcAcknowledgementCommitment, + PacketCommitment as IbcPacketCommitment, + }, + context::{ChannelKeeper, ChannelReader}, + error::Error as Ics04Error, + packet::{Receipt, Sequence}, + }, + ics05_port::{context::PortReader, error::Error as Ics05Error}, + ics24_host::{ + identifier::{ChannelId, ClientId, ConnectionId, PortId}, + path::{ + AcksPath, ChannelEndsPath, CommitmentsPath, ConnectionsPath, ReceiptsPath, + SeqAcksPath, SeqRecvsPath, SeqSendsPath, + }, + Path, + }, + ics26_routing::context::ModuleId, + }, + timestamp::Timestamp, + Height, +}; + +impl ChannelReader for Context { + fn channel_end(&self, port_channel_id: &(PortId, ChannelId)) -> Result { + let channel_end_path = + ChannelEndsPath(port_channel_id.0.clone(), port_channel_id.1.clone()) + .to_string() + .as_bytes() + .to_vec(); + + let data = >::get(channel_end_path); + + ChannelEnd::decode_vec(&*data).map_err(|_| { + Ics04Error::channel_not_found(port_channel_id.clone().0, port_channel_id.clone().1) + }) + } + + fn connection_end(&self, connection_id: &ConnectionId) -> Result { + ConnectionReader::connection_end(self, connection_id).map_err(Ics04Error::ics03_connection) + } + + /// Returns the `ChannelsConnection` for the given identifier `conn_id`. + fn connection_channels( + &self, + conn_id: &ConnectionId, + ) -> Result, Ics04Error> { + // store key + let connections_path = ConnectionsPath(conn_id.clone()).to_string().as_bytes().to_vec(); + + if >::contains_key(&connections_path) { + let channel_ends_paths = >::get(&connections_path); + + let mut result = vec![]; + + for item in channel_ends_paths.into_iter() { + let raw_path = + String::from_utf8(item).map_err(|_| Ics04Error::implementation_specific())?; + // decode key + let path = + Path::from_str(&raw_path).map_err(|_| Ics04Error::implementation_specific())?; + + if let Path::ChannelEnds(channel_ends_path) = path { + let ChannelEndsPath(port_id, channel_id) = channel_ends_path; + result.push((port_id, channel_id)); + } + } + + Ok(result) + } else { + Err(Ics04Error::connection_not_open(conn_id.clone())) + } + } + + fn client_state(&self, client_id: &ClientId) -> Result { + ClientReader::client_state(self, client_id) + .map_err(|_| Ics04Error::implementation_specific()) + } + + fn client_consensus_state( + &self, + client_id: &ClientId, + height: Height, + ) -> Result { + ClientReader::consensus_state(self, client_id, height) + .map_err(|_| Ics04Error::implementation_specific()) + } + + fn get_next_sequence_send( + &self, + port_channel_id: &(PortId, ChannelId), + ) -> Result { + let seq_sends_path = SeqSendsPath(port_channel_id.0.clone(), port_channel_id.1.clone()) + .to_string() + .as_bytes() + .to_vec(); + + if >::contains_key(&seq_sends_path) { + let sequence = >::get(&seq_sends_path); + Ok(Sequence::from(sequence)) + } else { + Err(Ics04Error::missing_next_send_seq(port_channel_id.clone())) + } + } + + fn get_next_sequence_recv( + &self, + port_channel_id: &(PortId, ChannelId), + ) -> Result { + let seq_recvs_path = SeqRecvsPath(port_channel_id.0.clone(), port_channel_id.1.clone()) + .to_string() + .as_bytes() + .to_vec(); + + if >::contains_key(&seq_recvs_path) { + let sequence = >::get(&seq_recvs_path); + + Ok(Sequence::from(sequence)) + } else { + Err(Ics04Error::missing_next_recv_seq(port_channel_id.clone())) + } + } + + fn get_next_sequence_ack( + &self, + port_channel_id: &(PortId, ChannelId), + ) -> Result { + let seq_acks_path = SeqAcksPath(port_channel_id.0.clone(), port_channel_id.1.clone()) + .to_string() + .as_bytes() + .to_vec(); + + if >::contains_key(&seq_acks_path) { + let sequence = >::get(&seq_acks_path); + + Ok(Sequence::from(sequence)) + } else { + Err(Ics04Error::missing_next_ack_seq(port_channel_id.clone())) + } + } + + /// Returns the `PacketCommitment` for the given identifier `(PortId, ChannelId, Sequence)`. + fn get_packet_commitment( + &self, + key: &(PortId, ChannelId, Sequence), + ) -> Result { + let packet_commitments_path = CommitmentsPath { + port_id: key.0.clone(), + channel_id: key.1.clone(), + sequence: key.2.clone(), + } + .to_string() + .as_bytes() + .to_vec(); + + if >::contains_key(&packet_commitments_path) { + let data = >::get(&packet_commitments_path); + + let packet_commitment = IbcPacketCommitment::from(data); + + Ok(packet_commitment) + } else { + Err(Ics04Error::packet_commitment_not_found(key.2)) + } + } + + fn get_packet_receipt( + &self, + key: &(PortId, ChannelId, Sequence), + ) -> Result { + let packet_receipt_path = ReceiptsPath { + port_id: key.0.clone(), + channel_id: key.1.clone(), + sequence: key.2.clone(), + } + .to_string() + .as_bytes() + .to_vec(); + + if >::contains_key(&packet_receipt_path) { + let data = >::get(&packet_receipt_path); + let data = + String::from_utf8(data).map_err(|_| Ics04Error::implementation_specific())?; + let data = match data.as_ref() { + "Ok" => Receipt::Ok, + _ => unreachable!(), + }; + Ok(data) + } else { + Err(Ics04Error::packet_receipt_not_found(key.2)) + } + } + + /// Returns the `Acknowledgements` for the given identifier `(PortId, ChannelId, Sequence)`. + fn get_packet_acknowledgement( + &self, + key: &(PortId, ChannelId, Sequence), + ) -> Result { + let acks_path = + AcksPath { port_id: key.0.clone(), channel_id: key.1.clone(), sequence: key.2.clone() } + .to_string() + .as_bytes() + .to_vec(); + + if >::contains_key(&acks_path) { + let data = >::get(&acks_path); + + let acknowledgement = IbcAcknowledgementCommitment::from(data); + Ok(acknowledgement) + } else { + Err(Ics04Error::packet_acknowledgement_not_found(key.2)) + } + } + + /// A hashing function for packet commitments + fn hash(&self, value: Vec) -> Vec { + sp_io::hashing::sha2_256(&value).to_vec() + } + + /// Returns the current height of the local chain. + fn host_height(&self) -> Height { + //todo this can improve + let block_number = format!("{:?}", >::block_number()); + let current_height: u64 = block_number.parse().unwrap_or_default(); + + Height::new(REVISION_NUMBER, current_height).expect("Contruct Height Never Faild") + } + + /// Returns the current timestamp of the local chain. + fn host_timestamp(&self) -> Timestamp { + use frame_support::traits::UnixTime; + let time = T::TimeProvider::now(); + + Timestamp::from_nanoseconds(time.as_nanos() as u64).expect("Convert Timestamp Never Faild") + } + + /// Returns the `AnyConsensusState` for the given identifier `height`. + fn host_consensus_state(&self, height: Height) -> Result { + ConnectionReader::host_consensus_state(self, height).map_err(Ics04Error::ics03_connection) + } + + fn pending_host_consensus_state(&self) -> Result { + ClientReader::pending_host_consensus_state(self) + .map_err(|e| Ics04Error::ics03_connection(ICS03Error::ics02_client(e))) + } + + /// Returns the `ClientProcessedTimes` for the given identifier `client_id` & `height`. + fn client_update_time( + &self, + client_id: &ClientId, + height: Height, + ) -> Result { + if >::contains_key( + client_id.as_bytes(), + height.encode_vec().map_err(|_| Ics04Error::implementation_specific())?, + ) { + let time = >::get( + client_id.as_bytes(), + height.encode_vec().map_err(|_| Ics04Error::implementation_specific())?, + ); + let timestamp = + String::from_utf8(time).map_err(|_| Ics04Error::implementation_specific())?; + let time: Timestamp = serde_json::from_str(×tamp) + .map_err(|_| Ics04Error::implementation_specific())?; + Ok(time) + } else { + Err(Ics04Error::processed_time_not_found(client_id.clone(), height)) + } + } + + fn client_update_height( + &self, + client_id: &ClientId, + height: Height, + ) -> Result { + if >::contains_key( + client_id.as_bytes(), + height.encode_vec().map_err(|_| Ics04Error::implementation_specific())?, + ) { + let host_height = >::get( + client_id.as_bytes(), + height.encode_vec().map_err(|_| Ics04Error::implementation_specific())?, + ); + Height::decode(&mut &host_height[..]).map_err(|_| Ics04Error::implementation_specific()) + } else { + Err(Ics04Error::processed_height_not_found(client_id.clone(), height)) + } + } + + /// Returns a counter on the number of channel ids have been created thus far. + /// The value of this counter should increase only via method + /// `ChannelKeeper::increase_channel_counter`. + fn channel_counter(&self) -> Result { + Ok( as Store>::ChannelCounter::get()) + } + + fn max_expected_time_per_block(&self) -> Duration { + Duration::from_secs(6) + } +} + +impl ChannelKeeper for Context { + fn store_packet_commitment( + &mut self, + key: (PortId, ChannelId, Sequence), + commitment: IbcPacketCommitment, + ) -> Result<(), Ics04Error> { + let packet_commitments_path = CommitmentsPath { + port_id: key.0.clone(), + channel_id: key.1.clone(), + sequence: key.2.clone(), + } + .to_string() + .as_bytes() + .to_vec(); + + // insert packet commitment key-value + >::insert(packet_commitments_path, commitment.into_vec()); + + Ok(()) + } + + fn delete_packet_commitment( + &mut self, + key: (PortId, ChannelId, Sequence), + ) -> Result<(), Ics04Error> { + let packet_commitments_path = CommitmentsPath { + port_id: key.0.clone(), + channel_id: key.1.clone(), + sequence: key.2.clone(), + } + .to_string() + .as_bytes() + .to_vec(); + + // delete packet commitment + >::remove(&packet_commitments_path); + + Ok(()) + } + + fn store_packet_receipt( + &mut self, + key: (PortId, ChannelId, Sequence), + receipt: Receipt, + ) -> Result<(), Ics04Error> { + let packet_receipt_path = ReceiptsPath { + port_id: key.0.clone(), + channel_id: key.1.clone(), + sequence: key.2.clone(), + } + .to_string() + .as_bytes() + .to_vec(); + + let receipt = match receipt { + Receipt::Ok => "Ok".as_bytes().to_vec(), + }; + + >::insert(packet_receipt_path, receipt); + + Ok(()) + } + + fn store_packet_acknowledgement( + &mut self, + key: (PortId, ChannelId, Sequence), + ack_commitment: IbcAcknowledgementCommitment, + ) -> Result<(), Ics04Error> { + let acks_path = + AcksPath { port_id: key.0.clone(), channel_id: key.1.clone(), sequence: key.2.clone() } + .to_string() + .as_bytes() + .to_vec(); + + // store packet acknowledgement key-value + >::insert(&acks_path, ack_commitment.into_vec()); + + Ok(()) + } + + fn delete_packet_acknowledgement( + &mut self, + key: (PortId, ChannelId, Sequence), + ) -> Result<(), Ics04Error> { + let acks_path = + AcksPath { port_id: key.0.clone(), channel_id: key.1.clone(), sequence: key.2.clone() } + .to_string() + .as_bytes() + .to_vec(); + + // remove acknowledgements + >::remove(&acks_path); + + Ok(()) + } + + fn store_connection_channels( + &mut self, + conn_id: ConnectionId, + port_channel_id: &(PortId, ChannelId), + ) -> Result<(), Ics04Error> { + // store key + let connections_path = ConnectionsPath(conn_id.clone()).to_string().as_bytes().to_vec(); + + // store value + let channel_ends_path = + ChannelEndsPath(port_channel_id.0.clone(), port_channel_id.1.clone()) + .to_string() + .as_bytes() + .to_vec(); + + if >::contains_key(&connections_path) { + // if connection_id exist + >::try_mutate( + &connections_path, + |val| -> Result<(), Ics04Error> { + val.push(channel_ends_path.clone()); + Ok(()) + }, + ) + .expect("channels Connection mutate Error") + } else { + >::insert(connections_path, vec![channel_ends_path]); + } + + Ok(()) + } + + /// Stores the given channel_end at a path associated with the port_id and channel_id. + fn store_channel( + &mut self, + port_channel_id: (PortId, ChannelId), + channel_end: &ChannelEnd, + ) -> Result<(), Ics04Error> { + let channel_end_path = + ChannelEndsPath(port_channel_id.0.clone(), port_channel_id.1.clone()) + .to_string() + .as_bytes() + .to_vec(); + let channel_end = + channel_end.encode_vec().map_err(|_| Ics04Error::implementation_specific())?; + + // store channels key-value + >::insert(channel_end_path, channel_end); + + Ok(()) + } + + fn store_next_sequence_send( + &mut self, + port_channel_id: (PortId, ChannelId), + seq: Sequence, + ) -> Result<(), Ics04Error> { + let seq_sends_path = SeqSendsPath(port_channel_id.0.clone(), port_channel_id.1.clone()) + .to_string() + .as_bytes() + .to_vec(); + + let sequence = u64::from(seq); + + >::insert(seq_sends_path, sequence); + + Ok(()) + } + + fn store_next_sequence_recv( + &mut self, + port_channel_id: (PortId, ChannelId), + seq: Sequence, + ) -> Result<(), Ics04Error> { + let seq_recvs_path = SeqRecvsPath(port_channel_id.0.clone(), port_channel_id.1.clone()) + .to_string() + .as_bytes() + .to_vec(); + let sequence = u64::from(seq); + + >::insert(seq_recvs_path, sequence); + + Ok(()) + } + + fn store_next_sequence_ack( + &mut self, + port_channel_id: (PortId, ChannelId), + seq: Sequence, + ) -> Result<(), Ics04Error> { + let seq_acks_path = SeqAcksPath(port_channel_id.0.clone(), port_channel_id.1.clone()) + .to_string() + .as_bytes() + .to_vec(); + let sequence = u64::from(seq); + + >::insert(seq_acks_path, sequence); + + Ok(()) + } + + /// Called upon channel identifier creation (Init or Try message processing). + /// Increases the counter which keeps track of how many channels have been created. + /// Should never fail. + fn increase_channel_counter(&mut self) { + let ret = >::try_mutate(|val| -> Result<(), Ics04Error> { + let new = val.checked_add(1).expect("Never Overflow"); + *val = new; + Ok(()) + }); + } +} diff --git a/frame/ibc/src/module/core/ics05_port.rs b/frame/ibc/src/module/core/ics05_port.rs new file mode 100644 index 0000000000000..5ac325bbb6ed8 --- /dev/null +++ b/frame/ibc/src/module/core/ics05_port.rs @@ -0,0 +1,41 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) 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. + +use crate::*; +use log::trace; + +use crate::context::Context; +use ibc::{ + applications::transfer::{ + MODULE_ID_STR as TRANSFER_MODULE_ID, PORT_ID_STR as TRANSFER_PORT_ID, + }, + core::{ + ics05_port::{context::PortReader, error::Error as ICS05Error}, + ics24_host::identifier::PortId, + ics26_routing::context::ModuleId, + }, +}; + +impl PortReader for Context { + fn lookup_module_by_port(&self, port_id: &PortId) -> Result { + match port_id.as_str() { + TRANSFER_PORT_ID => Ok(ModuleId::from_str(TRANSFER_MODULE_ID) + .map_err(|_| ICS05Error::module_not_found(port_id.clone()))?), + _ => Err(ICS05Error::module_not_found(port_id.clone())), + } + } +} diff --git a/frame/ibc/src/module/core/ics24_host.rs b/frame/ibc/src/module/core/ics24_host.rs new file mode 100644 index 0000000000000..6d08244f1f1e8 --- /dev/null +++ b/frame/ibc/src/module/core/ics24_host.rs @@ -0,0 +1,272 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) 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. + +use crate::{alloc::string::ToString, from_channel_id_to_vec, Config, Event, REVISION_NUMBER}; +use alloc::string::String; +use ibc::{ + core::{ + ics02_client::{client_type::ClientType as IbcClientType, height::Height as IbcHeight}, + ics04_channel::packet::{Packet as IbcPacket, Sequence as IbcSequence}, + ics24_host::{ + error::ValidationError, + identifier::{ + ChainId as IbcChainId, ChannelId as IbcChannelId, ClientId as IbcClientId, + ConnectionId as IbcConnectionId, PortId as IbcPortId, + }, + }, + }, + timestamp::Timestamp as IbcTimestamp, +}; +use sp_std::{str::FromStr, vec::Vec}; + +use codec::{Decode, Encode}; +use scale_info::TypeInfo; + +use sp_runtime::RuntimeDebug; + +use flex_error::{define_error, DisplayOnly, TraceError}; +use ibc::core::ics04_channel::timeout::TimeoutHeight; +use tendermint_proto::Error as TendermintError; + +define_error! { + #[derive(Debug, PartialEq, Eq)] + Error { + InvalidFromUtf8 + [DisplayOnly] + | _ | { "invalid from utf8 error" }, + InvalidDecode + [DisplayOnly] + | _ | { "invalid decode error" }, + ParseTimestampFailed + [DisplayOnly] + | _ | { "invalid parse timestamp error" }, + ValidationFailed + [DisplayOnly] + | _ | { "invalid validation error"}, + InvalidChainId + [DisplayOnly] + |_| { "invalid chain id error" }, + } +} + +/// ibc-rs' `PortId` representation in substrate +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct PortId(pub Vec); + +impl From for PortId { + fn from(value: IbcPortId) -> Self { + let value = value.as_str().as_bytes().to_vec(); + Self(value) + } +} + +impl From for IbcPortId { + fn from(value: PortId) -> Self { + let value = String::from_utf8(value.0).expect("convert Never faild"); + IbcPortId::from_str(&value).expect("convert Never faild") + } +} + +/// ibc-rs' `ChannelId` representation in substrate +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct ChannelId(pub Vec); + +impl From for ChannelId { + fn from(value: IbcChannelId) -> Self { + let value = from_channel_id_to_vec(value); + Self(value) + } +} + +impl From for IbcChannelId { + fn from(value: ChannelId) -> Self { + let value = String::from_utf8(value.0).expect("convert Never faild"); + Self::from_str(&value).expect("convert Never faild") + } +} + +/// ibc-rs' `Height` representation in substrate +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct Height { + /// Previously known as "epoch" + pub revision_number: u64, + + /// The height of a block + pub revision_height: u64, +} + +impl From for Height { + fn from(ibc_height: IbcHeight) -> Self { + Height::new(ibc_height.revision_number(), ibc_height.revision_height()) + } +} + +impl From for IbcHeight { + fn from(height: Height) -> Self { + IbcHeight::new(REVISION_NUMBER, height.revision_height) + .expect("Contruct IbcHeight Never faild") + } +} + +impl Height { + pub fn new(revision_number: u64, revision_height: u64) -> Self { + Self { revision_number, revision_height } + } +} + +/// ibc-rs' `ClientType` representation in substrate +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub enum ClientType { + Tendermint, + Grandpa, +} + +impl From for ClientType { + fn from(value: IbcClientType) -> Self { + match value { + IbcClientType::Tendermint => ClientType::Tendermint, + _ => unreachable!(), + } + } +} + +impl ClientType { + pub fn to_ibc_client_type(self) -> IbcClientType { + match self { + ClientType::Tendermint => IbcClientType::Tendermint, + _ => unreachable!(), + } + } +} + +/// ibc-rs' `ClientId` representation in substrate +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct ClientId(pub Vec); + +impl From for ClientId { + fn from(value: IbcClientId) -> Self { + let value = value.as_str().as_bytes().to_vec(); + Self(value) + } +} + +impl From for IbcClientId { + fn from(value: ClientId) -> Self { + let value = String::from_utf8(value.0).expect("convert Never faild"); + IbcClientId::from_str(&value).expect("convert Never faild") + } +} + +/// ibc-rs' `ConnectionId` representation in substrate +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct ConnectionId(pub Vec); + +impl From for ConnectionId { + fn from(value: IbcConnectionId) -> Self { + let value = value.as_str().as_bytes().to_vec(); + Self(value) + } +} + +impl From for IbcConnectionId { + fn from(value: ConnectionId) -> Self { + let value = String::from_utf8(value.0).expect("convert Never faild"); + IbcConnectionId::from_str(&value).expect("convert Never faild") + } +} + +/// ibc-rs' `Timestamp` representation in substrate +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct Timestamp { + pub time: Vec, +} + +impl From for Timestamp { + fn from(val: IbcTimestamp) -> Self { + Self { time: val.nanoseconds().to_string().as_bytes().to_vec() } + } +} + +impl From for IbcTimestamp { + fn from(value: Timestamp) -> Self { + let value = String::from_utf8(value.time).expect("convert Never faild"); + Self::from_str(&value).expect("convert Never faild") + } +} + +/// ibc-rs' `Sequence` representation in substrate + +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct Sequence(u64); + +impl From for Sequence { + fn from(val: IbcSequence) -> Self { + Self(u64::from(val)) + } +} + +impl From for IbcSequence { + fn from(val: Sequence) -> Self { + IbcSequence::from(val.0) + } +} + +/// ibc-rs' `Packet` representation in substrate +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct Packet { + pub sequence: Sequence, + pub source_port: PortId, + pub source_channel: ChannelId, + pub destination_port: PortId, + pub destination_channel: ChannelId, + pub data: Vec, + pub timeout_height: Height, + pub timeout_timestamp: Timestamp, +} + +impl From for Packet { + fn from(val: IbcPacket) -> Self { + Self { + sequence: val.sequence.into(), + source_port: val.source_port.into(), + source_channel: val.source_channel.into(), + destination_port: val.destination_port.into(), + destination_channel: val.destination_channel.into(), + data: val.data, + timeout_height: match val.timeout_height { + TimeoutHeight::Never => Height::new(REVISION_NUMBER, u64::MAX), + TimeoutHeight::At(value) => value.into(), + }, + timeout_timestamp: val.timeout_timestamp.into(), + } + } +} + +impl From for IbcPacket { + fn from(value: Packet) -> Self { + Self { + sequence: value.sequence.into(), + source_port: value.source_port.into(), + source_channel: value.source_channel.into(), + destination_port: value.destination_port.into(), + destination_channel: value.destination_channel.into(), + data: value.data, + timeout_height: TimeoutHeight::At(value.timeout_height.into()), + timeout_timestamp: value.timeout_timestamp.into(), + } + } +} diff --git a/frame/ibc/src/module/core/ics26_routing.rs b/frame/ibc/src/module/core/ics26_routing.rs new file mode 100644 index 0000000000000..fedfeba33e675 --- /dev/null +++ b/frame/ibc/src/module/core/ics26_routing.rs @@ -0,0 +1,82 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) 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. + +use crate::{context::Context, *}; +use alloc::{ + borrow::{Borrow, Cow, ToOwned}, + collections::BTreeMap, + fmt::format, + sync::Arc, +}; +use core::fmt::Formatter; +use ibc::core::ics26_routing::context::{Ics26Context, Module, ModuleId, RouterBuilder}; +use log::{error, info, trace, warn}; +use scale_info::TypeInfo; + +#[derive(Default)] +pub struct SubRouterBuilder(Router); + +impl RouterBuilder for SubRouterBuilder { + type Router = Router; + + fn add_route(mut self, module_id: ModuleId, module: impl Module) -> Result { + match self.0 .0.insert(module_id, Arc::new(module)) { + None => Ok(self), + Some(_) => Err("Duplicate module_id".to_owned()), + } + } + + fn build(self) -> Self::Router { + self.0 + } +} + +#[derive(Default, Clone)] +pub struct Router(BTreeMap>); + +impl Debug for Router { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + let mut keys = vec![]; + for (key, _) in self.0.iter() { + keys.push(format!("{}", key)); + } + + write!(f, "MockRouter(BTreeMap(key({:?})", keys.join(",")) + } +} + +impl ibc::core::ics26_routing::context::Router for Router { + fn get_route_mut(&mut self, module_id: &impl Borrow) -> Option<&mut dyn Module> { + self.0.get_mut(module_id.borrow()).and_then(Arc::get_mut) + } + + fn has_route(&self, module_id: &impl Borrow) -> bool { + self.0.get(module_id.borrow()).is_some() + } +} + +impl Ics26Context for Context { + type Router = Router; + + fn router(&self) -> &Self::Router { + &self.router + } + + fn router_mut(&mut self) -> &mut Self::Router { + &mut self.router + } +} diff --git a/frame/ibc/src/module/core/mod.rs b/frame/ibc/src/module/core/mod.rs new file mode 100644 index 0000000000000..9b9d7c270089d --- /dev/null +++ b/frame/ibc/src/module/core/mod.rs @@ -0,0 +1,23 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) 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. + +pub mod ics02_client; +pub mod ics03_connection; +pub mod ics04_channel; +pub mod ics05_port; +pub mod ics24_host; +pub mod ics26_routing; diff --git a/frame/ibc/src/module/mod.rs b/frame/ibc/src/module/mod.rs new file mode 100644 index 0000000000000..d475ce0d9fff3 --- /dev/null +++ b/frame/ibc/src/module/mod.rs @@ -0,0 +1,20 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) 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. + +pub mod applications; +pub mod core; +pub mod relayer; diff --git a/frame/ibc/src/module/relayer/mod.rs b/frame/ibc/src/module/relayer/mod.rs new file mode 100644 index 0000000000000..3f18d85aa2537 --- /dev/null +++ b/frame/ibc/src/module/relayer/mod.rs @@ -0,0 +1,64 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) 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. + +use crate::{ + alloc::string::ToString, context::Context, utils::host_height, Config, REVISION_NUMBER, +}; +use ibc::{ + core::{ + ics02_client::{client_state::AnyClientState, context::ClientReader, header::AnyHeader}, + ics24_host::identifier::ClientId, + ics26_routing::handler::{deliver, MsgReceipt}, + }, + events::IbcEvent, + relayer::ics18_relayer::{context::Ics18Context, error::Error as ICS18Error}, + signer::Signer, + Height, +}; +use ibc_proto::google::protobuf::Any; +use scale_info::prelude::{vec, vec::Vec}; + +impl Ics18Context for Context { + fn query_latest_height(&self) -> Height { + let revision_height = host_height::(); + Height::new(REVISION_NUMBER, revision_height).expect(&REVISION_NUMBER.to_string()) + } + + fn query_client_full_state(&self, client_id: &ClientId) -> Option { + // Forward call to Ics2. + ClientReader::client_state(self, client_id).ok() + } + + fn query_latest_header(&self) -> Option { + todo!() + } + + fn send(&mut self, msgs: Vec) -> Result, ICS18Error> { + // Forward call to Ics26 delivery method. + let mut all_events = vec![]; + for msg in msgs { + let MsgReceipt { mut events, .. } = + deliver(self, msg).map_err(ICS18Error::transaction_failed)?; + all_events.append(&mut events); + } + Ok(all_events) + } + + fn signer(&self) -> Signer { + "0CDA3F47EF3C4906693B170EF650EB968C5F4B2C".parse().unwrap() + } +} diff --git a/frame/ibc/src/tests.rs b/frame/ibc/src/tests.rs new file mode 100644 index 0000000000000..1a30744f43137 --- /dev/null +++ b/frame/ibc/src/tests.rs @@ -0,0 +1,430 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2021 Parity Technologies (UK) 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. + +//! Tests for the ibc pallet. +use super::*; +use crate::{mock::*, Context}; +use core::str::FromStr; + +use ibc::{ + applications::transfer::{context::Ics20Context, error::Error as ICS20Error}, + core::{ + ics02_client::{ + client_consensus::AnyConsensusState, + client_state::AnyClientState, + client_type::ClientType, + context::{ClientKeeper, ClientReader}, + error::Error as ICS02Error, + }, + ics03_connection::{ + connection::{ConnectionEnd, State}, + context::{ConnectionKeeper, ConnectionReader}, + error::Error as ICS03Error, + }, + ics04_channel::{ + channel::ChannelEnd, + context::{ChannelKeeper, ChannelReader}, + error::Error as ICS04Error, + packet::Sequence, + }, + ics23_commitment::commitment::CommitmentRoot, + ics24_host::identifier::{ChainId, ChannelId, ClientId, ConnectionId, PortId}, + }, + timestamp::Timestamp, + Height, +}; + +// test store and read client-type +#[test] +fn test_store_client_type_ok() { + let gp_client_type = ClientType::Tendermint; + let gp_client_id = ClientId::default(); + + let mut context: Context = Context::new(); + + new_test_ext().execute_with(|| { + assert!(context.store_client_type(gp_client_id.clone(), gp_client_type).is_ok()); + }) +} + +#[test] +fn test_read_client_type_failed_by_supply_error_client_id() { + let gp_client_type = ClientType::Tendermint; + let gp_client_id = ClientId::new(gp_client_type, 0).unwrap(); + let gp_client_id_failed = ClientId::new(gp_client_type, 1).unwrap(); + let mut context: Context = Context::new(); + + new_test_ext().execute_with(|| { + assert!(context.store_client_type(gp_client_id.clone(), gp_client_type).is_ok()); + + let ret = context.client_type(&gp_client_id_failed).unwrap_err().to_string(); + + assert_eq!(ret, ICS02Error::client_not_found(gp_client_id_failed).to_string()); + }) +} +#[test] +fn test_get_packet_commitment_state_ok() { + use ibc::core::ics04_channel::commitment::PacketCommitment; + + let mut context: Context = Context::new(); + + let range = (0..10).into_iter().collect::>(); + + let mut port_id_vec = vec![]; + let mut channel_id_vec = vec![]; + let mut sequence_vec = vec![]; + + for index in range.clone() { + port_id_vec.push(PortId::default()); + channel_id_vec.push(ChannelId::default()); + let sequence = Sequence::from(index as u64); + sequence_vec.push(sequence); + } + let com = PacketCommitment::from(vec![1, 2, 3]); + + new_test_ext().execute_with(|| { + for index in 0..range.len() { + assert!(context + .store_packet_commitment( + ( + port_id_vec[index].clone(), + channel_id_vec[index].clone(), + sequence_vec[index] + ), + com.clone(), + ) + .is_ok()); + } + }) +} + +#[test] +fn test_connection_ok() { + use codec::alloc::collections::HashMap; + + let mut input: HashMap = HashMap::new(); + + let connection_id0 = ConnectionId::default(); + let connection_end0 = ConnectionEnd::default(); + + let connection_id1 = ConnectionId::default(); + let connection_end1 = ConnectionEnd::default(); + + let connection_id2 = ConnectionId::default(); + let connection_end2 = ConnectionEnd::default(); + + input.insert(connection_id0.clone(), connection_end0.clone()); + input.insert(connection_id1.clone(), connection_end1.clone()); + input.insert(connection_id2.clone(), connection_end2.clone()); + + let mut context: Context = Context::new(); + new_test_ext().execute_with(|| { + assert_eq!( + ConnectionKeeper::store_connection( + &mut context, + connection_id0.clone(), + input.get(&connection_id0.clone()).unwrap() + ) + .is_ok(), + true + ); + + let ret = ConnectionReader::connection_end(&mut context, &connection_id0).unwrap(); + assert_eq!(ret, *input.get(&connection_id0.clone()).unwrap()); + + assert_eq!( + ConnectionKeeper::store_connection( + &mut context, + connection_id1.clone(), + input.get(&connection_id1.clone()).unwrap() + ) + .is_ok(), + true + ); + + assert_eq!( + ConnectionKeeper::store_connection( + &mut context, + connection_id2.clone(), + input.get(&connection_id2.clone()).unwrap() + ) + .is_ok(), + true + ); + }) +} + +#[test] +fn test_connection_fail() { + let connection_id0 = ConnectionId::default(); + let context: Context = Context::new(); + new_test_ext().execute_with(|| { + let ret = ConnectionReader::connection_end(&context, &connection_id0.clone()) + .unwrap_err() + .to_string(); + assert_eq!(ret, ICS03Error::connection_mismatch(connection_id0).to_string()); + }) +} + +#[test] +fn test_connection_client_ok() { + let gp_client_id = ClientId::default(); + let connection_id = ConnectionId::new(0); + let mut context: Context = Context::new(); + + new_test_ext().execute_with(|| { + assert!(context.store_connection_to_client(connection_id, &gp_client_id).is_ok()); + }) +} + +#[test] +fn test_delete_packet_acknowledgement_ok() { + use ibc::core::ics04_channel::commitment::AcknowledgementCommitment; + + let port_id = PortId::default(); + let channel_id = ChannelId::default(); + let sequence = Sequence::from(0); + let ack = AcknowledgementCommitment::from(vec![1, 2, 3]); + + let mut context: Context = Context::new(); + + new_test_ext().execute_with(|| { + assert!(context + .store_packet_acknowledgement( + (port_id.clone(), channel_id.clone(), sequence), + ack.clone() + ) + .is_ok()); + + assert!(context + .delete_packet_acknowledgement((port_id.clone(), channel_id.clone(), sequence)) + .is_ok()); + + let result = context + .get_packet_acknowledgement(&(port_id, channel_id, sequence)) + .unwrap_err() + .to_string(); + + assert_eq!(result, ICS04Error::packet_acknowledgement_not_found(sequence).to_string()); + }) +} + +#[test] +fn test_get_acknowledge_state() { + use ibc::core::ics04_channel::commitment::AcknowledgementCommitment; + let range = (0..10).into_iter().collect::>(); + + let mut port_id_vec = vec![]; + let mut channel_id_vec = vec![]; + let mut sequence_vec = vec![]; + let mut ack_vec = vec![]; + + let mut value_vec = vec![]; + + let mut context: Context = Context::new(); + + for index in 0..range.len() { + port_id_vec.push(PortId::default()); + channel_id_vec.push(ChannelId::default()); + let sequence = Sequence::from(index as u64); + sequence_vec.push(sequence); + ack_vec.push(AcknowledgementCommitment::from(vec![index as u8])); + value_vec.push(ChannelReader::hash(&context, vec![index as u8]).encode()); + } + + new_test_ext().execute_with(|| { + for index in 0..range.len() { + assert!(context + .store_packet_acknowledgement( + ( + port_id_vec[index].clone(), + channel_id_vec[index].clone(), + sequence_vec[index] + ), + ack_vec[index].clone() + ) + .is_ok()); + } + }) +} + +#[test] +fn test_store_connection_channles_ok() { + let connection_id = ConnectionId::default(); + let port_id = PortId::default(); + let channel_id = ChannelId::default(); + + let mut context: Context = Context::new(); + new_test_ext().execute_with(|| { + assert!(context + .store_connection_channels( + connection_id.clone(), + &(port_id.clone(), channel_id.clone()) + ) + .is_ok()); + + let result = context.connection_channels(&connection_id).unwrap(); + + assert_eq!(result.len(), 1); + + assert_eq!(result[0].0, port_id); + assert_eq!(result[0].1, channel_id); + }) +} + +#[test] +fn test_next_sequence_send_ok() { + let sequence_id = Sequence::from(0); + let port_channel = (PortId::default(), ChannelId::default()); + let mut context: Context = Context::new(); + + new_test_ext().execute_with(|| { + assert!(context.store_next_sequence_send(port_channel.clone(), sequence_id).is_ok()); + let result = context.get_next_sequence_send(&port_channel).unwrap(); + assert_eq!(result, sequence_id); + }) +} + +#[test] +fn test_read_conection_channels_failed_by_suppley_error_conneciton_id() { + let connection_id = ConnectionId::new(0); + let connection_id_failed = ConnectionId::new(1); + let port_id = PortId::from_str(String::from_str("port-0").unwrap().as_str()).unwrap(); + let channel_id = ChannelId::from_str(String::from_str("channel-0").unwrap().as_str()).unwrap(); + + let mut context: Context = Context::new(); + new_test_ext().execute_with(|| { + assert!(context + .store_connection_channels( + connection_id.clone(), + &(port_id.clone(), channel_id.clone()) + ) + .is_ok()); + + let result = context.connection_channels(&connection_id_failed).unwrap_err().to_string(); + + assert_eq!( + result, + ICS04Error::connection_not_open(connection_id_failed.clone()).to_string() + ); + }) +} + +#[test] +fn test_store_channel_ok() { + let port_id = PortId::default(); + let channel_id = ChannelId::default(); + let channel_end = ChannelEnd::default(); + + let mut context: Context = Context::new(); + + new_test_ext().execute_with(|| { + assert!(context + .store_channel((port_id.clone(), channel_id.clone()), &channel_end) + .is_ok()); + + let result = context.channel_end(&(port_id.clone(), channel_id.clone())).unwrap(); + + assert_eq!(result, channel_end); + }) +} + +#[test] + +fn test_next_sequence_send_fail() { + let port_channel = (PortId::default(), ChannelId::default()); + let context: Context = Context::new(); + + new_test_ext().execute_with(|| { + let result = context.get_next_sequence_send(&port_channel.clone()).unwrap_err().to_string(); + assert_eq!(result, ICS04Error::missing_next_send_seq(port_channel).to_string()); + }) +} + +#[test] +fn test_next_sequence_recv_ok() { + let sequence_id = Sequence::from(0); + let port_channel = (PortId::default(), ChannelId::default()); + let mut context: Context = Context::new(); + + new_test_ext().execute_with(|| { + assert!(context.store_next_sequence_recv(port_channel.clone(), sequence_id).is_ok()); + let result = context.get_next_sequence_recv(&port_channel).unwrap(); + assert_eq!(result, sequence_id); + }) +} + +#[test] +fn test_get_identified_channel_end() { + let range = (0..10).into_iter().collect::>(); + + let mut port_id_vec = vec![]; + let mut channel_id_vec = vec![]; + let channel_end_vec = vec![ChannelEnd::default(); range.len()]; + + for index in 0..range.len() { + port_id_vec.push(PortId::default()); + channel_id_vec.push(ChannelId::default()); + } + + let mut context: Context = Context::new(); + new_test_ext().execute_with(|| { + for index in 0..range.len() { + assert!(context + .store_channel( + (port_id_vec[index].clone(), channel_id_vec[index].clone()), + &channel_end_vec[index].clone() + ) + .is_ok()); + } + }) +} + +#[test] +fn test_next_sequence_recv_fail() { + let port_channel = (PortId::default(), ChannelId::default()); + let context: Context = Context::new(); + + new_test_ext().execute_with(|| { + let result = context.get_next_sequence_recv(&port_channel.clone()).unwrap_err().to_string(); + assert_eq!(result, ICS04Error::missing_next_recv_seq(port_channel).to_string()); + }) +} + +#[test] +fn test_next_sequence_ack_ok() { + let sequence_id = Sequence::from(0); + let port_channel = (PortId::default(), ChannelId::default()); + let mut context: Context = Context::new(); + + new_test_ext().execute_with(|| { + assert!(context.store_next_sequence_ack(port_channel.clone(), sequence_id).is_ok()); + let result = context.get_next_sequence_ack(&port_channel).unwrap(); + assert_eq!(result, sequence_id); + }) +} + +#[test] +fn test_next_sequence_ack_fail() { + let port_channel = (PortId::default(), ChannelId::default()); + let context: Context = Context::new(); + + new_test_ext().execute_with(|| { + let result = context.get_next_sequence_ack(&port_channel.clone()).unwrap_err().to_string(); + assert_eq!(result, ICS04Error::missing_next_ack_seq(port_channel).to_string()); + }) +} diff --git a/frame/ibc/src/traits.rs b/frame/ibc/src/traits.rs new file mode 100644 index 0000000000000..6ff204fc02a0c --- /dev/null +++ b/frame/ibc/src/traits.rs @@ -0,0 +1,27 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) 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. + +use alloc::vec::Vec; + +/// A trait handling asset ID and name +pub trait AssetIdAndNameProvider { + type Err; + + fn try_get_asset_id(name: impl AsRef<[u8]>) -> Result; + + fn try_get_asset_name(asset_id: AssetId) -> Result, Self::Err>; +} diff --git a/frame/ibc/src/utils.rs b/frame/ibc/src/utils.rs new file mode 100644 index 0000000000000..cd1917b5e7e98 --- /dev/null +++ b/frame/ibc/src/utils.rs @@ -0,0 +1,64 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) 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. + +use crate::Config; +use codec::Encode; +use scale_info::prelude::{fmt::Debug, format, vec::Vec}; + +use super::*; +use ibc::{ + applications::transfer::{error::Error as Ics20Error, VERSION}, + core::ics24_host::identifier::{ChannelId as IbcChannelId, PortId}, + signer::Signer, +}; + +use ibc::{ + core::{ + ics02_client::msgs::ClientMsg, + ics03_connection::msgs::ConnectionMsg, + ics04_channel::msgs::{ChannelMsg, PacketMsg}, + ics26_routing::{handler, msgs::Ics26Envelope}, + }, + events::IbcEvent, +}; + +/// Get the latest block height of the host chain +pub fn host_height() -> u64 { + let block_number = format!("{:?}", >::block_number()); + let current_height: u64 = block_number.parse().unwrap_or_default(); + current_height +} + +/// In ICS20 fungible token transfer, get the escrow address by channel ID and port ID +/// +/// Parameters: +/// - `port_id`: The ID of the port corresponding to the escrow. +/// - `channel_id`: The ID of the channel corresponding to the escrow. +pub fn get_channel_escrow_address( + port_id: &PortId, + channel_id: &IbcChannelId, +) -> Result { + let contents = format!("{}/{}", port_id, channel_id); + let mut data = VERSION.as_bytes().to_vec(); + data.extend_from_slice(&[0]); + data.extend_from_slice(contents.as_bytes()); + + let hash = sp_io::hashing::sha2_256(&data).to_vec(); + let mut hex_string = hex::encode_upper(hash); + hex_string.insert_str(0, "0x"); + hex_string.parse::().map_err(Ics20Error::signer) +}