diff --git a/.gitignore b/.gitignore index 3dc05158..e91ae694 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .DS_Store corelib/ target/ +**/Cargo.lock # vscode -.vscode/ \ No newline at end of file +.vscode/ diff --git a/Cargo.lock b/Cargo.lock index 40621740..b54ebf25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -811,6 +811,24 @@ dependencies = [ "serde", ] +[[package]] +name = "ibc-client-dummy" +version = "0.1.0" +dependencies = [ + "derive_more 0.99.18", + "ibc-core", +] + +[[package]] +name = "ibc-client-dummy-cw" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "ibc-client-cw", + "ibc-client-dummy", + "ibc-core", +] + [[package]] name = "ibc-client-starknet" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 4b4a1d8a..892394ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ resolver = "2" members = [ "light-client/*", + "dummy-light-client/*", ] [workspace.package] diff --git a/dummy-light-client/README.md b/dummy-light-client/README.md new file mode 100644 index 00000000..d69bb972 --- /dev/null +++ b/dummy-light-client/README.md @@ -0,0 +1,9 @@ +
+

Dummy Light Client

+
+ +A light client without any validation. Its purpose is to bootstrap IBC without +having a full light client implementation. + +> [!WARNING]\ +> This light client is not secure and should not be used in production. diff --git a/dummy-light-client/cw-contract/Cargo.toml b/dummy-light-client/cw-contract/Cargo.toml new file mode 100644 index 00000000..a9b105b3 --- /dev/null +++ b/dummy-light-client/cw-contract/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "ibc-client-dummy-cw" +authors = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +repository = { workspace = true } +rust-version = { workspace = true } +version = { workspace = true } +readme = "./../README.md" +keywords = [ "dummy", "ibc", "light-client", "CosmWasm" ] +description = "CosmWasm contract for dummy IBC client." + +[lib] +crate-type = [ "cdylib", "rlib" ] + +[dependencies] +# ibc dependencies +ibc-core = { workspace = true } +ibc-client-cw = { workspace = true } +ibc-client-dummy = { path = "./../impls" } + +# cosmwasm dependencies +cosmwasm-std = "2.1.0" + +[features] +default = [ "std" ] +std = [ + "ibc-core/std", + "ibc-client-cw/std", +] diff --git a/dummy-light-client/cw-contract/src/client_type.rs b/dummy-light-client/cw-contract/src/client_type.rs new file mode 100644 index 00000000..e4742476 --- /dev/null +++ b/dummy-light-client/cw-contract/src/client_type.rs @@ -0,0 +1,9 @@ +use ibc_client_cw::api::ClientType; +use ibc_client_dummy::{ClientState, ConsensusState}; + +pub struct DummyLightClient; + +impl<'a> ClientType<'a> for DummyLightClient { + type ClientState = ClientState; + type ConsensusState = ConsensusState; +} diff --git a/dummy-light-client/cw-contract/src/entrypoint.rs b/dummy-light-client/cw-contract/src/entrypoint.rs new file mode 100644 index 00000000..06db2753 --- /dev/null +++ b/dummy-light-client/cw-contract/src/entrypoint.rs @@ -0,0 +1,37 @@ +use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response}; +use ibc_client_cw::context::Context; +use ibc_client_cw::types::{ContractError, InstantiateMsg, QueryMsg, SudoMsg}; + +use crate::client_type::DummyLightClient; + +pub type DummyLightClientContext<'a> = Context<'a, DummyLightClient>; + +#[entry_point] +pub fn instantiate( + deps: DepsMut<'_>, + env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + let mut ctx = DummyLightClientContext::new_mut(deps, env)?; + + let data = ctx.instantiate(msg)?; + + Ok(Response::default().set_data(data)) +} + +#[entry_point] +pub fn sudo(deps: DepsMut<'_>, env: Env, msg: SudoMsg) -> Result { + let mut ctx = DummyLightClientContext::new_mut(deps, env)?; + + let data = ctx.sudo(msg)?; + + Ok(Response::default().set_data(data)) +} + +#[entry_point] +pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result { + let ctx = DummyLightClientContext::new_ref(deps, env)?; + + ctx.query(msg) +} diff --git a/dummy-light-client/cw-contract/src/lib.rs b/dummy-light-client/cw-contract/src/lib.rs new file mode 100644 index 00000000..54fb5442 --- /dev/null +++ b/dummy-light-client/cw-contract/src/lib.rs @@ -0,0 +1,2 @@ +pub mod client_type; +pub mod entrypoint; diff --git a/dummy-light-client/impls/Cargo.toml b/dummy-light-client/impls/Cargo.toml new file mode 100644 index 00000000..1c4df4db --- /dev/null +++ b/dummy-light-client/impls/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "ibc-client-dummy" +authors = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +rust-version = { workspace = true } +version = { workspace = true } +repository = { workspace = true } +readme = "./../README.md" +keywords = [ "dummy", "test", "ibc", "light-client" ] +description = "A light client without any validation. Only for testing." + +[lints] +workspace = true + +[dependencies] +# external dependencies +derive_more = { workspace = true } + +# ibc dependencies +ibc-core = { workspace = true } + +[features] +default = [ "std" ] +std = [ + "ibc-core/std", +] diff --git a/dummy-light-client/impls/src/client_state/common.rs b/dummy-light-client/impls/src/client_state/common.rs new file mode 100644 index 00000000..601a96c2 --- /dev/null +++ b/dummy-light-client/impls/src/client_state/common.rs @@ -0,0 +1,65 @@ +use ibc_core::client::context::client_state::ClientStateCommon; +use ibc_core::client::types::error::ClientError; +use ibc_core::client::types::Height; +use ibc_core::commitment_types::commitment::{ + CommitmentPrefix, CommitmentProofBytes, CommitmentRoot, +}; +use ibc_core::host::types::identifiers::ClientType; +use ibc_core::host::types::path::{Path, PathBytes}; +use ibc_core::primitives::proto::Any; + +use super::ClientState; + +impl ClientStateCommon for ClientState { + fn verify_consensus_state(&self, consensus_state: Any) -> Result<(), ClientError> { + Ok(()) + } + + fn client_type(&self) -> ClientType { + "blind-001".parse().unwrap() + } + + fn latest_height(&self) -> Height { + self.latest_height + } + + fn validate_proof_height(&self, proof_height: Height) -> Result<(), ClientError> { + Ok(()) + } + + fn verify_upgrade_client( + &self, + upgraded_client_state: Any, + upgraded_consensus_state: Any, + proof_upgrade_client: CommitmentProofBytes, + proof_upgrade_consensus_state: CommitmentProofBytes, + root: &CommitmentRoot, + ) -> Result<(), ClientError> { + Ok(()) + } + + fn serialize_path(&self, path: Path) -> Result { + Ok(path.to_string().as_bytes().to_vec().into()) + } + + fn verify_membership_raw( + &self, + prefix: &CommitmentPrefix, + proof: &CommitmentProofBytes, + root: &CommitmentRoot, + path: PathBytes, + value: Vec, + ) -> Result<(), ClientError> { + Ok(()) + } + + fn verify_non_membership_raw( + &self, + prefix: &CommitmentPrefix, + proof: &CommitmentProofBytes, + root: &CommitmentRoot, + path: PathBytes, + ) -> Result<(), ClientError> { + Ok(()) + } +} diff --git a/dummy-light-client/impls/src/client_state/execution.rs b/dummy-light-client/impls/src/client_state/execution.rs new file mode 100644 index 00000000..47ab0af9 --- /dev/null +++ b/dummy-light-client/impls/src/client_state/execution.rs @@ -0,0 +1,140 @@ +use ibc_core::client::context::client_state::ClientStateExecution; +use ibc_core::client::context::prelude::ClientStateCommon; +use ibc_core::client::context::ClientExecutionContext; +use ibc_core::client::types::error::ClientError; +use ibc_core::client::types::Height; +use ibc_core::host::types::identifiers::ClientId; +use ibc_core::host::types::path::{ClientConsensusStatePath, ClientStatePath}; +use ibc_core::primitives::proto::Any; +use ibc_core::primitives::Timestamp; + +use crate::ConsensusState; + +use super::ClientState; + +impl ClientStateExecution for ClientState +where + E: ClientExecutionContext, +{ + fn initialise( + &self, + ctx: &mut E, + client_id: &ClientId, + consensus_state: Any, + ) -> Result<(), ClientError> { + let latest_height = Height::min(0); + + update_client_and_consensus_state( + ctx, + latest_height, + client_id, + self.clone(), + consensus_state.try_into()?, + )?; + + Ok(()) + } + + fn update_state( + &self, + ctx: &mut E, + client_id: &ClientId, + header: Any, + ) -> Result, ClientError> { + let latest_height = ctx.client_state(client_id)?.latest_height().increment(); + + let new_client_state = ClientState { latest_height }; + + update_client_and_consensus_state( + ctx, + latest_height, + client_id, + new_client_state, + ConsensusState::default(), + )?; + + Ok(vec![latest_height]) + } + + fn update_state_on_misbehaviour( + &self, + ctx: &mut E, + client_id: &ClientId, + client_message: Any, + ) -> Result<(), ClientError> { + Ok(()) + } + + fn update_state_on_upgrade( + &self, + ctx: &mut E, + client_id: &ClientId, + upgraded_client_state: Any, + upgraded_consensus_state: Any, + ) -> Result { + let latest_height = ctx.client_state(client_id)?.latest_height().increment(); + + let new_client_state = ClientState { latest_height }; + + update_client_and_consensus_state( + ctx, + latest_height, + client_id, + upgraded_client_state.try_into()?, + upgraded_consensus_state.try_into()?, + )?; + + Ok(latest_height) + } + + fn update_on_recovery( + &self, + ctx: &mut E, + subject_client_id: &ClientId, + substitute_client_state: Any, + substitute_consensus_state: Any, + ) -> Result<(), ClientError> { + let latest_height = ctx + .client_state(subject_client_id)? + .latest_height() + .increment(); + + let new_client_state = ClientState { latest_height }; + + update_client_and_consensus_state( + ctx, + latest_height, + subject_client_id, + substitute_client_state.try_into()?, + substitute_consensus_state.try_into()?, + )?; + + Ok(()) + } +} + +fn update_client_and_consensus_state( + ctx: &mut E, + client_height: Height, + client_id: &ClientId, + client_state: E::ClientStateRef, + consensus_state: E::ConsensusStateRef, +) -> Result<(), ClientError> { + ctx.store_consensus_state( + ClientConsensusStatePath::new( + client_id.clone(), + client_height.revision_number(), + client_height.revision_height(), + ), + consensus_state, + )?; + ctx.store_client_state(ClientStatePath::new(client_id.clone()), client_state)?; + ctx.store_update_meta( + client_id.clone(), + client_height, + Timestamp::none(), + Height::min(0), + )?; + + Ok(()) +} diff --git a/dummy-light-client/impls/src/client_state/mod.rs b/dummy-light-client/impls/src/client_state/mod.rs new file mode 100644 index 00000000..2c0dd0fe --- /dev/null +++ b/dummy-light-client/impls/src/client_state/mod.rs @@ -0,0 +1,44 @@ +pub mod common; +pub mod execution; +pub mod validation; + +use ibc_core::client::types::error::ClientError; +use ibc_core::client::types::Height; +use ibc_core::primitives::proto::{Any, Protobuf}; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, derive_more::From)] +pub struct ClientState { + latest_height: Height, +} + +impl Protobuf for ClientState {} + +impl TryFrom for ClientState { + type Error = ClientError; + + fn try_from(raw: Any) -> Result { + if raw.type_url != "/DummyClientState" { + return Err(ClientError::from("invalid type URL or empty value")); + } + + let revision_number = u64::from_be_bytes(raw.value.try_into().unwrap()); + + Ok(Self { + latest_height: Height::min(revision_number), + }) + } +} + +impl From for Any { + fn from(client_state: ClientState) -> Self { + Self { + type_url: "/DummyClientState".to_string(), + value: client_state + .latest_height + .revision_number() + .to_be_bytes() + .to_vec(), + } + } +} diff --git a/dummy-light-client/impls/src/client_state/validation.rs b/dummy-light-client/impls/src/client_state/validation.rs new file mode 100644 index 00000000..29e80150 --- /dev/null +++ b/dummy-light-client/impls/src/client_state/validation.rs @@ -0,0 +1,41 @@ +use ibc_core::client::context::client_state::ClientStateValidation; +use ibc_core::client::context::ClientValidationContext; +use ibc_core::client::types::error::ClientError; +use ibc_core::client::types::Status; +use ibc_core::host::types::identifiers::ClientId; +use ibc_core::primitives::proto::Any; + +use crate::ConsensusState; + +use super::ClientState; + +impl ClientStateValidation for ClientState +where + V: ClientValidationContext, +{ + fn verify_client_message( + &self, + ctx: &V, + client_id: &ClientId, + client_message: Any, + ) -> Result<(), ClientError> { + Ok(()) + } + + fn check_for_misbehaviour( + &self, + ctx: &V, + client_id: &ClientId, + client_message: Any, + ) -> Result { + Ok(false) + } + + fn status(&self, ctx: &V, client_id: &ClientId) -> Result { + Ok(Status::Active) + } + + fn check_substitute(&self, ctx: &V, substitute_client_state: Any) -> Result<(), ClientError> { + Ok(()) + } +} diff --git a/dummy-light-client/impls/src/consensus_state.rs b/dummy-light-client/impls/src/consensus_state.rs new file mode 100644 index 00000000..058d1470 --- /dev/null +++ b/dummy-light-client/impls/src/consensus_state.rs @@ -0,0 +1,52 @@ +use ibc_core::client::context::consensus_state::ConsensusState as ConsensusStateTrait; +use ibc_core::client::types::error::ClientError; +use ibc_core::commitment_types::commitment::CommitmentRoot; +use ibc_core::primitives::proto::{Any, Protobuf}; +use ibc_core::primitives::Timestamp; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, derive_more::From)] +pub struct ConsensusState { + root: CommitmentRoot, +} + +impl Default for ConsensusState { + fn default() -> Self { + Self { + root: CommitmentRoot::from(vec![]), + } + } +} + +impl Protobuf for ConsensusState {} + +impl TryFrom for ConsensusState { + type Error = ClientError; + + fn try_from(raw: Any) -> Result { + if raw.type_url != "/DummyConsensusState" || !raw.value.is_empty() { + return Err(ClientError::from("invalid type URL or empty value")); + } + + Ok(Self::default()) + } +} + +impl From for Any { + fn from(consensus_state: ConsensusState) -> Self { + Self { + type_url: "/DummyConsensusState".to_string(), + value: vec![], + } + } +} + +impl ConsensusStateTrait for ConsensusState { + fn root(&self) -> &CommitmentRoot { + &self.root + } + + fn timestamp(&self) -> Timestamp { + Timestamp::none() + } +} diff --git a/dummy-light-client/impls/src/lib.rs b/dummy-light-client/impls/src/lib.rs new file mode 100644 index 00000000..eb528a0f --- /dev/null +++ b/dummy-light-client/impls/src/lib.rs @@ -0,0 +1,5 @@ +mod client_state; +mod consensus_state; + +pub use client_state::*; +pub use consensus_state::*;