diff --git a/madara/Cargo.lock b/madara/Cargo.lock index 3ad301b02d..fe7840920f 100644 --- a/madara/Cargo.lock +++ b/madara/Cargo.lock @@ -716,8 +716,8 @@ checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "apollo_compilation_utils" -version = "0.16.0-rc.0" -source = "git+https://github.com/karnotxyz/sequencer?rev=ff514ad8f39ac04302d0b176cef673a6a3820914#ff514ad8f39ac04302d0b176cef673a6a3820914" +version = "0.16.0-rc.1" +source = "git+https://github.com/karnotxyz/sequencer?rev=57052466290163f95ec085fc5ad877c4e04d5e2d#57052466290163f95ec085fc5ad877c4e04d5e2d" dependencies = [ "apollo_infra_utils", "cairo-lang-sierra 2.12.3", @@ -734,8 +734,8 @@ dependencies = [ [[package]] name = "apollo_compile_to_native" -version = "0.16.0-rc.0" -source = "git+https://github.com/karnotxyz/sequencer?rev=ff514ad8f39ac04302d0b176cef673a6a3820914#ff514ad8f39ac04302d0b176cef673a6a3820914" +version = "0.16.0-rc.1" +source = "git+https://github.com/karnotxyz/sequencer?rev=57052466290163f95ec085fc5ad877c4e04d5e2d#57052466290163f95ec085fc5ad877c4e04d5e2d" dependencies = [ "apollo_compilation_utils", "apollo_compile_to_native_types", @@ -747,8 +747,8 @@ dependencies = [ [[package]] name = "apollo_compile_to_native_types" -version = "0.16.0-rc.0" -source = "git+https://github.com/karnotxyz/sequencer?rev=ff514ad8f39ac04302d0b176cef673a6a3820914#ff514ad8f39ac04302d0b176cef673a6a3820914" +version = "0.16.0-rc.1" +source = "git+https://github.com/karnotxyz/sequencer?rev=57052466290163f95ec085fc5ad877c4e04d5e2d#57052466290163f95ec085fc5ad877c4e04d5e2d" dependencies = [ "apollo_config", "serde", @@ -757,8 +757,8 @@ dependencies = [ [[package]] name = "apollo_config" -version = "0.16.0-rc.0" -source = "git+https://github.com/karnotxyz/sequencer?rev=ff514ad8f39ac04302d0b176cef673a6a3820914#ff514ad8f39ac04302d0b176cef673a6a3820914" +version = "0.16.0-rc.1" +source = "git+https://github.com/karnotxyz/sequencer?rev=57052466290163f95ec085fc5ad877c4e04d5e2d#57052466290163f95ec085fc5ad877c4e04d5e2d" dependencies = [ "apollo_infra_utils", "clap", @@ -775,8 +775,8 @@ dependencies = [ [[package]] name = "apollo_infra_utils" -version = "0.16.0-rc.0" -source = "git+https://github.com/karnotxyz/sequencer?rev=ff514ad8f39ac04302d0b176cef673a6a3820914#ff514ad8f39ac04302d0b176cef673a6a3820914" +version = "0.16.0-rc.1" +source = "git+https://github.com/karnotxyz/sequencer?rev=57052466290163f95ec085fc5ad877c4e04d5e2d#57052466290163f95ec085fc5ad877c4e04d5e2d" dependencies = [ "apollo_proc_macros", "assert-json-diff", @@ -785,6 +785,7 @@ dependencies = [ "serde", "serde_json", "socket2 0.5.9", + "strum 0.25.0", "tempfile", "thiserror 1.0.69", "tokio", @@ -793,8 +794,8 @@ dependencies = [ [[package]] name = "apollo_metrics" -version = "0.16.0-rc.0" -source = "git+https://github.com/karnotxyz/sequencer?rev=ff514ad8f39ac04302d0b176cef673a6a3820914#ff514ad8f39ac04302d0b176cef673a6a3820914" +version = "0.16.0-rc.1" +source = "git+https://github.com/karnotxyz/sequencer?rev=57052466290163f95ec085fc5ad877c4e04d5e2d#57052466290163f95ec085fc5ad877c4e04d5e2d" dependencies = [ "indexmap 2.9.0", "metrics", @@ -805,8 +806,8 @@ dependencies = [ [[package]] name = "apollo_proc_macros" -version = "0.16.0-rc.0" -source = "git+https://github.com/karnotxyz/sequencer?rev=ff514ad8f39ac04302d0b176cef673a6a3820914#ff514ad8f39ac04302d0b176cef673a6a3820914" +version = "0.16.0-rc.1" +source = "git+https://github.com/karnotxyz/sequencer?rev=57052466290163f95ec085fc5ad877c4e04d5e2d#57052466290163f95ec085fc5ad877c4e04d5e2d" dependencies = [ "lazy_static", "proc-macro2", @@ -1738,8 +1739,8 @@ dependencies = [ [[package]] name = "blockifier" -version = "0.16.0-rc.0" -source = "git+https://github.com/karnotxyz/sequencer?rev=ff514ad8f39ac04302d0b176cef673a6a3820914#ff514ad8f39ac04302d0b176cef673a6a3820914" +version = "0.16.0-rc.1" +source = "git+https://github.com/karnotxyz/sequencer?rev=57052466290163f95ec085fc5ad877c4e04d5e2d#57052466290163f95ec085fc5ad877c4e04d5e2d" dependencies = [ "anyhow", "apollo_compilation_utils", @@ -1789,8 +1790,8 @@ dependencies = [ [[package]] name = "blockifier_test_utils" -version = "0.16.0-rc.0" -source = "git+https://github.com/karnotxyz/sequencer?rev=ff514ad8f39ac04302d0b176cef673a6a3820914#ff514ad8f39ac04302d0b176cef673a6a3820914" +version = "0.16.0-rc.1" +source = "git+https://github.com/karnotxyz/sequencer?rev=57052466290163f95ec085fc5ad877c4e04d5e2d#57052466290163f95ec085fc5ad877c4e04d5e2d" dependencies = [ "apollo_infra_utils", "cairo-lang-starknet-classes", @@ -3593,34 +3594,6 @@ dependencies = [ "xshell", ] -[[package]] -name = "cairo-lang-test-plugin" -version = "2.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e90cf75528c423cd6b6faaab2dde0c1b23efe36103e1e57f338293552ee16f" -dependencies = [ - "anyhow", - "cairo-lang-compiler 2.12.3", - "cairo-lang-debug 2.12.3", - "cairo-lang-defs 2.12.3", - "cairo-lang-filesystem 2.12.3", - "cairo-lang-lowering 2.12.3", - "cairo-lang-parser 2.12.3", - "cairo-lang-semantic 2.12.3", - "cairo-lang-sierra 2.12.3", - "cairo-lang-sierra-generator 2.12.3", - "cairo-lang-starknet 2.12.3", - "cairo-lang-starknet-classes", - "cairo-lang-syntax 2.12.3", - "cairo-lang-utils 2.12.3", - "indoc 2.0.6", - "itertools 0.14.0", - "num-bigint", - "num-traits", - "serde", - "starknet-types-core", -] - [[package]] name = "cairo-lang-test-utils" version = "2.12.3" @@ -3702,33 +3675,23 @@ dependencies = [ [[package]] name = "cairo-native" -version = "0.6.2" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a1b73479b3b3676bf81d2e3586f7e7d234227d7bdb6d8635b0256af7a41592" +checksum = "19404b3af952f1f8f1fcb68c58eafc06d5255002dc7a7c81c800676a0779004a" dependencies = [ - "anyhow", "aquamarine", "ark-ec 0.5.0", "ark-ff 0.5.0", "ark-secp256k1 0.5.0", "ark-secp256r1 0.5.0", "bumpalo", - "cairo-lang-compiler 2.12.3", - "cairo-lang-defs 2.12.3", - "cairo-lang-filesystem 2.12.3", "cairo-lang-runner", - "cairo-lang-semantic 2.12.3", "cairo-lang-sierra 2.12.3", "cairo-lang-sierra-ap-change 2.12.3", "cairo-lang-sierra-gas 2.12.3", "cairo-lang-sierra-to-casm 2.12.3", - "cairo-lang-starknet 2.12.3", "cairo-lang-starknet-classes", - "cairo-lang-test-plugin", "cairo-lang-utils 2.12.3", - "cc", - "clap", - "colored 2.2.0", "educe 0.5.11", "itertools 0.14.0", "keccak", @@ -3747,11 +3710,9 @@ dependencies = [ "sha2", "starknet-curve", "starknet-types-core", - "stats_alloc", "tempfile", "thiserror 2.0.12", "tracing", - "tracing-subscriber", "utf8_iter", ] @@ -9710,10 +9671,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +[[package]] +name = "size-of" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4e36eca171fddeda53901b0a436573b3f2391eaa9189d439b2bd8ea8cebd7e3" + [[package]] name = "sizeof" -version = "0.16.0-rc.0" -source = "git+https://github.com/karnotxyz/sequencer?rev=ff514ad8f39ac04302d0b176cef673a6a3820914#ff514ad8f39ac04302d0b176cef673a6a3820914" +version = "0.16.0-rc.1" +source = "git+https://github.com/karnotxyz/sequencer?rev=57052466290163f95ec085fc5ad877c4e04d5e2d#57052466290163f95ec085fc5ad877c4e04d5e2d" dependencies = [ "sizeof_internal", "sizeof_macro", @@ -9721,16 +9688,16 @@ dependencies = [ [[package]] name = "sizeof_internal" -version = "0.16.0-rc.0" -source = "git+https://github.com/karnotxyz/sequencer?rev=ff514ad8f39ac04302d0b176cef673a6a3820914#ff514ad8f39ac04302d0b176cef673a6a3820914" +version = "0.16.0-rc.1" +source = "git+https://github.com/karnotxyz/sequencer?rev=57052466290163f95ec085fc5ad877c4e04d5e2d#57052466290163f95ec085fc5ad877c4e04d5e2d" dependencies = [ "starknet-types-core", ] [[package]] name = "sizeof_macro" -version = "0.16.0-rc.0" -source = "git+https://github.com/karnotxyz/sequencer?rev=ff514ad8f39ac04302d0b176cef673a6a3820914#ff514ad8f39ac04302d0b176cef673a6a3820914" +version = "0.16.0-rc.1" +source = "git+https://github.com/karnotxyz/sequencer?rev=57052466290163f95ec085fc5ad877c4e04d5e2d#57052466290163f95ec085fc5ad877c4e04d5e2d" dependencies = [ "proc-macro2", "quote", @@ -10030,9 +9997,9 @@ dependencies = [ [[package]] name = "starknet-types-core" -version = "0.2.4" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90d23b1bc014ee4cce40056ab3114bcbcdc2dbc1e845bbfb1f8bd0bab63507d4" +checksum = "ab92594a86ac627dd4c8d3350362cc8035e55c548c27c71dfa4c9fc6b3b6ab1a" dependencies = [ "arbitrary", "blake2", @@ -10046,13 +10013,14 @@ dependencies = [ "parity-scale-codec", "rand 0.9.2", "serde", + "size-of", "zeroize", ] [[package]] name = "starknet_api" -version = "0.16.0-rc.0" -source = "git+https://github.com/karnotxyz/sequencer?rev=ff514ad8f39ac04302d0b176cef673a6a3820914#ff514ad8f39ac04302d0b176cef673a6a3820914" +version = "0.16.0-rc.1" +source = "git+https://github.com/karnotxyz/sequencer?rev=57052466290163f95ec085fc5ad877c4e04d5e2d#57052466290163f95ec085fc5ad877c4e04d5e2d" dependencies = [ "apollo_infra_utils", "base64 0.13.1", @@ -10090,12 +10058,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "stats_alloc" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c0e04424e733e69714ca1bbb9204c1a57f09f5493439520f9f68c132ad25eec" - [[package]] name = "string_cache" version = "0.8.9" @@ -10798,16 +10760,6 @@ dependencies = [ "web-time", ] -[[package]] -name = "tracing-serde" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" -dependencies = [ - "serde", - "tracing-core", -] - [[package]] name = "tracing-subscriber" version = "0.3.19" @@ -10818,15 +10770,12 @@ dependencies = [ "nu-ansi-term 0.46.0", "once_cell", "regex", - "serde", - "serde_json", "sharded-slab", "smallvec", "thread_local", "tracing", "tracing-core", "tracing-log", - "tracing-serde", ] [[package]] diff --git a/madara/Cargo.toml b/madara/Cargo.toml index a6fa09ac71..ed6ead44e8 100644 --- a/madara/Cargo.toml +++ b/madara/Cargo.toml @@ -126,18 +126,18 @@ starknet-providers = "0.16" starknet-signers = "0.14" starknet = "0.17.0" -starknet-types-core = { version = "0.2.1", default-features = false, features = [ +starknet-types-core = { version = "=0.2.3", default-features = false, features = [ "hash", ] } -blockifier = { git = "https://github.com/karnotxyz/sequencer", rev = "ff514ad8f39ac04302d0b176cef673a6a3820914", features = [ +blockifier = { git = "https://github.com/karnotxyz/sequencer", rev = "57052466290163f95ec085fc5ad877c4e04d5e2d", features = [ "node_api", "cairo_native", ] } -starknet_api = { git = "https://github.com/karnotxyz/sequencer", rev = "ff514ad8f39ac04302d0b176cef673a6a3820914" } +starknet_api = { git = "https://github.com/karnotxyz/sequencer", rev = "57052466290163f95ec085fc5ad877c4e04d5e2d" } cairo-lang-starknet-classes = "2.12.3" cairo-lang-utils = "2.12.3" cairo-vm = "2.5.0" -cairo-native = "0.6.2" +cairo-native = "0.7.1" alloy = { version = "0.8.3", features = [ "node-bindings", diff --git a/madara/crates/client/db/src/view/block_preconfirmed.rs b/madara/crates/client/db/src/view/block_preconfirmed.rs index dc552b41a4..9c0c708510 100644 --- a/madara/crates/client/db/src/view/block_preconfirmed.rs +++ b/madara/crates/client/db/src/view/block_preconfirmed.rs @@ -364,6 +364,7 @@ impl MadaraPreconfirmedBlockView { nonces, deployed_contracts, replaced_classes, + migrated_compiled_classes: vec![], // TODO(prakhar,22/11/2025): Update this }) } @@ -461,6 +462,7 @@ mod tests { }], old_declared_contracts: vec![Felt::from(400u64)], replaced_classes: vec![], + migrated_compiled_classes: vec![], // TODO(prakhar,22/11/2025): Add value here and update tests }, transactions: vec![], events: vec![], @@ -601,6 +603,7 @@ mod tests { NonceUpdate { contract_address: Felt::from(101u64), nonce: Felt::from(1u64) }, // 0->1 NonceUpdate { contract_address: Felt::from(102u64), nonce: Felt::from(2u64) }, // 0->2 ], + migrated_compiled_classes: vec![] // TODO(prakhar,22/11/2025): Add value here and update tests } ); } diff --git a/madara/crates/client/devnet/src/lib.rs b/madara/crates/client/devnet/src/lib.rs index aa548ce2a2..4130e95124 100644 --- a/madara/crates/client/devnet/src/lib.rs +++ b/madara/crates/client/devnet/src/lib.rs @@ -235,6 +235,7 @@ impl ChainGenesisDescription { deployed_contracts: self.deployed_contracts.as_state_diff(), replaced_classes: vec![], nonces: vec![], + migrated_compiled_classes: vec![], // TODO(prakhar,22/11/2025): Update this }, transactions: vec![], events: vec![], diff --git a/madara/crates/client/rpc/src/block_id.rs b/madara/crates/client/rpc/src/block_id.rs index 78769a5098..6be76531a8 100644 --- a/madara/crates/client/rpc/src/block_id.rs +++ b/madara/crates/client/rpc/src/block_id.rs @@ -139,6 +139,8 @@ impl BlockViewResolvable for mp_rpc::v0_9_0::BlockId { } } +// v0.10 rpc + impl Starknet { pub fn resolve_block_view( &self, diff --git a/madara/crates/client/rpc/src/lib.rs b/madara/crates/client/rpc/src/lib.rs index 5df5500804..cb48b3efec 100644 --- a/madara/crates/client/rpc/src/lib.rs +++ b/madara/crates/client/rpc/src/lib.rs @@ -2,7 +2,7 @@ //! interface for interacting with the Starknet node. This module implements the official Starknet //! JSON-RPC specification along with some Madara-specific extensions. //! -//! Madara fully supports the Starknet JSON-RPC specification versions `v0.7.1` and `v0.8.1`, with +//! Madara fully supports the Starknet JSON-RPC specification versions `v0.7.1`, `v0.8.1`, `v0.9.0`, and `v0.10.0`, with //! methods accessible through port **9944** by default (configurable via `--rpc-port`). The RPC //! server supports both HTTP and WebSocket connections on the same port. //! @@ -13,6 +13,8 @@ //! //! - Default (v0.7.1): `http://localhost:9944/` //! - Version 0.8.1: `http://localhost:9944/rpc/v0_8_1/` +//! - Version 0.9.0: `http://localhost:9944/rpc/v0_9_0/` +//! - Version 0.10.0: `http://localhost:9944/rpc/v0_10_0/` //! //! ## Available Endpoints //! @@ -881,6 +883,11 @@ pub fn rpc_api_user(starknet: &Starknet) -> anyhow::Result> { rpc_api.merge(versions::user::v0_9_0::StarknetWsRpcApiV0_9_0Server::into_rpc(starknet.clone()))?; rpc_api.merge(versions::user::v0_9_0::StarknetTraceRpcApiV0_9_0Server::into_rpc(starknet.clone()))?; + rpc_api.merge(versions::user::v0_10_0::StarknetReadRpcApiV0_10_0Server::into_rpc(starknet.clone()))?; + rpc_api.merge(versions::user::v0_10_0::StarknetWriteRpcApiV0_10_0Server::into_rpc(starknet.clone()))?; + rpc_api.merge(versions::user::v0_10_0::StarknetWsRpcApiV0_10_0Server::into_rpc(starknet.clone()))?; + rpc_api.merge(versions::user::v0_10_0::StarknetTraceRpcApiV0_10_0Server::into_rpc(starknet.clone()))?; + Ok(rpc_api) } diff --git a/madara/crates/client/rpc/src/test_utils.rs b/madara/crates/client/rpc/src/test_utils.rs index 6262413188..a1a633a944 100644 --- a/madara/crates/client/rpc/src/test_utils.rs +++ b/madara/crates/client/rpc/src/test_utils.rs @@ -649,6 +649,7 @@ pub fn make_sample_chain_for_state_updates(backend: &Arc) -> Samp deployed_contracts: vec![DeployedContractItem { address: contracts[0], class_hash: class_hashes[0] }], replaced_classes: vec![], nonces: vec![], + migrated_compiled_classes: vec![], }, StateDiff { storage_diffs: vec![ @@ -672,6 +673,7 @@ pub fn make_sample_chain_for_state_updates(backend: &Arc) -> Samp NonceUpdate { contract_address: contracts[0], nonce: 1.into() }, NonceUpdate { contract_address: contracts[2], nonce: 2.into() }, ], + migrated_compiled_classes: vec![], }, StateDiff { storage_diffs: vec![ @@ -689,6 +691,7 @@ pub fn make_sample_chain_for_state_updates(backend: &Arc) -> Samp deployed_contracts: vec![], replaced_classes: vec![], nonces: vec![], + migrated_compiled_classes: vec![], }, StateDiff { storage_diffs: vec![ContractStorageDiffItem { @@ -709,6 +712,7 @@ pub fn make_sample_chain_for_state_updates(backend: &Arc) -> Samp NonceUpdate { contract_address: contracts[0], nonce: 3.into() }, NonceUpdate { contract_address: contracts[1], nonce: 2.into() }, ], + migrated_compiled_classes: vec![], }, ]; diff --git a/madara/crates/client/rpc/src/versions/user/mod.rs b/madara/crates/client/rpc/src/versions/user/mod.rs index b241838735..97da754a91 100644 --- a/madara/crates/client/rpc/src/versions/user/mod.rs +++ b/madara/crates/client/rpc/src/versions/user/mod.rs @@ -1,3 +1,4 @@ +pub mod v0_10_0; pub mod v0_7_1; pub mod v0_8_1; pub mod v0_9_0; diff --git a/madara/crates/client/rpc/src/versions/user/v0_10_0/methods/mod.rs b/madara/crates/client/rpc/src/versions/user/v0_10_0/methods/mod.rs new file mode 100644 index 0000000000..a4a137fd46 --- /dev/null +++ b/madara/crates/client/rpc/src/versions/user/v0_10_0/methods/mod.rs @@ -0,0 +1,4 @@ +pub mod read; +pub mod trace; +pub mod write; +pub mod ws; diff --git a/madara/crates/client/rpc/src/versions/user/v0_10_0/methods/read/estimate_message_fee.rs b/madara/crates/client/rpc/src/versions/user/v0_10_0/methods/read/estimate_message_fee.rs new file mode 100644 index 0000000000..4a1f5b568c --- /dev/null +++ b/madara/crates/client/rpc/src/versions/user/v0_10_0/methods/read/estimate_message_fee.rs @@ -0,0 +1,62 @@ +use crate::errors::StarknetRpcApiError; +use crate::errors::StarknetRpcResult; +use crate::Starknet; +use anyhow::Context; +use mc_exec::execution::TxInfo; +use mc_exec::MadaraBlockViewExecutionExt; +use mc_exec::EXECUTION_UNSUPPORTED_BELOW_VERSION; +use mp_convert::ToFelt; +use mp_rpc::v0_10_0::{BlockId, MessageFeeEstimate, MsgFromL1}; +use mp_transactions::L1HandlerTransaction; +use starknet_api::transaction::{fields::Fee, TransactionHash}; + +/// Estimates the L2 fee for an L1 message. +/// +/// v0.10.0: Returns `CONTRACT_NOT_FOUND` if the L1 handler contract (`to_address`) doesn't exist on L2. +pub async fn estimate_message_fee( + starknet: &Starknet, + message: MsgFromL1, + block_id: BlockId, +) -> StarknetRpcResult { + tracing::debug!("estimate fee on block_id {block_id:?}"); + let view = starknet.resolve_block_view(block_id)?; + let mut exec_context = view.new_execution_context()?; + + if exec_context.protocol_version < EXECUTION_UNSUPPORTED_BELOW_VERSION { + return Err(StarknetRpcApiError::unsupported_txn_version()); + } + + let state_view = view.state_view(); + if state_view.get_contract_class_hash(&message.to_address)?.is_none() { + return Err(StarknetRpcApiError::ContractNotFound { + error: format!("Contract at address {:#x} not found", message.to_address).into(), + }); + } + + let l1_handler: L1HandlerTransaction = message.into(); + let tx_hash = l1_handler.compute_hash( + view.backend().chain_config().chain_id.to_felt(), + /* offset_version */ false, + /* legacy */ false, + ); + let tx: starknet_api::transaction::L1HandlerTransaction = l1_handler.try_into().unwrap(); + let transaction = blockifier::transaction::transaction_execution::Transaction::L1Handler( + starknet_api::executable_transaction::L1HandlerTransaction { + tx, + tx_hash: TransactionHash(tx_hash), + paid_fee_on_l1: Fee::default(), + }, + ); + + let tip = transaction.tip().unwrap_or_default(); + let (mut execution_results, exec_context) = mp_utils::spawn_blocking(move || { + Ok::<_, mc_exec::Error>((exec_context.execute_transactions([], [transaction])?, exec_context)) + }) + .await?; + + let execution_result = execution_results.pop().context("There should be one result")?; + + let fee_estimate = exec_context.execution_result_to_fee_estimate_v0_9(&execution_result, tip)?; + + Ok(MessageFeeEstimate { common: fee_estimate, unit: mp_rpc::v0_10_0::PriceUnitWei::Wei }) +} diff --git a/madara/crates/client/rpc/src/versions/user/v0_10_0/methods/read/get_events.rs b/madara/crates/client/rpc/src/versions/user/v0_10_0/methods/read/get_events.rs new file mode 100644 index 0000000000..92f2189817 --- /dev/null +++ b/madara/crates/client/rpc/src/versions/user/v0_10_0/methods/read/get_events.rs @@ -0,0 +1,70 @@ +use crate::constants::{MAX_EVENTS_CHUNK_SIZE, MAX_EVENTS_KEYS}; +use crate::errors::{StarknetRpcApiError, StarknetRpcResult}; +use crate::types::ContinuationToken; +use crate::Starknet; +use anyhow::Context; +use mc_db::EventFilter; +use mp_block::EventWithInfo; +use mp_rpc::v0_10_0::{EventFilterWithPageRequest, EventsChunk}; + +/// Returns events matching the filter with pagination support. +/// +/// v0.10.0: Events include `transaction_index` and `event_index`. +pub fn get_events(starknet: &Starknet, filter: EventFilterWithPageRequest) -> StarknetRpcResult { + let from_address = filter.address; + let keys = filter.keys; + let chunk_size = filter.chunk_size as usize; + + let view = starknet.backend.view_on_latest(); + + if keys.as_ref().map(|k| k.iter().map(|pattern| pattern.len()).sum()).unwrap_or(0) > MAX_EVENTS_KEYS { + return Err(StarknetRpcApiError::TooManyKeysInFilter); + } + if chunk_size > MAX_EVENTS_CHUNK_SIZE { + return Err(StarknetRpcApiError::PageSizeTooBig); + } + + let from_block_n = match filter.from_block { + Some(block_id) => starknet.resolve_view_on(block_id)?.latest_block_n().unwrap_or(0), + None => 0, + }; + let to_block_n = match filter.to_block { + Some(block_id) => starknet.resolve_view_on(block_id)?.latest_block_n().unwrap_or(0), + None => view.latest_block_n().unwrap_or(0), + }; + + let continuation_token = match filter.continuation_token { + Some(token) => ContinuationToken::parse(token).map_err(|_| StarknetRpcApiError::InvalidContinuationToken)?, + None => ContinuationToken { block_number: from_block_n, event_n: 0 }, + }; + + if from_block_n > to_block_n { + return Ok(EventsChunk { events: vec![], continuation_token: None }); + } + + let from_block = continuation_token.block_number; + let from_event_n = continuation_token.event_n as usize; + + let mut events_infos = view + .get_events(EventFilter { + start_block: from_block, + start_event_index: from_event_n, + end_block: to_block_n, + from_address, + keys_pattern: keys, + max_events: chunk_size + 1, + }) + .context("Error getting filtered events")?; + + let mut continuation_token = None; + if events_infos.len() > chunk_size { + continuation_token = events_infos.pop().map(|EventWithInfo { block_number, event_index_in_block, .. }| { + ContinuationToken { block_number, event_n: (event_index_in_block + 1) } + }); + } + + Ok(EventsChunk { + events: events_infos.into_iter().map(|event_info| event_info.into()).collect(), + continuation_token: continuation_token.map(|token| token.to_string()), + }) +} diff --git a/madara/crates/client/rpc/src/versions/user/v0_10_0/methods/read/get_state_update.rs b/madara/crates/client/rpc/src/versions/user/v0_10_0/methods/read/get_state_update.rs new file mode 100644 index 0000000000..ac276eb8e6 --- /dev/null +++ b/madara/crates/client/rpc/src/versions/user/v0_10_0/methods/read/get_state_update.rs @@ -0,0 +1,93 @@ +use crate::errors::StarknetRpcResult; +use crate::Starknet; +use mp_rpc::v0_10_0::{BlockId, MaybePreConfirmedStateUpdate, PreConfirmedStateUpdate, StateUpdate}; +use starknet_types_core::felt::Felt; + +/// Returns the state update for the requested block. +/// +/// v0.10.0: PreConfirmedStateUpdate no longer includes `old_root`. +pub fn get_state_update(starknet: &Starknet, block_id: BlockId) -> StarknetRpcResult { + let view = starknet.resolve_block_view(block_id)?; + let state_diff = view.get_state_diff()?; + let old_root = if let Some(parent) = view.parent_block() { + parent.get_block_info()?.header.global_state_root + } else { + Felt::ZERO + }; + + if let Some(confirmed) = view.as_confirmed() { + let block_info = confirmed.get_block_info()?; + Ok(MaybePreConfirmedStateUpdate::Block(StateUpdate { + block_hash: block_info.block_hash, + old_root, + new_root: block_info.header.global_state_root, + state_diff: state_diff.into(), + })) + } else { + Ok(MaybePreConfirmedStateUpdate::PreConfirmed(PreConfirmedStateUpdate { state_diff: state_diff.into() })) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + test_utils::{sample_chain_for_state_updates, SampleChainForStateUpdates}, + StarknetRpcApiError, + }; + use mp_rpc::v0_10_0::BlockTag; + use rstest::rstest; + + #[rstest] + fn test_get_state_update(sample_chain_for_state_updates: (SampleChainForStateUpdates, Starknet)) { + let (SampleChainForStateUpdates { block_hashes, state_roots, state_diffs, .. }, rpc) = + sample_chain_for_state_updates; + + // Block 0 + let res = MaybePreConfirmedStateUpdate::Block(StateUpdate { + block_hash: block_hashes[0], + old_root: Felt::ZERO, + new_root: state_roots[0], + state_diff: state_diffs[0].clone().into(), + }); + assert_eq!(get_state_update(&rpc, BlockId::Number(0)).unwrap(), res); + assert_eq!(get_state_update(&rpc, BlockId::Hash(block_hashes[0])).unwrap(), res); + + // Block 1 + let res = MaybePreConfirmedStateUpdate::Block(StateUpdate { + block_hash: block_hashes[1], + old_root: state_roots[0], + new_root: state_roots[1], + state_diff: state_diffs[1].clone().into(), + }); + assert_eq!(get_state_update(&rpc, BlockId::Number(1)).unwrap(), res); + assert_eq!(get_state_update(&rpc, BlockId::Hash(block_hashes[1])).unwrap(), res); + + // Block 2 + let res = MaybePreConfirmedStateUpdate::Block(StateUpdate { + block_hash: block_hashes[2], + old_root: state_roots[1], + new_root: state_roots[2], + state_diff: state_diffs[2].clone().into(), + }); + assert_eq!(get_state_update(&rpc, BlockId::Number(2)).unwrap(), res); + assert_eq!(get_state_update(&rpc, BlockId::Hash(block_hashes[2])).unwrap(), res); + assert_eq!(get_state_update(&rpc, BlockId::Tag(BlockTag::Latest)).unwrap(), res); + + // Pending + let res = MaybePreConfirmedStateUpdate::PreConfirmed(PreConfirmedStateUpdate { + state_diff: state_diffs[3].clone().into(), + }); + assert_eq!(get_state_update(&rpc, BlockId::Tag(BlockTag::PreConfirmed)).unwrap(), res); + } + + #[rstest] + + fn test_get_state_update_not_found(sample_chain_for_state_updates: (SampleChainForStateUpdates, Starknet)) { + let (SampleChainForStateUpdates { .. }, rpc) = sample_chain_for_state_updates; + + assert_eq!(get_state_update(&rpc, BlockId::Number(3)), Err(StarknetRpcApiError::BlockNotFound)); + let does_not_exist = Felt::from_hex_unchecked("0x7128638126378"); + assert_eq!(get_state_update(&rpc, BlockId::Hash(does_not_exist)), Err(StarknetRpcApiError::BlockNotFound)); + } +} diff --git a/madara/crates/client/rpc/src/versions/user/v0_10_0/methods/read/mod.rs b/madara/crates/client/rpc/src/versions/user/v0_10_0/methods/read/mod.rs new file mode 100644 index 0000000000..b4e9272419 --- /dev/null +++ b/madara/crates/client/rpc/src/versions/user/v0_10_0/methods/read/mod.rs @@ -0,0 +1,161 @@ +use crate::versions::user::v0_10_0::StarknetReadRpcApiV0_10_0Server; +use crate::versions::user::v0_8_1::StarknetReadRpcApiV0_8_1Server as V0_8_1Impl; +use crate::versions::user::v0_9_0::StarknetReadRpcApiV0_9_0Server as V0_9_0Impl; +use crate::{Starknet, StarknetRpcApiError}; +use jsonrpsee::core::{async_trait, RpcResult}; +use mp_chain_config::RpcVersion; +use mp_convert::Felt; +use mp_rpc::v0_10_0::{ + BlockHashAndNumber, BlockId, BroadcastedTxn, ContractStorageKeysItem, EventFilterWithPageRequest, EventsChunk, + FeeEstimate, FunctionCall, GetStorageProofResult, MaybeDeprecatedContractClass, MaybePreConfirmedBlockWithTxHashes, + MaybePreConfirmedBlockWithTxs, MaybePreConfirmedStateUpdate, MessageFeeEstimate, MsgFromL1, + SimulationFlagForEstimateFee, StarknetGetBlockWithTxsAndReceiptsResult, SyncingStatus, + TxnFinalityAndExecutionStatus, TxnReceiptWithBlockInfo, TxnWithHash, +}; + +// Only implement functions that have v0.10.0-specific changes: +// - spec_version: different return value +// - get_state_update: PreConfirmedStateUpdate structure changed (no old_root) +// - get_events: EmittedEvent structure changed (has transaction_index and event_index) +// - estimate_message_fee: needs CONTRACT_NOT_FOUND error check for L1 handler contract +// - get_storage_proof: ContractStorageKeysItem uses StorageKey instead of Felt +pub mod estimate_message_fee; +pub mod get_events; +pub mod get_state_update; + +#[async_trait] +impl StarknetReadRpcApiV0_10_0Server for Starknet { + fn spec_version(&self) -> RpcResult { + Ok(RpcVersion::RPC_VERSION_0_10_0.to_string()) + } + + fn block_number(&self) -> RpcResult { + V0_8_1Impl::block_number(self) + } + + fn block_hash_and_number(&self) -> RpcResult { + V0_8_1Impl::block_hash_and_number(self) + } + + fn chain_id(&self) -> RpcResult { + V0_8_1Impl::chain_id(self) + } + + fn syncing(&self) -> RpcResult { + V0_8_1Impl::syncing(self) + } + + async fn call(&self, request: FunctionCall, block_id: BlockId) -> RpcResult> { + V0_9_0Impl::call(self, request, block_id).await + } + + fn get_block_transaction_count(&self, block_id: BlockId) -> RpcResult { + V0_9_0Impl::get_block_transaction_count(self, block_id) + } + + async fn estimate_fee( + &self, + request: Vec, + simulation_flags: Vec, + block_id: BlockId, + ) -> RpcResult> { + V0_9_0Impl::estimate_fee(self, request, simulation_flags, block_id).await + } + + async fn estimate_message_fee(&self, message: MsgFromL1, block_id: BlockId) -> RpcResult { + Ok(estimate_message_fee::estimate_message_fee(self, message, block_id).await?) + } + + fn get_block_with_receipts(&self, block_id: BlockId) -> RpcResult { + V0_9_0Impl::get_block_with_receipts(self, block_id) + } + + fn get_block_with_tx_hashes(&self, block_id: BlockId) -> RpcResult { + V0_9_0Impl::get_block_with_tx_hashes(self, block_id) + } + + fn get_block_with_txs(&self, block_id: BlockId) -> RpcResult { + V0_9_0Impl::get_block_with_txs(self, block_id) + } + + fn get_class_at(&self, block_id: BlockId, contract_address: Felt) -> RpcResult { + V0_9_0Impl::get_class_at(self, block_id, contract_address) + } + + fn get_class_hash_at(&self, block_id: BlockId, contract_address: Felt) -> RpcResult { + V0_9_0Impl::get_class_hash_at(self, block_id, contract_address) + } + + fn get_class(&self, block_id: BlockId, class_hash: Felt) -> RpcResult { + V0_9_0Impl::get_class(self, block_id, class_hash) + } + + fn get_events(&self, filter: EventFilterWithPageRequest) -> RpcResult { + Ok(get_events::get_events(self, filter)?) + } + + fn get_nonce(&self, block_id: BlockId, contract_address: Felt) -> RpcResult { + V0_9_0Impl::get_nonce(self, block_id, contract_address) + } + + fn get_storage_at(&self, contract_address: Felt, key: Felt, block_id: BlockId) -> RpcResult { + V0_9_0Impl::get_storage_at(self, contract_address, key, block_id) + } + + fn get_transaction_by_block_id_and_index(&self, block_id: BlockId, index: u64) -> RpcResult { + V0_9_0Impl::get_transaction_by_block_id_and_index(self, block_id, index) + } + + fn get_transaction_by_hash(&self, transaction_hash: Felt) -> RpcResult { + V0_9_0Impl::get_transaction_by_hash(self, transaction_hash) + } + + fn get_transaction_receipt(&self, transaction_hash: Felt) -> RpcResult { + V0_9_0Impl::get_transaction_receipt(self, transaction_hash) + } + + async fn get_transaction_status(&self, transaction_hash: Felt) -> RpcResult { + V0_9_0Impl::get_transaction_status(self, transaction_hash).await + } + + fn get_state_update(&self, block_id: BlockId) -> RpcResult { + Ok(get_state_update::get_state_update(self, block_id)?) + } + fn get_storage_proof( + &self, + block_id: BlockId, + class_hashes: Option>, + contract_addresses: Option>, + contracts_storage_keys: Option>, + ) -> RpcResult { + let block_view = self.resolve_view_on(block_id)?; + + // Convert StorageKey to Felt for v0.8.1 compatibility + let contracts_storage_keys_v0_8_1 = contracts_storage_keys.map(|keys| { + keys.into_iter() + .map(|item| mp_rpc::v0_8_1::ContractStorageKeysItem { + contract_address: item.contract_address, + storage_keys: item + .storage_keys + .into_iter() + .map(|key| Felt::from_hex(&key).unwrap_or(Felt::ZERO)) + .collect(), + }) + .collect() + }); + + V0_8_1Impl::get_storage_proof( + self, + mp_rpc::v0_8_1::BlockId::Number( + block_view.latest_confirmed_block_n().ok_or(StarknetRpcApiError::NoBlocks)?, + ), + class_hashes, + contract_addresses, + contracts_storage_keys_v0_8_1, + ) + } + + fn get_compiled_casm(&self, class_hash: Felt) -> RpcResult { + V0_8_1Impl::get_compiled_casm(self, class_hash) + } +} diff --git a/madara/crates/client/rpc/src/versions/user/v0_10_0/methods/trace/mod.rs b/madara/crates/client/rpc/src/versions/user/v0_10_0/methods/trace/mod.rs new file mode 100644 index 0000000000..2dc7d66137 --- /dev/null +++ b/madara/crates/client/rpc/src/versions/user/v0_10_0/methods/trace/mod.rs @@ -0,0 +1,30 @@ +use crate::versions::user::v0_9_0::StarknetTraceRpcApiV0_9_0Server as V0_9_0Impl; +use crate::{versions::user::v0_10_0::StarknetTraceRpcApiV0_10_0Server, Starknet}; +use jsonrpsee::core::{async_trait, RpcResult}; +use mp_rpc::v0_10_0::{ + BlockId, BroadcastedTxn, SimulateTransactionsResult, SimulationFlag, TraceBlockTransactionsResult, + TraceTransactionResult, +}; +use starknet_types_core::felt::Felt; + +// Trace types unchanged in v0.10.0, delegating to v0.9.0 + +#[async_trait] +impl StarknetTraceRpcApiV0_10_0Server for Starknet { + async fn simulate_transactions( + &self, + block_id: BlockId, + transactions: Vec, + simulation_flags: Vec, + ) -> RpcResult> { + V0_9_0Impl::simulate_transactions(self, block_id, transactions, simulation_flags).await + } + + async fn trace_block_transactions(&self, block_id: BlockId) -> RpcResult> { + V0_9_0Impl::trace_block_transactions(self, block_id).await + } + + async fn trace_transaction(&self, transaction_hash: Felt) -> RpcResult { + V0_9_0Impl::trace_transaction(self, transaction_hash).await + } +} diff --git a/madara/crates/client/rpc/src/versions/user/v0_10_0/methods/write/mod.rs b/madara/crates/client/rpc/src/versions/user/v0_10_0/methods/write/mod.rs new file mode 100644 index 0000000000..e24d227a7b --- /dev/null +++ b/madara/crates/client/rpc/src/versions/user/v0_10_0/methods/write/mod.rs @@ -0,0 +1,28 @@ +use crate::versions::user::v0_8_1::StarknetWriteRpcApiV0_8_1Server as V0_8_1Impl; +use crate::{versions::user::v0_10_0::StarknetWriteRpcApiV0_10_0Server, Starknet}; +use jsonrpsee::core::{async_trait, RpcResult}; +use mp_rpc::v0_10_0::{ + AddInvokeTransactionResult, BroadcastedDeclareTxn, BroadcastedDeployAccountTxn, BroadcastedInvokeTxn, + ClassAndTxnHash, ContractAndTxnHash, +}; + +#[async_trait] +impl StarknetWriteRpcApiV0_10_0Server for Starknet { + async fn add_declare_transaction(&self, declare_transaction: BroadcastedDeclareTxn) -> RpcResult { + V0_8_1Impl::add_declare_transaction(self, declare_transaction).await + } + + async fn add_deploy_account_transaction( + &self, + deploy_account_transaction: BroadcastedDeployAccountTxn, + ) -> RpcResult { + V0_8_1Impl::add_deploy_account_transaction(self, deploy_account_transaction).await + } + + async fn add_invoke_transaction( + &self, + invoke_transaction: BroadcastedInvokeTxn, + ) -> RpcResult { + V0_8_1Impl::add_invoke_transaction(self, invoke_transaction).await + } +} diff --git a/madara/crates/client/rpc/src/versions/user/v0_10_0/methods/ws/lib.rs b/madara/crates/client/rpc/src/versions/user/v0_10_0/methods/ws/lib.rs new file mode 100644 index 0000000000..e55e802659 --- /dev/null +++ b/madara/crates/client/rpc/src/versions/user/v0_10_0/methods/ws/lib.rs @@ -0,0 +1,53 @@ +use mp_rpc::v0_10_0::BlockId; +use starknet_types_core::felt::Felt; + +use crate::versions::user::v0_10_0::StarknetWsRpcApiV0_10_0Server; +use crate::versions::user::v0_9_0::StarknetWsRpcApiV0_9_0Server as V0_9_0Impl; + +use super::starknet_unsubscribe::*; + +// Subscriptions disabled across all versions, delegating to v0.9.0 + +#[jsonrpsee::core::async_trait] +// FIXME(subscriptions): Remove this #[allow(unused)] once subscriptions are back. +#[allow(unused)] +impl StarknetWsRpcApiV0_10_0Server for crate::Starknet { + async fn subscribe_new_heads( + &self, + subscription_sink: jsonrpsee::PendingSubscriptionSink, + block: BlockId, + ) -> jsonrpsee::core::SubscriptionResult { + V0_9_0Impl::subscribe_new_heads(self, subscription_sink, block).await + } + + async fn subscribe_events( + &self, + subscription_sink: jsonrpsee::PendingSubscriptionSink, + from_address: Option, + keys: Option>>, + block: Option, + ) -> jsonrpsee::core::SubscriptionResult { + V0_9_0Impl::subscribe_events(self, subscription_sink, from_address, keys, block).await + } + + async fn subscribe_transaction_status( + &self, + subscription_sink: jsonrpsee::PendingSubscriptionSink, + transaction_hash: Felt, + ) -> jsonrpsee::core::SubscriptionResult { + V0_9_0Impl::subscribe_transaction_status(self, subscription_sink, transaction_hash).await + } + + async fn subscribe_pending_transactions( + &self, + subscription_sink: jsonrpsee::PendingSubscriptionSink, + transaction_details: bool, + sender_address: Vec, + ) -> jsonrpsee::core::SubscriptionResult { + V0_9_0Impl::subscribe_pending_transactions(self, subscription_sink, transaction_details, sender_address).await + } + + async fn starknet_unsubscribe(&self, subscription_id: u64) -> jsonrpsee::core::RpcResult { + Ok(starknet_unsubscribe(self, subscription_id).await?) + } +} diff --git a/madara/crates/client/rpc/src/versions/user/v0_10_0/methods/ws/mod.rs b/madara/crates/client/rpc/src/versions/user/v0_10_0/methods/ws/mod.rs new file mode 100644 index 0000000000..4678a99f2f --- /dev/null +++ b/madara/crates/client/rpc/src/versions/user/v0_10_0/methods/ws/mod.rs @@ -0,0 +1,36 @@ +pub mod lib; +pub mod starknet_unsubscribe; +// FIXME(subscriptions): Re-add subscriptions. +// pub mod subscribe_events; +// FIXME(subscriptions): Re-add subscriptions. +// pub mod subscribe_new_heads; +// FIXME(subscriptions): Re-add subscriptions. +// pub mod subscribe_pending_transactions; +// FIXME(subscriptions): Re-add subscriptions. +// pub mod subscribe_transaction_status; + +// FIXME(subscriptions): Remove this #[allow(unused)] once subscriptions are back. +#[allow(unused)] +const BLOCK_PAST_LIMIT: u64 = 1024; +// FIXME(subscriptions): Remove this #[allow(unused)] once subscriptions are back. +#[allow(unused)] +const ADDRESS_FILTER_LIMIT: u64 = 128; + +#[derive(PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)] +pub struct SubscriptionItem { + subscription_id: u64, + result: T, +} + +impl SubscriptionItem { + pub fn new(subscription_id: jsonrpsee::types::SubscriptionId, result: T) -> Self { + let subscription_id = match subscription_id { + jsonrpsee::types::SubscriptionId::Num(id) => id, + jsonrpsee::types::SubscriptionId::Str(_) => { + unreachable!("Jsonrpsee middleware has been configured to use u64 subscription ids") + } + }; + + Self { subscription_id, result } + } +} diff --git a/madara/crates/client/rpc/src/versions/user/v0_10_0/methods/ws/starknet_unsubscribe.rs b/madara/crates/client/rpc/src/versions/user/v0_10_0/methods/ws/starknet_unsubscribe.rs new file mode 100644 index 0000000000..9e60f817d2 --- /dev/null +++ b/madara/crates/client/rpc/src/versions/user/v0_10_0/methods/ws/starknet_unsubscribe.rs @@ -0,0 +1,56 @@ +//! # Caution +//! +//! This is a temporary workaround due to limitations in the way in which [jsonrpsee] works. If +//! possible at all, clients should prefer to use the unsubscribe methods defined in [api.rs]. These +//! follow the structure `starknet_unsubscribeMethodName`, so for example +//! `starknet_unsubscribeNewHeads`. +//! +//! Use these if you encounter any strange edge cases such as 500 error codes on unsubscribe. +//! +//! [api.rs]: super::super::super::api + +pub async fn starknet_unsubscribe(starknet: &crate::Starknet, subscription_id: u64) -> crate::StarknetRpcResult { + if starknet.ws_handles.subscription_close(subscription_id).await { + Ok(true) + } else { + Err(crate::StarknetRpcApiError::InvalidSubscriptionId) + } +} + +#[cfg(test)] +mod test { + #[rstest::fixture] + fn logs() { + let debug = tracing_subscriber::filter::LevelFilter::DEBUG; + let env = tracing_subscriber::EnvFilter::builder().with_default_directive(debug.into()).from_env_lossy(); + let _ = tracing_subscriber::fmt().with_test_writer().with_env_filter(env).with_line_number(true).try_init(); + } + + #[rstest::fixture] + fn starknet() -> crate::Starknet { + let chain_config = std::sync::Arc::new(mp_chain_config::ChainConfig::madara_test()); + let backend = mc_db::MadaraBackend::open_for_testing(chain_config); + let validation = mc_submit_tx::TransactionValidatorConfig { disable_validation: true, disable_fee: true }; + let mempool = std::sync::Arc::new(mc_mempool::Mempool::new( + std::sync::Arc::clone(&backend), + mc_mempool::MempoolConfig::default(), + )); + let mempool_validator = std::sync::Arc::new(mc_submit_tx::TransactionValidator::new( + mempool, + std::sync::Arc::clone(&backend), + validation, + )); + let context = mp_utils::service::ServiceContext::new_for_testing(); + + crate::Starknet::new(backend, mempool_validator, Default::default(), None, context) + } + + #[tokio::test] + #[rstest::rstest] + async fn starknet_unsubscribe_err(_logs: (), starknet: crate::Starknet) { + assert_eq!( + super::starknet_unsubscribe(&starknet, 0).await, + Err(crate::StarknetRpcApiError::InvalidSubscriptionId) + ) + } +} diff --git a/madara/crates/client/rpc/src/versions/user/v0_10_0/mod.rs b/madara/crates/client/rpc/src/versions/user/v0_10_0/mod.rs new file mode 100644 index 0000000000..128b1a3835 --- /dev/null +++ b/madara/crates/client/rpc/src/versions/user/v0_10_0/mod.rs @@ -0,0 +1,215 @@ +use jsonrpsee::core::RpcResult; +use m_proc_macros::versioned_rpc; +use mp_rpc::v0_10_0::{ + AddInvokeTransactionResult, BlockHashAndNumber, BlockId, BroadcastedDeclareTxn, BroadcastedDeployAccountTxn, + BroadcastedInvokeTxn, BroadcastedTxn, ClassAndTxnHash, ContractAndTxnHash, ContractStorageKeysItem, + EventFilterWithPageRequest, EventsChunk, FeeEstimate, FunctionCall, GetStorageProofResult, + MaybeDeprecatedContractClass, MaybePreConfirmedBlockWithTxHashes, MaybePreConfirmedBlockWithTxs, + MaybePreConfirmedStateUpdate, MessageFeeEstimate, MsgFromL1, SimulateTransactionsResult, SimulationFlag, + SimulationFlagForEstimateFee, StarknetGetBlockWithTxsAndReceiptsResult, SyncingStatus, + TraceBlockTransactionsResult, TraceTransactionResult, TxnFinalityAndExecutionStatus, TxnReceiptWithBlockInfo, + TxnWithHash, +}; +use starknet_types_core::felt::Felt; + +pub mod methods; + +#[versioned_rpc("V0_10_0", "starknet")] +pub trait StarknetReadRpcApi { + #[method(name = "specVersion")] + /// Get the Version of the StarkNet JSON-RPC Specification Being Used + fn spec_version(&self) -> RpcResult; + + /// Get the most recent accepted block number + #[method(name = "blockNumber")] + fn block_number(&self) -> RpcResult; + + // Get the most recent accepted block hash and number + #[method(name = "blockHashAndNumber")] + fn block_hash_and_number(&self) -> RpcResult; + + /// Get an object about the sync status, or false if the node is not syncing + #[method(name = "syncing")] + fn syncing(&self) -> RpcResult; + + /// Get the chain id + #[method(name = "chainId")] + fn chain_id(&self) -> RpcResult; + + /// Call a contract function at a given block id + #[method(name = "call")] + async fn call(&self, request: FunctionCall, block_id: BlockId) -> RpcResult>; + + /// Get the number of transactions in a block given a block id + #[method(name = "getBlockTransactionCount")] + fn get_block_transaction_count(&self, block_id: BlockId) -> RpcResult; + + /// Estimate the fee associated with transaction + #[method(name = "estimateFee")] + async fn estimate_fee( + &self, + request: Vec, + simulation_flags: Vec, + block_id: BlockId, + ) -> RpcResult>; + + /// Estimate the L2 fee of a message sent on L1 + #[method(name = "estimateMessageFee")] + async fn estimate_message_fee(&self, message: MsgFromL1, block_id: BlockId) -> RpcResult; + + /// Get block information with full transactions and receipts given the block id + #[method(name = "getBlockWithReceipts")] + fn get_block_with_receipts(&self, block_id: BlockId) -> RpcResult; + + /// Get block information with transaction hashes given the block id + #[method(name = "getBlockWithTxHashes")] + fn get_block_with_tx_hashes(&self, block_id: BlockId) -> RpcResult; + + /// Get block information with full transactions given the block id + #[method(name = "getBlockWithTxs")] + fn get_block_with_txs(&self, block_id: BlockId) -> RpcResult; + + /// Get the contract class at a given contract address for a given block id + #[method(name = "getClassAt")] + fn get_class_at(&self, block_id: BlockId, contract_address: Felt) -> RpcResult; + + /// Get the contract class hash in the given block for the contract deployed at the given + /// address + #[method(name = "getClassHashAt")] + fn get_class_hash_at(&self, block_id: BlockId, contract_address: Felt) -> RpcResult; + + /// Get the contract class definition in the given block associated with the given hash + #[method(name = "getClass")] + fn get_class(&self, block_id: BlockId, class_hash: Felt) -> RpcResult; + + /// Returns all events matching the given filter + #[method(name = "getEvents")] + fn get_events(&self, filter: EventFilterWithPageRequest) -> RpcResult; + + /// Get the nonce associated with the given address at the given block + #[method(name = "getNonce")] + fn get_nonce(&self, block_id: BlockId, contract_address: Felt) -> RpcResult; + + /// Get the value of the storage at the given address and key, at the given block id + #[method(name = "getStorageAt")] + fn get_storage_at(&self, contract_address: Felt, key: Felt, block_id: BlockId) -> RpcResult; + + /// Get the details of a transaction by a given block id and index + #[method(name = "getTransactionByBlockIdAndIndex")] + fn get_transaction_by_block_id_and_index(&self, block_id: BlockId, index: u64) -> RpcResult; + + /// Returns the information about a transaction by transaction hash. + #[method(name = "getTransactionByHash")] + fn get_transaction_by_hash(&self, transaction_hash: Felt) -> RpcResult; + + /// Returns the receipt of a transaction by transaction hash. + #[method(name = "getTransactionReceipt")] + fn get_transaction_receipt(&self, transaction_hash: Felt) -> RpcResult; + + /// Gets the Transaction Status, Including Mempool Status and Execution Details + #[method(name = "getTransactionStatus")] + async fn get_transaction_status(&self, transaction_hash: Felt) -> RpcResult; + + /// Get the information about the result of executing the requested block + #[method(name = "getStateUpdate")] + fn get_state_update(&self, block_id: BlockId) -> RpcResult; + + #[method(name = "getStorageProof")] + fn get_storage_proof( + &self, + block_id: BlockId, + class_hashes: Option>, + contract_addresses: Option>, + contracts_storage_keys: Option>, + ) -> RpcResult; + + #[method(name = "getCompiledCasm")] + fn get_compiled_casm(&self, class_hash: Felt) -> RpcResult; +} + +type SubscriptionItemPendingTxs = methods::ws::SubscriptionItem; +type SubscriptionItemEvents = methods::ws::SubscriptionItem; +type SubscriptionItemNewHeads = methods::ws::SubscriptionItem; +type SubscriptionItemTransactionStatus = methods::ws::SubscriptionItem; + +#[versioned_rpc("V0_10_0", "starknet")] +pub trait StarknetWsRpcApi { + #[subscription(name = "subscribeNewHeads", unsubscribe = "unsubscribeNewHeads", item = SubscriptionItemNewHeads, param_kind = map)] + async fn subscribe_new_heads(&self, block: BlockId) -> jsonrpsee::core::SubscriptionResult; + + #[subscription( + name = "subscribeEvents", + unsubscribe = "unsubscribeEvents", + item = SubscriptionItemEvents, + param_kind = map + )] + async fn subscribe_events( + &self, + from_address: Option, + keys: Option>>, + block: Option, + ) -> jsonrpsee::core::SubscriptionResult; + + #[subscription( + name = "subscribeTransactionStatus", + unsubscribe = "unsubscribeTransactionStatus", + item = SubscriptionItemTransactionStatus, + param_kind = map + )] + async fn subscribe_transaction_status(&self, transaction_hash: Felt) -> jsonrpsee::core::SubscriptionResult; + + #[subscription( + name = "subscribePendingTransactions", + unsubscribe = "unsubscribePendingTransactions", + item = SubscriptionItemPendingTxs, + param_kind = map + )] + async fn subscribe_pending_transactions( + &self, + transaction_details: bool, + sender_address: Vec, + ) -> jsonrpsee::core::SubscriptionResult; + #[method(name = "unsubscribe")] + async fn starknet_unsubscribe(&self, subscription_id: u64) -> RpcResult; +} + +#[versioned_rpc("V0_10_0", "starknet")] +pub trait StarknetWriteRpcApi { + /// Submit a new transaction to be added to the chain + #[method(name = "addInvokeTransaction")] + async fn add_invoke_transaction( + &self, + invoke_transaction: BroadcastedInvokeTxn, + ) -> RpcResult; + + /// Submit a new deploy account transaction + #[method(name = "addDeployAccountTransaction")] + async fn add_deploy_account_transaction( + &self, + deploy_account_transaction: BroadcastedDeployAccountTxn, + ) -> RpcResult; + + /// Submit a new class declaration transaction + #[method(name = "addDeclareTransaction")] + async fn add_declare_transaction(&self, declare_transaction: BroadcastedDeclareTxn) -> RpcResult; +} + +#[versioned_rpc("V0_10_0", "starknet")] +pub trait StarknetTraceRpcApi { + /// Returns the execution trace of a transaction by simulating it in the runtime. + #[method(name = "simulateTransactions")] + async fn simulate_transactions( + &self, + block_id: BlockId, + transactions: Vec, + simulation_flags: Vec, + ) -> RpcResult>; + + #[method(name = "traceBlockTransactions")] + /// Returns the execution traces of all transactions included in the given block + async fn trace_block_transactions(&self, block_id: BlockId) -> RpcResult>; + + #[method(name = "traceTransaction")] + /// Returns the execution trace of a transaction + async fn trace_transaction(&self, transaction_hash: Felt) -> RpcResult; +} diff --git a/madara/crates/client/submit_tx/src/validation.rs b/madara/crates/client/submit_tx/src/validation.rs index 9badd0a3e5..519ea925d4 100644 --- a/madara/crates/client/submit_tx/src/validation.rs +++ b/madara/crates/client/submit_tx/src/validation.rs @@ -104,7 +104,6 @@ impl From for SubmitTransactionError { | E::ValidateTransactionError { .. } | E::ContractConstructorExecutionFailed { .. } | E::PanicInValidate { .. } - | E::DeclareTransactionCasmHashMissMatch { .. } | E::ValidateCairo0Error(_)) => rejected(ValidateFailure, format!("{err:#}")), err @ (E::FeeCheckError(_) | E::FromStr(_) diff --git a/madara/crates/client/sync/src/sync_utils.rs b/madara/crates/client/sync/src/sync_utils.rs index c06005cea5..bb2fd4f30b 100644 --- a/madara/crates/client/sync/src/sync_utils.rs +++ b/madara/crates/client/sync/src/sync_utils.rs @@ -55,6 +55,7 @@ pub async fn compress_state_diff( old_declared_contracts: raw_state_diff.old_declared_contracts, nonces: raw_state_diff.nonces, replaced_classes, + migrated_compiled_classes: vec![], // TODO(prakhar,22/11/2025): Update this }; compressed_diff.sort(); @@ -169,6 +170,7 @@ impl StateDiffMap { old_declared_contracts: deprecated_declared_classes, nonces, replaced_classes, + migrated_compiled_classes: vec![], // TODO(prakhar,22/11/2025): Update this } } } diff --git a/madara/crates/primitives/block/src/event_with_info.rs b/madara/crates/primitives/block/src/event_with_info.rs index 8adc79fab9..8e331f3e8a 100644 --- a/madara/crates/primitives/block/src/event_with_info.rs +++ b/madara/crates/primitives/block/src/event_with_info.rs @@ -40,6 +40,20 @@ impl From for mp_rpc::v0_7_1::EmittedEvent { } } +impl From for mp_rpc::v0_10_0::EmittedEvent { + fn from(event_with_info: EventWithInfo) -> Self { + mp_rpc::v0_10_0::EmittedEvent { + event: event_with_info.event.into(), + block_hash: event_with_info.block_hash, + // v0_10_0 expects None when the event is in the pending block. + block_number: if event_with_info.in_preconfirmed { None } else { Some(event_with_info.block_number) }, + transaction_hash: event_with_info.transaction_hash, + transaction_index: event_with_info.transaction_index, + event_index: event_with_info.event_index_in_block, + } + } +} + /// Filters events based on the provided address and keys. /// /// This function checks if an event matches the given address and keys. diff --git a/madara/crates/primitives/chain_config/src/rpc_version.rs b/madara/crates/primitives/chain_config/src/rpc_version.rs index e2da7ba935..d204d94971 100644 --- a/madara/crates/primitives/chain_config/src/rpc_version.rs +++ b/madara/crates/primitives/chain_config/src/rpc_version.rs @@ -1,10 +1,11 @@ use std::hash::Hash; use std::str::FromStr; -const SUPPORTED_RPC_VERSIONS: [RpcVersion; 4] = [ +const SUPPORTED_RPC_VERSIONS: [RpcVersion; 5] = [ RpcVersion::RPC_VERSION_0_7_1, RpcVersion::RPC_VERSION_0_8_1, RpcVersion::RPC_VERSION_0_9_0, + RpcVersion::RPC_VERSION_0_10_0, RpcVersion::RPC_VERSION_ADMIN_0_1_0, ]; @@ -86,7 +87,8 @@ impl RpcVersion { pub const RPC_VERSION_0_7_1: RpcVersion = RpcVersion([0, 7, 1]); pub const RPC_VERSION_0_8_1: RpcVersion = RpcVersion([0, 8, 1]); pub const RPC_VERSION_0_9_0: RpcVersion = RpcVersion([0, 9, 0]); - pub const RPC_VERSION_LATEST: RpcVersion = Self::RPC_VERSION_0_9_0; + pub const RPC_VERSION_0_10_0: RpcVersion = RpcVersion([0, 10, 0]); + pub const RPC_VERSION_LATEST: RpcVersion = Self::RPC_VERSION_0_10_0; pub const RPC_VERSION_ADMIN_0_1_0: RpcVersion = RpcVersion([0, 1, 0]); pub const RPC_VERSION_LATEST_ADMIN: RpcVersion = Self::RPC_VERSION_ADMIN_0_1_0; diff --git a/madara/crates/primitives/gateway/src/state_update.rs b/madara/crates/primitives/gateway/src/state_update.rs index ea13040e46..01698ce7a1 100644 --- a/madara/crates/primitives/gateway/src/state_update.rs +++ b/madara/crates/primitives/gateway/src/state_update.rs @@ -102,6 +102,7 @@ impl From for mp_state_update::StateDiff { .into_iter() .map(|(contract_address, nonce)| mp_state_update::NonceUpdate { contract_address, nonce }) .collect(), + migrated_compiled_classes: vec![], // TODO(prakhar,22/11/2025): Add migrated compiled classes here } } } diff --git a/madara/crates/primitives/rpc/src/lib.rs b/madara/crates/primitives/rpc/src/lib.rs index ab5e204a97..df8e2731aa 100644 --- a/madara/crates/primitives/rpc/src/lib.rs +++ b/madara/crates/primitives/rpc/src/lib.rs @@ -1,6 +1,7 @@ mod custom_serde; pub mod admin; +pub mod v0_10_0; pub mod v0_7_1; pub mod v0_8_1; pub mod v0_9_0; diff --git a/madara/crates/primitives/rpc/src/v0_10_0/mod.rs b/madara/crates/primitives/rpc/src/v0_10_0/mod.rs new file mode 100644 index 0000000000..99dc2d647a --- /dev/null +++ b/madara/crates/primitives/rpc/src/v0_10_0/mod.rs @@ -0,0 +1,11 @@ +//! v0.10.0 of the API. +mod starknet_api_openrpc; +mod starknet_trace_api_openrpc; +mod starknet_ws_api; + +pub use self::starknet_api_openrpc::*; +pub use self::starknet_trace_api_openrpc::*; +pub use self::starknet_ws_api::*; + +// BlockId is unchanged from v0.9.0, so we reuse the same type +pub use crate::v0_9_0::BlockId; diff --git a/madara/crates/primitives/rpc/src/v0_10_0/starknet_api_openrpc.rs b/madara/crates/primitives/rpc/src/v0_10_0/starknet_api_openrpc.rs new file mode 100644 index 0000000000..d543505065 --- /dev/null +++ b/madara/crates/primitives/rpc/src/v0_10_0/starknet_api_openrpc.rs @@ -0,0 +1,107 @@ +pub use crate::v0_9_0::{ + AddDeclareTransactionParams, AddDeployAccountTransactionParams, AddInvokeTransactionParams, + AddInvokeTransactionResult, Address, BlockHash, BlockHashAndNumber, BlockHashAndNumberParams, BlockHashHelper, + BlockHeader, BlockNumber, BlockNumberHelper, BlockNumberParams, BlockStatus, BlockTag, BlockWithReceipts, + BlockWithTxHashes, BlockWithTxs, BroadcastedDeclareTxn, BroadcastedDeclareTxnV1, BroadcastedDeclareTxnV2, + BroadcastedDeclareTxnV3, BroadcastedDeployAccountTxn, BroadcastedInvokeTxn, BroadcastedTxn, CallParams, ChainId, + ChainIdParams, ClassAndTxnHash, CommonReceiptProperties, ContractAbi, ContractAbiEntry, ContractAndTxnHash, + ContractClass, ContractLeavesDataItem, ContractStorageDiffItem, ContractsProof, DaMode, DataAvailability, + DeclareTxn, DeclareTxnReceipt, DeclareTxnV0, DeclareTxnV1, DeclareTxnV2, DeclareTxnV3, DeployAccountTxn, + DeployAccountTxnReceipt, DeployAccountTxnV1, DeployAccountTxnV3, DeployTxn, DeployTxnReceipt, DeployedContractItem, + DeprecatedCairoEntryPoint, DeprecatedContractClass, DeprecatedEntryPointsByType, EntryPointsByType, + EstimateFeeParams, EstimateMessageFeeParams, EthAddress, Event, EventAbiEntry, EventAbiType, EventContent, + EventFilterWithPageRequest, EventsChunk, ExecutionResources, ExecutionStatus, FeeEstimate, FeeEstimateCommon, + FeePayment, FunctionAbiEntry, FunctionAbiType, FunctionCall, FunctionStateMutability, + GetBlockTransactionCountParams, GetBlockWithReceiptsParams, GetBlockWithTxHashesParams, GetBlockWithTxsParams, + GetClassAtParams, GetClassHashAtParams, GetClassParams, GetEventsParams, GetNonceParams, GetStateUpdateParams, + GetStorageAtParams, GetStorageProofResult, GetTransactionByBlockIdAndIndexParams, GetTransactionByHashParams, + GetTransactionReceiptParams, GetTransactionStatusParams, GlobalRoots, InvokeTxn, InvokeTxnReceipt, InvokeTxnV0, + InvokeTxnV1, InvokeTxnV3, KeyValuePair, L1DaMode, L1HandlerTxn, L1HandlerTxnReceipt, MaybeDeprecatedContractClass, + MaybePreConfirmedBlockWithTxHashes, MaybePreConfirmedBlockWithTxs, MerkleNode, MessageFeeEstimate, MsgFromL1, + MsgToL1, NewClasses, NodeHashToNodeMappingItem, NonceUpdate, PreConfirmedBlockHeader, + PreConfirmedBlockWithReceipts, PreConfirmedBlockWithTxHashes, PreConfirmedBlockWithTxs, PriceUnitFri, PriceUnitWei, + ReplacedClass, ResourceBounds, ResourceBoundsMapping, ResourcePrice, SierraEntryPoint, Signature, + SimulationFlagForEstimateFee, SpecVersionParams, StarknetGetBlockWithTxsAndReceiptsResult, StateUpdate, StorageKey, + StructAbiEntry, StructAbiType, StructMember, SyncStatus, SyncingParams, SyncingStatus, TransactionAndReceipt, Txn, + TxnExecutionStatus, TxnFinalityAndExecutionStatus, TxnFinalityStatus, TxnHash, TxnReceipt, TxnReceiptWithBlockInfo, + TxnStatus, TxnWithHash, TypedParameter, +}; +use serde::{Deserialize, Serialize}; +use starknet_types_core::felt::Felt; + +/// RPC 0.10.0 Changes: +/// 1. StateDiff: Added `migrated_compiled_classes` field +/// 2. PreConfirmedStateUpdate: Removed `old_root` field +/// 3. EmittedEvent: Added `transaction_index` and `event_index` fields +/// 4. ContractStorageKeysItem: Changed `storage_keys` type from `Vec` to `Vec` +/// +/// The change in state applied in this block, given as a mapping of addresses to the new values and/or new contracts +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub struct StateDiff { + /// The declared class hash and compiled class hash + pub declared_classes: Vec, + /// The deployed contracts + pub deployed_contracts: Vec, + /// The hash of the declared class + pub deprecated_declared_classes: Vec, + /// The nonce updates + pub nonces: Vec, + /// The replaced classes + pub replaced_classes: Vec, + /// The storage diffs + pub storage_diffs: Vec, + /// The migrated compiled classes (NEW in v0.10.0) + pub migrated_compiled_classes: Vec, +} + +/// A migrated class item representing a class that was migrated from Poseidon to BLAKE hash +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub struct MigratedClassItem { + /// The hash of the declared class + pub class_hash: Felt, + /// The new BLAKE hash (post-SNIP-34) + pub compiled_class_hash: Felt, +} + +#[derive(Eq, Hash, PartialEq, Serialize, Deserialize, Clone, Debug)] +#[serde(untagged)] +pub enum MaybePreConfirmedStateUpdate { + Block(StateUpdate), + PreConfirmed(PreConfirmedStateUpdate), +} + +/// Pre-confirmed state update (v0.10.0: removed `old_root` field) +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub struct PreConfirmedStateUpdate { + /// The state diff + pub state_diff: StateDiff, +} + +/// An event emitted as part of a transaction (v0.10.0: added transaction_index and event_index) +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub struct EmittedEvent { + /// The event information + #[serde(flatten)] + pub event: Event, + /// The hash of the block in which the event was emitted + #[serde(default)] + pub block_hash: Option, + /// The number of the block in which the event was emitted + #[serde(default)] + pub block_number: Option, + /// The transaction that emitted the event + pub transaction_hash: TxnHash, + /// The index of the transaction within the block + pub transaction_index: u64, + /// The index of the event within the transaction + pub event_index: u64, +} + +/// Contract storage keys item (v0.10.0: changed storage_keys type from Vec to Vec) +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ContractStorageKeysItem { + /// The address of the contract + pub contract_address: Felt, + /// The storage keys (changed from Vec to Vec in v0.10.0) + pub storage_keys: Vec, +} diff --git a/madara/crates/primitives/rpc/src/v0_10_0/starknet_trace_api_openrpc.rs b/madara/crates/primitives/rpc/src/v0_10_0/starknet_trace_api_openrpc.rs new file mode 100644 index 0000000000..123c5a172d --- /dev/null +++ b/madara/crates/primitives/rpc/src/v0_10_0/starknet_trace_api_openrpc.rs @@ -0,0 +1,5 @@ +pub use crate::v0_9_0::{ + CallType, DeclareTransactionTrace, DeployAccountTransactionTrace, EntryPointType, InvokeTransactionTrace, + L1HandlerTransactionTrace, OrderedEvent, OrderedMessage, RevertedInvocation, RevertibleFunctionInvocation, + SimulateTransactionsResult, SimulationFlag, TraceBlockTransactionsResult, TraceTransactionResult, TransactionTrace, +}; diff --git a/madara/crates/primitives/rpc/src/v0_10_0/starknet_ws_api.rs b/madara/crates/primitives/rpc/src/v0_10_0/starknet_ws_api.rs new file mode 100644 index 0000000000..0e451b9855 --- /dev/null +++ b/madara/crates/primitives/rpc/src/v0_10_0/starknet_ws_api.rs @@ -0,0 +1,14 @@ +use serde::{Deserialize, Serialize}; + +use super::{EmittedEvent, TxnFinalityStatus}; + +pub use crate::v0_9_0::FinalityStatus; + +/// An emitted event with finality status for WebSocket subscriptions +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub struct EmittedEventWithFinality { + #[serde(flatten)] + pub emitted_event: EmittedEvent, + #[serde(flatten)] + pub finality_status: TxnFinalityStatus, +} diff --git a/madara/crates/primitives/state_update/src/into_starknet_types.rs b/madara/crates/primitives/state_update/src/into_starknet_types.rs index ca23ec3b5a..bf0c1f264f 100644 --- a/madara/crates/primitives/state_update/src/into_starknet_types.rs +++ b/madara/crates/primitives/state_update/src/into_starknet_types.rs @@ -58,6 +58,7 @@ impl From for StateDiff { .map(|replaced_class| replaced_class.into()) .collect(), nonces: state_diff.nonces.into_iter().map(|nonce| nonce.into()).collect(), + migrated_compiled_classes: vec![], // v0.7.1 doesn't have migrated classes } } } @@ -171,6 +172,64 @@ impl From for mp_rpc::v0_7_1::NonceUpdate { } } +// v0.10.0 conversions +impl From for mp_rpc::v0_10_0::StateDiff { + fn from(state_diff: StateDiff) -> Self { + Self { + storage_diffs: state_diff + .storage_diffs + .into_iter() + .map(|diff| mp_rpc::v0_10_0::ContractStorageDiffItem { + address: diff.address, + storage_entries: diff + .storage_entries + .into_iter() + .map(|entry| mp_rpc::v0_10_0::KeyValuePair { key: entry.key, value: entry.value }) + .collect(), + }) + .collect(), + deprecated_declared_classes: state_diff.old_declared_contracts, + declared_classes: state_diff + .declared_classes + .into_iter() + .map(|item| mp_rpc::v0_10_0::NewClasses { + class_hash: item.class_hash, + compiled_class_hash: item.compiled_class_hash, + }) + .collect(), + deployed_contracts: state_diff + .deployed_contracts + .into_iter() + .map(|item| mp_rpc::v0_10_0::DeployedContractItem { + address: item.address, + class_hash: item.class_hash, + }) + .collect(), + replaced_classes: state_diff + .replaced_classes + .into_iter() + .map(|item| mp_rpc::v0_10_0::ReplacedClass { + contract_address: item.contract_address, + class_hash: item.class_hash, + }) + .collect(), + nonces: state_diff + .nonces + .into_iter() + .map(|item| mp_rpc::v0_10_0::NonceUpdate { contract_address: item.contract_address, nonce: item.nonce }) + .collect(), + migrated_compiled_classes: state_diff + .migrated_compiled_classes + .into_iter() + .map(|item| mp_rpc::v0_10_0::MigratedClassItem { + class_hash: item.class_hash, + compiled_class_hash: item.compiled_class_hash, + }) + .collect(), + } + } +} + #[cfg(test)] mod test { use super::*; diff --git a/madara/crates/primitives/state_update/src/lib.rs b/madara/crates/primitives/state_update/src/lib.rs index deba741560..6818a25bd3 100644 --- a/madara/crates/primitives/state_update/src/lib.rs +++ b/madara/crates/primitives/state_update/src/lib.rs @@ -144,6 +144,7 @@ impl TransactionStateUpdate { self.nonces.iter().map(|(&contract_address, &nonce)| NonceUpdate { contract_address, nonce }).collect(), |entry| entry.contract_address, ), + migrated_compiled_classes: Vec::new(), // TODO(prakhar,22/11/2025): Add migrated compiled classes here } } } @@ -188,6 +189,8 @@ pub struct StateDiff { pub replaced_classes: Vec, /// New contract nonce. Mapping contract_address => nonce. pub nonces: Vec, + /// Classes that were migrated from Poseidon to BLAKE hash (SNIP-34). Mapping class_hash => compiled_class_hash. + pub migrated_compiled_classes: Vec, } impl StateDiff { @@ -198,6 +201,7 @@ impl StateDiff { && self.nonces.is_empty() && self.replaced_classes.is_empty() && self.storage_diffs.is_empty() + && self.migrated_compiled_classes.is_empty() } pub fn len(&self) -> usize { @@ -207,6 +211,7 @@ impl StateDiff { result += self.old_declared_contracts.len(); result += self.nonces.len(); result += self.replaced_classes.len(); + result += self.migrated_compiled_classes.len(); for storage_diff in &self.storage_diffs { result += storage_diff.len(); @@ -222,6 +227,7 @@ impl StateDiff { self.deployed_contracts.sort_by_key(|deployed_contract| deployed_contract.address); self.replaced_classes.sort_by_key(|replaced_class| replaced_class.contract_address); self.nonces.sort_by_key(|nonce| nonce.contract_address); + self.migrated_compiled_classes.sort_by_key(|migrated_class| migrated_class.class_hash); } pub fn compute_hash(&self) -> Felt { @@ -265,6 +271,14 @@ impl StateDiff { storage_diffs }; + // Note: migrated_compiled_classes is NOT included as a separate section in the hash. + // Per the official Starknet implementation, migrated classes are MERGED into + // declared_classes (class_hash_to_compiled_class_hash) before hashing. + // TODO (mohit 27/11/2025): When SNIP-34 migration is implemented, merge + // migrated_compiled_classes into declared_classes before computing the hash. + // See: from_state_diff() in official sequencer where migrated classes are chained + // into class_hash_to_compiled_class_hash before hash computation. + let updated_contracts_len_as_felt = (updated_contracts_sorted.len() as u64).into(); let declared_classes_len_as_felt = (declared_classes_sorted.len() as u64).into(); let deprecated_declared_classes_len_as_felt = (deprecated_declared_classes_sorted.len() as u64).into(); @@ -360,6 +374,7 @@ impl From for StateDiff { deployed_contracts, replaced_classes, nonces, + migrated_compiled_classes: Vec::new(), // Migrated classes are handled separately } } } @@ -407,6 +422,13 @@ pub struct ReplacedClassItem { pub class_hash: Felt, } +#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MigratedClassItem { + pub class_hash: Felt, + pub compiled_class_hash: Felt, +} + #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct NonceUpdate { pub contract_address: Felt, @@ -499,6 +521,7 @@ mod tests { NonceUpdate { contract_address: Felt::from(25), nonce: Felt::from(26) }, NonceUpdate { contract_address: Felt::from(27), nonce: Felt::from(28) }, ], + migrated_compiled_classes: vec![], // TODO(prakhar,22/11/2025): Add value here and update the test } } }