diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 00000000..d4e1aba6 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,45 @@ +name: Rust +on: + pull_request: + paths: + - .github/workflows/rust.yaml + - contracts/** + - light-client/** + - "**/Cargo.toml" + - "**/Cargo.lock" + - justfile + - README.md + + push: + tags: + - v[0-9]+.* + branches: + - "release/*" + - main + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/') && github.ref != 'refs/heads/main' }} + +env: + CARGO_INCREMENTAL: 0 + CARGO_PROFILE_DEV_DEBUG: 1 + CARGO_PROFILE_RELEASE_DEBUG: 1 + CARGO_NET_RETRY: 10 + RUSTUP_MAX_RETRIES: 10 + +jobs: + build-cw-contract: + name: Build CosmWasm Light Client Contract + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@v4 + - name: Install Rust toolchains + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + target: wasm32-unknown-unknown + - name: Install Just + uses: extractions/setup-just@v1 + - name: Build CosmWasm light client contract + run: just build-cw-contract 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..93c68d8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -818,6 +818,7 @@ dependencies = [ "derive_more 0.99.18", "ibc-client-starknet-types", "ibc-core", + "serde", ] [[package]] @@ -833,6 +834,11 @@ dependencies = [ [[package]] name = "ibc-client-starknet-types" version = "0.1.0" +dependencies = [ + "derive_more 0.99.18", + "ibc-core", + "serde", +] [[package]] name = "ibc-client-wasm-types" diff --git a/Cargo.toml b/Cargo.toml index 4b4a1d8a..81e2e4d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ repository = "https://github.com/informalsystems/ibc-starknet" [workspace.lints.rust] unsafe_code = "forbid" dead_code = "deny" -rust_2018_idioms = "deny" +rust_2018_idioms = { level = "deny", priority = -1 } trivial_casts = "deny" unused_import_braces = "deny" unused_variables = "allow" @@ -34,6 +34,7 @@ uninlined_format_args = "deny" [workspace.dependencies] # external dependencies derive_more = { version = "0.99.11", features = [ "from", "try_into" ] } +serde = { version = "1.0.204", features = [ "derive" ] } # ibc depedenencies ibc-core = { version = "0.53.0", default-features = false, features = [ "borsh", "schema" ] } diff --git a/justfile b/justfile index a6dbbb39..c35e4549 100644 --- a/justfile +++ b/justfile @@ -3,6 +3,9 @@ install-tools: rustup component add rustfmt --toolchain nightly cargo install typos-cli taplo-cli +build-cw-contract: + cd ./light-client/cw-contract && cargo build --target wasm32-unknown-unknown + # Runs formatter and clippy for all the cargo and scarb packages lint: @cargo +nightly fmt --all -- --check && \ diff --git a/light-client/cw-contract/Cargo.toml b/light-client/cw-contract/Cargo.toml index 962f110e..b5f1a51b 100644 --- a/light-client/cw-contract/Cargo.toml +++ b/light-client/cw-contract/Cargo.toml @@ -1,15 +1,14 @@ [package] -name = "ibc-client-starknet-cw" -authors = { workspace = true } -edition = { workspace = true } -license = { workspace = true } -repository = { workspace = true } +name = "ibc-client-starknet-cw" +authors = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +repository = { workspace = true } rust-version = { workspace = true } -version = { workspace = true } -readme = "./../README.md" -keywords = [ "ibc", "light-client", "starknet", "CosmWasm" ] -description = """ -""" +version = { workspace = true } +readme = "./../README.md" +keywords = [ "ibc", "light-client", "starknet", "CosmWasm" ] +description = "" [lib] crate-type = [ "cdylib", "rlib" ] diff --git a/light-client/impls/Cargo.lock b/light-client/impls/Cargo.lock deleted file mode 100644 index 17afd10d..00000000 --- a/light-client/impls/Cargo.lock +++ /dev/null @@ -1,14 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "ibc-client-starknet" -version = "0.1.0" -dependencies = [ - "ibc-client-starknet-types", -] - -[[package]] -name = "ibc-client-starknet-types" -version = "0.1.0" diff --git a/light-client/impls/Cargo.toml b/light-client/impls/Cargo.toml index ac623464..b4f082e7 100644 --- a/light-client/impls/Cargo.toml +++ b/light-client/impls/Cargo.toml @@ -1,15 +1,14 @@ [package] -name = "ibc-client-starknet" -authors = { workspace = true } -edition = { workspace = true } -license = { workspace = true } +name = "ibc-client-starknet" +authors = { workspace = true } +edition = { workspace = true } +license = { workspace = true } rust-version = { workspace = true } -version = { workspace = true } -repository = { workspace = true } -readme = "./../README.md" -keywords = [ "starknet", "ibc", "light-client" ] -description = """ -""" +version = { workspace = true } +repository = { workspace = true } +readme = "./../README.md" +keywords = [ "starknet", "ibc", "light-client" ] +description = "" [lints] workspace = true @@ -17,6 +16,7 @@ workspace = true [dependencies] # external dependencies derive_more = { workspace = true } +serde = { workspace = true, optional = true } # ibc dependencies ibc-core = { workspace = true } @@ -28,3 +28,8 @@ std = [ "ibc-core/std", "ibc-client-starknet-types/std", ] +serde = [ + "dep:serde", + "ibc-core/serde", + "ibc-client-starknet-types/serde", +] diff --git a/light-client/impls/src/client_state/common.rs b/light-client/impls/src/client_state/common.rs index 052a9fdc..8a2ed8f3 100644 --- a/light-client/impls/src/client_state/common.rs +++ b/light-client/impls/src/client_state/common.rs @@ -12,19 +12,19 @@ use super::ClientState; impl ClientStateCommon for ClientState { fn verify_consensus_state(&self, consensus_state: Any) -> Result<(), ClientError> { - todo!() + Ok(()) } fn client_type(&self) -> ClientType { - todo!() + "blind-001".parse().unwrap() } - fn latest_height(&self) -> ibc_core::client::types::Height { - todo!() + fn latest_height(&self) -> Height { + self.0.latest_height } fn validate_proof_height(&self, proof_height: Height) -> Result<(), ClientError> { - todo!() + Ok(()) } fn verify_upgrade_client( @@ -35,11 +35,11 @@ impl ClientStateCommon for ClientState { proof_upgrade_consensus_state: CommitmentProofBytes, root: &CommitmentRoot, ) -> Result<(), ClientError> { - todo!() + Ok(()) } fn serialize_path(&self, path: Path) -> Result { - todo!() + Ok(path.to_string().as_bytes().to_vec().into()) } fn verify_membership_raw( @@ -50,7 +50,7 @@ impl ClientStateCommon for ClientState { path: PathBytes, value: Vec, ) -> Result<(), ClientError> { - todo!() + Ok(()) } fn verify_non_membership_raw( @@ -60,6 +60,6 @@ impl ClientStateCommon for ClientState { root: &CommitmentRoot, path: PathBytes, ) -> Result<(), ClientError> { - todo!() + Ok(()) } } diff --git a/light-client/impls/src/client_state/execution.rs b/light-client/impls/src/client_state/execution.rs index 65bd7df5..e3de32e9 100644 --- a/light-client/impls/src/client_state/execution.rs +++ b/light-client/impls/src/client_state/execution.rs @@ -1,15 +1,20 @@ +use ibc_client_starknet_types::ClientState as ClientStateType; 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 super::ClientState; +use crate::ConsensusState; impl ClientStateExecution for ClientState where - E: ClientExecutionContext, + E: ClientExecutionContext, { fn initialise( &self, @@ -17,7 +22,17 @@ where client_id: &ClientId, consensus_state: Any, ) -> Result<(), ClientError> { - todo!() + 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( @@ -26,7 +41,19 @@ where client_id: &ClientId, header: Any, ) -> Result, ClientError> { - todo!() + let latest_height = ctx.client_state(client_id)?.latest_height().increment(); + + let new_client_state = ClientStateType { latest_height }.into(); + + update_client_and_consensus_state( + ctx, + latest_height, + client_id, + new_client_state, + ConsensusState::default(), + )?; + + Ok(vec![latest_height]) } fn update_state_on_misbehaviour( @@ -35,7 +62,7 @@ where client_id: &ClientId, client_message: Any, ) -> Result<(), ClientError> { - todo!() + Ok(()) } fn update_state_on_upgrade( @@ -45,7 +72,17 @@ where upgraded_client_state: Any, upgraded_consensus_state: Any, ) -> Result { - todo!() + let latest_height = ctx.client_state(client_id)?.latest_height().increment(); + + 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( @@ -55,6 +92,45 @@ where substitute_client_state: Any, substitute_consensus_state: Any, ) -> Result<(), ClientError> { - todo!() + let latest_height = ctx + .client_state(subject_client_id)? + .latest_height() + .increment(); + + 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/light-client/impls/src/client_state/mod.rs b/light-client/impls/src/client_state/mod.rs index 5f198ab3..e40ece4a 100644 --- a/light-client/impls/src/client_state/mod.rs +++ b/light-client/impls/src/client_state/mod.rs @@ -2,13 +2,13 @@ pub mod common; pub mod execution; pub mod validation; -use ibc_client_starknet_types::ClientState as StarknetClientState; +use ibc_client_starknet_types::ClientState as ClientStateType; use ibc_core::client::types::error::ClientError; 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(StarknetClientState); +pub struct ClientState(pub ClientStateType); impl Protobuf for ClientState {} @@ -16,12 +16,12 @@ impl TryFrom for ClientState { type Error = ClientError; fn try_from(raw: Any) -> Result { - todo!() + ClientStateType::try_from(raw).map(Into::into) } } impl From for Any { fn from(client_state: ClientState) -> Self { - todo!() + client_state.0.into() } } diff --git a/light-client/impls/src/client_state/validation.rs b/light-client/impls/src/client_state/validation.rs index 8ad09cb1..572f387c 100644 --- a/light-client/impls/src/client_state/validation.rs +++ b/light-client/impls/src/client_state/validation.rs @@ -6,10 +6,11 @@ use ibc_core::host::types::identifiers::ClientId; use ibc_core::primitives::proto::Any; use super::ClientState; +use crate::ConsensusState; impl ClientStateValidation for ClientState where - V: ClientValidationContext, + V: ClientValidationContext, { fn verify_client_message( &self, @@ -17,7 +18,7 @@ where client_id: &ClientId, client_message: Any, ) -> Result<(), ClientError> { - todo!() + Ok(()) } fn check_for_misbehaviour( @@ -26,14 +27,14 @@ where client_id: &ClientId, client_message: Any, ) -> Result { - todo!() + Ok(false) } fn status(&self, ctx: &V, client_id: &ClientId) -> Result { - todo!() + Ok(Status::Active) } fn check_substitute(&self, ctx: &V, substitute_client_state: Any) -> Result<(), ClientError> { - todo!() + Ok(()) } } diff --git a/light-client/impls/src/consensus_state.rs b/light-client/impls/src/consensus_state.rs index afd61dba..24f2a13a 100644 --- a/light-client/impls/src/consensus_state.rs +++ b/light-client/impls/src/consensus_state.rs @@ -1,4 +1,4 @@ -use ibc_client_starknet_types::ConsensusState as StarknetConsensusState; +use ibc_client_starknet_types::ConsensusState as ConsensusStateType; use ibc_core::client::context::consensus_state::ConsensusState as ConsensusStateTrait; use ibc_core::client::types::error::ClientError; use ibc_core::commitment_types::commitment::CommitmentRoot; @@ -7,7 +7,13 @@ use ibc_core::primitives::Timestamp; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Debug, PartialEq, derive_more::From)] -pub struct ConsensusState(StarknetConsensusState); +pub struct ConsensusState(pub ConsensusStateType); + +impl Default for ConsensusState { + fn default() -> Self { + ConsensusStateType::default().into() + } +} impl Protobuf for ConsensusState {} @@ -15,22 +21,22 @@ impl TryFrom for ConsensusState { type Error = ClientError; fn try_from(raw: Any) -> Result { - todo!() + ConsensusStateType::try_from(raw).map(Into::into) } } impl From for Any { fn from(consensus_state: ConsensusState) -> Self { - todo!() + consensus_state.0.into() } } impl ConsensusStateTrait for ConsensusState { fn root(&self) -> &CommitmentRoot { - todo!() + self.0.root() } fn timestamp(&self) -> Timestamp { - todo!() + Timestamp::none() } } diff --git a/light-client/types/Cargo.toml b/light-client/types/Cargo.toml index 76960f74..126f9202 100644 --- a/light-client/types/Cargo.toml +++ b/light-client/types/Cargo.toml @@ -15,7 +15,14 @@ description = """ workspace = true [dependencies] +ibc-core = { workspace = true } +derive_more = { workspace = true } +serde = { workspace = true, optional = true } [features] default = [ "std" ] -std = [ ] +std = [ ] +serde = [ + "dep:serde", + "ibc-core/serde", +] diff --git a/light-client/types/src/client_state.rs b/light-client/types/src/client_state.rs index 438159c8..0afab710 100644 --- a/light-client/types/src/client_state.rs +++ b/light-client/types/src/client_state.rs @@ -1,2 +1,44 @@ -#[derive(Clone, Debug, PartialEq)] -pub struct ClientState {} +use ibc_core::client::types::error::ClientError; +use ibc_core::client::types::Height; +use ibc_core::primitives::proto::{Any, Protobuf}; + +pub const CLIENT_STATE_TYPE_URL: &str = "/StarknetClientState"; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, derive_more::From)] +pub struct ClientState { + pub latest_height: Height, +} + +impl Protobuf for ClientState {} + +impl TryFrom for ClientState { + type Error = ClientError; + + fn try_from(raw: Any) -> Result { + if raw.type_url != CLIENT_STATE_TYPE_URL { + return Err(ClientError::UnknownClientStateType { + client_state_type: raw.type_url, + }); + } + + 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: CLIENT_STATE_TYPE_URL.to_string(), + value: client_state + .latest_height + .revision_number() + .to_be_bytes() + .to_vec(), + } + } +} diff --git a/light-client/types/src/consensus_state.rs b/light-client/types/src/consensus_state.rs index f3b2dcc4..160eb7c6 100644 --- a/light-client/types/src/consensus_state.rs +++ b/light-client/types/src/consensus_state.rs @@ -1,2 +1,50 @@ -#[derive(Clone, Debug, PartialEq)] -pub struct ConsensusState {} +use ibc_core::client::types::error::ClientError; +use ibc_core::commitment_types::commitment::CommitmentRoot; +use ibc_core::primitives::proto::{Any, Protobuf}; + +pub const CONSENSUS_STATE_TYPE_URL: &str = "/StarknetConsensusState"; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, derive_more::From)] +pub struct ConsensusState { + root: CommitmentRoot, +} + +impl ConsensusState { + pub fn root(&self) -> &CommitmentRoot { + &self.root + } +} + +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 != CONSENSUS_STATE_TYPE_URL || !raw.value.is_empty() { + return Err(ClientError::UnknownConsensusStateType { + consensus_state_type: raw.type_url, + }); + } + + Ok(Self::default()) + } +} + +impl From for Any { + fn from(consensus_state: ConsensusState) -> Self { + Self { + type_url: CONSENSUS_STATE_TYPE_URL.to_string(), + value: vec![], + } + } +}