-
Notifications
You must be signed in to change notification settings - Fork 94
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement ibc-client-cw + ibc-client-tendermint-cw
- Loading branch information
1 parent
d5e3ba3
commit 9a24f3c
Showing
21 changed files
with
1,434 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,8 @@ members = [ | |
"ibc-clients/ics07-tendermint/types", | ||
"ibc-clients/ics07-tendermint", | ||
"ibc-clients/ics08-wasm/types", | ||
"ibc-clients/cosmwasm", | ||
"ibc-clients/ics07-tendermint/cw", | ||
"ibc-clients", | ||
"ibc-apps/ics20-transfer/types", | ||
"ibc-apps/ics20-transfer", | ||
|
@@ -53,6 +55,7 @@ authors = ["Informal Systems <[email protected]>"] | |
base64 = { version = "0.21", default-features = false } | ||
borsh = { version = "0.10", default-features = false } | ||
displaydoc = { version = "0.2", default-features = false } | ||
prost = { version = "0.12", default-features = false } | ||
derive_more = { version = "0.99.17", default-features = false, features = ["from", "into", "display", "try_into"] } | ||
rstest = "0.18.2" | ||
schemars = { version = "0.8.15" } | ||
|
@@ -76,6 +79,7 @@ ibc-core-host = { version = "0.51.0", path = "./ibc-core/ics24-host", de | |
ibc-core-handler = { version = "0.51.0", path = "./ibc-core/ics25-handler", default-features = false } | ||
ibc-core-router = { version = "0.51.0", path = "./ibc-core/ics26-routing", default-features = false } | ||
ibc-client-tendermint = { version = "0.51.0", path = "./ibc-clients/ics07-tendermint", default-features = false } | ||
ibc-client-cw = { version = "0.51.0", path = "./ibc-clients/cosmwasm", default-features = false } | ||
ibc-app-transfer = { version = "0.51.0", path = "./ibc-apps/ics20-transfer", default-features = false } | ||
ibc-app-nft-transfer = { version = "0.51.0", path = "./ibc-apps/ics721-nft-transfer", default-features = false } | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
[package] | ||
name = "ibc-client-cw" | ||
authors = { workspace = true } | ||
edition = { workspace = true } | ||
license = { workspace = true } | ||
repository = { workspace = true } | ||
rust-version = { workspace = true } | ||
version = { workspace = true } | ||
keywords = ["ibc", "light-client", "CosmWasm"] | ||
readme = "./../README.md" | ||
description = """ | ||
This repository contains definitions and utilities that integrate a light client, | ||
built using ibc-rs, into CosmWasm. It functions as a library, allowing users to import | ||
a ready-made `Context` object that is generic across light clients. Users can then | ||
install their concrete client type and integrate it into the contract's entrypoint. | ||
""" | ||
|
||
[dependencies] | ||
# external dependencies | ||
derive_more = { workspace = true } | ||
prost = { workspace = true } | ||
serde = { workspace = true, features = ["derive"] } | ||
|
||
# ibc dependencies | ||
ibc-core = { workspace = true } | ||
ibc-client-wasm-types = { workspace = true, features = ["cosmwasm"] } | ||
|
||
# cosmwasm dependencies | ||
cosmwasm-schema = "1.4.1" | ||
cosmwasm-std = "1.4.1" | ||
|
||
[features] | ||
default = ["std"] | ||
std = [ | ||
"prost/std", | ||
"serde/std", | ||
"ibc-core/std", | ||
"ibc-client-wasm-types/std", | ||
] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
use ibc_core::client::context::client_state::ClientStateExecution; | ||
use ibc_core::client::context::consensus_state::ConsensusState as ConsensusStateTrait; | ||
use ibc_core::client::types::error::ClientError; | ||
use ibc_core::primitives::proto::Any; | ||
|
||
use crate::context::Context; | ||
|
||
/// Enables the introduction of custom client and consensus state types tailored | ||
/// for Sovereign light clients. | ||
pub trait ClientType<'a>: Sized { | ||
type ClientState: ClientStateExecution<Context<'a, Self>> + Clone; | ||
type ConsensusState: ConsensusStateTrait + Into<Any> + TryFrom<Any, Error = ClientError>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
use ibc_client_wasm_types::client_state::ClientState as WasmClientState; | ||
use ibc_client_wasm_types::consensus_state::ConsensusState as WasmConsensusState; | ||
use ibc_core::client::context::{ClientExecutionContext, ClientValidationContext}; | ||
use ibc_core::client::types::error::ClientError; | ||
use ibc_core::client::types::Height; | ||
use ibc_core::handler::types::error::ContextError; | ||
use ibc_core::host::types::identifiers::ClientId; | ||
use ibc_core::host::types::path::{iteration_key, ClientConsensusStatePath, ClientStatePath}; | ||
use ibc_core::primitives::proto::{Any, Protobuf}; | ||
use ibc_core::primitives::Timestamp; | ||
|
||
use super::Context; | ||
use crate::api::ClientType; | ||
use crate::utils::AnyCodec; | ||
|
||
impl<'a, C: ClientType<'a>> ClientValidationContext for Context<'a, C> { | ||
type ClientStateRef = C::ClientState; | ||
type ConsensusStateRef = C::ConsensusState; | ||
|
||
fn client_state(&self, _client_id: &ClientId) -> Result<Self::ClientStateRef, ContextError> { | ||
let client_state_value = self.retrieve(ClientStatePath::leaf())?; | ||
|
||
let any_wasm: WasmClientState = Protobuf::<Any>::decode(client_state_value.as_slice()) | ||
.map_err(|e| ClientError::Other { | ||
description: e.to_string(), | ||
})?; | ||
|
||
let sov_client_state = C::ClientState::decode_thru_any(any_wasm.data)?; | ||
|
||
Ok(sov_client_state) | ||
} | ||
|
||
fn consensus_state( | ||
&self, | ||
client_cons_state_path: &ClientConsensusStatePath, | ||
) -> Result<Self::ConsensusStateRef, ContextError> { | ||
let consensus_state_value = self.retrieve(client_cons_state_path.leaf())?; | ||
|
||
let any_wasm: WasmConsensusState = | ||
C::ConsensusState::decode_thru_any(consensus_state_value)?; | ||
|
||
let consensus_state = C::ConsensusState::decode_thru_any(any_wasm.data)?; | ||
|
||
Ok(consensus_state) | ||
} | ||
|
||
fn client_update_meta( | ||
&self, | ||
_client_id: &ClientId, | ||
height: &Height, | ||
) -> Result<(Timestamp, Height), ContextError> { | ||
let time_key = self.client_update_time_key(height); | ||
|
||
let time_vec = self.retrieve(time_key)?; | ||
|
||
let time = u64::from_be_bytes(time_vec.try_into().expect("invalid timestamp")); | ||
|
||
let timestamp = | ||
Timestamp::from_nanoseconds(time).map_err(ClientError::InvalidPacketTimestamp)?; | ||
|
||
let height_key = self.client_update_height_key(height); | ||
|
||
let revision_height_vec = self.retrieve(height_key)?; | ||
|
||
let revision_height = | ||
u64::from_be_bytes(revision_height_vec.try_into().expect("invalid height")); | ||
|
||
let height = Height::new(0, revision_height)?; | ||
|
||
Ok((timestamp, height)) | ||
} | ||
} | ||
|
||
impl<'a, C: ClientType<'a>> ClientExecutionContext for Context<'a, C> { | ||
type ClientStateMut = C::ClientState; | ||
|
||
fn store_client_state( | ||
&mut self, | ||
_client_state_path: ClientStatePath, | ||
client_state: Self::ClientStateMut, | ||
) -> Result<(), ContextError> { | ||
let prefixed_key = self.prefixed_key(ClientStatePath::leaf()); | ||
|
||
let encoded_client_state = self.encode_client_state(client_state)?; | ||
|
||
self.insert(prefixed_key, encoded_client_state); | ||
|
||
Ok(()) | ||
} | ||
|
||
fn store_consensus_state( | ||
&mut self, | ||
consensus_state_path: ClientConsensusStatePath, | ||
consensus_state: Self::ConsensusStateRef, | ||
) -> Result<(), ContextError> { | ||
let prefixed_key = self.prefixed_key(consensus_state_path.leaf()); | ||
|
||
let encoded_consensus_state = C::ConsensusState::encode_thru_any(consensus_state); | ||
|
||
let wasm_consensus_state = WasmConsensusState { | ||
data: encoded_consensus_state, | ||
}; | ||
|
||
let encoded_wasm_consensus_state = C::ConsensusState::encode_thru_any(wasm_consensus_state); | ||
|
||
self.insert(prefixed_key, encoded_wasm_consensus_state); | ||
|
||
Ok(()) | ||
} | ||
|
||
fn delete_consensus_state( | ||
&mut self, | ||
consensus_state_path: ClientConsensusStatePath, | ||
) -> Result<(), ContextError> { | ||
let prefixed_key = self.prefixed_key(consensus_state_path.leaf()); | ||
|
||
self.remove(prefixed_key); | ||
|
||
Ok(()) | ||
} | ||
|
||
fn store_update_meta( | ||
&mut self, | ||
_client_id: ClientId, | ||
height: Height, | ||
host_timestamp: Timestamp, | ||
host_height: Height, | ||
) -> Result<(), ContextError> { | ||
let time_key = self.client_update_time_key(&height); | ||
|
||
let prefixed_time_key = self.prefixed_key(time_key); | ||
|
||
let time_vec: [u8; 8] = host_timestamp.nanoseconds().to_be_bytes(); | ||
|
||
self.insert(prefixed_time_key, time_vec); | ||
|
||
let height_key = self.client_update_height_key(&height); | ||
|
||
let prefixed_height_key = self.prefixed_key(height_key); | ||
|
||
let revision_height_vec: [u8; 8] = host_height.revision_height().to_be_bytes(); | ||
|
||
self.insert(prefixed_height_key, revision_height_vec); | ||
|
||
let iteration_key = iteration_key(height.revision_number(), height.revision_height()); | ||
|
||
let height_vec = height.to_string().into_bytes(); | ||
|
||
self.insert(iteration_key, height_vec); | ||
|
||
Ok(()) | ||
} | ||
|
||
fn delete_update_meta( | ||
&mut self, | ||
_client_id: ClientId, | ||
height: Height, | ||
) -> Result<(), ContextError> { | ||
let time_key = self.client_update_time_key(&height); | ||
|
||
let prefixed_time_key = self.prefixed_key(time_key); | ||
|
||
self.remove(prefixed_time_key); | ||
|
||
let height_key = self.client_update_height_key(&height); | ||
|
||
let prefixed_height_key = self.prefixed_key(height_key); | ||
|
||
self.remove(prefixed_height_key); | ||
|
||
let iteration_key = iteration_key(height.revision_number(), height.revision_height()); | ||
|
||
self.remove(iteration_key); | ||
|
||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
use ibc_core::client::context::prelude::*; | ||
use ibc_core::client::types::Height; | ||
use ibc_core::handler::types::error::ContextError; | ||
use ibc_core::host::types::identifiers::ClientId; | ||
use ibc_core::host::types::path::ClientConsensusStatePath; | ||
use ibc_core::primitives::Timestamp; | ||
|
||
use super::Context; | ||
use crate::api::ClientType; | ||
use crate::types::HeightTravel; | ||
|
||
impl<'a, C: ClientType<'a>> ExtClientValidationContext for Context<'a, C> { | ||
fn host_timestamp(&self) -> Result<Timestamp, ContextError> { | ||
let time = self.env().block.time; | ||
|
||
let host_timestamp = Timestamp::from_nanoseconds(time.nanos()).expect("invalid timestamp"); | ||
|
||
Ok(host_timestamp) | ||
} | ||
|
||
fn host_height(&self) -> Result<Height, ContextError> { | ||
let host_height = Height::new(0, self.env().block.height)?; | ||
|
||
Ok(host_height) | ||
} | ||
|
||
fn consensus_state_heights(&self, _client_id: &ClientId) -> Result<Vec<Height>, ContextError> { | ||
let heights = self.get_heights()?; | ||
|
||
Ok(heights) | ||
} | ||
fn next_consensus_state( | ||
&self, | ||
client_id: &ClientId, | ||
height: &Height, | ||
) -> Result<Option<Self::ConsensusStateRef>, ContextError> { | ||
let next_height = self.get_adjacent_height(height, HeightTravel::Next)?; | ||
|
||
match next_height { | ||
Some(h) => { | ||
let cons_state_path = ClientConsensusStatePath::new( | ||
client_id.clone(), | ||
h.revision_number(), | ||
h.revision_height(), | ||
); | ||
self.consensus_state(&cons_state_path).map(Some) | ||
} | ||
None => Ok(None), | ||
} | ||
} | ||
|
||
fn prev_consensus_state( | ||
&self, | ||
client_id: &ClientId, | ||
height: &Height, | ||
) -> Result<Option<Self::ConsensusStateRef>, ContextError> { | ||
let prev_height = self.get_adjacent_height(height, HeightTravel::Prev)?; | ||
|
||
match prev_height { | ||
Some(prev_height) => { | ||
let cons_state_path = ClientConsensusStatePath::new( | ||
client_id.clone(), | ||
prev_height.revision_number(), | ||
prev_height.revision_height(), | ||
); | ||
self.consensus_state(&cons_state_path).map(Some) | ||
} | ||
None => Ok(None), | ||
} | ||
} | ||
} |
Oops, something went wrong.