Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
418 changes: 394 additions & 24 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ members = [
"voyager/plugins/event-source/evm",
# "voyager/plugins/event-source/movement",
"voyager/plugins/event-source/sui",
"voyager/plugins/event-source/starknet",

"voyager/plugins/transaction/cosmos",
"voyager/plugins/transaction/evm",
Expand Down Expand Up @@ -295,6 +296,9 @@ members = [
"cosmwasm/pausable",
"cosmwasm/gatekeeper",
"cosmwasm/proxy-account-factory",
"lib/starknet-light-client-types",
"lib/starknet-verifier",
"lib/starknet-light-client",
]

[workspace.package]
Expand Down Expand Up @@ -419,6 +423,8 @@ serde-utils = { path = "lib/serde-utils", default-
solidity-slot = { path = "lib/solidity-slot", default-features = false }
ssz = { path = "lib/ssz", default-features = false }
ssz-derive = { path = "lib/ssz-derive", default-features = false }
starknet-light-client-types = { path = "lib/starknet-light-client-types", default-features = false }
starknet-storage-verifier = { path = "lib/starknet-storage-verifier", default-features = false }
state-lens-ics23-ics23-light-client-types = { path = "lib/state-lens-ics23-ics23-light-client-types", default-features = false }
state-lens-ics23-mpt-light-client = { path = "cosmwasm/ibc-union/lightclient/state-lens-ics23-mpt", default-features = false }
state-lens-ics23-mpt-light-client-types = { path = "lib/state-lens-ics23-mpt-light-client-types", default-features = false }
Expand Down Expand Up @@ -528,8 +534,8 @@ serde_with = { version = "3.12.0", default-features = false, featu
sha2 = { version = "0.10.9", default-features = false }
sha3 = { version = "0.10.8", default-features = false }
sqlx = { version = "0.7.4", default-features = false }
starknet = { version = "0.17.0", default-features = false }
starknet-core = { version = "0.16.0", default-features = false }
starknet-crypto = { version = "0.8.1", default-features = false }
static_assertions = { git = "https://github.com/nvzqz/static-assertions" } # https://github.com/nvzqz/static-assertions/pull/28
strum = { version = "0.27", default-features = false }
subtle-encoding = { version = "0.5.1", default-features = false }
Expand Down
14 changes: 12 additions & 2 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,12 @@
mv $out/bin/cast $out/bin/cast-cursed

cat <<EOF >> $out/bin/cast
export LD_LIBRARY_PATH=${lib.makeLibraryPath [ super.stdenv.cc.cc.lib ]}
export LD_LIBRARY_PATH=${
lib.makeLibraryPath [
super.gcc14.cc.lib
# super.stdenv.cc.cc.lib
]
}
$out/bin/cast-cursed "\$@"
unset LD_LIBRARY_PATH
EOF
Expand All @@ -385,7 +390,12 @@
mv $out/bin/forge $out/bin/forge-cursed

cat <<EOF >> $out/bin/forge
export LD_LIBRARY_PATH=${lib.makeLibraryPath [ super.stdenv.cc.cc.lib ]}
export LD_LIBRARY_PATH=${
lib.makeLibraryPath [
super.gcc14
# super.stdenv.cc.cc.lib
]
}
$out/bin/forge-cursed "\$@"
unset LD_LIBRARY_PATH
EOF
Expand Down
36 changes: 36 additions & 0 deletions lib/starknet-light-client-types/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[package]
name = "starknet-light-client-types"
version = "0.0.0"

authors = { workspace = true }
edition = { workspace = true }
license-file = "LICENSE"
publish = { workspace = true }
repository = { workspace = true }

[lints]
workspace = true

[dependencies]
alloy = { workspace = true, features = ["sol-types"], optional = true }
bincode = { workspace = true, features = ["alloc", "derive"], optional = true }
ethereum-light-client-types = { workspace = true }
ibc-union-spec = { workspace = true }
pathfinder-crypto = "0.21.3"
serde = { workspace = true, optional = true, features = ["derive"] }
starknet-core = { workspace = true } # REVIEW: Do we want to use this crate in the public api of this client's types?
starknet-types-core = { version = "1.0.0", features = ["hash"] }
unionlabs = { workspace = true }

[features]
bincode = [
"dep:bincode",
"unionlabs/bincode",
"ethereum-light-client-types/bincode",
"ibc-union-spec/bincode",
]
ethabi = ["dep:alloy", "ethereum-light-client-types/ethabi", "ibc-union-spec/ethabi"]
serde = ["dep:serde", "ethereum-light-client-types/serde", "ibc-union-spec/serde"]

[dev-dependencies]
hex-literal = { workspace = true }
27 changes: 27 additions & 0 deletions lib/starknet-light-client-types/src/client_state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use pathfinder_crypto::Felt;
use unionlabs::primitives::{H160, H256};

#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "version", content = "data", rename_all = "snake_case")
)]
#[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))]
pub enum ClientState {
V1(ClientStateV1),
}

#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))]
pub struct ClientStateV1 {
pub chain_id: Felt,
pub latest_height: u64,
pub ibc_contract_address: Felt,
/// https://docs.starknet.io/learn/cheatsheets/chain-info#important-addresses
///
/// Mainnet: `0xc662c410C0ECf747543f5bA90660f6ABeBD9C8c4`
/// Sepolia: `0xE2Bb56ee936fd6433DC0F6e7e3b8365C906AA057`
pub l1_contract_address: H160,
}
47 changes: 47 additions & 0 deletions lib/starknet-light-client-types/src/consensus_state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use ibc_union_spec::Timestamp;
use unionlabs::primitives::H256;

#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ConsensusState {
pub global_root: H256,
pub ibc_storage_root: H256,
pub timestamp: Timestamp,
}

