From 007a241f1f9d09b2b2f7b2cde698ef542e9afe2f Mon Sep 17 00:00:00 2001 From: benluelo Date: Sun, 28 Apr 2024 21:18:38 -0400 Subject: [PATCH] feat(lightclients): clean up errors feat(lightclients): clean up more errors feat(lightclients): clean up more errors feat(lightclients): clean up more errors --- Cargo.lock | 26 +-- lib/chain-utils/src/ethereum.rs | 2 +- lib/ethereum-verifier/src/error.rs | 67 ++++--- lib/ethereum-verifier/src/verify.rs | 16 +- lib/ics-008-wasm-client/src/ibc_client.rs | 188 +++++++++--------- lib/ics-008-wasm-client/src/storage_utils.rs | 140 +++++++------ lib/scroll-codec/src/lib.rs | 2 +- lib/scroll-verifier/src/verify.rs | 25 +-- lib/unionlabs/src/cosmos/ics23/batch_entry.rs | 9 +- lib/unionlabs/src/cosmos/ics23/batch_proof.rs | 5 +- .../src/cosmos/ics23/commitment_proof.rs | 15 +- .../cosmos/ics23/compressed_batch_entry.rs | 9 +- .../cosmos/ics23/compressed_batch_proof.rs | 8 +- .../ics23/compressed_existence_proof.rs | 9 +- .../ics23/compressed_non_existence_proof.rs | 9 +- lib/unionlabs/src/cosmos/ics23/inner_spec.rs | 21 +- .../src/cosmos/ics23/non_existence_proof.rs | 5 +- lib/unionlabs/src/cosmos/ics23/proof_spec.rs | 11 +- .../src/cosmwasm/wasm/union/custom_query.rs | 2 +- lib/unionlabs/src/google/protobuf/any.rs | 42 +++- lib/unionlabs/src/google/protobuf/duration.rs | 7 +- .../src/google/protobuf/timestamp.rs | 8 +- .../src/ibc/core/commitment/merkle_proof.rs | 5 +- .../src/ibc/core/commitment/merkle_root.rs | 2 +- .../ibc/lightclients/cometbls/client_state.rs | 3 +- .../lightclients/cometbls/consensus_state.rs | 2 +- .../src/ibc/lightclients/cometbls/header.rs | 10 +- .../ibc/lightclients/cometbls/light_header.rs | 22 +- .../lightclients/ethereum/account_proof.rs | 2 +- .../lightclients/ethereum/account_update.rs | 2 +- .../ethereum/beacon_block_header.rs | 2 +- .../ibc/lightclients/ethereum/client_state.rs | 2 +- .../lightclients/ethereum/consensus_state.rs | 2 +- .../ethereum/execution_payload_header.rs | 2 +- .../src/ibc/lightclients/ethereum/fork.rs | 2 +- .../lightclients/ethereum/fork_parameters.rs | 2 +- .../src/ibc/lightclients/ethereum/header.rs | 2 +- .../ethereum/light_client_header.rs | 2 +- .../ethereum/light_client_update.rs | 30 +-- .../ibc/lightclients/ethereum/misbehaviour.rs | 2 +- .../src/ibc/lightclients/ethereum/proof.rs | 8 +- .../lightclients/ethereum/storage_proof.rs | 7 +- .../lightclients/ethereum/sync_aggregate.rs | 2 +- .../lightclients/ethereum/sync_committee.rs | 2 +- .../ethereum/trusted_sync_committee.rs | 3 +- .../ibc/lightclients/scroll/client_state.rs | 24 ++- .../lightclients/scroll/consensus_state.rs | 5 +- .../src/ibc/lightclients/scroll/header.rs | 2 +- .../lightclients/tendermint/client_state.rs | 18 +- .../tendermint/consensus_state.rs | 2 +- .../ibc/lightclients/tendermint/fraction.rs | 5 +- .../src/ibc/lightclients/tendermint/header.rs | 2 +- .../src/ibc/lightclients/wasm/client_state.rs | 10 + .../ibc/lightclients/wasm/consensus_state.rs | 11 +- lib/unionlabs/src/ics24.rs | 8 +- lib/unionlabs/src/lib.rs | 6 +- .../src/tendermint/crypto/public_key.rs | 2 +- .../src/tendermint/types/validator.rs | 2 +- .../src/tendermint/types/validator_set.rs | 2 +- .../cometbls-light-client/src/client.rs | 92 ++++----- .../cometbls-light-client/src/contract.rs | 14 +- .../cometbls-light-client/src/errors.rs | 155 ++------------- .../cometbls-light-client/src/zkp_verifier.rs | 6 +- .../ethereum-light-client/src/client.rs | 155 ++++++++------- .../ethereum-light-client/src/contract.rs | 8 +- .../ethereum-light-client/src/custom_query.rs | 17 +- .../ethereum-light-client/src/errors.rs | 67 ++----- .../scroll-light-client/src/client.rs | 91 ++++----- .../scroll-light-client/src/contract.rs | 12 +- .../scroll-light-client/src/errors.rs | 125 ++++++------ .../tendermint-light-client/src/client.rs | 106 +++++----- .../tendermint-light-client/src/contract.rs | 8 +- .../tendermint-light-client/src/errors.rs | 177 ++++------------- 73 files changed, 880 insertions(+), 996 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3ddd4e278f..61005c130c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2224,7 +2224,7 @@ dependencies = [ [[package]] name = "ethers" version = "2.0.10" -source = "git+https://github.com/unionlabs/ethers-rs?branch=ethers-core-wasm#dd64d33c3a9ad2eaad6f2806c88663ebbcab22c0" +source = "git+https://github.com/unionlabs/ethers-rs?branch=ethers-core-wasm#fd10abba9a9d5347b71bd4cde6418a6f6f2f1279" dependencies = [ "ethers-addressbook", "ethers-contract", @@ -2239,7 +2239,7 @@ dependencies = [ [[package]] name = "ethers-addressbook" version = "2.0.10" -source = "git+https://github.com/unionlabs/ethers-rs?branch=ethers-core-wasm#dd64d33c3a9ad2eaad6f2806c88663ebbcab22c0" +source = "git+https://github.com/unionlabs/ethers-rs?branch=ethers-core-wasm#fd10abba9a9d5347b71bd4cde6418a6f6f2f1279" dependencies = [ "ethers-core", "once_cell", @@ -2250,7 +2250,7 @@ dependencies = [ [[package]] name = "ethers-contract" version = "2.0.10" -source = "git+https://github.com/unionlabs/ethers-rs?branch=ethers-core-wasm#dd64d33c3a9ad2eaad6f2806c88663ebbcab22c0" +source = "git+https://github.com/unionlabs/ethers-rs?branch=ethers-core-wasm#fd10abba9a9d5347b71bd4cde6418a6f6f2f1279" dependencies = [ "const-hex", "ethers-contract-abigen", @@ -2268,7 +2268,7 @@ dependencies = [ [[package]] name = "ethers-contract-abigen" version = "2.0.10" -source = "git+https://github.com/unionlabs/ethers-rs?branch=ethers-core-wasm#dd64d33c3a9ad2eaad6f2806c88663ebbcab22c0" +source = "git+https://github.com/unionlabs/ethers-rs?branch=ethers-core-wasm#fd10abba9a9d5347b71bd4cde6418a6f6f2f1279" dependencies = [ "Inflector", "const-hex", @@ -2291,7 +2291,7 @@ dependencies = [ [[package]] name = "ethers-contract-derive" version = "2.0.10" -source = "git+https://github.com/unionlabs/ethers-rs?branch=ethers-core-wasm#dd64d33c3a9ad2eaad6f2806c88663ebbcab22c0" +source = "git+https://github.com/unionlabs/ethers-rs?branch=ethers-core-wasm#fd10abba9a9d5347b71bd4cde6418a6f6f2f1279" dependencies = [ "Inflector", "const-hex", @@ -2306,7 +2306,7 @@ dependencies = [ [[package]] name = "ethers-core" version = "2.0.10" -source = "git+https://github.com/unionlabs/ethers-rs?branch=ethers-core-wasm#dd64d33c3a9ad2eaad6f2806c88663ebbcab22c0" +source = "git+https://github.com/unionlabs/ethers-rs?branch=ethers-core-wasm#fd10abba9a9d5347b71bd4cde6418a6f6f2f1279" dependencies = [ "arrayvec", "bytes", @@ -2336,7 +2336,7 @@ dependencies = [ [[package]] name = "ethers-etherscan" version = "2.0.10" -source = "git+https://github.com/unionlabs/ethers-rs?branch=ethers-core-wasm#dd64d33c3a9ad2eaad6f2806c88663ebbcab22c0" +source = "git+https://github.com/unionlabs/ethers-rs?branch=ethers-core-wasm#fd10abba9a9d5347b71bd4cde6418a6f6f2f1279" dependencies = [ "ethers-core", "reqwest", @@ -2350,7 +2350,7 @@ dependencies = [ [[package]] name = "ethers-middleware" version = "2.0.10" -source = "git+https://github.com/unionlabs/ethers-rs?branch=ethers-core-wasm#dd64d33c3a9ad2eaad6f2806c88663ebbcab22c0" +source = "git+https://github.com/unionlabs/ethers-rs?branch=ethers-core-wasm#fd10abba9a9d5347b71bd4cde6418a6f6f2f1279" dependencies = [ "async-trait", "auto_impl", @@ -2376,7 +2376,7 @@ dependencies = [ [[package]] name = "ethers-providers" version = "2.0.10" -source = "git+https://github.com/unionlabs/ethers-rs?branch=ethers-core-wasm#dd64d33c3a9ad2eaad6f2806c88663ebbcab22c0" +source = "git+https://github.com/unionlabs/ethers-rs?branch=ethers-core-wasm#fd10abba9a9d5347b71bd4cde6418a6f6f2f1279" dependencies = [ "async-trait", "auto_impl", @@ -2413,7 +2413,7 @@ dependencies = [ [[package]] name = "ethers-signers" version = "2.0.10" -source = "git+https://github.com/unionlabs/ethers-rs?branch=ethers-core-wasm#dd64d33c3a9ad2eaad6f2806c88663ebbcab22c0" +source = "git+https://github.com/unionlabs/ethers-rs?branch=ethers-core-wasm#fd10abba9a9d5347b71bd4cde6418a6f6f2f1279" dependencies = [ "async-trait", "coins-bip32", @@ -2431,7 +2431,7 @@ dependencies = [ [[package]] name = "ethers-solc" version = "2.0.10" -source = "git+https://github.com/unionlabs/ethers-rs?branch=ethers-core-wasm#dd64d33c3a9ad2eaad6f2806c88663ebbcab22c0" +source = "git+https://github.com/unionlabs/ethers-rs?branch=ethers-core-wasm#fd10abba9a9d5347b71bd4cde6418a6f6f2f1279" dependencies = [ "cfg-if", "const-hex", @@ -4665,7 +4665,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" dependencies = [ "anyhow", - "itertools 0.11.0", + "itertools 0.10.5", "proc-macro2", "quote", "syn 2.0.56", @@ -7089,7 +7089,7 @@ checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ "cfg-if", "digest 0.10.7", - "rand 0.8.5", + "rand 0.4.6", "static_assertions", ] diff --git a/lib/chain-utils/src/ethereum.rs b/lib/chain-utils/src/ethereum.rs index 23e52bc941..d8a31e0c9a 100644 --- a/lib/chain-utils/src/ethereum.rs +++ b/lib/chain-utils/src/ethereum.rs @@ -11,7 +11,7 @@ use contracts::{ GetConnectionCall, GetConnectionReturn, GetConsensusStateCall, GetConsensusStateReturn, GetHashedPacketAcknowledgementCommitmentCall, GetHashedPacketAcknowledgementCommitmentReturn, GetHashedPacketCommitmentCall, - GetHashedPacketCommitmentReturn, HasPacketReceiptCall, HasPacketReceiptReturn, IBCHandler, + GetHashedPacketCommitmentReturn, HasPacketReceiptCall, IBCHandler, IbcCoreConnectionV1ConnectionEndData, NextClientSequenceCall, NextConnectionSequenceCall, OwnershipTransferredFilter, }, diff --git a/lib/ethereum-verifier/src/error.rs b/lib/ethereum-verifier/src/error.rs index fe44a210a1..33051f0737 100644 --- a/lib/ethereum-verifier/src/error.rs +++ b/lib/ethereum-verifier/src/error.rs @@ -1,8 +1,16 @@ use milagro_bls::AmclError; use trie_db::TrieError; -use unionlabs::{bls::BlsPublicKey, hash::H256}; +use unionlabs::{ + bls::{BlsPublicKey, BlsSignature}, + hash::H256, +}; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone, thiserror::Error)] +#[error("invalid merkle branch \ + (leaf: {leaf}, branch: [{branch}], \ + depth: {depth}, index: {index}, root: {root})", + branch = .branch.iter().map(ToString::to_string).collect::>().join(", ") +)] pub struct InvalidMerkleBranch { pub leaf: H256, pub branch: Vec, @@ -11,11 +19,19 @@ pub struct InvalidMerkleBranch { pub root: H256, } -#[derive(Debug, PartialEq, thiserror::Error)] +#[derive(Debug, PartialEq, thiserror::Error, Clone)] +#[error("signature cannot be verified (public_keys: {public_keys:?}, msg: {msg}, signature: {signature})", msg = serde_utils::to_hex(.msg))] +pub struct InvalidSignature { + pub public_keys: Vec, + pub msg: Vec, + pub signature: BlsSignature, +} + +#[derive(Debug, PartialEq, thiserror::Error, Clone)] pub enum Error { - #[error("invalid merkle branch ({0:?})")] - InvalidMerkleBranch(InvalidMerkleBranch), - #[error("invalid chain conversion")] + #[error(transparent)] + InvalidMerkleBranch(#[from] InvalidMerkleBranch), + #[error("invalid chain version")] InvalidChainVersion, #[error("crypto error")] Crypto, @@ -48,54 +64,47 @@ pub enum Error { stored_period: u64, }, #[error( - "next sync committee ({got}) does not match with the one in the current state ({expected})" + "next sync committee ({found}) does not match with the one in the current state ({expected})" )] NextSyncCommitteeMismatch { expected: BlsPublicKey, - got: BlsPublicKey, + found: BlsPublicKey, }, #[error("insufficient number of sync committee participants ({0})")] InsufficientSyncCommitteeParticipants(usize), #[error("bls error ({0:?})")] Bls(AmclError), - #[error("proof is invalid due to value mismatch expected: {e}, actual: {a}", e = serde_utils::to_hex(expected), a = serde_utils::to_hex(actual))] + #[error( + "proof is invalid due to value mismatch, expected: {expected}, actual: {actual}", + expected = serde_utils::to_hex(expected), + actual = serde_utils::to_hex(actual) + )] ValueMismatch { expected: Vec, actual: Vec }, #[error("proof is invalid due to missing value: {v}", v = serde_utils::to_hex(value))] ValueMissing { value: Vec }, #[error("trie error ({0:?})")] Trie(Box>), #[error("rlp decoding failed ({0:?})")] - RlpDecode(rlp::DecoderError), - #[error("custom query error: ({0})")] - CustomError(String), - #[error("update header contains deneb specific informations")] + RlpDecode(#[from] rlp::DecoderError), + #[error("custom query error")] + CustomQuery(#[from] unionlabs::cosmwasm::wasm::union::custom_query::Error), + // boxed as this variant is significantly larger than the rest of the variants (due to the BlsSignature contained within) + #[error(transparent)] + InvalidSignature(Box), + #[error("update header contains deneb specific information")] MustBeDeneb, #[error("finalized slot cannot be the genesis slot")] FinalizedSlotIsGenesis, } -#[derive(Debug, thiserror::Error, PartialEq)] -#[error("verify storage absence error: {0}")] -pub struct VerifyStorageAbsenceError(#[from] pub Error); - -#[derive(Debug, thiserror::Error, PartialEq)] -#[error("validate light client error: {0}")] -pub struct ValidateLightClientError(#[from] pub Error); - -#[derive(Debug, thiserror::Error, PartialEq)] -#[error("verify account storage root error: {0}")] -pub struct VerifyAccountStorageRootError(#[from] pub Error); - -#[derive(Debug, thiserror::Error, PartialEq)] -#[error("verify storage proof error: {0}")] -pub struct VerifyStorageProofError(#[from] pub Error); - +// NOTE: Implemented here instead of via #[from] since AmclError doesn't implement std::error::Error impl From for Error { fn from(e: AmclError) -> Self { Error::Bls(e) } } +// NOTE: Implemented here instead of via #[from] since Box> doesn't implement std::error::Error impl From>> for Error { fn from(e: Box>) -> Self { Error::Trie(e) diff --git a/lib/ethereum-verifier/src/verify.rs b/lib/ethereum-verifier/src/verify.rs index 48331a93b5..2d2d73c52e 100644 --- a/lib/ethereum-verifier/src/verify.rs +++ b/lib/ethereum-verifier/src/verify.rs @@ -31,8 +31,7 @@ use crate::{ compute_domain, compute_epoch_at_slot, compute_fork_version, compute_signing_root, compute_sync_committee_period_at_slot, validate_merkle_branch, }, - Error, LightClientContext, ValidateLightClientError, VerifyAccountStorageRootError, - VerifyStorageAbsenceError, VerifyStorageProofError, + Error, LightClientContext, }; type MinSyncCommitteeParticipants = @@ -71,7 +70,7 @@ pub fn validate_light_client_update( current_slot: u64, genesis_validators_root: H256, bls_verifier: V, -) -> Result<(), ValidateLightClientError> { +) -> Result<(), Error> { // Verify sync committee has sufficient participants let sync_aggregate = &update.sync_aggregate; ensure( @@ -171,7 +170,7 @@ pub fn validate_light_client_update( next_sync_committee == stored_next_sync_committee, Error::NextSyncCommitteeMismatch { expected: stored_next_sync_committee.aggregate_pubkey, - got: next_sync_committee.aggregate_pubkey, + found: next_sync_committee.aggregate_pubkey, }, )?; } @@ -255,7 +254,7 @@ pub fn verify_account_storage_root( address: &H160, proof: impl IntoIterator>, storage_root: &H256, -) -> Result<(), VerifyAccountStorageRootError> { +) -> Result<(), Error> { match get_node(root, address.as_ref(), proof)? { Some(account) => { let account = rlp::decode::(account.as_ref()).map_err(Error::RlpDecode)?; @@ -287,7 +286,7 @@ pub fn verify_storage_proof( key: U256, expected_value: &[u8], proof: impl IntoIterator>, -) -> Result<(), VerifyStorageProofError> { +) -> Result<(), Error> { match get_node(root, key.to_be_bytes(), proof)? { Some(value) if value == expected_value => Ok(()), Some(value) => Err(Error::ValueMismatch { @@ -311,7 +310,7 @@ pub fn verify_storage_absence( root: H256, key: U256, proof: impl IntoIterator>, -) -> Result { +) -> Result { Ok(get_node(root, key.to_be_bytes(), proof)?.is_none()) } @@ -506,7 +505,6 @@ mod tests { INITIAL_DATA.genesis_validators_root, BlsVerifier, ) - .map_err(|e| e.0) } #[test] @@ -770,7 +768,7 @@ mod tests { proof_value.as_ref(), VALID_PROOF.storage_proof.proof.iter() ), - Err(VerifyStorageProofError(Error::ValueMismatch { .. })) + Err(Error::ValueMismatch { .. }) )); } diff --git a/lib/ics-008-wasm-client/src/ibc_client.rs b/lib/ics-008-wasm-client/src/ibc_client.rs index 0b2f8b96a0..a374f97ac3 100644 --- a/lib/ics-008-wasm-client/src/ibc_client.rs +++ b/lib/ics-008-wasm-client/src/ibc_client.rs @@ -1,12 +1,16 @@ use core::fmt::Debug; use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, StdError}; -use frame_support_procedural::DebugNoBound; +use frame_support_procedural::{DebugNoBound, PartialEqNoBound}; use unionlabs::{ - encoding::{Decode, Encoding}, - ibc::core::{ - client::{genesis_metadata::GenesisMetadata, height::Height}, - commitment::merkle_path::MerklePath, + encoding::{Decode, DecodeAs, DecodeErrorOf, Encode, Encoding, Proto}, + google::protobuf::any::Any, + ibc::{ + core::{ + client::{genesis_metadata::GenesisMetadata, height::Height}, + commitment::merkle_path::MerklePath, + }, + lightclients::wasm, }, }; @@ -33,15 +37,26 @@ pub enum StorageState { Empty, } -#[derive(DebugNoBound)] +#[derive(DebugNoBound, PartialEqNoBound, thiserror::Error)] pub enum DecodeError { - Header(>::Error), - Misbehaviour(>::Error), - ClientState(>::Error), - ConsensusState(>::Error), + #[error("unable to decode header")] + Header(DecodeErrorOf), + #[error("unable to decode misbehaviour")] + Misbehaviour(DecodeErrorOf), + #[error("unable to decode client state")] + ClientState(DecodeErrorOf), + #[error("unable to decode consensus state")] + ConsensusState(DecodeErrorOf), + + #[error("unable to decode protobuf encoded `Any>` client state")] + AnyWasmClientState(DecodeErrorOf>>), + #[error("unable to decode protobuf encoded `Any>` consensus state")] + AnyWasmConsensusState( + DecodeErrorOf>>, + ), } -#[derive(thiserror::Error, DebugNoBound)] +#[derive(DebugNoBound, PartialEqNoBound, thiserror::Error)] pub enum IbcClientError { #[error("decode error ({0:?})")] Decode(#[from] DecodeError), @@ -58,20 +73,25 @@ pub enum IbcClientError { } pub trait IbcClient: Sized { - type Error: std::error::Error; + type Error: std::error::Error + PartialEq + Into>; type CustomQuery: cosmwasm_std::CustomQuery; - type Header: Decode + Debug; - type Misbehaviour: Decode + Debug; - type ClientState: Decode + Debug; - type ConsensusState: Decode + Debug; + type Header: Decode + Debug; + type Misbehaviour: Decode + Debug; + type ClientState: Decode + + Decode + + Encode + + Debug; + type ConsensusState: Decode + + Decode + + Encode + + Debug; type Encoding: Encoding; fn sudo( deps: DepsMut, env: Env, msg: SudoMsg, - ) -> Result> -where { + ) -> Result> { match msg { SudoMsg::VerifyMembership { height, @@ -80,43 +100,34 @@ where { proof, path, value, - } => to_json_binary( - &Self::verify_membership( - deps.as_ref(), - height, - delay_time_period, - delay_block_period, - proof.into(), - path, - StorageState::Occupied(value.0), - ) - .map_err(IbcClientError::ClientSpecific)?, - ), + } => to_json_binary(&Self::verify_membership( + deps.as_ref(), + height, + delay_time_period, + delay_block_period, + proof.into(), + path, + StorageState::Occupied(value.0), + )?), SudoMsg::VerifyNonMembership { height, delay_time_period, delay_block_period, proof, path, - } => to_json_binary( - &Self::verify_membership( - deps.as_ref(), - height, - delay_time_period, - delay_block_period, - proof.into(), - path, - StorageState::Empty, - ) - .map_err(IbcClientError::ClientSpecific)?, - ), + } => to_json_binary(&Self::verify_membership( + deps.as_ref(), + height, + delay_time_period, + delay_block_period, + proof.into(), + path, + StorageState::Empty, + )?), SudoMsg::UpdateState { client_message } => { - if let Ok(header) = - >::decode(&client_message.0) - { + if let Ok(header) = Self::Header::decode_as::(&client_message.0) { to_json_binary(&UpdateStateResult { - heights: Self::update_state(deps, env, header) - .map_err(IbcClientError::ClientSpecific)?, + heights: Self::update_state(deps, env, header)?, }) } else { return Err(IbcClientError::UnexpectedCallDataFromHostModule( @@ -125,8 +136,7 @@ where { } } SudoMsg::UpdateStateOnMisbehaviour { client_message } => { - Self::update_state_on_misbehaviour(deps, env, client_message.0) - .map_err(IbcClientError::ClientSpecific)?; + Self::update_state_on_misbehaviour(deps, env, client_message.0)?; to_json_binary(&EmptyResult {}) } SudoMsg::VerifyUpgradeAndUpdateState { @@ -137,23 +147,20 @@ where { } => { Self::verify_upgrade_and_update_state( deps, - >::decode( - upgrade_client_state.as_slice(), - ) - .map_err(DecodeError::ClientState)?, - >::decode( + Self::ClientState::decode_as::(upgrade_client_state.as_slice()) + .map_err(DecodeError::ClientState)?, + Self::ConsensusState::decode_as::( upgrade_consensus_state.as_slice(), ) .map_err(DecodeError::ConsensusState)?, proof_upgrade_client.into(), proof_upgrade_consensus_state.into(), - ) - .map_err(IbcClientError::ClientSpecific)?; + )?; to_json_binary(&EmptyResult {}) } SudoMsg::MigrateClientStore {} => { - Self::migrate_client_store(deps).map_err(IbcClientError::ClientSpecific)?; + Self::migrate_client_store(deps)?; to_json_binary(&EmptyResult {}) } } @@ -166,57 +173,43 @@ where { msg: QueryMsg, ) -> Result> { match msg { - QueryMsg::Status {} => to_json_binary(&Into::::into( - Self::status(deps, &env).map_err(IbcClientError::ClientSpecific)?, - )), + QueryMsg::Status {} => { + to_json_binary(&Into::::into(Self::status(deps, &env)?)) + } QueryMsg::ExportMetadata {} => to_json_binary(&ExportMetadataResult { - genesis_metadata: Self::export_metadata(deps, &env) - .map_err(IbcClientError::ClientSpecific)?, + genesis_metadata: Self::export_metadata(deps, &env)?, }), QueryMsg::VerifyClientMessage { client_message } => { - if let Ok(header) = - >::decode(&client_message.0) - { - to_json_binary( - &Self::verify_header(deps, env, header) - .map_err(IbcClientError::ClientSpecific)?, - ) + if let Ok(header) = Self::Header::decode_as::(&client_message.0) { + to_json_binary(&Self::verify_header(deps, env, header)?) } else if let Ok(misbehaviour) = - >::decode(&client_message.0) + Self::Misbehaviour::decode_as::(&client_message.0) { - to_json_binary( - &Self::verify_misbehaviour(deps, env, misbehaviour) - .map_err(IbcClientError::ClientSpecific)?, - ) + to_json_binary(&Self::verify_misbehaviour(deps, env, misbehaviour)?) } else { return Err(IbcClientError::InvalidClientMessage(client_message.0)); } } QueryMsg::CheckForMisbehaviour { client_message } => { - if let Ok(header) = - >::decode(&client_message.0) - { + if let Ok(header) = Self::Header::decode_as::(&client_message.0) { to_json_binary(&CheckForMisbehaviourResult { - found_misbehaviour: Self::check_for_misbehaviour_on_header(deps, header) - .map_err(IbcClientError::ClientSpecific)?, + found_misbehaviour: Self::check_for_misbehaviour_on_header(deps, header)?, }) } else if let Ok(misbehaviour) = - >::decode(&client_message.0) + Self::Misbehaviour::decode_as::(&client_message.0) { to_json_binary(&CheckForMisbehaviourResult { found_misbehaviour: Self::check_for_misbehaviour_on_misbehaviour( deps, misbehaviour, - ) - .map_err(IbcClientError::ClientSpecific)?, + )?, }) } else { return Err(IbcClientError::InvalidClientMessage(client_message.0)); } } QueryMsg::TimestampAtHeight { height } => to_json_binary(&TimestampAtHeightResult { - timestamp: Self::timestamp_at_height(deps, height) - .map_err(IbcClientError::ClientSpecific)?, + timestamp: Self::timestamp_at_height(deps, height)?, }), } .map_err(Into::into) @@ -231,43 +224,44 @@ where { proof: Vec, path: MerklePath, value: StorageState, - ) -> Result<(), Self::Error>; + ) -> Result<(), IbcClientError>; fn verify_header( deps: Deps, env: Env, header: Self::Header, - ) -> Result<(), Self::Error>; + ) -> Result<(), IbcClientError>; fn verify_misbehaviour( deps: Deps, env: Env, misbehaviour: Self::Misbehaviour, - ) -> Result<(), Self::Error>; + ) -> Result<(), IbcClientError>; fn update_state( deps: DepsMut, env: Env, header: Self::Header, - ) -> Result, Self::Error>; + ) -> Result, IbcClientError>; /// `client_message` is being left without decoding because it could be either `Header` /// or `Misbehaviour` and it is generally not being used. + // TODO: Use an enum generic over Header/ Misbehaviour fn update_state_on_misbehaviour( deps: DepsMut, env: Env, client_message: Vec, - ) -> Result<(), Self::Error>; + ) -> Result<(), IbcClientError>; fn check_for_misbehaviour_on_header( deps: Deps, header: Self::Header, - ) -> Result; + ) -> Result>; fn check_for_misbehaviour_on_misbehaviour( deps: Deps, misbehaviour: Self::Misbehaviour, - ) -> Result; + ) -> Result>; fn verify_upgrade_and_update_state( deps: DepsMut, @@ -275,19 +269,21 @@ where { upgrade_consensus_state: Self::ConsensusState, proof_upgrade_client: Vec, proof_upgrade_consensus_state: Vec, - ) -> Result<(), Self::Error>; + ) -> Result<(), IbcClientError>; - fn migrate_client_store(deps: DepsMut) -> Result<(), Self::Error>; + fn migrate_client_store(deps: DepsMut) -> Result<(), IbcClientError>; - fn status(deps: Deps, env: &Env) -> Result; + fn status(deps: Deps, env: &Env) -> Result>; fn export_metadata( deps: Deps, env: &Env, - ) -> Result, Self::Error>; + ) -> Result, IbcClientError>; fn timestamp_at_height( deps: Deps, height: Height, - ) -> Result; + ) -> Result>; } + +pub type CustomQueryOf = ::CustomQuery; diff --git a/lib/ics-008-wasm-client/src/storage_utils.rs b/lib/ics-008-wasm-client/src/storage_utils.rs index a9bfce825b..d694bc615c 100644 --- a/lib/ics-008-wasm-client/src/storage_utils.rs +++ b/lib/ics-008-wasm-client/src/storage_utils.rs @@ -1,6 +1,6 @@ use core::fmt::Debug; -use cosmwasm_std::{CustomQuery, Deps, DepsMut}; +use cosmwasm_std::{Deps, DepsMut}; use prost::Message; use protos::{ google::protobuf::Any as ProtoAny, @@ -9,11 +9,13 @@ use protos::{ }, }; use unionlabs::{ - encoding::{Decode, Encode, Proto}, + encoding::{Decode, Encode, EncodeAs, Proto}, google::protobuf::any::Any, ibc::{core::client::height::Height, lightclients::wasm}, }; +use crate::{DecodeError, IbcClient, IbcClientError}; + #[derive(thiserror::Error, Debug)] pub enum Error { #[error("client state not found")] @@ -46,13 +48,14 @@ pub fn consensus_db_key(height: &Height) -> String { /// - code_id: Code ID of this contract's code /// - latest_height: Latest height that the state is updated /// - data: Contract defined raw bytes, which we use as protobuf encoded concrete client state. -pub fn read_client_state( - deps: Deps, -) -> Result, Error> +pub fn read_client_state( + deps: Deps, +) -> Result, IbcClientError> where - Any>: Decode, + T: IbcClient, + Any>: Decode, { - read_prefixed_client_state(deps, "") + read_prefixed_client_state::(deps, "") } /// Reads the consensus state at a specific height from the host. @@ -63,19 +66,20 @@ where /// - value: (PROTO_ENCODED_WASM_CLIENT_STATE) /// - timestamp: Time of this consensus state. /// - data: Contract defined raw bytes, which we use as protobuf encoded concrete consensus state. -pub fn read_consensus_state( - deps: Deps, +pub fn read_consensus_state( + deps: Deps, height: &Height, -) -> Result>, Error> +) -> Result>, IbcClientError> where - Any>: Decode, + Any>: Decode, + T: IbcClient, { - read_prefixed_consensus_state(deps, height, "") + read_prefixed_consensus_state::(deps, height, "") } -pub fn save_client_state>( - deps: DepsMut, - wasm_client_state: wasm::client_state::ClientState, +pub fn save_client_state( + deps: DepsMut, + wasm_client_state: wasm::client_state::ClientState, ) { let any_state = Any(wasm_client_state); deps.storage.set( @@ -84,8 +88,8 @@ pub fn save_client_state>( ); } -pub fn save_proto_client_state( - deps: DepsMut, +pub fn save_proto_client_state( + deps: DepsMut, proto_wasm_state: ProtoClientState, ) { let any_state = ProtoAny { @@ -100,9 +104,9 @@ pub fn save_proto_client_state( } /// Update the client state on the host store. -pub fn update_client_state>( - deps: DepsMut, - mut wasm_client_state: wasm::client_state::ClientState, +pub fn update_client_state( + deps: DepsMut, + mut wasm_client_state: wasm::client_state::ClientState, latest_height: u64, ) { wasm_client_state.latest_height = Height { @@ -110,12 +114,12 @@ pub fn update_client_state>( revision_height: latest_height, }; - save_client_state(deps, wasm_client_state); + save_client_state::(deps, wasm_client_state); } -pub fn save_consensus_state>( - deps: DepsMut, - wasm_consensus_state: wasm::consensus_state::ConsensusState, +pub fn save_consensus_state( + deps: DepsMut, + wasm_consensus_state: wasm::consensus_state::ConsensusState, height: &Height, ) { deps.storage.set( @@ -124,8 +128,8 @@ pub fn save_consensus_state>( ); } -pub fn save_proto_consensus_state( - deps: DepsMut, +pub fn save_proto_consensus_state( + deps: DepsMut, proto_wasm_state: ProtoConsensusState, height: &Height, ) { @@ -141,29 +145,33 @@ pub fn save_proto_consensus_state( } /// Reads the client state from the subject's (this client) store -pub fn read_subject_client_state( - deps: Deps, -) -> Result, Error> +pub fn read_subject_client_state( + deps: Deps, +) -> Result, IbcClientError> where - Any>: Decode, + Any>: Decode, + T: IbcClient, { - read_prefixed_client_state(deps, SUBJECT_CLIENT_STORE_PREFIX) + read_prefixed_client_state::(deps, SUBJECT_CLIENT_STORE_PREFIX) } /// Reads the client state from the substitute's (other client) store -pub fn read_substitute_client_state( - deps: Deps, -) -> Result, Error> +pub fn read_substitute_client_state( + deps: Deps, +) -> Result, IbcClientError> where - Any>: Decode, + Any>: Decode, + T: IbcClient, { - read_prefixed_client_state(deps, SUBSTITUTE_CLIENT_STORE_PREFIX) + read_prefixed_client_state::(deps, SUBSTITUTE_CLIENT_STORE_PREFIX) } -pub fn save_subject_client_state>( - deps: DepsMut, - wasm_client_state: wasm::client_state::ClientState, -) { +pub fn save_subject_client_state( + deps: DepsMut, + wasm_client_state: wasm::client_state::ClientState, +) where + T: IbcClient, +{ let any_state = Any(wasm_client_state); deps.storage.set( format!("{SUBJECT_CLIENT_STORE_PREFIX}{HOST_CLIENT_STATE_KEY}").as_bytes(), @@ -171,65 +179,69 @@ pub fn save_subject_client_state>( ); } -pub fn read_subject_consensus_state( - deps: Deps, +pub fn read_subject_consensus_state( + deps: Deps, height: &Height, -) -> Result>, Error> +) -> Result>, IbcClientError> where - Any>: Decode, + T: IbcClient, { read_prefixed_consensus_state(deps, height, SUBJECT_CLIENT_STORE_PREFIX) } -pub fn read_substitute_consensus_state( - deps: Deps, +pub fn read_substitute_consensus_state( + deps: Deps, height: &Height, -) -> Result>, Error> +) -> Result>, IbcClientError> where - Any>: Decode, + T: IbcClient, { read_prefixed_consensus_state(deps, height, SUBSTITUTE_CLIENT_STORE_PREFIX) } -pub fn save_subject_consensus_state>( - deps: DepsMut, - wasm_consensus_state: wasm::consensus_state::ConsensusState, +pub fn save_subject_consensus_state( + deps: DepsMut, + wasm_consensus_state: wasm::consensus_state::ConsensusState, height: &Height, -) { +) where + T: IbcClient, +{ deps.storage.set( format!("{SUBJECT_CLIENT_STORE_PREFIX}{}", consensus_db_key(height)).as_bytes(), - &Any(wasm_consensus_state).encode(), + &Any(wasm_consensus_state).encode_as::(), ); } -fn read_prefixed_client_state( - deps: Deps, +fn read_prefixed_client_state( + deps: Deps, prefix: &str, -) -> Result, Error> +) -> Result, IbcClientError> where - Any>: Decode, + T: IbcClient, { let any_state = deps .storage .get(format!("{prefix}{HOST_CLIENT_STATE_KEY}").as_bytes()) - .ok_or(Error::ClientStateNotFound)?; + .ok_or(IbcClientError::::ClientStateNotFound)?; Any::decode(any_state.as_slice()) .map(|any| any.0) - .map_err(|_| Error::ClientStateDecode) + .map_err(DecodeError::AnyWasmClientState) + .map_err(IbcClientError::::Decode) } -fn read_prefixed_consensus_state( - deps: Deps, +fn read_prefixed_consensus_state( + deps: Deps, height: &Height, prefix: &str, -) -> Result>, Error> +) -> Result>, IbcClientError> where - Any>: Decode, + T: IbcClient, { deps.storage .get(format!("{prefix}{}", consensus_db_key(height)).as_bytes()) .map(|bytes| Any::decode(&bytes).map(|any| any.0)) .transpose() - .map_err(|_| Error::ConsensusStateDecode) + .map_err(DecodeError::AnyWasmConsensusState) + .map_err(IbcClientError::::Decode) } diff --git a/lib/scroll-codec/src/lib.rs b/lib/scroll-codec/src/lib.rs index b633d260bb..9bb59712c2 100644 --- a/lib/scroll-codec/src/lib.rs +++ b/lib/scroll-codec/src/lib.rs @@ -130,7 +130,7 @@ pub fn commit_batch( } } -#[derive(Debug, thiserror::Error)] +#[derive(Debug, PartialEq, thiserror::Error)] pub enum CommitBatchError { #[error("error decoding parent batch header")] ParentBatchHeaderDecode(#[from] BatchHeaderDecodeError), diff --git a/lib/scroll-verifier/src/verify.rs b/lib/scroll-verifier/src/verify.rs index 7a471244b5..1b897e8bc9 100644 --- a/lib/scroll-verifier/src/verify.rs +++ b/lib/scroll-verifier/src/verify.rs @@ -1,9 +1,6 @@ use core::fmt::Debug; -use ethereum_verifier::{ - verify_account_storage_root, verify_storage_proof, VerifyAccountStorageRootError, - VerifyStorageProofError, -}; +use ethereum_verifier::{verify_account_storage_root, verify_storage_proof}; use ethers_core::abi::{AbiDecode, AbiError}; use scroll_codec::CommitBatchError; use sha3::Digest; @@ -15,12 +12,12 @@ use unionlabs::{ }; use zktrie::{decode_smt_proofs, Byte32, Database, Hash, MemDB, PoseidonHash, TrieData, ZkTrie}; -#[derive(thiserror::Error, Debug)] +#[derive(thiserror::Error, Debug, PartialEq)] pub enum Error { + #[error(transparent)] + InvalidContractAddressProof(ethereum_verifier::Error), #[error("{0}")] - InvalidContractAddressProof(#[from] VerifyAccountStorageRootError), - #[error("{0}")] - InvalidRollupProof(#[from] VerifyStorageProofError), + InvalidRollupProof(ethereum_verifier::Error), #[error("invalid zktrie")] ZkTrie(zktrie::Error), #[error("node value mismatch")] @@ -42,7 +39,8 @@ pub fn verify_header( &client_state.rollup_contract_address, &header.l1_account_proof.proof, &header.l1_account_proof.storage_root, - )?; + ) + .map_err(Error::InvalidContractAddressProof)?; // Verify that the latest batch index is part of the rollup account root verify_storage_proof( @@ -50,7 +48,8 @@ pub fn verify_header( client_state.latest_batch_index_slot, &rlp::encode(&header.last_batch_index), &header.last_batch_index_proof.proofs[0].proof, - )?; + ) + .map_err(Error::InvalidRollupProof)?; // Verify that the rollup finalized state root is part of the rollup account root verify_storage_proof( @@ -61,7 +60,8 @@ pub fn verify_header( ), &rlp::encode(&header.l2_state_root), &header.l2_state_proof.proofs[0].proof, - )?; + ) + .map_err(Error::InvalidRollupProof)?; let batch_hash = scroll_codec::commit_batch( ::decode(header.commit_batch_calldata)?, @@ -78,7 +78,8 @@ pub fn verify_header( ), &rlp::encode(&batch_hash), &header.batch_hash_proof.proofs[0].proof, - )?; + ) + .map_err(Error::InvalidRollupProof)?; // Verify that the ibc account root is part of the rollup root scroll_verify_zktrie_account_storage_root( diff --git a/lib/unionlabs/src/cosmos/ics23/batch_entry.rs b/lib/unionlabs/src/cosmos/ics23/batch_entry.rs index a4b6870e91..77d0211a42 100644 --- a/lib/unionlabs/src/cosmos/ics23/batch_entry.rs +++ b/lib/unionlabs/src/cosmos/ics23/batch_entry.rs @@ -14,11 +14,14 @@ pub enum BatchEntry { Nonexist(NonExistenceProof), } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone, thiserror::Error)] pub enum TryFromBatchEntryError { + #[error(transparent)] MissingField(MissingField), - Exist(TryFromExistenceProofError), - Nonexist(TryFromNonExistenceProofError), + #[error("invalid existence proof")] + Exist(#[from] TryFromExistenceProofError), + #[error("invalid non existence proof")] + Nonexist(#[from] TryFromNonExistenceProofError), } impl TryFrom for BatchEntry { diff --git a/lib/unionlabs/src/cosmos/ics23/batch_proof.rs b/lib/unionlabs/src/cosmos/ics23/batch_proof.rs index ddf7d5688b..b8d802f296 100644 --- a/lib/unionlabs/src/cosmos/ics23/batch_proof.rs +++ b/lib/unionlabs/src/cosmos/ics23/batch_proof.rs @@ -7,9 +7,10 @@ pub struct BatchProof { pub entries: Vec, } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone, thiserror::Error)] pub enum TryFromBatchProofError { - Entries(TryFromBatchEntryError), + #[error("invalid entries")] + Entries(#[from] TryFromBatchEntryError), } impl TryFrom for BatchProof { diff --git a/lib/unionlabs/src/cosmos/ics23/commitment_proof.rs b/lib/unionlabs/src/cosmos/ics23/commitment_proof.rs index fc045542c4..e4a5ff5b38 100644 --- a/lib/unionlabs/src/cosmos/ics23/commitment_proof.rs +++ b/lib/unionlabs/src/cosmos/ics23/commitment_proof.rs @@ -18,13 +18,18 @@ pub enum CommitmentProof { CompressedBatch(CompressedBatchProof), } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone, thiserror::Error)] pub enum TryFromCommitmentProofError { + #[error(transparent)] MissingField(MissingField), - Exist(TryFromExistenceProofError), - Nonexist(TryFromNonExistenceProofError), - Batch(TryFromBatchProofError), - CompressedBatch(TryFromCompressedBatchProofProofError), + #[error("invalid existence proof")] + Exist(#[from] TryFromExistenceProofError), + #[error("invalid non existence proof")] + Nonexist(#[from] TryFromNonExistenceProofError), + #[error("invalid batch proof")] + Batch(#[from] TryFromBatchProofError), + #[error("invalid compressed batch proof")] + CompressedBatch(#[from] TryFromCompressedBatchProofProofError), } impl TryFrom for CommitmentProof { diff --git a/lib/unionlabs/src/cosmos/ics23/compressed_batch_entry.rs b/lib/unionlabs/src/cosmos/ics23/compressed_batch_entry.rs index 2ed48cedb0..5e1bf182d3 100644 --- a/lib/unionlabs/src/cosmos/ics23/compressed_batch_entry.rs +++ b/lib/unionlabs/src/cosmos/ics23/compressed_batch_entry.rs @@ -35,11 +35,14 @@ impl From for protos::cosmos::ics23::v1::CompressedBatchEn } } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone, thiserror::Error)] pub enum TryFromCompressedBatchEntryProofError { + #[error(transparent)] MissingField(MissingField), - Exist(TryFromCompressedExistenceProofError), - Nonexist(TryFromCompressedNonExistenceProofError), + #[error("invalid compressed existence proof")] + Exist(#[from] TryFromCompressedExistenceProofError), + #[error("invalid compressed non existence proof")] + Nonexist(#[from] TryFromCompressedNonExistenceProofError), } impl TryFrom for CompressedBatchEntry { diff --git a/lib/unionlabs/src/cosmos/ics23/compressed_batch_proof.rs b/lib/unionlabs/src/cosmos/ics23/compressed_batch_proof.rs index 6bb6bd4565..265c9997c7 100644 --- a/lib/unionlabs/src/cosmos/ics23/compressed_batch_proof.rs +++ b/lib/unionlabs/src/cosmos/ics23/compressed_batch_proof.rs @@ -11,10 +11,12 @@ pub struct CompressedBatchProof { pub lookup_inners: Vec, } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone, thiserror::Error)] pub enum TryFromCompressedBatchProofProofError { - Entries(TryFromCompressedBatchEntryProofError), - LookupInners(TryFromInnerOpError), + #[error("invalid entries")] + Entries(#[from] TryFromCompressedBatchEntryProofError), + #[error("invalid lookup inners")] + LookupInners(#[from] TryFromInnerOpError), } impl TryFrom for CompressedBatchProof { diff --git a/lib/unionlabs/src/cosmos/ics23/compressed_existence_proof.rs b/lib/unionlabs/src/cosmos/ics23/compressed_existence_proof.rs index a1de550406..10aa2522db 100644 --- a/lib/unionlabs/src/cosmos/ics23/compressed_existence_proof.rs +++ b/lib/unionlabs/src/cosmos/ics23/compressed_existence_proof.rs @@ -19,11 +19,14 @@ pub struct CompressedExistenceProof { pub path: Vec>, } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone, thiserror::Error)] pub enum TryFromCompressedExistenceProofError { + #[error(transparent)] MissingField(MissingField), - Leaf(TryFromLeafOpError), - Path(BoundedIntError), + #[error("invalid leaf")] + Leaf(#[from] TryFromLeafOpError), + #[error("invalid path")] + Path(#[source] BoundedIntError), } impl TryFrom for CompressedExistenceProof { diff --git a/lib/unionlabs/src/cosmos/ics23/compressed_non_existence_proof.rs b/lib/unionlabs/src/cosmos/ics23/compressed_non_existence_proof.rs index 6f14485be6..eb9a911382 100644 --- a/lib/unionlabs/src/cosmos/ics23/compressed_non_existence_proof.rs +++ b/lib/unionlabs/src/cosmos/ics23/compressed_non_existence_proof.rs @@ -20,11 +20,14 @@ pub struct CompressedNonExistenceProof { pub right: Option, } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone, thiserror::Error)] pub enum TryFromCompressedNonExistenceProofError { + #[error(transparent)] MissingField(MissingField), - Left(TryFromCompressedExistenceProofError), - Right(TryFromCompressedExistenceProofError), + #[error("left proof invalid")] + Left(#[source] TryFromCompressedExistenceProofError), + #[error("right proof invalid")] + Right(#[source] TryFromCompressedExistenceProofError), } impl TryFrom diff --git a/lib/unionlabs/src/cosmos/ics23/inner_spec.rs b/lib/unionlabs/src/cosmos/ics23/inner_spec.rs index 16ce162db5..aa9e034f41 100644 --- a/lib/unionlabs/src/cosmos/ics23/inner_spec.rs +++ b/lib/unionlabs/src/cosmos/ics23/inner_spec.rs @@ -56,16 +56,25 @@ impl From for protos::cosmos::ics23::v1::InnerSpec { } } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone, thiserror::Error)] pub enum TryFromInnerSpecError { - Hash(UnknownEnumVariant), - ChildOrder(BoundedIntError), - ChildSize(BoundedIntError), - MinPrefixLength(BoundedIntError), - MaxPrefixLength(BoundedIntError), + #[error("invalid hash")] + Hash(#[from] UnknownEnumVariant), + #[error("invalid child order")] + ChildOrder(#[source] BoundedIntError), + #[error("invalid child size")] + ChildSize(#[source] BoundedIntError), + #[error("invalid min prefix length")] + MinPrefixLength(#[source] BoundedIntError), + #[error("invalid max prefix length")] + MaxPrefixLength(#[source] BoundedIntError), + #[error("negative child order")] NegativeChildOrder, + #[error("negative child size")] NegativeChildSize, + #[error("negative min prefix length")] NegativeMinPrefixLength, + #[error("negative max prefix length")] NegativeMaxPrefixLength, } diff --git a/lib/unionlabs/src/cosmos/ics23/non_existence_proof.rs b/lib/unionlabs/src/cosmos/ics23/non_existence_proof.rs index 02d07c6140..01c3c19eaf 100644 --- a/lib/unionlabs/src/cosmos/ics23/non_existence_proof.rs +++ b/lib/unionlabs/src/cosmos/ics23/non_existence_proof.rs @@ -14,10 +14,13 @@ pub struct NonExistenceProof { pub right: Option, } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone, thiserror::Error)] pub enum TryFromNonExistenceProofError { + #[error(transparent)] MissingField(MissingField), + #[error("left proof invalid")] Left(TryFromExistenceProofError), + #[error("right proof invalid")] Right(TryFromExistenceProofError), } diff --git a/lib/unionlabs/src/cosmos/ics23/proof_spec.rs b/lib/unionlabs/src/cosmos/ics23/proof_spec.rs index b1104442dd..26cffecbdf 100644 --- a/lib/unionlabs/src/cosmos/ics23/proof_spec.rs +++ b/lib/unionlabs/src/cosmos/ics23/proof_spec.rs @@ -39,12 +39,17 @@ impl From for protos::cosmos::ics23::v1::ProofSpec { } } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone, thiserror::Error)] pub enum TryFromProofSpecError { + #[error(transparent)] MissingField(MissingField), - LeafSpec(TryFromLeafOpError), - InnerSpec(TryFromInnerSpecError), + #[error("invalid leaf spec")] + LeafSpec(#[from] TryFromLeafOpError), + #[error("invalid inner spec")] + InnerSpec(#[from] TryFromInnerSpecError), + #[error("negative max depth")] NegativeMinDepth, + #[error("negative min depth")] NegativeMaxDepth, } diff --git a/lib/unionlabs/src/cosmwasm/wasm/union/custom_query.rs b/lib/unionlabs/src/cosmwasm/wasm/union/custom_query.rs index 3c068f0c4c..1863ee211d 100644 --- a/lib/unionlabs/src/cosmwasm/wasm/union/custom_query.rs +++ b/lib/unionlabs/src/cosmwasm/wasm/union/custom_query.rs @@ -4,7 +4,7 @@ use cosmwasm_std::{Binary, Deps, QueryRequest}; use crate::bls::BlsPublicKey; -#[derive(thiserror::Error, Debug, PartialEq)] +#[derive(thiserror::Error, Debug, PartialEq, Clone)] pub enum Error { #[error("error while running `fast_aggregate_verify` query ({0})")] FastAggregateVerify(String), diff --git a/lib/unionlabs/src/google/protobuf/any.rs b/lib/unionlabs/src/google/protobuf/any.rs index caab683f25..e6447f686c 100644 --- a/lib/unionlabs/src/google/protobuf/any.rs +++ b/lib/unionlabs/src/google/protobuf/any.rs @@ -1,5 +1,6 @@ use core::{fmt::Debug, marker::PhantomData}; +use frame_support_procedural::DebugNoBound; use prost::Message; use serde::{ de::{self, Visitor}, @@ -147,17 +148,35 @@ impl + TypeUrl> Encode for Any { // const TYPE_URL: &'static str = "/google.protobuf.Any"; // } -#[derive(Debug)] -pub enum TryFromAnyError { - IncorrectTypeUrl { found: String, expected: String }, - Decode(E), +#[derive(DebugNoBound, thiserror::Error)] +pub enum TryFromAnyError + TypeUrl> { + #[error( + "incorrect type url, expected `{expected}` but found `{found}`", + expected = T::type_url() + )] + IncorrectTypeUrl { found: String }, + #[error("unable to decode inner type")] + Decode(DecodeErrorOf), +} + +impl + TypeUrl> PartialEq for TryFromAnyError { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + ( + TryFromAnyError::IncorrectTypeUrl { found: this }, + TryFromAnyError::IncorrectTypeUrl { found: other }, + ) => this == other, + (TryFromAnyError::Decode(this), TryFromAnyError::Decode(other)) => this == other, + _ => false, + } + } } impl TryFrom for Any where T: Decode + TypeUrl, { - type Error = TryFromAnyError; + type Error = TryFromAnyError; fn try_from(value: protos::google::protobuf::Any) -> Result { if value.type_url == T::type_url() { @@ -167,26 +186,29 @@ where } else { Err(TryFromAnyError::IncorrectTypeUrl { found: value.type_url, - expected: T::type_url(), }) } } } -impl + TypeUrl> Decode for Any { - type Error = TryFromProtoBytesError>>; +impl Decode for Any +where + T: Decode + TypeUrl, +{ + type Error = TryFromProtoBytesError>; fn decode(bytes: &[u8]) -> Result { ::decode(bytes) - .map_err(crate::TryFromProtoBytesError::Decode) + .map_err(TryFromProtoBytesError::Decode) .and_then(|proto| { proto .try_into() - .map_err(crate::TryFromProtoBytesError::TryFromProto) + .map_err(TryFromProtoBytesError::TryFromProto) }) } } +// for use with raw prost generated types #[must_use] pub fn mk_any(t: &T) -> protos::google::protobuf::Any { let bz = t.encode_to_vec(); diff --git a/lib/unionlabs/src/google/protobuf/duration.rs b/lib/unionlabs/src/google/protobuf/duration.rs index 8a5acf0c55..d0184fa648 100644 --- a/lib/unionlabs/src/google/protobuf/duration.rs +++ b/lib/unionlabs/src/google/protobuf/duration.rs @@ -276,7 +276,6 @@ impl Duration { // false positive (fixed in newer versions) // https://github.com/rust-lang/rust-clippy/pull/10811 - #[allow(clippy::match_wild_err_arm)] #[allow(clippy::cast_possible_truncation)] // invariant checked above match BoundedI32::new(value as i32) { Ok(ok) => ok, @@ -287,11 +286,13 @@ impl Duration { } } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone, thiserror::Error)] pub enum DurationError { + #[error("invalid seconds")] Seconds(BoundedIntError), + #[error("invalid nanos")] Nanos(BoundedIntError), - /// The nanos field was the incorrect sign. + #[error("incorrect sign for nanos")] Sign, } diff --git a/lib/unionlabs/src/google/protobuf/timestamp.rs b/lib/unionlabs/src/google/protobuf/timestamp.rs index 6528bc4119..795a28e947 100644 --- a/lib/unionlabs/src/google/protobuf/timestamp.rs +++ b/lib/unionlabs/src/google/protobuf/timestamp.rs @@ -265,10 +265,12 @@ impl From for protos::google::protobuf::Timestamp { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, thiserror::Error)] pub enum TryFromTimestampError { - Seconds(BoundedIntError), - Nanos(BoundedIntError), + #[error("invalid seconds")] + Seconds(#[source] BoundedIntError), + #[error("invalid nanos")] + Nanos(#[source] BoundedIntError), } impl TryFrom for Timestamp { diff --git a/lib/unionlabs/src/ibc/core/commitment/merkle_proof.rs b/lib/unionlabs/src/ibc/core/commitment/merkle_proof.rs index cfa2de92aa..915a22e4ce 100644 --- a/lib/unionlabs/src/ibc/core/commitment/merkle_proof.rs +++ b/lib/unionlabs/src/ibc/core/commitment/merkle_proof.rs @@ -10,9 +10,10 @@ pub struct MerkleProof { pub proofs: Vec, } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone, thiserror::Error)] pub enum TryFromMerkleProofError { - Proofs(TryFromCommitmentProofError), + #[error("invalid proofs")] + Proofs(#[from] TryFromCommitmentProofError), } impl TryFrom for MerkleProof { diff --git a/lib/unionlabs/src/ibc/core/commitment/merkle_root.rs b/lib/unionlabs/src/ibc/core/commitment/merkle_root.rs index 4dcc8a8bbe..2c906e9fc2 100644 --- a/lib/unionlabs/src/ibc/core/commitment/merkle_root.rs +++ b/lib/unionlabs/src/ibc/core/commitment/merkle_root.rs @@ -24,7 +24,7 @@ impl From for protos::ibc::core::commitment::v1::MerkleRoot { } } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone)] pub enum TryFromMerkleRootError { Hash(InvalidLength), } diff --git a/lib/unionlabs/src/ibc/lightclients/cometbls/client_state.rs b/lib/unionlabs/src/ibc/lightclients/cometbls/client_state.rs index 95b99e9d5c..25d01c4720 100644 --- a/lib/unionlabs/src/ibc/lightclients/cometbls/client_state.rs +++ b/lib/unionlabs/src/ibc/lightclients/cometbls/client_state.rs @@ -39,8 +39,9 @@ impl From for protos::union::ibc::lightclients::cometbls::v1::Clien } } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone, thiserror::Error)] pub enum TryFromClientStateError { + #[error(transparent)] MissingField(MissingField), } diff --git a/lib/unionlabs/src/ibc/lightclients/cometbls/consensus_state.rs b/lib/unionlabs/src/ibc/lightclients/cometbls/consensus_state.rs index b684806f7e..6e2c7199af 100644 --- a/lib/unionlabs/src/ibc/lightclients/cometbls/consensus_state.rs +++ b/lib/unionlabs/src/ibc/lightclients/cometbls/consensus_state.rs @@ -20,7 +20,7 @@ pub struct ConsensusState { pub next_validators_hash: H256, } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone)] pub enum TryFromConsensusStateError { MissingField(MissingField), Root(TryFromMerkleRootError), diff --git a/lib/unionlabs/src/ibc/lightclients/cometbls/header.rs b/lib/unionlabs/src/ibc/lightclients/cometbls/header.rs index d5c0be2e5d..c2f019afdf 100644 --- a/lib/unionlabs/src/ibc/lightclients/cometbls/header.rs +++ b/lib/unionlabs/src/ibc/lightclients/cometbls/header.rs @@ -51,10 +51,12 @@ impl From
for contracts::glue::UnionIbcLightclientsCometblsV1HeaderData } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, thiserror::Error)] pub enum TryFromHeaderError { + #[error(transparent)] MissingField(MissingField), - SignedHeader(TryFromLightHeaderError), + #[error("invalid signed header")] + SignedHeader(#[from] TryFromLightHeaderError), } impl TryFrom for Header { @@ -64,9 +66,7 @@ impl TryFrom for Header value: protos::union::ibc::lightclients::cometbls::v1::Header, ) -> Result { Ok(Self { - signed_header: required!(value.signed_header)? - .try_into() - .map_err(TryFromHeaderError::SignedHeader)?, + signed_header: required!(value.signed_header)?.try_into()?, trusted_height: required!(value.trusted_height)?.into(), zero_knowledge_proof: value.zero_knowledge_proof, }) diff --git a/lib/unionlabs/src/ibc/lightclients/cometbls/light_header.rs b/lib/unionlabs/src/ibc/lightclients/cometbls/light_header.rs index 873cac3231..71905dde5d 100644 --- a/lib/unionlabs/src/ibc/lightclients/cometbls/light_header.rs +++ b/lib/unionlabs/src/ibc/lightclients/cometbls/light_header.rs @@ -55,14 +55,20 @@ impl From for LightHeader { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, thiserror::Error)] pub enum TryFromLightHeaderError { + #[error(transparent)] MissingField(MissingField), - Height(BoundedIntError), - Timestamp(TryFromTimestampError), - ValidatorsHash(InvalidLength), - NextValidatorsHash(InvalidLength), - AppHash(InvalidLength), + #[error("invalid height")] + Height(#[source] BoundedIntError), + #[error("invalid timestamp")] + Timestamp(#[from] TryFromTimestampError), + #[error("invalid validators hash")] + ValidatorsHash(#[source] InvalidLength), + #[error("invalid next validators hash")] + NextValidatorsHash(#[source] InvalidLength), + #[error("invalid app hash")] + AppHash(#[source] InvalidLength), } impl TryFrom for LightHeader { @@ -76,9 +82,7 @@ impl TryFrom for Li .height .try_into() .map_err(TryFromLightHeaderError::Height)?, - time: required!(value.time)? - .try_into() - .map_err(TryFromLightHeaderError::Timestamp)?, + time: required!(value.time)?.try_into()?, validators_hash: value .validators_hash .try_into() diff --git a/lib/unionlabs/src/ibc/lightclients/ethereum/account_proof.rs b/lib/unionlabs/src/ibc/lightclients/ethereum/account_proof.rs index 370b37d207..7441cf3527 100644 --- a/lib/unionlabs/src/ibc/lightclients/ethereum/account_proof.rs +++ b/lib/unionlabs/src/ibc/lightclients/ethereum/account_proof.rs @@ -14,7 +14,7 @@ pub struct AccountProof { pub proof: Vec>, } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone)] pub enum TryFromAccountProofError { ContractAddress(InvalidLength), StorageRoot(InvalidLength), diff --git a/lib/unionlabs/src/ibc/lightclients/ethereum/account_update.rs b/lib/unionlabs/src/ibc/lightclients/ethereum/account_update.rs index fc624c379e..5a19057f70 100644 --- a/lib/unionlabs/src/ibc/lightclients/ethereum/account_update.rs +++ b/lib/unionlabs/src/ibc/lightclients/ethereum/account_update.rs @@ -22,7 +22,7 @@ impl From for protos::union::ibc::lightclients::ethereum::v1::Acc } } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone)] pub enum TryFromAccountUpdateError { MissingField(MissingField), AccountProof(TryFromAccountProofError), diff --git a/lib/unionlabs/src/ibc/lightclients/ethereum/beacon_block_header.rs b/lib/unionlabs/src/ibc/lightclients/ethereum/beacon_block_header.rs index 179461c361..51e37be66c 100644 --- a/lib/unionlabs/src/ibc/lightclients/ethereum/beacon_block_header.rs +++ b/lib/unionlabs/src/ibc/lightclients/ethereum/beacon_block_header.rs @@ -31,7 +31,7 @@ impl From for protos::union::ibc::lightclients::ethereum::v1: } } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone)] pub enum TryFromBeaconBlockHeaderError { ParentRoot(InvalidLength), StateRoot(InvalidLength), diff --git a/lib/unionlabs/src/ibc/lightclients/ethereum/client_state.rs b/lib/unionlabs/src/ibc/lightclients/ethereum/client_state.rs index ad22bdf87f..259e14b781 100644 --- a/lib/unionlabs/src/ibc/lightclients/ethereum/client_state.rs +++ b/lib/unionlabs/src/ibc/lightclients/ethereum/client_state.rs @@ -61,7 +61,7 @@ impl From for protos::union::ibc::lightclients::ethereum::v1::Clien } } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum TryFromClientStateError { MissingField(MissingField), ChainId(FromDecStrErr), diff --git a/lib/unionlabs/src/ibc/lightclients/ethereum/consensus_state.rs b/lib/unionlabs/src/ibc/lightclients/ethereum/consensus_state.rs index 8e4429d2fb..8659e1f5a1 100644 --- a/lib/unionlabs/src/ibc/lightclients/ethereum/consensus_state.rs +++ b/lib/unionlabs/src/ibc/lightclients/ethereum/consensus_state.rs @@ -35,7 +35,7 @@ impl From for protos::union::ibc::lightclients::ethereum::v1::Co } } -#[derive(Debug)] +#[derive(Debug, Clone, PartialEq)] pub enum TryFromConsensusStateError { CurrentSyncCommittee(InvalidLength), NextSyncCommittee(InvalidLength), diff --git a/lib/unionlabs/src/ibc/lightclients/ethereum/execution_payload_header.rs b/lib/unionlabs/src/ibc/lightclients/ethereum/execution_payload_header.rs index ecf92b41c1..5e89328f8f 100644 --- a/lib/unionlabs/src/ibc/lightclients/ethereum/execution_payload_header.rs +++ b/lib/unionlabs/src/ibc/lightclients/ethereum/execution_payload_header.rs @@ -132,7 +132,7 @@ impl From for protos::union::ibc::lightclients::ethereum::v1::Fork { } } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone)] pub enum TryFromForkError { Version(InvalidLength), } diff --git a/lib/unionlabs/src/ibc/lightclients/ethereum/fork_parameters.rs b/lib/unionlabs/src/ibc/lightclients/ethereum/fork_parameters.rs index a3164e9fec..a6580fbc8a 100644 --- a/lib/unionlabs/src/ibc/lightclients/ethereum/fork_parameters.rs +++ b/lib/unionlabs/src/ibc/lightclients/ethereum/fork_parameters.rs @@ -42,7 +42,7 @@ impl From for protos::union::ibc::lightclients::ethereum::v1::Fo } } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone)] pub enum TryFromForkParametersError { MissingField(MissingField), InvalidLength(InvalidLength), diff --git a/lib/unionlabs/src/ibc/lightclients/ethereum/header.rs b/lib/unionlabs/src/ibc/lightclients/ethereum/header.rs index 1c74502f4f..0d2ac58a12 100644 --- a/lib/unionlabs/src/ibc/lightclients/ethereum/header.rs +++ b/lib/unionlabs/src/ibc/lightclients/ethereum/header.rs @@ -34,7 +34,7 @@ impl From< } } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone)] pub enum TryFromHeaderError { MissingField(MissingField), TrustedSyncCommittee(TryFromTrustedSyncCommitteeError), diff --git a/lib/unionlabs/src/ibc/lightclients/ethereum/light_client_header.rs b/lib/unionlabs/src/ibc/lightclients/ethereum/light_client_header.rs index ce4390367e..716e997695 100644 --- a/lib/unionlabs/src/ibc/lightclients/ethereum/light_client_header.rs +++ b/lib/unionlabs/src/ibc/lightclients/ethereum/light_client_header.rs @@ -46,7 +46,7 @@ impl From> } } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone)] pub enum TryFromLightClientHeaderError { MissingField(MissingField), BeaconBlockHeader(TryFromBeaconBlockHeaderError), diff --git a/lib/unionlabs/src/ibc/lightclients/ethereum/light_client_update.rs b/lib/unionlabs/src/ibc/lightclients/ethereum/light_client_update.rs index 85284ae082..f1b07bfce7 100644 --- a/lib/unionlabs/src/ibc/lightclients/ethereum/light_client_update.rs +++ b/lib/unionlabs/src/ibc/lightclients/ethereum/light_client_update.rs @@ -1,10 +1,9 @@ use core::fmt::Debug; -use frame_support_procedural::DebugNoBound; use macros::model; use crate::{ - errors::{InvalidLength, MissingField}, + errors::{required, InvalidLength, MissingField}, ethereum::config::{ consts::{floorlog2, FINALIZED_ROOT_INDEX, NEXT_SYNC_COMMITTEE_INDEX}, BYTES_PER_LOGS_BLOOM, MAX_EXTRA_DATA_BYTES, SYNC_COMMITTEE_SIZE, @@ -75,7 +74,7 @@ impl } } -#[derive(DebugNoBound)] +#[derive(Debug, PartialEq, Clone)] pub enum TryFromLightClientUpdateError { MissingField(MissingField), AttestedHeader(TryFromLightClientHeaderError), @@ -96,11 +95,7 @@ impl value: protos::union::ibc::lightclients::ethereum::v1::LightClientUpdate, ) -> Result { Ok(Self { - attested_header: value - .attested_header - .ok_or(TryFromLightClientUpdateError::MissingField(MissingField( - "attested_header", - )))? + attested_header: required!(value.attested_header)? .try_into() .map_err(TryFromLightClientUpdateError::AttestedHeader)?, next_sync_committee: value @@ -116,20 +111,12 @@ impl .map_err(TryFromLightClientUpdateError::NextSyncCommitteeBranch)?, ) }, - finalized_header: value - .finalized_header - .ok_or(TryFromLightClientUpdateError::MissingField(MissingField( - "finalized_header", - )))? + finalized_header: required!(value.finalized_header)? .try_into() .map_err(TryFromLightClientUpdateError::FinalizedHeader)?, finality_branch: try_from_proto_branch(value.finality_branch) .map_err(TryFromLightClientUpdateError::FinalityBranch)?, - sync_aggregate: value - .sync_aggregate - .ok_or(TryFromLightClientUpdateError::MissingField(MissingField( - "sync_aggregate", - )))? + sync_aggregate: required!(value.sync_aggregate)? .try_into() .map_err(TryFromLightClientUpdateError::SyncAggregate)?, signature_slot: value.signature_slot, @@ -139,7 +126,7 @@ impl fn try_from_proto_branch(proto: Vec>) -> Result> where - T: TryFrom, Error: Debug + PartialEq + Eq>, + T: TryFrom, Error: Debug + PartialEq + Eq + Clone>, { proto .into_iter() @@ -150,10 +137,11 @@ where .map_err(TryFromBranchError::Branch) } -#[derive(Debug, PartialEq, Eq)] +// TODO: Remove the bounds on T::Error and only require said bounds when implementing the respective traits, will clean up try_from_proto_branch as well +#[derive(Debug, PartialEq, Eq, Clone)] pub enum TryFromBranchError where - T: TryFrom, Error: Debug + PartialEq + Eq>, + T: TryFrom, Error: Debug + PartialEq + Eq + Clone>, { Branch(>>::Error), BranchNode(InvalidLength), diff --git a/lib/unionlabs/src/ibc/lightclients/ethereum/misbehaviour.rs b/lib/unionlabs/src/ibc/lightclients/ethereum/misbehaviour.rs index c1138bb3a5..cb1b58840f 100644 --- a/lib/unionlabs/src/ibc/lightclients/ethereum/misbehaviour.rs +++ b/lib/unionlabs/src/ibc/lightclients/ethereum/misbehaviour.rs @@ -34,7 +34,7 @@ impl From< } } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone)] pub enum TryFromMisbehaviourError { MissingField(MissingField), TrustedSyncCommittee(TryFromTrustedSyncCommitteeError), diff --git a/lib/unionlabs/src/ibc/lightclients/ethereum/proof.rs b/lib/unionlabs/src/ibc/lightclients/ethereum/proof.rs index e044b40100..561a969a29 100644 --- a/lib/unionlabs/src/ibc/lightclients/ethereum/proof.rs +++ b/lib/unionlabs/src/ibc/lightclients/ethereum/proof.rs @@ -13,10 +13,12 @@ pub struct Proof { pub proof: Vec>, } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone, thiserror::Error)] pub enum TryFromProofError { - Key(InvalidLength), - Value(InvalidLength), + #[error("unable to decode key")] + Key(#[source] InvalidLength), + #[error("unable to decode value")] + Value(#[source] InvalidLength), } impl TryFrom for Proof { diff --git a/lib/unionlabs/src/ibc/lightclients/ethereum/storage_proof.rs b/lib/unionlabs/src/ibc/lightclients/ethereum/storage_proof.rs index 33dedb235f..41ed971da6 100644 --- a/lib/unionlabs/src/ibc/lightclients/ethereum/storage_proof.rs +++ b/lib/unionlabs/src/ibc/lightclients/ethereum/storage_proof.rs @@ -19,9 +19,10 @@ impl From for protos::union::ibc::lightclients::ethereum::v1::Stor } } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone, thiserror::Error)] pub enum TryFromStorageProofError { - Proofs(TryFromProofError), + #[error("unable to decode proofs")] + Proofs(#[from] TryFromProofError), } impl TryFrom for StorageProof { @@ -34,7 +35,7 @@ impl TryFrom for S proofs: value .proofs .into_iter() - .map(|proof| proof.try_into().map_err(TryFromStorageProofError::Proofs)) + .map(TryInto::try_into) .collect::>()?, }) } diff --git a/lib/unionlabs/src/ibc/lightclients/ethereum/sync_aggregate.rs b/lib/unionlabs/src/ibc/lightclients/ethereum/sync_aggregate.rs index d831126b2d..4914bafea4 100644 --- a/lib/unionlabs/src/ibc/lightclients/ethereum/sync_aggregate.rs +++ b/lib/unionlabs/src/ibc/lightclients/ethereum/sync_aggregate.rs @@ -39,7 +39,7 @@ impl From> } } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone)] pub enum TryFromSyncAggregateError { Bits(ssz::types::Error), Signature(InvalidLength), diff --git a/lib/unionlabs/src/ibc/lightclients/ethereum/sync_committee.rs b/lib/unionlabs/src/ibc/lightclients/ethereum/sync_committee.rs index f35bcc406d..698f5262c6 100644 --- a/lib/unionlabs/src/ibc/lightclients/ethereum/sync_committee.rs +++ b/lib/unionlabs/src/ibc/lightclients/ethereum/sync_committee.rs @@ -31,7 +31,7 @@ impl From> } } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone)] pub enum TryFromSyncCommitteeError { /// One of the `pubkeys` had an invalid length PubKey(InvalidLength), diff --git a/lib/unionlabs/src/ibc/lightclients/ethereum/trusted_sync_committee.rs b/lib/unionlabs/src/ibc/lightclients/ethereum/trusted_sync_committee.rs index d61699dcc3..a8f80b06a6 100644 --- a/lib/unionlabs/src/ibc/lightclients/ethereum/trusted_sync_committee.rs +++ b/lib/unionlabs/src/ibc/lightclients/ethereum/trusted_sync_committee.rs @@ -1,4 +1,3 @@ -use frame_support_procedural::DebugNoBound; use macros::model; use ssz::{Decode, Encode, TreeHash}; @@ -76,7 +75,7 @@ impl From> } } -#[derive(DebugNoBound)] +#[derive(Debug, PartialEq, Clone)] pub enum TryFromTrustedSyncCommitteeError { MissingField(MissingField), SyncCommittee(TryFromSyncCommitteeError), diff --git a/lib/unionlabs/src/ibc/lightclients/scroll/client_state.rs b/lib/unionlabs/src/ibc/lightclients/scroll/client_state.rs index a1aaa6e183..22d6537167 100644 --- a/lib/unionlabs/src/ibc/lightclients/scroll/client_state.rs +++ b/lib/unionlabs/src/ibc/lightclients/scroll/client_state.rs @@ -44,16 +44,22 @@ impl From for protos::union::ibc::lightclients::scroll::v1::ClientS } } -#[derive(Debug)] +#[derive(Debug, PartialEq, thiserror::Error)] pub enum TryFromClientStateError { - L1ClientId(FromDecStrErr), - ChainId(FromDecStrErr), - LatestBatchIndexSlot(InvalidLength), - RollupContractAddress(InvalidLength), - RollupFinalizedStateRootsSlot(InvalidLength), - IbcContractAddress(InvalidLength), - IbcCommitmentSlot(InvalidLength), - RollupCommittedBatchesSlot(InvalidLength), + #[error("unable to parse chain id")] + ChainId(#[source] FromDecStrErr), + #[error("invalid latest batch index slot")] + LatestBatchIndexSlot(#[source] InvalidLength), + #[error("invalid rollup contract address")] + RollupContractAddress(#[source] InvalidLength), + #[error("invalid rollup finalized state roots slot")] + RollupFinalizedStateRootsSlot(#[source] InvalidLength), + #[error("invalid ibc contract address")] + IbcContractAddress(#[source] InvalidLength), + #[error("invalid ibc commitment slot")] + IbcCommitmentSlot(#[source] InvalidLength), + #[error("invalid ibc committed batches slot")] + RollupCommittedBatchesSlot(#[source] InvalidLength), } impl TryFrom for ClientState { diff --git a/lib/unionlabs/src/ibc/lightclients/scroll/consensus_state.rs b/lib/unionlabs/src/ibc/lightclients/scroll/consensus_state.rs index 8802e4809e..c676a0530f 100644 --- a/lib/unionlabs/src/ibc/lightclients/scroll/consensus_state.rs +++ b/lib/unionlabs/src/ibc/lightclients/scroll/consensus_state.rs @@ -21,9 +21,10 @@ impl From for protos::union::ibc::lightclients::scroll::v1::Cons } } -#[derive(Debug)] +#[derive(Debug, PartialEq, thiserror::Error)] pub enum TryFromConsensusStateError { - IbcStorageRoot(InvalidLength), + #[error("invalid ibc storage root")] + IbcStorageRoot(#[source] InvalidLength), } impl TryFrom for ConsensusState { diff --git a/lib/unionlabs/src/ibc/lightclients/scroll/header.rs b/lib/unionlabs/src/ibc/lightclients/scroll/header.rs index 41b14d4963..4c2d803cc8 100644 --- a/lib/unionlabs/src/ibc/lightclients/scroll/header.rs +++ b/lib/unionlabs/src/ibc/lightclients/scroll/header.rs @@ -63,7 +63,7 @@ impl From
for protos::union::ibc::lightclients::scroll::v1::Header { } } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone)] pub enum TryFromHeaderError { MissingField(MissingField), L1AccountProof(TryFromAccountProofError), diff --git a/lib/unionlabs/src/ibc/lightclients/tendermint/client_state.rs b/lib/unionlabs/src/ibc/lightclients/tendermint/client_state.rs index 7abd7d6dec..c9e95352c3 100644 --- a/lib/unionlabs/src/ibc/lightclients/tendermint/client_state.rs +++ b/lib/unionlabs/src/ibc/lightclients/tendermint/client_state.rs @@ -48,14 +48,20 @@ impl From for protos::ibc::lightclients::tendermint::v1::ClientStat } } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone, thiserror::Error)] pub enum TryFromClientStateError { + #[error(transparent)] MissingField(MissingField), - TrustLevel(TryFromFractionError), - TrustingPeriod(DurationError), - UnbondingPeriod(DurationError), - MaxClockDrift(DurationError), - ProofSpecs(TryFromProofSpecError), + #[error("invalid trust level")] + TrustLevel(#[source] TryFromFractionError), + #[error("invalid trusting period")] + TrustingPeriod(#[source] DurationError), + #[error("invalid unbonding period")] + UnbondingPeriod(#[source] DurationError), + #[error("invalid max clock drift")] + MaxClockDrift(#[source] DurationError), + #[error("invalid proof specs")] + ProofSpecs(#[source] TryFromProofSpecError), } impl TryFrom for ClientState { diff --git a/lib/unionlabs/src/ibc/lightclients/tendermint/consensus_state.rs b/lib/unionlabs/src/ibc/lightclients/tendermint/consensus_state.rs index a2d1b99929..646cfa73e7 100644 --- a/lib/unionlabs/src/ibc/lightclients/tendermint/consensus_state.rs +++ b/lib/unionlabs/src/ibc/lightclients/tendermint/consensus_state.rs @@ -18,7 +18,7 @@ pub struct ConsensusState { pub next_validators_hash: H256, } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone)] pub enum TryFromConsensusStateError { MissingField(MissingField), Root(TryFromMerkleRootError), diff --git a/lib/unionlabs/src/ibc/lightclients/tendermint/fraction.rs b/lib/unionlabs/src/ibc/lightclients/tendermint/fraction.rs index 9082fb8f01..6098d6ec1d 100644 --- a/lib/unionlabs/src/ibc/lightclients/tendermint/fraction.rs +++ b/lib/unionlabs/src/ibc/lightclients/tendermint/fraction.rs @@ -17,8 +17,9 @@ impl From for protos::ibc::lightclients::tendermint::v1::Fraction { } } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone, thiserror::Error)] pub enum TryFromFractionError { + #[error("zero denominator")] ZeroDenominator, } @@ -38,7 +39,6 @@ impl TryFrom for Fraction { } } -// TODO(benluelo): This will be replaced with tendermint once the solidity contract types are regenerated #[cfg(feature = "ethabi")] impl From for contracts::glue::IbcLightclientsTendermintV1FractionData { fn from(value: Fraction) -> Self { @@ -54,6 +54,7 @@ impl From for Fraction fn from(value: contracts::glue::IbcLightclientsTendermintV1FractionData) -> Self { Self { numerator: value.numerator, + // TODO: Don't panic here lol denominator: NonZeroU64::new(value.denominator).expect("non-zero denominator"), } } diff --git a/lib/unionlabs/src/ibc/lightclients/tendermint/header.rs b/lib/unionlabs/src/ibc/lightclients/tendermint/header.rs index 05a82f67c1..b2a703161a 100644 --- a/lib/unionlabs/src/ibc/lightclients/tendermint/header.rs +++ b/lib/unionlabs/src/ibc/lightclients/tendermint/header.rs @@ -28,7 +28,7 @@ impl From
for protos::ibc::lightclients::tendermint::v1::Header { } } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone)] pub enum TryFromHeaderError { MissingField(MissingField), SignedHeader(TryFromSignedHeaderError), diff --git a/lib/unionlabs/src/ibc/lightclients/wasm/client_state.rs b/lib/unionlabs/src/ibc/lightclients/wasm/client_state.rs index 5db3adab26..1a1efc3d56 100644 --- a/lib/unionlabs/src/ibc/lightclients/wasm/client_state.rs +++ b/lib/unionlabs/src/ibc/lightclients/wasm/client_state.rs @@ -28,6 +28,16 @@ where } } +impl> PartialEq for TryFromWasmClientStateError { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Data(this), Self::Data(other)) => this == other, + (Self::CodeId(this), Self::CodeId(other)) => this == other, + _ => false, + } + } +} + #[derive(DebugNoBound)] pub enum TryFromWasmClientStateError> { Data(DecodeErrorOf), diff --git a/lib/unionlabs/src/ibc/lightclients/wasm/consensus_state.rs b/lib/unionlabs/src/ibc/lightclients/wasm/consensus_state.rs index 3881b77924..70ac1d2ea0 100644 --- a/lib/unionlabs/src/ibc/lightclients/wasm/consensus_state.rs +++ b/lib/unionlabs/src/ibc/lightclients/wasm/consensus_state.rs @@ -1,3 +1,4 @@ +use frame_support_procedural::DebugNoBound; use macros::model; use crate::encoding::{Decode, DecodeErrorOf, Encode, Proto}; @@ -17,11 +18,19 @@ impl> From> } } -#[derive(Debug)] +#[derive(DebugNoBound)] pub enum TryFromWasmConsensusStateError> { Data(DecodeErrorOf), } +impl> PartialEq for TryFromWasmConsensusStateError { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Data(this), Self::Data(other)) => this == other, + } + } +} + impl TryFrom for ConsensusState where Data: Decode, diff --git a/lib/unionlabs/src/ics24.rs b/lib/unionlabs/src/ics24.rs index 3fdce469c1..a22801d977 100644 --- a/lib/unionlabs/src/ics24.rs +++ b/lib/unionlabs/src/ics24.rs @@ -206,16 +206,22 @@ impl IbcPath for NextClientSequencePath { type Value = u64; } -#[derive(Debug)] +#[derive(Debug, PartialEq, thiserror::Error)] pub enum PathParseError { + #[error("invalid static segment, expected `{expected}` but found `{found}`")] InvalidStaticSegment { expected: &'static str, found: String, }, + #[error("missing static segment `{0}`")] MissingStaticSegment(&'static str), + // TODO: Figure out a way to provide more context here? + #[error("missing segment")] MissingSegment, + #[error("too many segments")] TooManySegments, // contains the stringified parse error + #[error("error parsing segment: {0}")] Parse(String), } diff --git a/lib/unionlabs/src/lib.rs b/lib/unionlabs/src/lib.rs index ee286dd4a0..a1eeb45b79 100644 --- a/lib/unionlabs/src/lib.rs +++ b/lib/unionlabs/src/lib.rs @@ -92,9 +92,11 @@ pub mod errors; #[allow(clippy::missing_panics_doc)] pub mod test_utils; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, thiserror::Error)] pub enum TryFromProtoBytesError { - TryFromProto(E), + #[error("unable to convert from the raw prost type")] + TryFromProto(#[source] E), + #[error("unable to decode from raw proto bytes")] Decode(prost::DecodeError), } diff --git a/lib/unionlabs/src/tendermint/crypto/public_key.rs b/lib/unionlabs/src/tendermint/crypto/public_key.rs index 495f8605e9..81d3554fc4 100644 --- a/lib/unionlabs/src/tendermint/crypto/public_key.rs +++ b/lib/unionlabs/src/tendermint/crypto/public_key.rs @@ -38,7 +38,7 @@ impl From for protos::tendermint::crypto::PublicKey { } } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone)] pub enum TryFromPublicKeyError { MissingField(MissingField), } diff --git a/lib/unionlabs/src/tendermint/types/validator.rs b/lib/unionlabs/src/tendermint/types/validator.rs index b3526c798f..0249b081b4 100644 --- a/lib/unionlabs/src/tendermint/types/validator.rs +++ b/lib/unionlabs/src/tendermint/types/validator.rs @@ -27,7 +27,7 @@ impl From for protos::tendermint::types::Validator { } } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone)] pub enum TryFromValidatorError { MissingField(MissingField), Address(InvalidLength), diff --git a/lib/unionlabs/src/tendermint/types/validator_set.rs b/lib/unionlabs/src/tendermint/types/validator_set.rs index 5181384eb6..ad66839d7a 100644 --- a/lib/unionlabs/src/tendermint/types/validator_set.rs +++ b/lib/unionlabs/src/tendermint/types/validator_set.rs @@ -23,7 +23,7 @@ impl From for protos::tendermint::types::ValidatorSet { } } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone)] pub enum TryFromValidatorSetError { Validators(TryFromValidatorError), MissingField(MissingField), diff --git a/light-clients/cometbls-light-client/src/client.rs b/light-clients/cometbls-light-client/src/client.rs index 581df9db11..0a6b7d5e06 100644 --- a/light-clients/cometbls-light-client/src/client.rs +++ b/light-clients/cometbls-light-client/src/client.rs @@ -7,7 +7,7 @@ use ics008_wasm_client::{ read_substitute_client_state, read_substitute_consensus_state, save_client_state, save_consensus_state, save_subject_client_state, save_subject_consensus_state, }, - IbcClient, Status, StorageState, ZERO_HEIGHT, + IbcClient, IbcClientError, Status, StorageState, ZERO_HEIGHT, }; use ics23::ibc_api::SDK_SPECS; use unionlabs::{ @@ -33,16 +33,16 @@ use crate::{ get_current_or_next_consensus_state_meta, get_current_or_prev_consensus_state_meta, save_consensus_state_metadata, }, - zkp_verifier::ZKPVerifier, + zkp_verifier::ZkpVerifier, }; type WasmClientState = unionlabs::ibc::lightclients::wasm::client_state::ClientState; type WasmConsensusState = unionlabs::ibc::lightclients::wasm::consensus_state::ConsensusState; -pub struct CometblsLightClient(PhantomData); +pub struct CometblsLightClient(PhantomData); -impl IbcClient for CometblsLightClient { +impl IbcClient for CometblsLightClient { type Error = Error; type CustomQuery = Empty; @@ -66,14 +66,11 @@ impl IbcClient for CometblsLightClient { proof: Vec, path: MerklePath, value: StorageState, - ) -> Result<(), Self::Error> { + ) -> Result<(), IbcClientError> { let consensus_state: WasmConsensusState = read_consensus_state(deps, &height)?.ok_or(Error::ConsensusStateNotFound(height))?; - let merkle_proof = - MerkleProof::decode(proof.as_ref()).map_err(|e| Error::DecodeFromProto { - reason: format!("{:?}", e), - })?; + let merkle_proof = MerkleProof::decode(proof.as_ref()).map_err(Error::MerkleProofDecode)?; match value { StorageState::Occupied(value) => ics23::ibc_api::verify_membership( @@ -91,13 +88,14 @@ impl IbcClient for CometblsLightClient { ), } .map_err(Error::VerifyMembership) + .map_err(Into::into) } fn verify_header( deps: Deps, env: Env, header: Self::Header, - ) -> Result<(), Self::Error> { + ) -> Result<(), IbcClientError> { let client_state: WasmClientState = read_client_state(deps)?; let consensus_state: WasmConsensusState = read_consensus_state(deps, &header.trusted_height)? @@ -169,21 +167,22 @@ impl IbcClient for CometblsLightClient { &header.zero_knowledge_proof, ) .map_err(Error::InvalidZKP) + .map_err(Into::into) } fn verify_misbehaviour( _deps: Deps, _env: Env, _misbehaviour: Self::Misbehaviour, - ) -> Result<(), Self::Error> { - Err(Error::Unimplemented) + ) -> Result<(), IbcClientError> { + Err(Error::Unimplemented.into()) } fn update_state( mut deps: DepsMut, _env: Env, header: Self::Header, - ) -> Result, Self::Error> { + ) -> Result, IbcClientError> { let mut client_state: WasmClientState = read_client_state(deps.as_ref())?; let mut consensus_state: WasmConsensusState = read_consensus_state(deps.as_ref(), &header.trusted_height)? @@ -207,13 +206,13 @@ impl IbcClient for CometblsLightClient { // Normalized to nanoseconds to follow tendermint convention consensus_state.data.timestamp = header.signed_header.time.unix_nanos(); - save_client_state(deps.branch(), client_state); + save_client_state::(deps.branch(), client_state); save_consensus_state_metadata( deps.branch(), consensus_state.data.timestamp, untrusted_height, ); - save_consensus_state(deps, consensus_state, &untrusted_height); + save_consensus_state::(deps, consensus_state, &untrusted_height); Ok(vec![untrusted_height]) } @@ -222,14 +221,14 @@ impl IbcClient for CometblsLightClient { _deps: DepsMut, _env: Env, _client_message: Vec, - ) -> Result<(), Self::Error> { - Err(Error::Unimplemented) + ) -> Result<(), IbcClientError> { + Err(Error::Unimplemented.into()) } fn check_for_misbehaviour_on_header( deps: Deps, header: Self::Header, - ) -> Result { + ) -> Result> { let height = height_from_header(&header); let expected_timestamp: u64 = header.signed_header.time.unix_nanos(); @@ -244,8 +243,9 @@ impl IbcClient for CometblsLightClient { app_hash: MerkleRoot { hash }, next_validators_hash, }, - }) = read_consensus_state::<_, ConsensusState>(deps, &height)? + }) = read_consensus_state::(deps, &height)? { + // NOTE: Expanded for clarity, could just be Ok(condition) if timestamp != expected_timestamp || hash != header.signed_header.app_hash || next_validators_hash != header.signed_header.next_validators_hash @@ -282,8 +282,8 @@ impl IbcClient for CometblsLightClient { fn check_for_misbehaviour_on_misbehaviour( _deps: Deps, _misbehaviour: Self::Misbehaviour, - ) -> Result { - Err(Error::Unimplemented) + ) -> Result> { + Err(Error::Unimplemented.into()) } fn verify_upgrade_and_update_state( @@ -292,11 +292,13 @@ impl IbcClient for CometblsLightClient { _upgrade_consensus_state: Self::ConsensusState, _proof_upgrade_client: Vec, _proof_upgrade_consensus_state: Vec, - ) -> Result<(), Self::Error> { - Err(Error::Unimplemented) + ) -> Result<(), IbcClientError> { + Err(Error::Unimplemented.into()) } - fn migrate_client_store(mut deps: DepsMut) -> Result<(), Self::Error> { + fn migrate_client_store( + mut deps: DepsMut, + ) -> Result<(), IbcClientError> { let subject_client_state: WasmClientState = read_subject_client_state(deps.as_ref())?; let substitute_client_state: WasmClientState = read_substitute_client_state(deps.as_ref())?; @@ -322,14 +324,14 @@ impl IbcClient for CometblsLightClient { substitute_client_state.latest_height, ); - save_subject_consensus_state( + save_subject_consensus_state::( deps.branch(), substitute_consensus_state, &substitute_client_state.latest_height, ); let scs = substitute_client_state.data; - save_subject_client_state( + save_subject_client_state::( deps, WasmClientState { data: ClientState { @@ -350,17 +352,15 @@ impl IbcClient for CometblsLightClient { fn status( deps: Deps, env: &cosmwasm_std::Env, - ) -> Result { + ) -> Result> { let client_state: WasmClientState = read_client_state(deps)?; if client_state.data.frozen_height != ZERO_HEIGHT { return Ok(Status::Frozen); } - let Some(consensus_state) = read_consensus_state::( - deps, - &client_state.latest_height, - )? + let Some(consensus_state) = + read_consensus_state::(deps, &client_state.latest_height)? else { return Ok(Status::Expired); }; @@ -379,20 +379,18 @@ impl IbcClient for CometblsLightClient { fn export_metadata( _deps: Deps, _env: &cosmwasm_std::Env, - ) -> Result, Self::Error> { + ) -> Result, IbcClientError> { Ok(Vec::new()) } fn timestamp_at_height( deps: Deps, height: Height, - ) -> Result { - Ok( - read_consensus_state::(deps, &height)? - .ok_or(Error::ConsensusStateNotFound(height))? - .data - .timestamp, - ) + ) -> Result> { + Ok(read_consensus_state::(deps, &height)? + .ok_or(Error::ConsensusStateNotFound(height))? + .data + .timestamp) } } @@ -537,7 +535,8 @@ mod tests { CometblsLightClient::<()>::migrate_client_store(deps.as_mut()).unwrap(); - let wasm_client_state: WasmClientState = read_subject_client_state(deps.as_ref()).unwrap(); + let wasm_client_state: WasmClientState = + read_subject_client_state::>(deps.as_ref()).unwrap(); // we didn't miss updating any fields assert_eq!(wasm_client_state, substitute_wasm_client_state); // client is unfrozen @@ -545,9 +544,12 @@ mod tests { // the new consensus state is saved under the correct height assert_eq!( - read_subject_consensus_state(deps.as_ref(), &INITIAL_SUBSTITUTE_CONSENSUS_STATE_HEIGHT) - .unwrap() - .unwrap(), + read_subject_consensus_state::>( + deps.as_ref(), + &INITIAL_SUBSTITUTE_CONSENSUS_STATE_HEIGHT + ) + .unwrap() + .unwrap(), substitute_wasm_consensus_state ); @@ -596,7 +598,7 @@ mod tests { ); assert_eq!( CometblsLightClient::<()>::migrate_client_store(deps.as_mut()), - Err(Error::MigrateFieldsChanged) + Err(Error::MigrateFieldsChanged.into()) ); } } @@ -623,7 +625,7 @@ mod tests { assert_eq!( CometblsLightClient::<()>::migrate_client_store(deps.as_mut()), - Err(Error::SubstituteClientFrozen) + Err(Error::SubstituteClientFrozen.into()) ); } } diff --git a/light-clients/cometbls-light-client/src/contract.rs b/light-clients/cometbls-light-client/src/contract.rs index 72c84fcd40..aecf871d75 100644 --- a/light-clients/cometbls-light-client/src/contract.rs +++ b/light-clients/cometbls-light-client/src/contract.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{entry_point, DepsMut, Env, MessageInfo, Response}; use ics008_wasm_client::{ define_cosmwasm_light_client_contract, storage_utils::{save_proto_client_state, save_proto_consensus_state}, - InstantiateMsg, + CustomQueryOf, InstantiateMsg, }; use protos::ibc::lightclients::wasm::v1::{ ClientState as ProtoClientState, ConsensusState as ProtoConsensusState, @@ -19,28 +19,26 @@ use crate::{client::CometblsLightClient, errors::Error}; // of IBC (probably v9). When that feature is implemented, we can move this to the ics008 macro. #[entry_point] pub fn instantiate( - mut deps: DepsMut, + mut deps: DepsMut>, _env: Env, _info: MessageInfo, msg: InstantiateMsg, ) -> Result { let client_state = - ClientState::decode_as::(&msg.client_state).map_err(|e| Error::DecodeFromProto { - reason: format!("{:?}", e), - })?; + ClientState::decode_as::(&msg.client_state).map_err(Error::ClientStateDecode)?; if client_state.chain_id.len() > 31 { - return Err(Error::InvalidChainID); + return Err(Error::InvalidChainId); } - save_proto_consensus_state( + save_proto_consensus_state::( deps.branch(), ProtoConsensusState { data: msg.consensus_state.into(), }, &client_state.latest_height, ); - save_proto_client_state( + save_proto_client_state::( deps, ProtoClientState { data: msg.client_state.into(), diff --git a/light-clients/cometbls-light-client/src/errors.rs b/light-clients/cometbls-light-client/src/errors.rs index da1d3b6a23..b8768db14c 100644 --- a/light-clients/cometbls-light-client/src/errors.rs +++ b/light-clients/cometbls-light-client/src/errors.rs @@ -1,12 +1,16 @@ -use cosmwasm_std::StdError; -use thiserror::Error as ThisError; +use ics008_wasm_client::IbcClientError; use unionlabs::{ encoding::{DecodeErrorOf, Proto}, hash::H256, - ibc::{core::client::height::Height, lightclients::cometbls::header::Header}, + ibc::{ + core::{client::height::Height, commitment::merkle_proof::MerkleProof}, + lightclients::cometbls, + }, }; -#[derive(ThisError, Debug, PartialEq)] +use crate::{client::CometblsLightClient, zkp_verifier::ZkpVerifier}; + +#[derive(thiserror::Error, Debug, PartialEq)] pub enum InvalidHeaderError { #[error("signed header's height ({signed_height}) must be greater than trusted height ({trusted_height})")] SignedHeaderHeightMustBeMoreRecent { @@ -20,131 +24,41 @@ pub enum InvalidHeaderError { }, #[error("header with timestamp ({0}) is expired")] HeaderExpired(u64), - #[error("negative header timestamp seconds ({0})")] - NegativeTimestamp(i64), - #[error("negative header timestamp nanos ({0})")] - NegativeTimestampNanos(i32), #[error("signed header timestamp ({signed_timestamp}) cannot exceed the max clock drift ({max_clock_drift})")] SignedHeaderCannotExceedMaxClockDrift { signed_timestamp: u64, max_clock_drift: u64, }, - #[error("commit hash ({commit_hash}) does not match with the signed header root ({signed_header_root})")] - SignedHeaderMismatchWithCommitHash { - commit_hash: H256, - signed_header_root: H256, - }, #[error("the validators hash ({actual}) doesn't match the trusted validators hash ({expected}) for an adjacent block")] InvalidValidatorsHash { expected: H256, actual: H256 }, } -#[derive(ThisError, Debug, PartialEq)] +#[derive(thiserror::Error, Debug, PartialEq)] pub enum Error { - #[error("{0}")] - Std(#[from] StdError), - #[error("math operation with overflow")] MathOverflow, - #[error("timestamp is negative ({0})")] - NegativeTimestamp(i64), - - #[error("error while decoding proto ({reason})")] - DecodeFromProto { reason: String }, - #[error("unimplemented feature")] Unimplemented, - #[error("Unable to decode header: {0:?}")] - HeaderDecode(DecodeErrorOf), + #[error("unable to decode merkle proof")] + MerkleProofDecode(#[source] DecodeErrorOf), - #[error("Unknown type url")] - UnknownTypeUrl, + #[error("unable to decode client state")] + ClientStateDecode(#[source] DecodeErrorOf), #[error("Client state not found")] ClientStateNotFound, - #[error("Invalid proof format")] - InvalidProofFormat, - - #[error("Invalid client id")] - InvalidClientId, - - #[error("Invalid public key: {0}")] - InvalidPublicKey(String), - - #[error("Invalid height")] - InvalidHeight, - #[error(transparent)] InvalidHeader(#[from] InvalidHeaderError), #[error("Invalid ZKP: {0:?}")] InvalidZKP(cometbls_groth16_verifier::Error), - #[error("Invalid sync committee")] - InvalidSyncCommittee, - - #[error("Merkle root cannot be calculated")] - UnableToCalculateMerkleRoot, - - #[error("No next sync committee")] - NoNextSyncCommittee, - #[error("Consensus state not found for {0}")] ConsensusStateNotFound(Height), - #[error("Overflow happened during summing durations.")] - DurationAdditionOverflow, - - #[error("Timestamp not set")] - TimestampNotSet, - - #[error("Verification error: {0}")] - Verification(String), - - #[error("Unexpected timestamp: Expected timestamp {0}, got {1}")] - UnexpectedTimestamp(u64, u64), - - #[error("Future period")] - FuturePeriod, - - #[error("Cannot generate proof")] - CannotGenerateProof, - - #[error("Invalid chain version")] - InvalidChainVersion, - - #[error("Invalid path {0}")] - InvalidPath(String), - - #[error("Invalid membership value")] - InvalidValue, - - #[error("Invalid commitment key. Expected {0}, got {1}.")] - InvalidCommitmentKey(String, String), - - #[error("Missing field in the protobuf encoded data")] - MissingProtoField, - - #[error("Client's store period must be equal to update's finalized period")] - StorePeriodMustBeEqualToFinalizedPeriod, - - #[error("Proof is empty")] - EmptyProof, - - #[error("Batching proofs are not supported")] - BatchingProofsNotSupported, - - #[error("Expected value: '{0}' and stored value '{1}' doesn't match")] - ExpectedAndStoredValueMismatch(String, String), - - #[error("Custom query: {0}")] - CustomQuery(String), - - #[error("Wasm client error: {0}")] - Wasm(String), - #[error("verify membership error: {0}")] VerifyMembership(#[from] ics23::ibc_api::VerifyMembershipError), @@ -155,44 +69,19 @@ pub enum Error { MigrateFieldsChanged, #[error("the chain id cannot be more than 31 bytes long to fit in the bn254 scalar field")] - InvalidChainID, + InvalidChainId, } -impl Error { - pub fn invalid_public_key(s: S) -> Error { - Error::InvalidPublicKey(s.to_string()) - } - - pub fn invalid_commitment_key, B2: AsRef<[u8]>>( - expected: B1, - got: B2, - ) -> Error { - Error::InvalidCommitmentKey(hex::encode(expected), hex::encode(got)) - } - - pub fn stored_value_mismatch, B2: AsRef<[u8]>>(expected: B1, got: B2) -> Error { - Error::ExpectedAndStoredValueMismatch(hex::encode(expected), hex::encode(got)) - } - - pub fn custom_query(s: S) -> Error { - Error::CustomQuery(s.to_string()) +// required for IbcClient trait +impl From for IbcClientError> { + fn from(value: Error) -> Self { + IbcClientError::ClientSpecific(value) } } -impl From for Error { - fn from(error: ics008_wasm_client::storage_utils::Error) -> Self { - match error { - ics008_wasm_client::storage_utils::Error::ClientStateNotFound => { - Error::ClientStateNotFound - } - ics008_wasm_client::storage_utils::Error::ClientStateDecode => Error::DecodeFromProto { - reason: error.to_string(), - }, - ics008_wasm_client::storage_utils::Error::ConsensusStateDecode => { - Error::DecodeFromProto { - reason: error.to_string(), - } - } - } +// convenience +impl From for IbcClientError> { + fn from(value: InvalidHeaderError) -> Self { + IbcClientError::ClientSpecific(Error::InvalidHeader(value)) } } diff --git a/light-clients/cometbls-light-client/src/zkp_verifier.rs b/light-clients/cometbls-light-client/src/zkp_verifier.rs index 221c782067..4523af690d 100644 --- a/light-clients/cometbls-light-client/src/zkp_verifier.rs +++ b/light-clients/cometbls-light-client/src/zkp_verifier.rs @@ -1,6 +1,6 @@ use unionlabs::{hash::H256, ibc::lightclients::cometbls::light_header::LightHeader}; -pub trait ZKPVerifier { +pub trait ZkpVerifier { fn verify_zkp( chain_id: &str, trusted_validators_hash: H256, @@ -11,11 +11,11 @@ pub trait ZKPVerifier { } } -impl ZKPVerifier for () {} +impl ZkpVerifier for () {} pub struct MockZKPVerifier; -impl ZKPVerifier for MockZKPVerifier { +impl ZkpVerifier for MockZKPVerifier { fn verify_zkp( _chain_id: &str, _trusted_validators_hash: H256, diff --git a/light-clients/ethereum-light-client/src/client.rs b/light-clients/ethereum-light-client/src/client.rs index a0249ded5f..49d263b250 100644 --- a/light-clients/ethereum-light-client/src/client.rs +++ b/light-clients/ethereum-light-client/src/client.rs @@ -11,7 +11,7 @@ use ics008_wasm_client::{ save_consensus_state, save_subject_client_state, save_subject_consensus_state, update_client_state, }, - IbcClient, Status, StorageState, FROZEN_HEIGHT, ZERO_HEIGHT, + IbcClient, IbcClientError, Status, StorageState, FROZEN_HEIGHT, ZERO_HEIGHT, }; use sha3::Digest; use unionlabs::{ @@ -73,7 +73,7 @@ impl IbcClient for EthereumLightClient { proof: Vec, mut path: MerklePath, value: ics008_wasm_client::StorageState, - ) -> Result<(), Self::Error> { + ) -> Result<(), IbcClientError> { let consensus_state: WasmConsensusState = read_consensus_state(deps, &height)?.ok_or(Error::ConsensusStateNotFound(height))?; let client_state: WasmClientState = read_client_state(deps)?; @@ -116,7 +116,7 @@ impl IbcClient for EthereumLightClient { deps: Deps, env: Env, header: Self::Header, - ) -> Result<(), Self::Error> { + ) -> Result<(), IbcClientError> { let trusted_sync_committee = header.trusted_sync_committee; let wasm_consensus_state = read_consensus_state(deps, &trusted_sync_committee.trusted_height)?.ok_or( @@ -146,7 +146,8 @@ impl IbcClient for EthereumLightClient { current_slot, wasm_client_state.data.genesis_validators_root, VerificationContext { deps }, - )?; + ) + .map_err(Error::ValidateLightClient)?; // check whether at least 2/3 of the sync committee signed ensure( @@ -163,7 +164,8 @@ impl IbcClient for EthereumLightClient { &wasm_client_state.data.ibc_contract_address, &proof_data.proof, &proof_data.storage_root, - )?; + ) + .map_err(Error::VerifyAccountStorageRoot)?; Ok(()) } @@ -172,7 +174,7 @@ impl IbcClient for EthereumLightClient { deps: Deps, env: Env, misbehaviour: Self::Misbehaviour, - ) -> Result<(), Self::Error> { + ) -> Result<(), IbcClientError> { // There is no point to check for misbehaviour when the headers are not for the same height // TODO(aeryz): this will be `finalized_header` when we implement tracking justified header let (slot_1, slot_2) = ( @@ -212,7 +214,8 @@ impl IbcClient for EthereumLightClient { current_slot, wasm_client_state.data.genesis_validators_root, VerificationContext { deps }, - )?; + ) + .map_err(Error::ValidateLightClient)?; validate_light_client_update::, VerificationContext>( &ctx, @@ -220,7 +223,8 @@ impl IbcClient for EthereumLightClient { current_slot, wasm_client_state.data.genesis_validators_root, VerificationContext { deps }, - )?; + ) + .map_err(Error::ValidateLightClient)?; Ok(()) } @@ -229,7 +233,7 @@ impl IbcClient for EthereumLightClient { mut deps: DepsMut, _env: Env, header: Self::Header, - ) -> Result, Self::Error> { + ) -> Result, IbcClientError> { let trusted_sync_committee = header.trusted_sync_committee; let trusted_height = trusted_sync_committee.trusted_height; @@ -288,7 +292,7 @@ impl IbcClient for EthereumLightClient { if client_state.data.latest_slot < consensus_update.attested_header.beacon.slot { client_state.data.latest_slot = consensus_update.attested_header.beacon.slot; - update_client_state(deps.branch(), client_state, updated_height); + update_client_state::(deps.branch(), client_state, updated_height); } } @@ -297,7 +301,7 @@ impl IbcClient for EthereumLightClient { revision_height: updated_height, }; - save_consensus_state(deps, consensus_state, &updated_height); + save_consensus_state::(deps, consensus_state, &updated_height); Ok(vec![updated_height]) } @@ -306,10 +310,10 @@ impl IbcClient for EthereumLightClient { deps: DepsMut, _env: Env, _client_message: Vec, - ) -> Result<(), Self::Error> { + ) -> Result<(), IbcClientError> { let mut client_state: WasmClientState = read_client_state(deps.as_ref())?; client_state.data.frozen_height = FROZEN_HEIGHT; - save_client_state(deps, client_state); + save_client_state::(deps, client_state); Ok(()) } @@ -317,15 +321,13 @@ impl IbcClient for EthereumLightClient { fn check_for_misbehaviour_on_header( deps: Deps, header: Self::Header, - ) -> Result { + ) -> Result> { let height = Height { revision_number: 0, revision_height: header.consensus_update.attested_header.beacon.slot, }; - if let Some(consensus_state) = - read_consensus_state::(deps, &height)? - { + if let Some(consensus_state) = read_consensus_state::(deps, &height)? { // New header is given with the same height but the storage roots don't match. if consensus_state.data.storage_root != header.account_update.account_proof.storage_root || consensus_state.data.slot != header.consensus_update.attested_header.beacon.slot @@ -346,7 +348,7 @@ impl IbcClient for EthereumLightClient { fn check_for_misbehaviour_on_misbehaviour( _deps: Deps, misbehaviour: Self::Misbehaviour, - ) -> Result { + ) -> Result> { if misbehaviour.update_1.attested_header.beacon.slot == misbehaviour.update_2.attested_header.beacon.slot { @@ -366,11 +368,13 @@ impl IbcClient for EthereumLightClient { _upgrade_consensus_state: Self::ConsensusState, _proof_upgrade_client: Vec, _proof_upgrade_consensus_state: Vec, - ) -> Result<(), Self::Error> { - Err(Error::Unimplemented) + ) -> Result<(), IbcClientError> { + Err(Error::Unimplemented.into()) } - fn migrate_client_store(mut deps: DepsMut) -> Result<(), Self::Error> { + fn migrate_client_store( + mut deps: DepsMut, + ) -> Result<(), IbcClientError> { let subject_client_state: WasmClientState = read_subject_client_state(deps.as_ref())?; let substitute_client_state: WasmClientState = read_substitute_client_state(deps.as_ref())?; @@ -390,14 +394,14 @@ impl IbcClient for EthereumLightClient { substitute_client_state.latest_height, ))?; - save_subject_consensus_state( + save_subject_consensus_state::( deps.branch(), substitute_consensus_state, &substitute_client_state.latest_height, ); let scs = substitute_client_state.data; - save_subject_client_state( + save_subject_client_state::( deps, WasmClientState { data: ClientState { @@ -421,17 +425,15 @@ impl IbcClient for EthereumLightClient { Ok(()) } - fn status(deps: Deps, env: &Env) -> Result { + fn status(deps: Deps, env: &Env) -> Result> { let client_state: WasmClientState = read_client_state(deps)?; if client_state.data.frozen_height != ZERO_HEIGHT { return Ok(Status::Frozen); } - let Some(consensus_state) = read_consensus_state::( - deps, - &client_state.latest_height, - )? + let Some(consensus_state) = + read_consensus_state::(deps, &client_state.latest_height)? else { return Ok(Status::Expired); }; @@ -450,20 +452,18 @@ impl IbcClient for EthereumLightClient { fn export_metadata( _deps: Deps, _env: &Env, - ) -> Result, Self::Error> { + ) -> Result, IbcClientError> { Ok(Vec::new()) } fn timestamp_at_height( deps: Deps, height: Height, - ) -> Result { - Ok( - read_consensus_state::(deps, &height)? - .ok_or(Error::ConsensusStateNotFound(height))? - .data - .timestamp, - ) + ) -> Result> { + Ok(read_consensus_state::(deps, &height)? + .ok_or(Error::ConsensusStateNotFound(height))? + .data + .timestamp) } } @@ -540,7 +540,7 @@ fn do_verify_membership( &rlp::encode(&storage_proof.value), &storage_proof.proof, ) - .map_err(Into::into) + .map_err(Error::VerifyStorageProof) } /// Verifies that no value is committed at `path` in the counterparty light client's storage. @@ -556,7 +556,9 @@ fn do_verify_non_membership( H256(storage_proof.key.to_be_bytes()), )?; - if verify_storage_absence(storage_root, storage_proof.key, &storage_proof.proof)? { + if verify_storage_absence(storage_root, storage_proof.key, &storage_proof.proof) + .map_err(Error::VerifyStorageAbsence)? + { Ok(()) } else { Err(Error::CounterpartyStorageNotNil) @@ -689,9 +691,9 @@ mod test { let wasm_consensus_state: WasmConsensusState = serde_json::from_str(include_str!("./test/consensus_state.json")).unwrap(); - save_client_state(deps.as_mut(), wasm_client_state); + save_client_state::(deps.as_mut(), wasm_client_state); - save_consensus_state( + save_consensus_state::( deps.as_mut(), wasm_consensus_state, &INITIAL_CONSENSUS_STATE_HEIGHT, @@ -721,7 +723,7 @@ mod test { wasm_client_state.data.frozen_height = FROZEN_HEIGHT; - save_client_state(deps.as_mut(), wasm_client_state); + save_client_state::(deps.as_mut(), wasm_client_state); assert_eq!( EthereumLightClient::status(deps.as_ref(), &mock_env()), @@ -742,7 +744,7 @@ mod test { let mut wasm_client_state: WasmClientState = serde_json::from_str(include_str!("./test/client_state.json")).unwrap(); - save_client_state(deps.as_mut(), wasm_client_state.clone()); + save_client_state::(deps.as_mut(), wasm_client_state.clone()); // Client returns expired here because it cannot find the consensus state assert_eq!( @@ -753,14 +755,14 @@ mod test { let wasm_consensus_state: WasmConsensusState = serde_json::from_str(include_str!("./test/consensus_state.json")).unwrap(); - save_consensus_state( + save_consensus_state::( deps.as_mut(), wasm_consensus_state.clone(), &INITIAL_CONSENSUS_STATE_HEIGHT, ); wasm_client_state.data.trusting_period = 10; - save_client_state(deps.as_mut(), wasm_client_state.clone()); + save_client_state::(deps.as_mut(), wasm_client_state.clone()); let mut env = mock_env(); env.block.time = Timestamp::from_nanos( @@ -796,8 +798,8 @@ mod test { let wasm_consensus_state: WasmConsensusState = serde_json::from_str(include_str!("./test/consensus_state.json")).unwrap(); - save_client_state(deps.as_mut(), wasm_client_state); - save_consensus_state( + save_client_state::(deps.as_mut(), wasm_client_state); + save_consensus_state::( deps.as_mut(), wasm_consensus_state, &INITIAL_CONSENSUS_STATE_HEIGHT, @@ -817,15 +819,16 @@ mod test { > update.trusted_sync_committee.trusted_height.revision_height { // It's a finality update - let wasm_consensus_state: WasmConsensusState = read_consensus_state( - deps.as_ref(), - &Height { - revision_number: 0, - revision_height: update.consensus_update.attested_header.beacon.slot, - }, - ) - .unwrap() - .unwrap(); + let wasm_consensus_state: WasmConsensusState = + read_consensus_state::( + deps.as_ref(), + &Height { + revision_number: 0, + revision_height: update.consensus_update.attested_header.beacon.slot, + }, + ) + .unwrap() + .unwrap(); // Slot is updated. assert_eq!( wasm_consensus_state.data.slot, @@ -838,7 +841,8 @@ mod test { ); // Latest slot is updated. // TODO(aeryz): Add cases for `store_period == update_period` and `update_period == store_period + 1` - let wasm_client_state: WasmClientState = read_client_state(deps.as_ref()).unwrap(); + let wasm_client_state: WasmClientState = + read_client_state::(deps.as_ref()).unwrap(); assert_eq!( wasm_client_state.data.latest_slot, update.consensus_update.attested_header.beacon.slot @@ -849,15 +853,16 @@ mod test { update.trusted_sync_committee.trusted_height.revision_height, update.consensus_update.attested_header.beacon.slot, ); - let wasm_consensus_state: WasmConsensusState = read_consensus_state( - deps.as_ref(), - &Height { - revision_number: 0, - revision_height: updated_height, - }, - ) - .unwrap() - .unwrap(); + let wasm_consensus_state: WasmConsensusState = + read_consensus_state::( + deps.as_ref(), + &Height { + revision_number: 0, + revision_height: updated_height, + }, + ) + .unwrap() + .unwrap(); assert_eq!( wasm_consensus_state.data.next_sync_committee.unwrap(), @@ -933,8 +938,8 @@ mod test { serde_json::from_str(&fs::read_to_string("src/test/consensus_state.json").unwrap()) .unwrap(); - save_client_state(deps.as_mut(), wasm_client_state); - save_consensus_state( + save_client_state::(deps.as_mut(), wasm_client_state); + save_consensus_state::( deps.as_mut(), wasm_consensus_state.clone(), &INITIAL_CONSENSUS_STATE_HEIGHT, @@ -1235,7 +1240,8 @@ mod test { EthereumLightClient::migrate_client_store(deps.as_mut()).unwrap(); - let wasm_client_state: WasmClientState = read_subject_client_state(deps.as_ref()).unwrap(); + let wasm_client_state: WasmClientState = + read_subject_client_state::(deps.as_ref()).unwrap(); // we didn't miss updating any fields assert_eq!(wasm_client_state, substitute_wasm_client_state); // client is unfrozen @@ -1243,9 +1249,12 @@ mod test { // the new consensus state is saved under the correct height assert_eq!( - read_subject_consensus_state(deps.as_ref(), &INITIAL_SUBSTITUTE_CONSENSUS_STATE_HEIGHT) - .unwrap() - .unwrap(), + read_subject_consensus_state::( + deps.as_ref(), + &INITIAL_SUBSTITUTE_CONSENSUS_STATE_HEIGHT + ) + .unwrap() + .unwrap(), substitute_wasm_consensus_state ) } @@ -1285,7 +1294,7 @@ mod test { ); assert_eq!( EthereumLightClient::migrate_client_store(deps.as_mut()), - Err(Error::MigrateFieldsChanged) + Err(Error::MigrateFieldsChanged.into()) ); } } @@ -1312,7 +1321,7 @@ mod test { assert_eq!( EthereumLightClient::migrate_client_store(deps.as_mut()), - Err(Error::SubstituteClientFrozen) + Err(Error::SubstituteClientFrozen.into()) ); } } diff --git a/light-clients/ethereum-light-client/src/contract.rs b/light-clients/ethereum-light-client/src/contract.rs index c5297f1d82..b7cee4fde7 100644 --- a/light-clients/ethereum-light-client/src/contract.rs +++ b/light-clients/ethereum-light-client/src/contract.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{entry_point, DepsMut, Env, MessageInfo, Response}; use ics008_wasm_client::{ define_cosmwasm_light_client_contract, storage_utils::{save_proto_client_state, save_proto_consensus_state}, - InstantiateMsg, + CustomQueryOf, InstantiateMsg, }; use protos::ibc::lightclients::wasm::v1::{ ClientState as ProtoClientState, ConsensusState as ProtoConsensusState, @@ -19,7 +19,7 @@ use crate::{client::EthereumLightClient, errors::Error}; // of IBC (probably v9). When that feature is implemented, we can move this to the ics008 macro. #[entry_point] pub fn instantiate( - mut deps: DepsMut, + mut deps: DepsMut>, _env: Env, _info: MessageInfo, msg: InstantiateMsg, @@ -29,7 +29,7 @@ pub fn instantiate( reason: format!("{:?}", e), })?; - save_proto_consensus_state( + save_proto_consensus_state::( deps.branch(), ProtoConsensusState { data: msg.consensus_state.into(), @@ -39,7 +39,7 @@ pub fn instantiate( revision_height: client_state.latest_slot, }, ); - save_proto_client_state( + save_proto_client_state::( deps, ProtoClientState { data: msg.client_state.into(), diff --git a/light-clients/ethereum-light-client/src/custom_query.rs b/light-clients/ethereum-light-client/src/custom_query.rs index 65f85306c3..51279193a1 100644 --- a/light-clients/ethereum-light-client/src/custom_query.rs +++ b/light-clients/ethereum-light-client/src/custom_query.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{Binary, Deps}; -use ethereum_verifier::BlsVerify; +use ethereum_verifier::{BlsVerify, InvalidSignature}; use unionlabs::{ bls::{BlsPublicKey, BlsSignature}, cosmwasm::wasm::union::custom_query::{query_fast_aggregate_verify, UnionCustomQuery}, @@ -17,11 +17,11 @@ impl<'a> BlsVerify for VerificationContext<'a> { msg: Vec, signature: BlsSignature, ) -> Result<(), ethereum_verifier::Error> { - let public_keys_: Vec<_> = public_keys.into_iter().cloned().collect(); + let public_keys: Vec<_> = public_keys.into_iter().cloned().collect(); let is_valid = query_fast_aggregate_verify( self.deps, - public_keys_ + public_keys .clone() .into_iter() .map(|x| Binary(x.into())) @@ -29,14 +29,15 @@ impl<'a> BlsVerify for VerificationContext<'a> { msg.clone().into(), Binary(signature.into()), ) - .map_err(|e| ethereum_verifier::Error::CustomError(e.to_string()))?; + .map_err(ethereum_verifier::Error::CustomQuery)?; ensure( is_valid, - ethereum_verifier::Error::CustomError(format!( - "signature cannot be verified: public_keys: {:#?}, msg: {:#?}, signature: {}", - public_keys_, msg, signature - )), + ethereum_verifier::Error::InvalidSignature(Box::new(InvalidSignature { + public_keys, + msg, + signature, + })), ) } } diff --git a/light-clients/ethereum-light-client/src/errors.rs b/light-clients/ethereum-light-client/src/errors.rs index 43e6195f76..7a193d75ed 100644 --- a/light-clients/ethereum-light-client/src/errors.rs +++ b/light-clients/ethereum-light-client/src/errors.rs @@ -1,16 +1,10 @@ -use cosmwasm_std::StdError; -use ethereum_verifier::{ - ValidateLightClientError, VerifyAccountStorageRootError, VerifyStorageAbsenceError, - VerifyStorageProofError, -}; -use thiserror::Error as ThisError; +use ics008_wasm_client::IbcClientError; use unionlabs::{bls::BlsPublicKey, hash::H256, ibc::core::client::height::Height}; -#[derive(ThisError, Debug, PartialEq)] -pub enum Error { - #[error("{0}")] - Std(#[from] StdError), +use crate::client::EthereumLightClient; +#[derive(thiserror::Error, Debug, PartialEq)] +pub enum Error { #[error("unimplemented feature")] Unimplemented, @@ -20,8 +14,8 @@ pub enum Error { #[error("client state not found")] ClientStateNotFound, - #[error("invalid proof format ({0})")] - InvalidProofFormat(String), + #[error("custom query error")] + CustomQuery(#[from] unionlabs::cosmwasm::wasm::union::custom_query::Error), #[error( "given trusted sync committee doesn't match the given aggregate public \ @@ -38,17 +32,17 @@ pub enum Error { #[error("consensus state not found at height {0}")] ConsensusStateNotFound(Height), - #[error("{0}")] - ValidateLightClient(#[from] ValidateLightClientError), + #[error("validate light client error")] + ValidateLightClient(#[source] ethereum_verifier::Error), - #[error("{0}")] - VerifyAccountStorageRoot(#[from] VerifyAccountStorageRootError), + #[error("verify account storage root error")] + VerifyAccountStorageRoot(#[source] ethereum_verifier::Error), - #[error("{0}")] - VerifyStorageAbsence(#[from] VerifyStorageAbsenceError), + #[error("verify storage absence error")] + VerifyStorageAbsence(#[source] ethereum_verifier::Error), - #[error("{0}")] - VerifyStorageProof(#[from] VerifyStorageProofError), + #[error("verify storage proof error")] + VerifyStorageProof(#[source] ethereum_verifier::Error), #[error("IBC path is empty")] EmptyIbcPath, @@ -71,24 +65,9 @@ pub enum Error { #[error("expected value ({expected:?}) and stored value ({stored:?}) don't match")] StoredValueMismatch { expected: H256, stored: H256 }, - #[error("storage root mismatch, expected `{expected}` but found `{found}`")] - StorageRootMismatch { expected: H256, found: H256 }, - - #[error("wasm client error ({0})")] - Wasm(String), - - #[error("next sync committee can't be changed after being set")] - NextSyncCommitteeCannotBeModified, - - #[error("the slot number that is saved previously to the consensus state cannot be changed")] - SlotCannotBeModified, - #[error("the proof path {0} is not unknown")] UnknownIbcPath(String), - #[error("error while calling custom query: {0}")] - CustomQuery(#[from] unionlabs::cosmwasm::wasm::union::custom_query::Error), - #[error("not enough signatures")] NotEnoughSignatures, @@ -105,20 +84,8 @@ pub enum Error { MisbehaviourCannotExist(u64, u64), } -impl From for Error { - fn from(error: ics008_wasm_client::storage_utils::Error) -> Self { - match error { - ics008_wasm_client::storage_utils::Error::ClientStateNotFound => { - Error::ClientStateNotFound - } - ics008_wasm_client::storage_utils::Error::ClientStateDecode => Error::DecodeFromProto { - reason: error.to_string(), - }, - ics008_wasm_client::storage_utils::Error::ConsensusStateDecode => { - Error::DecodeFromProto { - reason: error.to_string(), - } - } - } +impl From for IbcClientError { + fn from(value: Error) -> Self { + IbcClientError::ClientSpecific(value) } } diff --git a/light-clients/scroll-light-client/src/client.rs b/light-clients/scroll-light-client/src/client.rs index 89e0145b6c..03a2b5c07b 100644 --- a/light-clients/scroll-light-client/src/client.rs +++ b/light-clients/scroll-light-client/src/client.rs @@ -5,7 +5,7 @@ use ics008_wasm_client::{ read_client_state, read_consensus_state, save_client_state, save_consensus_state, update_client_state, }, - IbcClient, Status, StorageState, + IbcClient, IbcClientError, Status, StorageState, }; use scroll_codec::{ batch_header::BatchHeader, @@ -68,7 +68,7 @@ impl IbcClient for ScrollLightClient { proof: Vec, mut path: MerklePath, value: ics008_wasm_client::StorageState, - ) -> Result<(), Self::Error> { + ) -> Result<(), IbcClientError> { let consensus_state: WasmConsensusState = read_consensus_state(deps, &height)?.ok_or(Error::ConsensusStateNotFound(height))?; let client_state: WasmClientState = read_client_state(deps)?; @@ -80,12 +80,10 @@ impl IbcClient for ScrollLightClient { let storage_proof = { let mut proofs = StorageProof::decode(&proof) - .map_err(|e| Error::DecodeFromProto { - reason: format!("when decoding storage proof: {e:#?}"), - })? + .map_err(Error::StorageProofDecode)? .proofs; if proofs.len() > 1 { - return Err(Error::BatchingProofsNotSupported); + return Err(Error::BatchingProofsNotSupported.into()); } proofs.pop().ok_or(Error::EmptyProof)? }; @@ -113,19 +111,21 @@ impl IbcClient for ScrollLightClient { deps: Deps, env: Env, header: Self::Header, - ) -> Result<(), Self::Error> { + ) -> Result<(), IbcClientError> { let client_state: WasmClientState = read_client_state(deps)?; let l1_consensus_state = query_consensus_state::( deps, &env, client_state.data.l1_client_id.clone(), header.l1_height, - )?; + ) + .map_err(Error::CustomQuery)?; scroll_verifier::verify_header( client_state.data, header, l1_consensus_state.data.state_root, - )?; + ) + .map_err(Error::Verify)?; Ok(()) } @@ -133,32 +133,37 @@ impl IbcClient for ScrollLightClient { _deps: Deps, _env: Env, _misbehaviour: Self::Misbehaviour, - ) -> Result<(), Self::Error> { - Err(Error::Unimplemented) + ) -> Result<(), IbcClientError> { + Err(Error::Unimplemented.into()) } fn update_state( mut deps: DepsMut, _env: Env, header: Self::Header, - ) -> Result, Self::Error> { + ) -> Result, IbcClientError> { let mut client_state: WasmClientState = read_client_state(deps.as_ref())?; - let call = ::decode(header.commit_batch_calldata)?; + let call = ::decode(header.commit_batch_calldata) + .map_err(Error::CommitBatchDecode)?; - let timestamp = match BatchHeader::decode(call.parent_batch_header)? { + let timestamp = match BatchHeader::decode(call.parent_batch_header) + .map_err(Error::BatchHeaderDecode)? + { BatchHeader::V0(_) => { call.chunks .last() .map(ChunkV0::decode) - .ok_or(Error::EmptyBatch)?? + .ok_or(Error::EmptyBatch)? + .map_err(Error::ChunkV0Decode)? .blocks } BatchHeader::V1(_) => { call.chunks .last() .map(ChunkV1::decode) - .ok_or(Error::EmptyBatch)?? + .ok_or(Error::EmptyBatch)? + .map_err(Error::ChunkV1Decode)? .blocks } } @@ -168,7 +173,7 @@ impl IbcClient for ScrollLightClient { if client_state.data.latest_batch_index < header.last_batch_index { client_state.data.latest_batch_index = header.last_batch_index; - update_client_state(deps.branch(), client_state, header.last_batch_index); + update_client_state::(deps.branch(), client_state, header.last_batch_index); } let consensus_state = WasmConsensusState { @@ -178,7 +183,7 @@ impl IbcClient for ScrollLightClient { timestamp: 1_000_000_000 * timestamp, }, }; - save_consensus_state(deps, consensus_state, &header.l1_height); + save_consensus_state::(deps, consensus_state, &header.l1_height); Ok(vec![header.l1_height]) } @@ -186,28 +191,28 @@ impl IbcClient for ScrollLightClient { deps: DepsMut, env: Env, _client_message: Vec, - ) -> Result<(), Self::Error> { + ) -> Result<(), IbcClientError> { let mut client_state: WasmClientState = read_client_state(deps.as_ref())?; client_state.data.frozen_height = Height { revision_number: client_state.latest_height.revision_number, revision_height: env.block.height, }; - save_client_state(deps, client_state); + save_client_state::(deps, client_state); Ok(()) } fn check_for_misbehaviour_on_header( _deps: Deps, _header: Self::Header, - ) -> Result { + ) -> Result> { Ok(false) } fn check_for_misbehaviour_on_misbehaviour( _deps: Deps, _misbehaviour: Self::Misbehaviour, - ) -> Result { - Err(Error::Unimplemented) + ) -> Result> { + Err(Error::Unimplemented.into()) } fn verify_upgrade_and_update_state( @@ -216,26 +221,22 @@ impl IbcClient for ScrollLightClient { _upgrade_consensus_state: Self::ConsensusState, _proof_upgrade_client: Vec, _proof_upgrade_consensus_state: Vec, - ) -> Result<(), Self::Error> { - Err(Error::Unimplemented) + ) -> Result<(), IbcClientError> { + Err(Error::Unimplemented.into()) } - fn migrate_client_store(_deps: DepsMut) -> Result<(), Self::Error> { - Err(Error::Unimplemented) + fn migrate_client_store(_deps: DepsMut) -> Result<(), IbcClientError> { + Err(Error::Unimplemented.into()) } - fn status(deps: Deps, _env: &Env) -> Result { + fn status(deps: Deps, _env: &Env) -> Result> { let client_state: WasmClientState = read_client_state(deps)?; if client_state.data.frozen_height != Height::default() { return Ok(Status::Frozen); } - let Some(_) = read_consensus_state::( - deps, - &client_state.latest_height, - )? - else { + let Some(_) = read_consensus_state::(deps, &client_state.latest_height)? else { return Ok(Status::Expired); }; @@ -245,20 +246,18 @@ impl IbcClient for ScrollLightClient { fn export_metadata( _deps: Deps, _env: &Env, - ) -> Result, Self::Error> { + ) -> Result, IbcClientError> { Ok(Vec::new()) } fn timestamp_at_height( deps: Deps, height: Height, - ) -> Result { - Ok( - read_consensus_state::(deps, &height)? - .ok_or(Error::ConsensusStateNotFound(height))? - .data - .timestamp, - ) + ) -> Result> { + Ok(read_consensus_state::(deps, &height)? + .ok_or(Error::ConsensusStateNotFound(height))? + .data + .timestamp) } } @@ -277,23 +276,19 @@ fn do_verify_membership( let path = path .parse::>() - .map_err(|_| Error::UnknownIbcPath(path))?; + .map_err(Error::PathParse)?; let canonical_value = match path { Path::ClientState(_) => { Any::::decode(raw_value.as_ref()) - .map_err(|e| Error::DecodeFromProto { - reason: format!("{e:?}"), - })? + .map_err(Error::CometblsClientStateDecode)? .0 .encode_as::() } Path::ClientConsensusState(_) => Any::< wasm::consensus_state::ConsensusState, >::decode(raw_value.as_ref()) - .map_err(|e| Error::DecodeFromProto { - reason: format!("{e:?}"), - })? + .map_err(Error::CometblsConsensusStateDecode)? .0 .data .encode_as::(), diff --git a/light-clients/scroll-light-client/src/contract.rs b/light-clients/scroll-light-client/src/contract.rs index 3e07f41db1..cf8493d8e3 100644 --- a/light-clients/scroll-light-client/src/contract.rs +++ b/light-clients/scroll-light-client/src/contract.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{entry_point, DepsMut, Env, MessageInfo, Response}; use ics008_wasm_client::{ define_cosmwasm_light_client_contract, storage_utils::{save_proto_client_state, save_proto_consensus_state}, - InstantiateMsg, + CustomQueryOf, InstantiateMsg, }; use protos::ibc::lightclients::wasm::v1::{ ClientState as ProtoClientState, ConsensusState as ProtoConsensusState, @@ -16,17 +16,15 @@ use crate::{client::ScrollLightClient, errors::Error}; #[entry_point] pub fn instantiate( - mut deps: DepsMut, + mut deps: DepsMut>, _env: Env, _info: MessageInfo, msg: InstantiateMsg, ) -> Result { let client_state = - ClientState::decode_as::(&msg.client_state).map_err(|e| Error::DecodeFromProto { - reason: format!("{:?}", e), - })?; + ClientState::decode_as::(&msg.client_state).map_err(Error::ClientStateDecode)?; - save_proto_consensus_state( + save_proto_consensus_state::( deps.branch(), ProtoConsensusState { data: msg.consensus_state.into(), @@ -36,7 +34,7 @@ pub fn instantiate( revision_height: client_state.latest_batch_index, }, ); - save_proto_client_state( + save_proto_client_state::( deps, ProtoClientState { data: msg.client_state.into(), diff --git a/light-clients/scroll-light-client/src/errors.rs b/light-clients/scroll-light-client/src/errors.rs index 071399a6fe..4a86539bd7 100644 --- a/light-clients/scroll-light-client/src/errors.rs +++ b/light-clients/scroll-light-client/src/errors.rs @@ -1,47 +1,63 @@ -use cosmwasm_std::StdError; -use ethereum_verifier::{ - ValidateLightClientError, VerifyAccountStorageRootError, VerifyStorageAbsenceError, - VerifyStorageProofError, -}; +use ics008_wasm_client::IbcClientError; use scroll_codec::{ batch_header::BatchHeaderDecodeError, chunk::{ChunkV0DecodeError, ChunkV1DecodeError}, }; -use thiserror::Error as ThisError; use unionlabs::{ - hash::{H160, H256}, - ibc::core::client::height::Height, + encoding::{DecodeErrorOf, Proto}, + google::protobuf::any::Any, + hash::H256, + ibc::{core::client::height::Height, lightclients::wasm}, + ics24::PathParseError, }; -#[derive(ThisError, Debug)] -pub enum Error { - #[error("{0}")] - Std(#[from] StdError), - - #[error("error while decoding proto ({reason})")] - DecodeFromProto { reason: String }, - - #[error("client state not found")] - ClientStateNotFound, - - #[error("invalid proof format ({0})")] - InvalidProofFormat(String), +use crate::client::ScrollLightClient; +#[derive(thiserror::Error, Debug, PartialEq)] +pub enum Error { + #[error("unable to decode storage proof")] + StorageProofDecode( + #[source] + DecodeErrorOf, + ), + #[error("unable to decode counterparty's stored cometbls client state")] + CometblsClientStateDecode( + #[source] + DecodeErrorOf< + Proto, + Any, + >, + ), + #[error("unable to decode counterparty's stored cometbls consensus state")] + CometblsConsensusStateDecode( + #[source] + DecodeErrorOf< + Proto, + Any< + wasm::consensus_state::ConsensusState< + unionlabs::ibc::lightclients::cometbls::consensus_state::ConsensusState, + >, + >, + >, + ), + #[error("unable to decode client state")] + ClientStateDecode( + #[source] + DecodeErrorOf, + ), + #[error("unable to decode consensus state")] + ConsensusStateDecode( + #[source] + DecodeErrorOf< + Proto, + unionlabs::ibc::lightclients::scroll::consensus_state::ConsensusState, + >, + ), + + // REVIEW: Move this variant to IbcClientError? #[error("consensus state not found at height {0}")] ConsensusStateNotFound(Height), - #[error("{0}")] - ValidateLightClient(#[from] ValidateLightClientError), - - #[error("{0}")] - VerifyAccountStorageRoot(#[from] VerifyAccountStorageRootError), - - #[error("{0}")] - VerifyStorageAbsence(#[from] VerifyStorageAbsenceError), - - #[error("{0}")] - VerifyStorageProof(#[from] VerifyStorageProofError), - #[error("IBC path is empty")] EmptyIbcPath, @@ -51,29 +67,17 @@ pub enum Error { #[error("proof is empty")] EmptyProof, - #[error("counterparty storage not nil")] - CounterpartyStorageNotNil, - #[error("batching proofs are not supported")] BatchingProofsNotSupported, #[error("expected value ({expected}) and stored value ({stored}) don't match")] StoredValueMismatch { expected: H256, stored: H256 }, - #[error("storage root mismatch, expected `{expected}` but found `{found}`")] - StorageRootMismatch { expected: H256, found: H256 }, - - #[error("wasm client error ({0})")] - Wasm(String), - - #[error("the proof path {0} is not unknown")] - UnknownIbcPath(String), - - #[error("the given contract address ({given}) doesn't match the stored value ({expected})")] - IbcContractAddressMismatch { given: H160, expected: H160 }, + #[error("unable to parse ics24 path")] + PathParse(#[from] PathParseError), - #[error("failed to verify scroll header: {0}")] - Verifier(#[from] scroll_verifier::Error), + #[error("failed to verify scroll header")] + Verify(#[from] scroll_verifier::Error), #[error("the operation has not been implemented yet")] Unimplemented, @@ -81,36 +85,21 @@ pub enum Error { #[error("error while calling custom query: {0}")] CustomQuery(#[from] unionlabs::cosmwasm::wasm::union::custom_query::Error), + // TODO: Condense all of these together? #[error("error decoding commit batch calldata")] CommitBatchDecode(#[from] ethers_core::abi::AbiError), - #[error("empty batch")] EmptyBatch, - #[error("error decoding v0 chunk")] ChunkV0Decode(#[from] ChunkV0DecodeError), - #[error("error decoding v1 chunk")] ChunkV1Decode(#[from] ChunkV1DecodeError), - #[error("error decoding batch header")] BatchHeaderDecode(#[from] BatchHeaderDecodeError), } -impl From for Error { - fn from(error: ics008_wasm_client::storage_utils::Error) -> Self { - match error { - ics008_wasm_client::storage_utils::Error::ClientStateNotFound => { - Error::ClientStateNotFound - } - ics008_wasm_client::storage_utils::Error::ClientStateDecode => Error::DecodeFromProto { - reason: error.to_string(), - }, - ics008_wasm_client::storage_utils::Error::ConsensusStateDecode => { - Error::DecodeFromProto { - reason: error.to_string(), - } - } - } +impl From for IbcClientError { + fn from(value: Error) -> Self { + IbcClientError::ClientSpecific(value) } } diff --git a/light-clients/tendermint-light-client/src/client.rs b/light-clients/tendermint-light-client/src/client.rs index a98e7f2d95..cad1f5d130 100644 --- a/light-clients/tendermint-light-client/src/client.rs +++ b/light-clients/tendermint-light-client/src/client.rs @@ -5,7 +5,7 @@ use ics008_wasm_client::{ read_substitute_client_state, read_substitute_consensus_state, save_client_state, save_consensus_state, save_subject_client_state, save_subject_consensus_state, }, - IbcClient, Status, StorageState, ZERO_HEIGHT, + IbcClient, IbcClientError, Status, StorageState, ZERO_HEIGHT, }; use ics23::ibc_api::SDK_SPECS; use tendermint_verifier::types::SignatureVerifier; @@ -68,15 +68,12 @@ impl IbcClient for TendermintLightClient { proof: Vec, path: MerklePath, value: StorageState, - ) -> Result<(), Self::Error> { + ) -> Result<(), IbcClientError> { let consensus_state: WasmConsensusState = read_consensus_state(deps, &height)?.ok_or(Error::ConsensusStateNotFound(height))?; - let merkle_proof = MerkleProof::decode_as::(proof.as_ref()).map_err(|e| { - Error::DecodeFromProto { - reason: format!("{:?}", e), - } - })?; + let merkle_proof = + MerkleProof::decode_as::(proof.as_ref()).map_err(Error::MerkleProofDecode)?; // TODO(aeryz): delay period check @@ -96,13 +93,14 @@ impl IbcClient for TendermintLightClient { ), } .map_err(Error::VerifyMembership) + .map_err(Into::into) } fn verify_header( deps: Deps, env: Env, mut header: Self::Header, - ) -> Result<(), Self::Error> { + ) -> Result<(), IbcClientError> { set_total_voting_power(&mut header.validator_set)?; set_total_voting_power(&mut header.trusted_validators)?; @@ -119,9 +117,10 @@ impl IbcClient for TendermintLightClient { if revision_number != header.trusted_height.revision_number { return Err(Error::RevisionNumberMismatch { - trusted_rn: revision_number, - header_rn: header.trusted_height.revision_number, - }); + trusted_revision_number: revision_number, + header_revision_number: header.trusted_height.revision_number, + } + .into()); } let signed_height = header @@ -130,7 +129,8 @@ impl IbcClient for TendermintLightClient { .height .inner() .try_into() - .map_err(|_| Error::InvalidHeight)?; + .expect("value is bounded >= 0; qed;"); + if signed_height <= header.trusted_height.revision_height { return Err(InvalidHeaderError::SignedHeaderHeightMustBeMoreRecent { signed_height, @@ -143,9 +143,16 @@ impl IbcClient for TendermintLightClient { &construct_partial_header( client_state.data.chain_id, i64::try_from(header.trusted_height.revision_height) - .map_err(|_| Error::InvalidHeight)? // TODO(aeryz): add context #1333 + .map_err(|_| { + Error::IbcHeightTooLargeForTendermintHeight( + header.trusted_height.revision_height, + ) + })? .try_into() - .map_err(|_| Error::InvalidHeight)?, + .expect( + "value is converted from u64, which is positive, \ + and the expected bounded type is >= 0; qed;", + ), consensus_state.data.timestamp, consensus_state.data.next_validators_hash, ), @@ -160,7 +167,8 @@ impl IbcClient for TendermintLightClient { client_state.data.max_clock_drift, client_state.data.trust_level, &SignatureVerifier::new(Ed25519Verifier::new(deps)), - )?; + ) + .map_err(Error::TendermintVerify)?; Ok(()) } @@ -169,17 +177,17 @@ impl IbcClient for TendermintLightClient { _deps: Deps, _env: Env, _misbehaviour: Self::Misbehaviour, - ) -> Result<(), Self::Error> { - Err(Error::Unimplemented) + ) -> Result<(), IbcClientError> { + Err(Error::Unimplemented.into()) } fn update_state( mut deps: DepsMut, _env: Env, header: Self::Header, - ) -> Result, Self::Error> { + ) -> Result, IbcClientError> { let update_height = height_from_header(&header); - if read_consensus_state::<_, ConsensusState>(deps.as_ref(), &update_height)?.is_some() { + if read_consensus_state::(deps.as_ref(), &update_height)?.is_some() { return Ok(vec![update_height]); } @@ -192,13 +200,13 @@ impl IbcClient for TendermintLightClient { client_state.data.latest_height = update_height; } - save_client_state(deps.branch(), client_state); + save_client_state::(deps.branch(), client_state); save_consensus_state_metadata( deps.branch(), header.signed_header.header.time, update_height, ); - save_consensus_state( + save_consensus_state::( deps, WasmConsensusState { data: ConsensusState { @@ -219,14 +227,14 @@ impl IbcClient for TendermintLightClient { _deps: DepsMut, _env: Env, _client_message: Vec, - ) -> Result<(), Self::Error> { - Err(Error::Unimplemented) + ) -> Result<(), IbcClientError> { + Err(Error::Unimplemented.into()) } fn check_for_misbehaviour_on_header( deps: Deps, header: Self::Header, - ) -> Result { + ) -> Result> { let height = height_from_header(&header); // If there is already a header at this height, it should be exactly the same as the header that @@ -239,7 +247,7 @@ impl IbcClient for TendermintLightClient { next_validators_hash, root: MerkleRoot { hash }, }, - }) = read_consensus_state::<_, ConsensusState>(deps, &height)? + }) = read_consensus_state::(deps, &height)? { if timestamp != header.signed_header.header.time || hash != header.signed_header.header.app_hash @@ -277,8 +285,8 @@ impl IbcClient for TendermintLightClient { fn check_for_misbehaviour_on_misbehaviour( _deps: Deps, _misbehaviour: Self::Misbehaviour, - ) -> Result { - Err(Error::Unimplemented) + ) -> Result> { + Err(Error::Unimplemented.into()) } fn verify_upgrade_and_update_state( @@ -287,11 +295,13 @@ impl IbcClient for TendermintLightClient { _upgrade_consensus_state: Self::ConsensusState, _proof_upgrade_client: Vec, _proof_upgrade_consensus_state: Vec, - ) -> Result<(), Self::Error> { - Err(Error::Unimplemented) + ) -> Result<(), IbcClientError> { + Err(Error::Unimplemented.into()) } - fn migrate_client_store(mut deps: DepsMut) -> Result<(), Self::Error> { + fn migrate_client_store( + mut deps: DepsMut, + ) -> Result<(), IbcClientError> { let subject_client_state: WasmClientState = read_subject_client_state(deps.as_ref())?; let substitute_client_state: WasmClientState = read_substitute_client_state(deps.as_ref())?; @@ -321,14 +331,14 @@ impl IbcClient for TendermintLightClient { substitute_client_state.latest_height, ); - save_subject_consensus_state( + save_subject_consensus_state::( deps.branch(), substitute_consensus_state, &substitute_client_state.latest_height, ); let scs = substitute_client_state.data; - save_subject_client_state( + save_subject_client_state::( deps, WasmClientState { data: ClientState { @@ -349,7 +359,7 @@ impl IbcClient for TendermintLightClient { fn status( deps: Deps, env: &cosmwasm_std::Env, - ) -> Result { + ) -> Result> { let client_state: WasmClientState = read_client_state(deps)?; // TODO(aeryz): when refactoring the tm client, we should consider making this non-optional @@ -358,10 +368,8 @@ impl IbcClient for TendermintLightClient { return Ok(Status::Frozen); } - let Some(consensus_state) = read_consensus_state::( - deps, - &client_state.latest_height, - )? + let Some(consensus_state) = + read_consensus_state::(deps, &client_state.latest_height)? else { return Ok(Status::Expired); }; @@ -383,23 +391,25 @@ impl IbcClient for TendermintLightClient { fn export_metadata( _deps: Deps, _env: &cosmwasm_std::Env, - ) -> Result, Self::Error> { + ) -> Result, IbcClientError> { Ok(Vec::new()) } fn timestamp_at_height( deps: Deps, height: Height, - ) -> Result { - let timestamp = read_consensus_state::(deps, &height)? + ) -> Result> { + let timestamp = read_consensus_state::(deps, &height)? .ok_or(Error::ConsensusStateNotFound(height))? .data .timestamp .seconds .inner(); + timestamp .try_into() .map_err(|_| Error::NegativeTimestamp(timestamp)) + .map_err(Into::into) } } @@ -613,7 +623,8 @@ mod tests { TendermintLightClient::migrate_client_store(deps.as_mut()).unwrap(); - let wasm_client_state: WasmClientState = read_subject_client_state(deps.as_ref()).unwrap(); + let wasm_client_state: WasmClientState = + read_subject_client_state::(deps.as_ref()).unwrap(); // we didn't miss updating any fields assert_eq!(wasm_client_state, substitute_wasm_client_state); // client is unfrozen @@ -621,9 +632,12 @@ mod tests { // the new consensus state is saved under the correct height assert_eq!( - read_subject_consensus_state(deps.as_ref(), &INITIAL_SUBSTITUTE_CONSENSUS_STATE_HEIGHT) - .unwrap() - .unwrap(), + read_subject_consensus_state::( + deps.as_ref(), + &INITIAL_SUBSTITUTE_CONSENSUS_STATE_HEIGHT + ) + .unwrap() + .unwrap(), substitute_wasm_consensus_state ); @@ -676,7 +690,7 @@ mod tests { ); assert_eq!( TendermintLightClient::migrate_client_store(deps.as_mut()), - Err(Error::MigrateFieldsChanged) + Err(Error::MigrateFieldsChanged.into()) ); } } @@ -703,7 +717,7 @@ mod tests { assert_eq!( TendermintLightClient::migrate_client_store(deps.as_mut()), - Err(Error::SubstituteClientFrozen) + Err(Error::SubstituteClientFrozen.into()) ); } } diff --git a/light-clients/tendermint-light-client/src/contract.rs b/light-clients/tendermint-light-client/src/contract.rs index 541e849842..deb2c21fc1 100644 --- a/light-clients/tendermint-light-client/src/contract.rs +++ b/light-clients/tendermint-light-client/src/contract.rs @@ -25,18 +25,16 @@ pub fn instantiate( msg: InstantiateMsg, ) -> Result { let client_state = - ClientState::decode_as::(&msg.client_state).map_err(|e| Error::DecodeFromProto { - reason: format!("{:?}", e), - })?; + ClientState::decode_as::(&msg.client_state).map_err(Error::ClientStateDecode)?; - save_proto_consensus_state( + save_proto_consensus_state::( deps.branch(), ProtoConsensusState { data: msg.consensus_state.into(), }, &client_state.latest_height, ); - save_proto_client_state( + save_proto_client_state::( deps, ProtoClientState { data: msg.client_state.into(), diff --git a/light-clients/tendermint-light-client/src/errors.rs b/light-clients/tendermint-light-client/src/errors.rs index 60f926fb77..b0e0d1d3e9 100644 --- a/light-clients/tendermint-light-client/src/errors.rs +++ b/light-clients/tendermint-light-client/src/errors.rs @@ -1,12 +1,16 @@ -use cosmwasm_std::StdError; -use thiserror::Error as ThisError; +use ics008_wasm_client::IbcClientError; use unionlabs::{ encoding::{DecodeErrorOf, Proto}, hash::H256, - ibc::{core::client::height::Height, lightclients::cometbls::header::Header}, + ibc::{ + core::{client::height::Height, commitment::merkle_proof::MerkleProof}, + lightclients::{cometbls::header::Header, tendermint}, + }, }; -#[derive(ThisError, Debug, PartialEq)] +use crate::client::TendermintLightClient; + +#[derive(thiserror::Error, Debug, PartialEq)] pub enum InvalidHeaderError { #[error("signed header's height ({signed_height}) must be greater than trusted height ({trusted_height})")] SignedHeaderHeightMustBeMoreRecent { @@ -27,129 +31,52 @@ pub enum InvalidHeaderError { signed_timestamp: u64, max_clock_drift: u64, }, - #[error("commit hash ({commit_hash}) does not match with the signed header root ({signed_header_root})")] - SignedHeaderMismatchWithCommitHash { - commit_hash: H256, - signed_header_root: H256, - }, } -#[derive(ThisError, Debug, PartialEq)] +#[derive(thiserror::Error, Debug, PartialEq)] pub enum Error { - #[error("{0}")] - Std(#[from] StdError), - #[error("math operation with overflow")] MathOverflow, #[error("timestamp is negative ({0})")] NegativeTimestamp(i64), - #[error("error while decoding proto ({reason})")] - DecodeFromProto { reason: String }, - #[error("unimplemented feature")] + // TODO: Remove this from this variant Unimplemented, - #[error("Unable to decode header: {0:?}")] - HeaderDecode(DecodeErrorOf), + #[error("unable to decode header")] + HeaderDecode(#[source] DecodeErrorOf), - #[error("Unknown type url")] - UnknownTypeUrl, + #[error("unable to decode merkle proof")] + MerkleProofDecode(#[source] DecodeErrorOf), - #[error("Client state not found")] - ClientStateNotFound, + #[error("unable to decode client state")] + ClientStateDecode(#[source] DecodeErrorOf), - #[error("Invalid proof format")] - InvalidProofFormat, + #[error("the ibc height.revision_height does not fit in an i64 ({0})")] + IbcHeightTooLargeForTendermintHeight(u64), - #[error("Invalid client id")] - InvalidClientId, - - #[error("Invalid public key: {0}")] - InvalidPublicKey(String), - - #[error("Invalid height")] - InvalidHeight, - - #[error("trusted revision number ({trusted_rn}) does not match the header ({header_rn})")] - RevisionNumberMismatch { trusted_rn: u64, header_rn: u64 }, + #[error("trusted revision number ({trusted_revision_number}) does not match the header ({header_revision_number})")] + RevisionNumberMismatch { + trusted_revision_number: u64, + header_revision_number: u64, + }, - #[error(transparent)] + #[error("invalid header")] InvalidHeader(#[from] InvalidHeaderError), - #[error("Invalid ZKP")] - InvalidZKP, - - #[error("Invalid sync committee")] - InvalidSyncCommittee, - - #[error("Merkle root cannot be calculated")] - UnableToCalculateMerkleRoot, - - #[error("No next sync committee")] - NoNextSyncCommittee, - - #[error("Consensus state not found for {0}")] + #[error("consensus state not found for {0}")] + // TODO: Move this variant into IbcClientError ConsensusStateNotFound(Height), - #[error("Overflow happened during summing durations.")] - DurationAdditionOverflow, - - #[error("Timestamp not set")] - TimestampNotSet, - - #[error("Verification error: {0}")] - Verification(String), - - #[error("Unexpected timestamp: Expected timestamp {0}, got {1}")] - UnexpectedTimestamp(u64, u64), - - #[error("Future period")] - FuturePeriod, - - #[error("Cannot generate proof")] - CannotGenerateProof, - - #[error("Invalid chain version")] - InvalidChainVersion, - - #[error("Invalid chain id ({0})")] + // NOTE: This is only emitted when it's not possible to parse the revision number from the chain id; perhaps make this more descriptive? + #[error("invalid chain id ({0})")] InvalidChainId(String), - #[error("Invalid path {0}")] - InvalidPath(String), - - #[error("Invalid membership value")] - InvalidValue, - #[error("trusted validators hash ({0}) does not match the saved one ({1})")] TrustedValidatorsMismatch(H256, H256), - #[error("Invalid commitment key. Expected {0}, got {1}.")] - InvalidCommitmentKey(String, String), - - #[error("Missing field in the protobuf encoded data")] - MissingProtoField, - - #[error("Client's store period must be equal to update's finalized period")] - StorePeriodMustBeEqualToFinalizedPeriod, - - #[error("Proof is empty")] - EmptyProof, - - #[error("Batching proofs are not supported")] - BatchingProofsNotSupported, - - #[error("Expected value: '{0}' and stored value '{1}' doesn't match")] - ExpectedAndStoredValueMismatch(String, String), - - #[error("Custom query: {0}")] - CustomQuery(String), - - #[error("Wasm client error: {0}")] - Wasm(String), - #[error("verify membership error: {0}")] VerifyMembership(#[from] ics23::ibc_api::VerifyMembershipError), @@ -166,41 +93,23 @@ pub enum Error { MigrateFieldsChanged, } -impl Error { - pub fn invalid_public_key(s: S) -> Error { - Error::InvalidPublicKey(s.to_string()) - } - - pub fn invalid_commitment_key, B2: AsRef<[u8]>>( - expected: B1, - got: B2, - ) -> Error { - Error::InvalidCommitmentKey(hex::encode(expected), hex::encode(got)) - } - - pub fn stored_value_mismatch, B2: AsRef<[u8]>>(expected: B1, got: B2) -> Error { - Error::ExpectedAndStoredValueMismatch(hex::encode(expected), hex::encode(got)) - } - - pub fn custom_query(s: S) -> Error { - Error::CustomQuery(s.to_string()) +// required for IbcClient trait +impl From for IbcClientError { + fn from(value: Error) -> Self { + IbcClientError::ClientSpecific(value) } } -impl From for Error { - fn from(error: ics008_wasm_client::storage_utils::Error) -> Self { - match error { - ics008_wasm_client::storage_utils::Error::ClientStateNotFound => { - Error::ClientStateNotFound - } - ics008_wasm_client::storage_utils::Error::ClientStateDecode => Error::DecodeFromProto { - reason: error.to_string(), - }, - ics008_wasm_client::storage_utils::Error::ConsensusStateDecode => { - Error::DecodeFromProto { - reason: error.to_string(), - } - } - } +// convenience +impl From for IbcClientError { + fn from(value: InvalidHeaderError) -> Self { + IbcClientError::ClientSpecific(Error::InvalidHeader(value)) } } + +// would be nice, but both foreign types :( +// impl From for IbcClientError { +// fn from(value: ics23::ibc_api::VerifyMembershipError) -> Self { +// IbcClientError::ClientSpecific(Error::VerifyMembership(value)) +// } +// }