Skip to content

Commit

Permalink
implement ics008-wasm-client library (closes #515) (#581)
Browse files Browse the repository at this point in the history
Implements #515

The purpose of the library is gathering common things to wasm in a
single library and also restricting the contracts to supporting
predefined execute messages and queries since this is not ordinary
cosmwasm and contracts are only meant to implement specific endpoints.
  • Loading branch information
aeryz authored Sep 4, 2023
2 parents bfe9d1e + 337a43e commit 9b3c4bb
Show file tree
Hide file tree
Showing 25 changed files with 1,787 additions and 1,625 deletions.
29 changes: 15 additions & 14 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ members = [
"lib/*/fuzz",
"light-clients/ethereum-light-client",
"light-clients/cometbls-light-client",
"light-clients/wasm-light-client-types",
"generated/rust",
"generated/contracts",
"unionvisor",
Expand Down Expand Up @@ -45,7 +44,7 @@ contracts = { path = "generated/contracts", default-features = false }
serde-utils = { path = "lib/serde-utils", default-features = false }
ethereum-verifier = { path = "lib/ethereum-verifier", default-features = false }
cometbls-groth16-verifier = { path = "lib/cometbls-groth16-verifier", default-features = false }
wasm-light-client-types = { path = "light-clients/wasm-light-client-types", default-features = false }
ics008-wasm-client = { path = "lib/ics-008-wasm-client", default-features = false }
protos = { path = "generated/rust", default-features = false }
unionlabs = { path = "lib/unionlabs", default-features = false }
beacon-api = { path = "lib/beacon-api", default-features = false }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name = "wasm-light-client-types"
name = "ics008-wasm-client"
version = "0.1.0"
authors = ["Union Labs"]
edition = "2021"
Expand All @@ -13,3 +13,6 @@ prost = { version = "0.11", default-features = false }
protos = { workspace = true, default-features = false, features = ["proto_full", "std"] }
serde = { version = "1.0", default-features = false, features = ["derive"] }
unionlabs = { workspace = true, default-features = false }

[dev-dependencies]
serde_json = "1.0.0"
202 changes: 202 additions & 0 deletions lib/ics-008-wasm-client/src/ibc_client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
use core::fmt::Debug;

use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo};
use unionlabs::{
ibc::{
core::client::height::Height,
lightclients::wasm::{client_state::ClientState, consensus_state::ConsensusState},
},
Proto, TryFromProto, TryFromProtoBytesError, TryFromProtoErrorOf,
};

use crate::{
msg::{ClientMessage, ContractResult, ExecuteMsg, MerklePath, QueryMsg, QueryResponse},
Error,
};

pub enum StorageState {
Occupied(Vec<u8>),
Empty,
}

pub trait IbcClient {
type Error: From<TryFromProtoBytesError<TryFromProtoErrorOf<Self::Header>>> + From<Error>;
type CustomQuery: cosmwasm_std::CustomQuery;
// TODO(aeryz): see #583
type Header: TryFromProto;
// TODO(aeryz): see #583, #588
type Misbehaviour;
type ClientState: TryFromProto;
type ConsensusState: TryFromProto;

fn execute(
deps: DepsMut<Self::CustomQuery>,
env: Env,
_info: MessageInfo,
msg: ExecuteMsg,
) -> Result<ContractResult, Self::Error>
where
// NOTE(aeryz): unfortunately bounding to `Debug` in associated type creates a
// recursion in the compiler, see this issue: https://github.com/rust-lang/rust/issues/87755
<Self::ClientState as Proto>::Proto: prost::Message + Default,
TryFromProtoErrorOf<Self::ClientState>: Debug,
<Self::ConsensusState as Proto>::Proto: prost::Message + Default,
TryFromProtoErrorOf<Self::ConsensusState>: Debug,
{
match msg {
ExecuteMsg::VerifyMembership {
height,
delay_time_period,
delay_block_period,
proof,
path,
value,
} => Self::verify_membership(
deps.as_ref(),
height,
delay_time_period,
delay_block_period,
proof,
path,
StorageState::Occupied(value.0),
),
ExecuteMsg::VerifyNonMembership {
height,
delay_time_period,
delay_block_period,
proof,
path,
} => Self::verify_membership(
deps.as_ref(),
height,
delay_time_period,
delay_block_period,
proof,
path,
StorageState::Empty,
),
ExecuteMsg::VerifyClientMessage { client_message } => match client_message {
ClientMessage::Header(header) => {
let header = Self::Header::try_from_proto_bytes(&header.data)?;
Self::verify_header(deps.as_ref(), env, header)
}
ClientMessage::Misbehaviour(_misbehaviour) => {
Ok(ContractResult::invalid("Not implemented".to_string()))
}
},
ExecuteMsg::UpdateState { client_message } => match client_message {
ClientMessage::Header(header) => {
let header = Self::Header::try_from_proto_bytes(&header.data)?;
Self::update_state(deps, env, header)
}
ClientMessage::Misbehaviour(_) => Err(Error::UnexpectedCallDataFromHostModule(
"`UpdateState` cannot be called with `Misbehaviour`".to_string(),
)
.into()),
},
ExecuteMsg::UpdateStateOnMisbehaviour { client_message } => {
Self::update_state_on_misbehaviour(deps, client_message)
}
ExecuteMsg::CheckForMisbehaviour { client_message } => match client_message {
ClientMessage::Header(header) => {
let header = Self::Header::try_from_proto_bytes(&header.data)?;
Self::verify_header(deps.as_ref(), env, header)
}
ClientMessage::Misbehaviour(_) => {
Ok(ContractResult::invalid("Not implemented".to_string()))
}
},
ExecuteMsg::VerifyUpgradeAndUpdateState {
upgrade_client_state,
upgrade_consensus_state,
proof_upgrade_client,
proof_upgrade_consensus_state,
} => Self::verify_upgrade_and_update_state(
deps,
<_>::try_from_proto(upgrade_client_state)
.map_err(|err| Error::Decode(format!("{err:?}")))?,
<_>::try_from_proto(upgrade_consensus_state)
.map_err(|err| Error::Decode(format!("{err:?}")))?,
proof_upgrade_client,
proof_upgrade_consensus_state,
),
ExecuteMsg::CheckSubstituteAndUpdateState {} => {
Self::check_substitute_and_update_state(deps.as_ref())
}
}
}

fn query(
deps: Deps<Self::CustomQuery>,
env: Env,
msg: QueryMsg,
) -> Result<QueryResponse, Self::Error> {
match msg {
QueryMsg::Status {} => Self::status(deps, &env),
QueryMsg::ExportMetadata {} => Self::export_metadata(deps, &env),
}
}

#[allow(clippy::too_many_arguments)]
fn verify_membership(
deps: Deps<Self::CustomQuery>,
height: Height,
delay_time_period: u64,
delay_block_period: u64,
proof: Binary,
path: MerklePath,
value: StorageState,
) -> Result<ContractResult, Self::Error>;

fn verify_header(
deps: Deps<Self::CustomQuery>,
env: Env,
header: Self::Header,
) -> Result<ContractResult, Self::Error>;

fn verify_misbehaviour(
deps: Deps<Self::CustomQuery>,
misbehaviour: Self::Misbehaviour,
) -> Result<ContractResult, Self::Error>;

fn update_state(
deps: DepsMut<Self::CustomQuery>,
env: Env,
header: Self::Header,
) -> Result<ContractResult, Self::Error>;

// TODO(aeryz): make this client message generic over the underlying types
fn update_state_on_misbehaviour(
deps: DepsMut<Self::CustomQuery>,
client_message: ClientMessage,
) -> Result<ContractResult, Self::Error>;

fn check_for_misbehaviour_on_header(
deps: Deps<Self::CustomQuery>,
header: Self::Header,
) -> Result<ContractResult, Self::Error>;

fn check_for_misbehaviour_on_misbehaviour(
deps: Deps<Self::CustomQuery>,
misbehaviour: Self::Misbehaviour,
) -> Result<ContractResult, Self::Error>;

fn verify_upgrade_and_update_state(
deps: DepsMut<Self::CustomQuery>,
upgrade_client_state: ClientState<Self::ClientState>,
upgrade_consensus_state: ConsensusState<Self::ConsensusState>,
proof_upgrade_client: Binary,
proof_upgrade_consensus_state: Binary,
) -> Result<ContractResult, Self::Error>;

fn check_substitute_and_update_state(
deps: Deps<Self::CustomQuery>,
) -> Result<ContractResult, Self::Error>;

fn status(deps: Deps<Self::CustomQuery>, env: &Env) -> Result<QueryResponse, Self::Error>;

fn export_metadata(
deps: Deps<Self::CustomQuery>,
env: &Env,
) -> Result<QueryResponse, Self::Error>;
}
15 changes: 15 additions & 0 deletions lib/ics-008-wasm-client/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#![recursion_limit = "512"]

mod ibc_client;
mod msg;
pub mod storage_utils;

pub use ibc_client::*;
pub use msg::*;

#[derive(Debug)]
pub enum Error {
Decode(String),
UnexpectedCallDataFromHostModule(String),
ClientStateNotFound,
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ pub struct MerklePath {
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct ClientMessage {
pub header: Option<Header>,
pub misbehaviour: Option<Misbehaviour>,
#[serde(deny_unknown_fields, rename_all = "snake_case")]
pub enum ClientMessage {
Header(Header),
Misbehaviour(Misbehaviour),
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
Expand Down Expand Up @@ -96,18 +97,17 @@ pub enum ExecuteMsg {
},

CheckSubstituteAndUpdateState {},

ExportMetadata {},
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(deny_unknown_fields, rename_all = "snake_case")]
pub enum QueryMsg {
Status {},
ExportMetadata {},
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct StatusResponse {
pub struct QueryResponse {
pub status: String,
pub genesis_metadata: Vec<GenesisMetadata>,
}
Expand All @@ -129,11 +129,40 @@ impl Display for Status {
}
}

impl From<Status> for StatusResponse {
impl From<Status> for QueryResponse {
fn from(value: Status) -> Self {
StatusResponse {
QueryResponse {
status: value.to_string(),
genesis_metadata: Vec::new(),
}
}
}

#[cfg(test)]
mod tests {
use protos::ibc::lightclients::wasm::v1::Header;

use crate::{ClientMessage, ExecuteMsg};

#[test]
fn execute_msg_snake_case_encoded() {
let msg = ExecuteMsg::CheckSubstituteAndUpdateState {};
assert_eq!(
serde_json::to_string(&msg).unwrap(),
r#"{"check_substitute_and_update_state":{}}"#
)
}

#[test]
fn client_msg_snake_case_encoded() {
let msg = ClientMessage::Header(Header {
data: vec![],
height: None,
});

assert_eq!(
serde_json::to_string(&msg).unwrap(),
r#"{"header":{"data":"","height":null}}"#
)
}
}
Loading

0 comments on commit 9b3c4bb

Please sign in to comment.