#[cfg(feature = "ethabi")]
pub mod ethabi {
use unionlabs::impl_ethabi_via_try_from_into;

use super::*;

impl_ethabi_via_try_from_into!(ConsensusState => SolConsensusState);

alloy::sol! {
struct SolConsensusState {
bytes32 global_root;
bytes32 ibc_storage_root;
uint64 timestamp;
}
}

impl From<ConsensusState> for SolConsensusState {
fn from(value: ConsensusState) -> Self {
Self {
global_root: value.global_root.get().into(),
ibc_storage_root: value.storage_root.get().into(),
timestamp: value.timestamp.as_nanos(),
}
}
}

impl From<SolConsensusState> for ConsensusState {
fn from(value: SolConsensusState) -> Self {
Self {
global_root: H256::new(value.global_root.0),
storage_root: H256::new(value.ibc_storage_root.0),
timestamp: Timestamp::from_nanos(value.timestamp),
}
}
}
}
220 changes: 220 additions & 0 deletions lib/starknet-light-client-types/src/header.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
pub use ethereum_light_client_types::AccountProof;
use pathfinder_crypto::{
Felt,
hash::{pedersen_hash, poseidon_hash_many},
};
use unionlabs::primitives::{Bytes, H256, U256};

use crate::storage_proof::MerkleNode;

#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(deny_unknown_fields, rename_all = "snake_case")
)]
#[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))]
pub struct Header {
pub l1_height: u64,
pub l1_contract_account_proof: AccountProof,
pub l1_block_hash_proof: Vec<Bytes>,
pub l2_block: L2Block,
pub l2_ibc_contract_proof: ContractProof,
}

#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(deny_unknown_fields, rename_all = "snake_case")
)]
#[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))]
pub struct L2Block {
pub block_number: u64,
pub parent_block_hash: Felt,
pub contracts_trie_root: Felt,
pub classes_trie_root: Felt,
pub sequencer_address: Felt,
// SECONDS
pub block_timestamp: u64,
pub transaction_count: u32,
pub events_count: u32,
pub state_diff_length: u32,
pub state_diff_commitment: Felt,
pub transactions_commitment: Felt,
pub events_commitment: Felt,
pub receipts_commitment: Felt,
pub l1_gas_price: (u128, u128),
pub l1_data_gas_price: (u128, u128),
pub l2_gas_price: (u128, u128),
pub l1_da_mode: L1DaMode,
pub protocol_version: String,
}

#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "snake_case")
)]
#[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))]
pub enum L1DaMode {
Blob,
Calldata,
}

#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "snake_case")
)]
#[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))]
pub struct ContractProof {
/// The nodes in the union of the paths from the contracts tree root to the requested leaves
pub nodes: Vec<MerkleNode>,
pub contract_leaf_data: ContractLeafData,
}

#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "snake_case")
)]
#[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))]
pub struct ContractLeafData {
pub nonce: Felt,
pub class_hash: Felt,
pub storage_root: Felt,
}

impl ContractLeafData {
pub fn hash(&self) -> Felt {
pedersen_hash(
pedersen_hash(
pedersen_hash(self.class_hash, self.storage_root),
self.nonce,
),
Felt::ZERO,
)
}
}

