From 4c3de756d9911fcf0da29b3dd1e42e4b43da4d83 Mon Sep 17 00:00:00 2001 From: benluelo Date: Sun, 5 Jan 2025 21:44:13 +0000 Subject: [PATCH] feat(lightclients): add codec tests to ethereum-light-client --- Cargo.lock | 1 + .../light-clients/ethereum/src/client.rs | 2 +- lib/ethereum-light-client-types/Cargo.toml | 9 +- .../src/client_state.rs | 54 +++++ .../src/consensus_state.rs | 32 +++ lib/ethereum-light-client-types/src/header.rs | 114 ++++++++++ .../src/light_client_update.rs | 208 ++++++++++++++++++ .../src/storage_proof.rs | 36 ++- lib/unionlabs-primitives/src/bytes.rs | 6 + lib/unionlabs/src/uint.rs | 1 + .../modules/client/state-lens/evm/src/main.rs | 2 +- voyager/modules/proof/ethereum/src/main.rs | 6 +- 12 files changed, 456 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3a96e65c0b..09f756e3c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4844,6 +4844,7 @@ dependencies = [ "alloy", "beacon-api-types", "bincode 2.0.0-rc.3", + "ethereum-light-client-types", "hex-literal", "serde", "thiserror", diff --git a/cosmwasm/ibc-union/light-clients/ethereum/src/client.rs b/cosmwasm/ibc-union/light-clients/ethereum/src/client.rs index db27fa926a..9c1a7e76b7 100644 --- a/cosmwasm/ibc-union/light-clients/ethereum/src/client.rs +++ b/cosmwasm/ibc-union/light-clients/ethereum/src/client.rs @@ -161,7 +161,7 @@ pub fn verify_membership( storage_root, storage_proof.key, &rlp::encode(&storage_proof.value), - &storage_proof.proof, + storage_proof.proof, ) .map_err(Error::VerifyStorageProof) } diff --git a/lib/ethereum-light-client-types/Cargo.toml b/lib/ethereum-light-client-types/Cargo.toml index f4d1660b8b..169466b5d0 100644 --- a/lib/ethereum-light-client-types/Cargo.toml +++ b/lib/ethereum-light-client-types/Cargo.toml @@ -12,8 +12,9 @@ thiserror = { workspace = true } unionlabs = { workspace = true } [features] -bincode = ["dep:bincode", "beacon-api-types/bincode"] -default = ["serde"] +default = [] + +bincode = ["dep:bincode", "unionlabs/bincode", "beacon-api-types/bincode"] ethabi = ["unionlabs/ethabi", "dep:alloy"] serde = [ # TODO: Feature gate serde in unionlabs @@ -23,4 +24,6 @@ serde = [ ] [dev-dependencies] -hex-literal = { workspace = true } +ethereum-light-client-types = { workspace = true, features = ["bincode", "ethabi", "serde"] } +hex-literal = { workspace = true } +unionlabs = { workspace = true, features = ["test-utils"] } diff --git a/lib/ethereum-light-client-types/src/client_state.rs b/lib/ethereum-light-client-types/src/client_state.rs index 2cbf45e6bc..ced7565071 100644 --- a/lib/ethereum-light-client-types/src/client_state.rs +++ b/lib/ethereum-light-client-types/src/client_state.rs @@ -20,3 +20,57 @@ pub struct ClientState { /// the ibc contract on the counterparty chain that contains the ICS23 commitments pub ibc_contract_address: H160, } + +#[cfg(test)] +mod tests { + use beacon_api_types::{Fork, Version}; + use unionlabs::{ + encoding::{Bincode, Json}, + primitives::{FixedBytes, H256}, + test_utils::assert_codec_iso, + }; + + use super::*; + + fn mk_client_state() -> ClientState { + ClientState { + chain_id: U256::from(1u64), + chain_spec: PresetBaseKind::Minimal, + genesis_validators_root: H256::new([0xAA; 32]), + genesis_time: 123, + fork_parameters: ForkParameters { + genesis_fork_version: Version(FixedBytes::new([1, 2, 3, 4])), + genesis_slot: 1, + altair: Fork { + version: Version(FixedBytes::new([2, 3, 4, 5])), + epoch: 2, + }, + bellatrix: Fork { + version: Version(FixedBytes::new([3, 4, 5, 6])), + epoch: 3, + }, + capella: Fork { + version: Version(FixedBytes::new([4, 5, 6, 7])), + epoch: 4, + }, + deneb: Fork { + version: Version(FixedBytes::new([5, 6, 7, 8])), + epoch: 5, + }, + }, + latest_height: 987, + frozen_height: Height::new(1), + ibc_contract_address: H160::new([0xAA; 20]), + } + } + + #[test] + fn bincode_iso() { + assert_codec_iso::<_, Bincode>(&mk_client_state()); + } + + #[test] + fn json_iso() { + assert_codec_iso::<_, Json>(&mk_client_state()); + } +} diff --git a/lib/ethereum-light-client-types/src/consensus_state.rs b/lib/ethereum-light-client-types/src/consensus_state.rs index 5b41aa3df7..341ad0cd7b 100644 --- a/lib/ethereum-light-client-types/src/consensus_state.rs +++ b/lib/ethereum-light-client-types/src/consensus_state.rs @@ -79,3 +79,35 @@ pub mod ethabi { } } } + +#[cfg(test)] +mod tests { + use unionlabs::{ + encoding::{EthAbi, Json}, + primitives::H256, + test_utils::assert_codec_iso, + }; + + use super::*; + + fn mk_consensus_state() -> ConsensusState { + ConsensusState { + slot: 42, + state_root: H256::new([0xAA; 32]), + storage_root: H256::new([0xAA; 32]), + timestamp: 123_456_789, + current_sync_committee: H384::new([0xAA; 48]), + next_sync_committee: H384::new([0xAA; 48]), + } + } + + #[test] + fn ethabi_iso() { + assert_codec_iso::<_, EthAbi>(&mk_consensus_state()); + } + + #[test] + fn json_iso() { + assert_codec_iso::<_, Json>(&mk_consensus_state()); + } +} diff --git a/lib/ethereum-light-client-types/src/header.rs b/lib/ethereum-light-client-types/src/header.rs index 9a3925ad70..bc25fe7962 100644 --- a/lib/ethereum-light-client-types/src/header.rs +++ b/lib/ethereum-light-client-types/src/header.rs @@ -15,3 +15,117 @@ pub struct Header { /// Proof of the IBC handler contract against the execution state root provided in `consensus_update`. pub ibc_account_proof: AccountProof, } + +#[cfg(test)] +mod tests { + use beacon_api_types::{ + execution_payload_header::ExecutionPayloadHeader, BeaconBlockHeader, LightClientHeader, + SyncAggregate, SyncCommittee, + }; + use unionlabs::{ + encoding::{Bincode, Json}, + primitives::{H160, H256, H384, H768}, + test_utils::assert_codec_iso, + uint::U256, + }; + + use super::*; + use crate::{EpochChangeUpdate, LightClientUpdateData}; + + fn mk_header() -> Header { + Header { + trusted_height: Height::new(123), + consensus_update: LightClientUpdate::EpochChange(Box::new(EpochChangeUpdate { + sync_committee: SyncCommittee { + pubkeys: vec![H384::new([0xAA; 48])], + aggregate_pubkey: H384::new([0xAA; 48]), + }, + next_sync_committee: SyncCommittee { + pubkeys: vec![H384::new([0xAA; 48])], + aggregate_pubkey: H384::new([0xAA; 48]), + }, + next_sync_committee_branch: [H256::new([0xAA; 32]); 5], + update_data: LightClientUpdateData { + attested_header: LightClientHeader { + beacon: BeaconBlockHeader { + slot: 123, + proposer_index: 456, + parent_root: H256::new([0xAA; 32]), + state_root: H256::new([0xBB; 32]), + body_root: H256::new([0xCC; 32]), + }, + execution: ExecutionPayloadHeader { + parent_hash: H256::new([0xAA; 32]), + fee_recipient: H160::new([0xAA; 20]), + state_root: H256::new([0xAA; 32]), + receipts_root: H256::new([0xAA; 32]), + logs_bloom: b"bloom".into(), + prev_randao: H256::new([0xAA; 32]), + block_number: 69, + gas_limit: 1_987_654_321, + gas_used: 987_654_321, + timestamp: 123_456_789, + extra_data: b"extra".into(), + base_fee_per_gas: U256::from(1u64), + block_hash: H256::new([0xAA; 32]), + transactions_root: H256::new([0xAA; 32]), + withdrawals_root: H256::new([0xAA; 32]), + blob_gas_used: 100, + excess_blob_gas: 100, + }, + execution_branch: [H256::new([0xAA; 32]); 4], + }, + finalized_header: LightClientHeader { + beacon: BeaconBlockHeader { + slot: 123, + proposer_index: 456, + parent_root: H256::new([0xAA; 32]), + state_root: H256::new([0xBB; 32]), + body_root: H256::new([0xCC; 32]), + }, + execution: ExecutionPayloadHeader { + parent_hash: H256::new([0xAA; 32]), + fee_recipient: H160::new([0xAA; 20]), + state_root: H256::new([0xAA; 32]), + receipts_root: H256::new([0xAA; 32]), + logs_bloom: b"bloom".into(), + prev_randao: H256::new([0xAA; 32]), + block_number: 69, + gas_limit: 1_987_654_321, + gas_used: 987_654_321, + timestamp: 123_456_789, + extra_data: b"extra".into(), + base_fee_per_gas: U256::from(1u64), + block_hash: H256::new([0xAA; 32]), + transactions_root: H256::new([0xAA; 32]), + withdrawals_root: H256::new([0xAA; 32]), + blob_gas_used: 100, + excess_blob_gas: 100, + }, + execution_branch: [H256::new([0xAA; 32]); 4], + }, + finality_branch: [H256::new([0xAA; 32]); 6], + sync_aggregate: SyncAggregate { + sync_committee_bits: [1, 2, 3].to_vec(), + sync_committee_signature: H768::new([0xAA; 96]), + }, + signature_slot: 123, + }, + })), + ibc_account_proof: AccountProof { + storage_root: H256::new([0xAA; 32]), + proof: vec![b"ooga".to_vec(), b"booga".to_vec()], + }, + } + } + + #[test] + fn bincode_iso() { + assert_codec_iso::<_, Bincode>(&mk_header()); + } + + #[test] + fn json_iso() { + assert_codec_iso::<_, Json>(&mk_header()); + } +} diff --git a/lib/ethereum-light-client-types/src/light_client_update.rs b/lib/ethereum-light-client-types/src/light_client_update.rs index ba0583fa2a..dbf964c9e8 100644 --- a/lib/ethereum-light-client-types/src/light_client_update.rs +++ b/lib/ethereum-light-client-types/src/light_client_update.rs @@ -81,3 +81,211 @@ impl From for beacon_api_types::LightClientUpdate { } } } + +#[cfg(test)] +mod tests { + use beacon_api_types::{ + execution_payload_header::ExecutionPayloadHeader, BeaconBlockHeader, LightClientHeader, + SyncAggregate, SyncCommittee, + }; + use unionlabs::{ + encoding::{Bincode, Json}, + primitives::{H160, H256, H384, H768}, + test_utils::assert_codec_iso, + uint::U256, + }; + + use super::*; + use crate::{EpochChangeUpdate, LightClientUpdateData}; + + fn mk_epoch_change_update() -> EpochChangeUpdate { + EpochChangeUpdate { + sync_committee: SyncCommittee { + pubkeys: vec![H384::new([0xAA; 48])], + aggregate_pubkey: H384::new([0xAA; 48]), + }, + next_sync_committee: SyncCommittee { + pubkeys: vec![H384::new([0xAA; 48])], + aggregate_pubkey: H384::new([0xAA; 48]), + }, + next_sync_committee_branch: [H256::new([0xAA; 32]); 5], + update_data: LightClientUpdateData { + attested_header: LightClientHeader { + beacon: BeaconBlockHeader { + slot: 123, + proposer_index: 456, + parent_root: H256::new([0xAA; 32]), + state_root: H256::new([0xBB; 32]), + body_root: H256::new([0xCC; 32]), + }, + execution: ExecutionPayloadHeader { + parent_hash: H256::new([0xAA; 32]), + fee_recipient: H160::new([0xAA; 20]), + state_root: H256::new([0xAA; 32]), + receipts_root: H256::new([0xAA; 32]), + logs_bloom: b"bloom".into(), + prev_randao: H256::new([0xAA; 32]), + block_number: 69, + gas_limit: 1_987_654_321, + gas_used: 987_654_321, + timestamp: 123_456_789, + extra_data: b"extra".into(), + base_fee_per_gas: U256::from(1u64), + block_hash: H256::new([0xAA; 32]), + transactions_root: H256::new([0xAA; 32]), + withdrawals_root: H256::new([0xAA; 32]), + blob_gas_used: 100, + excess_blob_gas: 100, + }, + execution_branch: [H256::new([0xAA; 32]); 4], + }, + finalized_header: LightClientHeader { + beacon: BeaconBlockHeader { + slot: 123, + proposer_index: 456, + parent_root: H256::new([0xAA; 32]), + state_root: H256::new([0xBB; 32]), + body_root: H256::new([0xCC; 32]), + }, + execution: ExecutionPayloadHeader { + parent_hash: H256::new([0xAA; 32]), + fee_recipient: H160::new([0xAA; 20]), + state_root: H256::new([0xAA; 32]), + receipts_root: H256::new([0xAA; 32]), + logs_bloom: b"bloom".into(), + prev_randao: H256::new([0xAA; 32]), + block_number: 69, + gas_limit: 1_987_654_321, + gas_used: 987_654_321, + timestamp: 123_456_789, + extra_data: b"extra".into(), + base_fee_per_gas: U256::from(1u64), + block_hash: H256::new([0xAA; 32]), + transactions_root: H256::new([0xAA; 32]), + withdrawals_root: H256::new([0xAA; 32]), + blob_gas_used: 100, + excess_blob_gas: 100, + }, + execution_branch: [H256::new([0xAA; 32]); 4], + }, + finality_branch: [H256::new([0xAA; 32]); 6], + sync_aggregate: SyncAggregate { + sync_committee_bits: [1, 2, 3].to_vec(), + sync_committee_signature: H768::new([0xAA; 96]), + }, + signature_slot: 123, + }, + } + } + + fn mk_within_epoch_update() -> WithinEpochUpdate { + WithinEpochUpdate { + sync_committee: SyncCommittee { + pubkeys: vec![H384::new([0xAA; 48])], + aggregate_pubkey: H384::new([0xAA; 48]), + }, + update_data: mk_light_client_update_data(), + } + } + + fn mk_light_client_update_data() -> LightClientUpdateData { + LightClientUpdateData { + attested_header: LightClientHeader { + beacon: BeaconBlockHeader { + slot: 123, + proposer_index: 456, + parent_root: H256::new([0xAA; 32]), + state_root: H256::new([0xBB; 32]), + body_root: H256::new([0xCC; 32]), + }, + execution: ExecutionPayloadHeader { + parent_hash: H256::new([0xAA; 32]), + fee_recipient: H160::new([0xAA; 20]), + state_root: H256::new([0xAA; 32]), + receipts_root: H256::new([0xAA; 32]), + logs_bloom: b"bloom".into(), + prev_randao: H256::new([0xAA; 32]), + block_number: 69, + gas_limit: 1_987_654_321, + gas_used: 987_654_321, + timestamp: 123_456_789, + extra_data: b"extra".into(), + base_fee_per_gas: U256::from(1u64), + block_hash: H256::new([0xAA; 32]), + transactions_root: H256::new([0xAA; 32]), + withdrawals_root: H256::new([0xAA; 32]), + blob_gas_used: 100, + excess_blob_gas: 100, + }, + execution_branch: [H256::new([0xAA; 32]); 4], + }, + finalized_header: LightClientHeader { + beacon: BeaconBlockHeader { + slot: 123, + proposer_index: 456, + parent_root: H256::new([0xAA; 32]), + state_root: H256::new([0xBB; 32]), + body_root: H256::new([0xCC; 32]), + }, + execution: ExecutionPayloadHeader { + parent_hash: H256::new([0xAA; 32]), + fee_recipient: H160::new([0xAA; 20]), + state_root: H256::new([0xAA; 32]), + receipts_root: H256::new([0xAA; 32]), + logs_bloom: b"bloom".into(), + prev_randao: H256::new([0xAA; 32]), + block_number: 69, + gas_limit: 1_987_654_321, + gas_used: 987_654_321, + timestamp: 123_456_789, + extra_data: b"extra".into(), + base_fee_per_gas: U256::from(1u64), + block_hash: H256::new([0xAA; 32]), + transactions_root: H256::new([0xAA; 32]), + withdrawals_root: H256::new([0xAA; 32]), + blob_gas_used: 100, + excess_blob_gas: 100, + }, + execution_branch: [H256::new([0xAA; 32]); 4], + }, + finality_branch: [H256::new([0xAA; 32]); 6], + sync_aggregate: SyncAggregate { + sync_committee_bits: [1, 2, 3].to_vec(), + sync_committee_signature: H768::new([0xAA; 96]), + }, + signature_slot: 123, + } + } + + #[test] + fn epoch_change_update_bincode_iso() { + assert_codec_iso::<_, Bincode>(&mk_epoch_change_update()); + assert_codec_iso::<_, Bincode>(&LightClientUpdate::EpochChange(Box::new( + mk_epoch_change_update(), + ))); + } + + #[test] + fn epoch_change_update_json_iso() { + assert_codec_iso::<_, Json>(&mk_epoch_change_update()); + assert_codec_iso::<_, Json>(&LightClientUpdate::EpochChange(Box::new( + mk_epoch_change_update(), + ))); + } + + #[test] + fn within_epoch_update_bincode_iso() { + assert_codec_iso::<_, Bincode>(&mk_within_epoch_update()); + assert_codec_iso::<_, Bincode>(&LightClientUpdate::WithinEpoch(Box::new( + mk_within_epoch_update(), + ))); + } + + #[test] + fn within_epoch_update_json_iso() { + assert_codec_iso::<_, Json>(&mk_within_epoch_update()); + assert_codec_iso::<_, Json>(&LightClientUpdate::WithinEpoch(Box::new( + mk_within_epoch_update(), + ))); + } +} diff --git a/lib/ethereum-light-client-types/src/storage_proof.rs b/lib/ethereum-light-client-types/src/storage_proof.rs index ec34d70ef0..9c79a6c76b 100644 --- a/lib/ethereum-light-client-types/src/storage_proof.rs +++ b/lib/ethereum-light-client-types/src/storage_proof.rs @@ -1,13 +1,39 @@ -use unionlabs::uint::U256; +use unionlabs::{primitives::Bytes, uint::U256}; #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] pub struct StorageProof { - // #[serde(with = "crate::uint::u256_big_endian_hex")] pub key: U256, - // #[serde(with = "crate::uint::u256_big_endian_hex")] pub value: U256, - // #[serde(with = "::serde_utils::hex_string_list")] - pub proof: Vec>, + pub proof: Vec, +} + +#[cfg(test)] +mod tests { + use unionlabs::{ + encoding::{Bincode, Json}, + test_utils::assert_codec_iso, + uint::U256, + }; + + use super::*; + + fn mk_storage_proof() -> StorageProof { + StorageProof { + key: U256::from(123u64), + value: U256::from(123u64), + proof: vec![b"proof".into()], + } + } + + #[test] + fn bincode_iso() { + assert_codec_iso::<_, Bincode>(&mk_storage_proof()); + } + + #[test] + fn json_iso() { + assert_codec_iso::<_, Json>(&mk_storage_proof()); + } } diff --git a/lib/unionlabs-primitives/src/bytes.rs b/lib/unionlabs-primitives/src/bytes.rs index de02252365..0b34a750b5 100644 --- a/lib/unionlabs-primitives/src/bytes.rs +++ b/lib/unionlabs-primitives/src/bytes.rs @@ -35,6 +35,12 @@ impl FromIterator for Bytes { } } +impl<'a, E: Encoding> FromIterator<&'a u8> for Bytes { + fn from_iter>(iter: T) -> Self { + Self::new(iter.into_iter().copied().collect::>()) + } +} + impl Bytes { #[must_use = "constructing a Bytes has no effect"] pub fn new(bytes: impl Into>) -> Self { diff --git a/lib/unionlabs/src/uint.rs b/lib/unionlabs/src/uint.rs index a08ebd45da..975c0b56d3 100644 --- a/lib/unionlabs/src/uint.rs +++ b/lib/unionlabs/src/uint.rs @@ -65,6 +65,7 @@ impl bincode::Decode for U256 { } } } +#[cfg(feature = "bincode")] bincode::impl_borrow_decode!(U256); #[cfg(feature = "bincode")] diff --git a/voyager/modules/client/state-lens/evm/src/main.rs b/voyager/modules/client/state-lens/evm/src/main.rs index 56c658bd93..7044f690e2 100644 --- a/voyager/modules/client/state-lens/evm/src/main.rs +++ b/voyager/modules/client/state-lens/evm/src/main.rs @@ -218,6 +218,6 @@ impl ClientModuleServer for Module { })?; // TODO: extract to unionlabs? this is MPT proofs encoding for EVM // the solidity MPT verifier expects the proof RLP nodes to be serialized in sequence - Ok(proof.proof.concat().into()) + Ok(proof.proof.into_iter().flatten().collect()) } } diff --git a/voyager/modules/proof/ethereum/src/main.rs b/voyager/modules/proof/ethereum/src/main.rs index dfb9048147..26de18ec5f 100644 --- a/voyager/modules/proof/ethereum/src/main.rs +++ b/voyager/modules/proof/ethereum/src/main.rs @@ -121,11 +121,7 @@ impl ProofModuleServer for Module { let proof = StorageProof { key: U256::from_be_bytes(proof.key.as_b256().0), value: U256::from_be_bytes(proof.value.to_be_bytes()), - proof: proof - .proof - .into_iter() - .map(|bytes| bytes.to_vec()) - .collect(), + proof: proof.proof.into_iter().map(|bytes| bytes.into()).collect(), }; Ok(into_value(proof))