impl L2Block {
/// <https://docs.starknet.io/learn/protocol/blocks#block-hash>
/// <https://github.com/starkware-libs/sequencer/blob/079ed26ce95b3b10de40c9916ffa332aaecd9f06/crates/starknet_api/src/block_hash/block_hash_calculator.rs#L134>
// TODO: Handle different versions
pub fn hash(&self) -> Felt {
poseidon_hash_many(
&[
// hex(b"STARKNET_BLOCK_HASH1")
Felt::from_hex_str("0x535441524b4e45545f424c4f434b5f4841534831").unwrap(),
self.block_number.into(),
poseidon_hash_many(
&[
// hex(b"STARKNET_STATE_V0")
Felt::from_hex_str("0x535441524b4e45545f53544154455f5630").unwrap(),
self.contracts_trie_root,
self.classes_trie_root,
]
.map(Into::into),
)
.into(),
self.sequencer_address,
self.block_timestamp.into(),
// https://github.com/starkware-libs/sequencer/blob/079ed26ce95b3b10de40c9916ffa332aaecd9f06/crates/starknet_api/src/block_hash/block_hash_calculator.rs#L230
Felt::from_be_slice(
[
(self.transaction_count as u64),
(self.events_count as u64),
(self.state_diff_length as u64),
match self.l1_da_mode {
// 0b0000_0000 ++ 7 bytes 0 padding
L1DaMode::Calldata => 0_u64,
// 0b1000_0000 ++ 7 bytes 0 padding
L1DaMode::Blob => 1 << 63,
},
]
.map(u64::to_be_bytes)
.as_flattened(),
)
.unwrap(),
self.state_diff_commitment,
self.transactions_commitment,
self.events_commitment,
self.receipts_commitment,
poseidon_hash_many(
&[
// hex(b"STARKNET_GAS_PRICES0")
Felt::from_hex_str("0x535441524b4e45545f4741535f50524943455330").unwrap(),
self.l1_gas_price.0.into(),
self.l1_gas_price.1.into(),
self.l1_data_gas_price.0.into(),
self.l1_data_gas_price.1.into(),
self.l2_gas_price.0.into(),
self.l2_gas_price.1.into(),
]
.map(Into::into),
)
.into(),
Felt::from_be_slice(self.protocol_version.as_bytes()).unwrap(),
Felt::ZERO,
self.parent_block_hash,
]
.map(Into::into),
)
.into()
}
}

#[test]
fn l2_block_hash_3996475() {
use hex_literal::hex;

// https://feeder.alpha-mainnet.starknet.io/feeder_gateway/get_block?blockNumber=3996475
let block = L2Block {
block_number: 3996475,
parent_block_hash: hex!("07488afa914e19281d6a859f1673d91f84b124576677bc90790954934bcf6a90")
.into(),
classes_trie_root: hex!("052dedb4984ca5bde1fa31f46bdedd2462779d7a6db3039be87eb0c532d79470")
.into(),
contracts_trie_root: hex!(
"02c6e3ddcdcf9bcd4b9e01c4b94408b6cf8b82ca9a1b40d808612483278b5afb"
)
.into(),
sequencer_address: hex!("01176a1bd84444c89232ec27754698e5d2e7e1a7f1539f12027f28b23ec9f3d8")
.into(),
block_timestamp: 1764693045,
transaction_count: 8,
events_count: 14 + 7 + 104 + 5 + 3 + 7 + 5 + 5,
state_diff_length: 108,
state_diff_commitment: hex!(
"000d69e24d96773a920991dcd7f86fea0526acb3dae9bb3955caf840c71b54f6"
)
.into(),
transactions_commitment: hex!(
"01df3ce5acd86d8c2d7f1155997a70a004ee0a0c36c67c9baafe87ace22f30d9"
)
.into(),
events_commitment: hex!("030a53d5d62958b18f1094b66c4ad4c3bcee8dd2a36666fc5fc8b46ddaa5b37c")
.into(),
receipts_commitment: hex!(
"0494e30696606f6208ac02b701f2350460c35b0be17cdf23e4017c79a6a69f2f"
)
.into(),
l1_gas_price: (0x6df5cf40, 0x27d11e1709d4),
l1_data_gas_price: (0x1, 0x5cb2),
l2_gas_price: (0x1edd2, 0xb2d05e00),
l1_da_mode: L1DaMode::Blob,
protocol_version: "0.14.0".to_owned(),
};

dbg!(&block);

assert_eq!(
block.hash(),
Felt::from_hex_str("0x366cae7718ded291ef9c5f4c2aba8c3c27baa0e563fd64ba72fe51c2abc4675")
.unwrap()
);
}
Loading
Loading