diff --git a/Cargo.lock b/Cargo.lock index 4a0a42c76..84248d0fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -336,7 +336,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -346,7 +346,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1439,7 +1439,7 @@ dependencies = [ "iana-time-zone", "num-traits 0.2.17", "serde", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -1545,7 +1545,7 @@ checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" dependencies = [ "is-terminal", "lazy_static", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1567,6 +1567,19 @@ dependencies = [ "xdg", ] +[[package]] +name = "console" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.45.0", +] + [[package]] name = "const-fnv1a-hash" version = "1.1.0" @@ -1971,6 +1984,12 @@ dependencies = [ "log", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "encoding_rs" version = "0.8.33" @@ -1999,7 +2018,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2378,7 +2397,7 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2591,6 +2610,19 @@ dependencies = [ "serde", ] +[[package]] +name = "indicatif" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25" +dependencies = [ + "console", + "instant", + "number_prefix", + "portable-atomic", + "unicode-width", +] + [[package]] name = "indoc" version = "2.0.4" @@ -2629,7 +2661,7 @@ checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2955,7 +2987,7 @@ dependencies = [ "libc", "log", "wasi", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -3084,6 +3116,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "object" version = "0.32.1" @@ -3213,7 +3251,7 @@ dependencies = [ "libc", "redox_syscall 0.3.5", "smallvec", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -3376,6 +3414,12 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "portable-atomic" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bccab0e7fd7cc19f820a1c8c91720af652d0c88dc9664dd72aef2614f04af3b" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -3639,6 +3683,20 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c707298afce11da2efef2f600116fa93ffa7a032b5d7b628aa17711ec81383ca" +[[package]] +name = "replay" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "indicatif", + "rpc_state_reader", + "starknet_api", + "starknet_in_rust", + "tracing", + "tracing-subscriber", +] + [[package]] name = "reqwest" version = "0.11.22" @@ -3773,7 +3831,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -4180,7 +4238,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -4626,7 +4684,7 @@ dependencies = [ "fastrand", "redox_syscall 0.3.5", "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -4647,7 +4705,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -4812,7 +4870,7 @@ dependencies = [ "signal-hook-registry", "socket2 0.5.4", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -5019,6 +5077,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + [[package]] name = "unicode-xid" version = "0.2.4" @@ -5262,7 +5326,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", ] [[package]] @@ -5271,7 +5344,22 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -5280,51 +5368,93 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -5347,7 +5477,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ "cfg-if", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index d14f87c95..e861e837d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,13 @@ cairo_1_tests = [] metrics = [] [workspace] -members = ["cli", "fuzzer", "rpc_state_reader", "examples/contract_execution"] +members = [ + "cli", + "fuzzer", + "rpc_state_reader", + "replay", + "examples/contract_execution" +] [workspace.dependencies] cairo-lang-casm = "2.2.0" @@ -77,3 +83,7 @@ path = "bench/native_bench.rs" name = "cairo_native" required-features = ["cairo-native"] harness = false + +[profile.release] +codegen-units = 1 +lto = "fat" diff --git a/replay/Cargo.toml b/replay/Cargo.toml new file mode 100644 index 000000000..a93decb99 --- /dev/null +++ b/replay/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "replay" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +# starknet specific crates +starknet_in_rust = { path = "../", version = "0.4.0" } +rpc_state_reader = { path = "../rpc_state_reader", features = ["starknet_in_rust"] } +starknet_api = { workspace = true } +# CLI specific crates +clap = { version = "4.4.6", features = ["derive"] } +indicatif = "0.17.7" +# logs +tracing = "0.1" +tracing-subscriber = "0.3.17" +# error handling +anyhow = "1.0" diff --git a/replay/src/main.rs b/replay/src/main.rs new file mode 100644 index 000000000..6dd2e4625 --- /dev/null +++ b/replay/src/main.rs @@ -0,0 +1,136 @@ +use clap::{Parser, Subcommand}; +use rpc_state_reader::{ + execute_tx_configurable, get_transaction_hashes, + rpc_state::{RpcChain, RpcTransactionReceipt}, +}; +use starknet_api::block::BlockNumber; +use starknet_in_rust::execution::TransactionExecutionInfo; + +#[derive(Debug, Parser)] +#[command(about = "Replay is a tool for executing Starknet transactions.", long_about = None)] +struct ReplayCLI { + #[command(subcommand)] + subcommand: ReplayExecute, +} + +#[derive(Subcommand, Debug)] +enum ReplayExecute { + #[clap(about = "Execute a single transaction given a transaction hash.")] + Tx { + tx_hash: String, + chain: String, + block_number: u64, + silent: Option, + }, + #[clap(about = "Execute all the invoke transactions in a given block.")] + Block { + chain: String, + block_number: u64, + silent: Option, + }, + #[clap(about = "Execute all the invoke transactions in a given range of blocks.")] + BlockRange { + block_start: u64, + block_end: u64, + chain: String, + silent: Option, + }, +} + +fn main() { + let cli = ReplayCLI::parse(); + + match cli.subcommand { + ReplayExecute::Tx { + tx_hash, + chain, + block_number, + silent, + } => { + show_execution_data(tx_hash, &chain, block_number, silent); + } + ReplayExecute::Block { + block_number, + chain, + silent, + } => { + println!("Executing block number: {}", block_number); + let rpc_chain = parse_network(&chain); + let block_number = BlockNumber(block_number); + let transaction_hashes = get_transaction_hashes(block_number, rpc_chain) + .expect("Unable to fetch the transaction hashes."); + + for tx_hash in transaction_hashes { + show_execution_data(tx_hash, &chain, block_number.0, silent); + } + } + ReplayExecute::BlockRange { + block_start, + block_end, + chain, + silent, + } => { + println!("Executing block range: {} - {}", block_start, block_end); + let rpc_chain = parse_network(&chain); + for block_number in block_start..=block_end { + let block_number = BlockNumber(block_number); + let transaction_hashes = get_transaction_hashes(block_number, rpc_chain) + .expect("Unable to fetch the transaction hashes."); + + for tx_hash in transaction_hashes { + show_execution_data(tx_hash, &chain, block_number.0, silent); + } + } + } + } +} + +fn parse_network(network: &str) -> RpcChain { + match network.to_lowercase().as_str() { + "mainnet" => RpcChain::MainNet, + "testnet" => RpcChain::TestNet, + "testnet2" => RpcChain::TestNet2, + _ => panic!("Invalid network name, it should be one of: mainnet, testnet, testnet2"), + } +} + +fn show_execution_data(tx_hash: String, chain: &str, block_number: u64, silent: Option) { + let rpc_chain = parse_network(chain); + if silent.is_none() || !silent.unwrap() { + println!("Executing transaction with hash: {}", tx_hash); + println!("Block number: {}", block_number); + println!("Chain: {}", chain); + } + let previous_block_number = BlockNumber(block_number - 1); + + let (tx_info, _trace, receipt) = + match execute_tx_configurable(&tx_hash, rpc_chain, previous_block_number, false, true) { + Ok(x) => x, + Err(error_reason) => { + println!("Error: {}", error_reason); + return; + } + }; + let TransactionExecutionInfo { + revert_error, + actual_fee, + .. + } = tx_info; + + let sir_actual_fee = actual_fee; + + let RpcTransactionReceipt { + actual_fee, + execution_status, + .. + } = receipt; + + if silent.is_none() || !silent.unwrap() { + println!("[RPC] Execution status: {:?}", execution_status); + if let Some(revert_error) = revert_error { + println!("[SIR] Revert error: {}", revert_error); + } + println!("[RPC] Actual fee: {} wei", actual_fee); + println!("[SIR] Actual fee: {} wei", sir_actual_fee); + } +} diff --git a/rpc_state_reader/Cargo.toml b/rpc_state_reader/Cargo.toml index e4d65831f..c1dd96ee9 100644 --- a/rpc_state_reader/Cargo.toml +++ b/rpc_state_reader/Cargo.toml @@ -3,9 +3,9 @@ name = "rpc_state_reader" version = "0.4.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [features] +default = ["starknet_in_rust"] +starknet_in_rust = [] cairo-native = ["starknet_in_rust/cairo-native"] [dependencies] diff --git a/rpc_state_reader/src/lib.rs b/rpc_state_reader/src/lib.rs index 7965a3859..3c5022d89 100644 --- a/rpc_state_reader/src/lib.rs +++ b/rpc_state_reader/src/lib.rs @@ -2,6 +2,16 @@ pub mod rpc_state; pub mod rpc_state_errors; pub mod utils; +// only export the sir_state_reader module when the starknet_in_rust feature +// is enabled. +#[cfg(feature = "starknet_in_rust")] +mod sir_state_reader; +#[cfg(feature = "starknet_in_rust")] +pub use sir_state_reader::{ + execute_tx, execute_tx_configurable, execute_tx_without_validate, get_transaction_hashes, + RpcStateReader, +}; + #[cfg(test)] mod tests { use cairo_vm::vm::runners::cairo_runner::ExecutionResources; diff --git a/rpc_state_reader/src/rpc_state.rs b/rpc_state_reader/src/rpc_state.rs index 587e15d8d..b1f3a9790 100644 --- a/rpc_state_reader/src/rpc_state.rs +++ b/rpc_state_reader/src/rpc_state.rs @@ -537,4 +537,38 @@ impl RpcState { self.rpc_call_result("starknet_getTransactionReceipt", &json!([hash.to_string()])) .map_err(|e| RpcStateError::RpcCall(e.to_string())) } + + pub fn get_transaction_hashes(&self) -> Result, RpcStateError> { + let params = &json![vec![self.block.to_value()?]]; + let payload = serde_json::json!({ + "jsonrpc": "2.0", + "method": "starknet_getBlockWithTxHashes", + "params": params, + "id": 1 + }); + let response: serde_json::Value = self + .rpc_call_no_deserialize(&payload) + .unwrap() + .into_json()?; + let hashes: Vec = response + .get("result") + .and_then(|res| res.get("transactions")) + .and_then(|txs| txs.as_array()) + .map(|arr| { + arr.iter() + .filter_map(|tx| tx.as_str().map(|x| x.to_string())) + .collect() + }) + .unwrap_or_default(); + Ok(hashes) + } +} + +#[test] +fn test_tx_hashes() { + let rpc_state = + RpcState::new_infura(RpcChain::MainNet, BlockValue::Number(BlockNumber(397709))).unwrap(); + + let hashes = rpc_state.get_transaction_hashes().unwrap(); + assert_eq!(hashes.len(), 211); } diff --git a/rpc_state_reader/src/sir_state_reader.rs b/rpc_state_reader/src/sir_state_reader.rs new file mode 100644 index 000000000..ce423d8fe --- /dev/null +++ b/rpc_state_reader/src/sir_state_reader.rs @@ -0,0 +1,298 @@ +use std::sync::Arc; + +use cairo_vm::felt::{felt_str, Felt252}; +use starknet_api::{ + block::BlockNumber, + core::{ClassHash as SNClassHash, ContractAddress, PatriciaKey}, + hash::{StarkFelt, StarkHash}, + stark_felt, + state::StorageKey, + transaction::{Transaction as SNTransaction, TransactionHash, TransactionVersion}, +}; +use starknet_in_rust::{ + core::{contract_address::compute_casm_class_hash, errors::state_errors::StateError}, + definitions::{ + block_context::{BlockContext, StarknetChainId, StarknetOsConfig}, + constants::{ + DEFAULT_CAIRO_RESOURCE_FEE_WEIGHTS, DEFAULT_CONTRACT_STORAGE_COMMITMENT_TREE_HEIGHT, + DEFAULT_GLOBAL_STATE_COMMITMENT_TREE_HEIGHT, DEFAULT_INVOKE_TX_MAX_N_STEPS, + DEFAULT_VALIDATE_MAX_N_STEPS, + }, + }, + execution::TransactionExecutionInfo, + services::api::contract_classes::compiled_class::CompiledClass, + state::{ + cached_state::CachedState, contract_class_cache::PermanentContractClassCache, + state_api::StateReader, state_cache::StorageEntry, BlockInfo, + }, + transaction::{ + error::TransactionError, Declare, DeclareV2, DeployAccount, InvokeFunction, L1Handler, + }, + utils::{Address, ClassHash}, +}; + +use crate::{ + rpc_state::{ + BlockValue, RpcBlockInfo, RpcChain, RpcState, RpcTransactionReceipt, TransactionTrace, + }, + rpc_state_errors::RpcStateError, +}; + +#[derive(Debug)] +pub struct RpcStateReader(RpcState); + +impl RpcStateReader { + pub fn new(state: RpcState) -> Self { + Self(state) + } +} + +impl StateReader for RpcStateReader { + fn get_contract_class(&self, class_hash: &ClassHash) -> Result { + let hash = SNClassHash(StarkHash::new(class_hash.0).unwrap()); + let contract_class = self + .0 + .get_contract_class(&hash) + .ok_or(StateError::MissingCasmClass(*class_hash))?; + Ok(CompiledClass::from(contract_class)) + } + + fn get_class_hash_at(&self, contract_address: &Address) -> Result { + let address = ContractAddress( + PatriciaKey::try_from( + StarkHash::new(contract_address.clone().0.to_be_bytes()).unwrap(), + ) + .unwrap(), + ); + let mut bytes = [0u8; 32]; + bytes.copy_from_slice(self.0.get_class_hash_at(&address).0.bytes()); + Ok(ClassHash(bytes)) + } + + fn get_nonce_at(&self, contract_address: &Address) -> Result { + let address = ContractAddress( + PatriciaKey::try_from( + StarkHash::new(contract_address.clone().0.to_be_bytes()).unwrap(), + ) + .unwrap(), + ); + let nonce = self.0.get_nonce_at(&address); + Ok(Felt252::from_bytes_be(nonce.bytes())) + } + + fn get_storage_at(&self, storage_entry: &StorageEntry) -> Result { + let (contract_address, key) = storage_entry; + let address = ContractAddress( + PatriciaKey::try_from( + StarkHash::new(contract_address.clone().0.to_be_bytes()).unwrap(), + ) + .unwrap(), + ); + let key = StorageKey(PatriciaKey::try_from(StarkHash::new(*key).unwrap()).unwrap()); + let value = self.0.get_storage_at(&address, &key); + Ok(Felt252::from_bytes_be(value.bytes())) + } + + fn get_compiled_class_hash(&self, class_hash: &ClassHash) -> Result { + Ok(*class_hash) + } +} + +pub fn execute_tx_configurable( + tx_hash: &str, + network: RpcChain, + block_number: BlockNumber, + skip_validate: bool, + skip_nonce_check: bool, +) -> Result< + ( + TransactionExecutionInfo, + TransactionTrace, + RpcTransactionReceipt, + ), + TransactionError, +> { + let fee_token_address = Address(felt_str!( + "049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", + 16 + )); + + let tx_hash = tx_hash.strip_prefix("0x").unwrap(); + + // Instantiate the RPC StateReader and the CachedState + let rpc_reader = RpcStateReader(RpcState::new_infura(network, block_number.into()).unwrap()); + let gas_price = rpc_reader.0.get_gas_price(block_number.0).unwrap(); + + // Get values for block context before giving ownership of the reader + let chain_id = match rpc_reader.0.chain { + RpcChain::MainNet => StarknetChainId::MainNet, + RpcChain::TestNet => StarknetChainId::TestNet, + RpcChain::TestNet2 => StarknetChainId::TestNet2, + }; + let starknet_os_config = + StarknetOsConfig::new(chain_id.to_felt(), fee_token_address, gas_price); + let block_info = { + let RpcBlockInfo { + block_number, + block_timestamp, + sequencer_address, + .. + } = rpc_reader.0.get_block_info().unwrap(); + + let block_number = block_number.0; + let block_timestamp = block_timestamp.0; + let sequencer_address = Address(Felt252::from_bytes_be(sequencer_address.0.key().bytes())); + + BlockInfo { + block_number, + block_timestamp, + gas_price, + sequencer_address, + } + }; + + // Get transaction before giving ownership of the reader + let tx_hash = TransactionHash(stark_felt!(tx_hash)); + let tx = match rpc_reader.0.get_transaction(&tx_hash).unwrap() { + SNTransaction::Invoke(tx) => InvokeFunction::from_invoke_transaction(tx, chain_id) + .unwrap() + .create_for_simulation(skip_validate, false, false, false, skip_nonce_check), + SNTransaction::DeployAccount(tx) => { + DeployAccount::from_sn_api_transaction(tx, chain_id.to_felt()) + .unwrap() + .create_for_simulation(skip_validate, false, false, false, skip_nonce_check) + } + SNTransaction::Declare(tx) => { + // Fetch the contract_class from the next block (as we don't have it in the previous one) + let next_block_state_reader = RpcStateReader( + RpcState::new_infura(network, (block_number.next()).into()).unwrap(), + ); + let class_hash = tx.class_hash().0.bytes().try_into().unwrap(); + let contract_class = next_block_state_reader + .get_contract_class(&ClassHash(class_hash)) + .unwrap(); + + if tx.version() != TransactionVersion(2_u8.into()) { + let contract_class = match contract_class { + CompiledClass::Deprecated(cc) => cc.as_ref().clone(), + _ => unreachable!(), + }; + + let class_hash = tx.class_hash().0.bytes().try_into().unwrap(); + let declare = Declare::new_with_tx_and_class_hash( + contract_class, + Address(Felt252::from_bytes_be(tx.sender_address().0.key().bytes())), + tx.max_fee().0, + Felt252::from_bytes_be(tx.version().0.bytes()), + tx.signature() + .0 + .iter() + .map(|f| Felt252::from_bytes_be(f.bytes())) + .collect(), + Felt252::from_bytes_be(tx.nonce().0.bytes()), + Felt252::from_bytes_be(tx_hash.0.bytes()), + ClassHash(class_hash), + ) + .unwrap(); + declare.create_for_simulation(skip_validate, false, false, false, skip_nonce_check) + } else { + let contract_class = match contract_class { + CompiledClass::Casm(cc) => cc.as_ref().clone(), + _ => unreachable!(), + }; + + let compiled_class_hash = compute_casm_class_hash(&contract_class).unwrap(); + + let declare = DeclareV2::new_with_sierra_class_hash_and_tx_hash( + None, + Felt252::from_bytes_be(tx.class_hash().0.bytes()), + Some(contract_class), + compiled_class_hash, + Address(Felt252::from_bytes_be(tx.sender_address().0.key().bytes())), + tx.max_fee().0, + Felt252::from_bytes_be(tx.version().0.bytes()), + tx.signature() + .0 + .iter() + .map(|f| Felt252::from_bytes_be(f.bytes())) + .collect(), + Felt252::from_bytes_be(tx.nonce().0.bytes()), + Felt252::from_bytes_be(tx_hash.0.bytes()), + ) + .unwrap(); + declare.create_for_simulation(skip_validate, false, false, false, skip_nonce_check) + } + } + SNTransaction::L1Handler(tx) => L1Handler::from_sn_api_tx( + tx, + Felt252::from_bytes_be(tx_hash.0.bytes()), + Some(Felt252::from(u128::MAX)), + ) + .unwrap() + .create_for_simulation(skip_validate, false), + SNTransaction::Deploy(_) => unimplemented!(), + }; + + let trace = rpc_reader.0.get_transaction_trace(&tx_hash).unwrap(); + let receipt = rpc_reader.0.get_transaction_receipt(&tx_hash).unwrap(); + + let class_cache = PermanentContractClassCache::default(); + let mut state = CachedState::new(Arc::new(rpc_reader), Arc::new(class_cache)); + + let block_context = BlockContext::new( + starknet_os_config, + DEFAULT_CONTRACT_STORAGE_COMMITMENT_TREE_HEIGHT, + DEFAULT_GLOBAL_STATE_COMMITMENT_TREE_HEIGHT, + DEFAULT_CAIRO_RESOURCE_FEE_WEIGHTS.clone(), + DEFAULT_INVOKE_TX_MAX_N_STEPS, + DEFAULT_VALIDATE_MAX_N_STEPS, + block_info, + Default::default(), + true, + ); + + #[cfg(not(feature = "cairo-native"))] + let sir_execution = tx.execute(&mut state, &block_context, u128::MAX)?; + #[cfg(feature = "cairo-native")] + let sir_execution = tx.execute(&mut state, &block_context, u128::MAX, None)?; + + Ok((sir_execution, trace, receipt)) +} + +pub fn execute_tx( + tx_hash: &str, + network: RpcChain, + block_number: BlockNumber, +) -> Result< + ( + TransactionExecutionInfo, + TransactionTrace, + RpcTransactionReceipt, + ), + TransactionError, +> { + execute_tx_configurable(tx_hash, network, block_number, false, false) +} + +pub fn execute_tx_without_validate( + tx_hash: &str, + network: RpcChain, + block_number: BlockNumber, +) -> Result< + ( + TransactionExecutionInfo, + TransactionTrace, + RpcTransactionReceipt, + ), + TransactionError, +> { + execute_tx_configurable(tx_hash, network, block_number, true, true) +} + +pub fn get_transaction_hashes( + block_number: BlockNumber, + network: RpcChain, +) -> Result, RpcStateError> { + let rpc_state = RpcState::new_infura(network, BlockValue::Number(block_number))?; + rpc_state.get_transaction_hashes() +} diff --git a/rpc_state_reader/tests/sir_tests.rs b/rpc_state_reader/tests/sir_tests.rs index 38161a30f..893277cbf 100644 --- a/rpc_state_reader/tests/sir_tests.rs +++ b/rpc_state_reader/tests/sir_tests.rs @@ -1,278 +1,23 @@ -use std::sync::Arc; +#![cfg(feature = "starknet_in_rust")] -use cairo_vm::felt::{felt_str, Felt252}; +use cairo_vm::felt::Felt252; use pretty_assertions_sorted::{assert_eq, assert_eq_sorted}; +use rpc_state_reader::{ + execute_tx, execute_tx_configurable, execute_tx_without_validate, rpc_state::*, +}; use starknet_api::{ block::BlockNumber, - core::{ClassHash as SNClassHash, ContractAddress, PatriciaKey}, - hash::{StarkFelt, StarkHash}, + hash::StarkFelt, stark_felt, - state::StorageKey, - transaction::{Transaction as SNTransaction, TransactionHash, TransactionVersion}, + transaction::{Transaction as SNTransaction, TransactionHash}, }; use starknet_in_rust::{ - core::{contract_address::compute_casm_class_hash, errors::state_errors::StateError}, - definitions::{ - block_context::{BlockContext, StarknetChainId, StarknetOsConfig}, - constants::{ - DEFAULT_CAIRO_RESOURCE_FEE_WEIGHTS, DEFAULT_CONTRACT_STORAGE_COMMITMENT_TREE_HEIGHT, - DEFAULT_GLOBAL_STATE_COMMITMENT_TREE_HEIGHT, DEFAULT_INVOKE_TX_MAX_N_STEPS, - DEFAULT_VALIDATE_MAX_N_STEPS, - }, - }, + definitions::block_context::StarknetChainId, execution::{CallInfo, TransactionExecutionInfo}, - services::api::contract_classes::compiled_class::CompiledClass, - state::{ - cached_state::CachedState, contract_class_cache::PermanentContractClassCache, - state_api::StateReader, state_cache::StorageEntry, BlockInfo, - }, - transaction::{Declare, DeclareV2, DeployAccount, InvokeFunction, L1Handler}, - utils::{Address, ClassHash}, + transaction::InvokeFunction, }; - use test_case::test_case; -use rpc_state_reader::rpc_state::*; - -#[derive(Debug)] -pub struct RpcStateReader(RpcState); - -impl StateReader for RpcStateReader { - fn get_contract_class(&self, class_hash: &ClassHash) -> Result { - let hash = SNClassHash(StarkHash::new(class_hash.0).unwrap()); - Ok(CompiledClass::from( - self.0.get_contract_class(&hash).unwrap(), - )) - } - - fn get_class_hash_at(&self, contract_address: &Address) -> Result { - let address = ContractAddress( - PatriciaKey::try_from( - StarkHash::new(contract_address.clone().0.to_be_bytes()).unwrap(), - ) - .unwrap(), - ); - let mut bytes = [0u8; 32]; - bytes.copy_from_slice(self.0.get_class_hash_at(&address).0.bytes()); - Ok(ClassHash(bytes)) - } - - fn get_nonce_at(&self, contract_address: &Address) -> Result { - let address = ContractAddress( - PatriciaKey::try_from( - StarkHash::new(contract_address.clone().0.to_be_bytes()).unwrap(), - ) - .unwrap(), - ); - let nonce = self.0.get_nonce_at(&address); - Ok(Felt252::from_bytes_be(nonce.bytes())) - } - - fn get_storage_at(&self, storage_entry: &StorageEntry) -> Result { - let (contract_address, key) = storage_entry; - let address = ContractAddress( - PatriciaKey::try_from( - StarkHash::new(contract_address.clone().0.to_be_bytes()).unwrap(), - ) - .unwrap(), - ); - let key = StorageKey(PatriciaKey::try_from(StarkHash::new(*key).unwrap()).unwrap()); - let value = self.0.get_storage_at(&address, &key); - Ok(Felt252::from_bytes_be(value.bytes())) - } - - fn get_compiled_class_hash(&self, class_hash: &ClassHash) -> Result { - Ok(*class_hash) - } -} - -#[allow(unused)] -pub fn execute_tx_configurable( - tx_hash: &str, - network: RpcChain, - block_number: BlockNumber, - skip_validate: bool, - skip_nonce_check: bool, -) -> ( - TransactionExecutionInfo, - TransactionTrace, - RpcTransactionReceipt, -) { - let fee_token_address = Address(felt_str!( - "049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", - 16 - )); - - let tx_hash = tx_hash.strip_prefix("0x").unwrap(); - - // Instantiate the RPC StateReader and the CachedState - let rpc_reader = RpcStateReader(RpcState::new_infura(network, block_number.into()).unwrap()); - let gas_price = rpc_reader.0.get_gas_price(block_number.0).unwrap(); - - // Get values for block context before giving ownership of the reader - let chain_id = match rpc_reader.0.chain { - RpcChain::MainNet => StarknetChainId::MainNet, - RpcChain::TestNet => StarknetChainId::TestNet, - RpcChain::TestNet2 => StarknetChainId::TestNet2, - }; - let starknet_os_config = - StarknetOsConfig::new(chain_id.to_felt(), fee_token_address, gas_price); - let block_info = { - let RpcBlockInfo { - block_number, - block_timestamp, - sequencer_address, - .. - } = rpc_reader.0.get_block_info().unwrap(); - - let block_number = block_number.0; - let block_timestamp = block_timestamp.0; - let sequencer_address = Address(Felt252::from_bytes_be(sequencer_address.0.key().bytes())); - - BlockInfo { - block_number, - block_timestamp, - gas_price, - sequencer_address, - } - }; - - // Get transaction before giving ownership of the reader - let tx_hash = TransactionHash(stark_felt!(tx_hash)); - let tx = match rpc_reader.0.get_transaction(&tx_hash).unwrap() { - SNTransaction::Invoke(tx) => InvokeFunction::from_invoke_transaction(tx, chain_id) - .unwrap() - .create_for_simulation(skip_validate, false, false, false, skip_nonce_check), - SNTransaction::DeployAccount(tx) => { - DeployAccount::from_sn_api_transaction(tx, chain_id.to_felt()) - .unwrap() - .create_for_simulation(skip_validate, false, false, false, skip_nonce_check) - } - SNTransaction::Declare(tx) => { - // Fetch the contract_class from the next block (as we don't have it in the previous one) - let next_block_state_reader = RpcStateReader( - RpcState::new_infura(network, (block_number.next()).into()).unwrap(), - ); - let contract_class = next_block_state_reader - .get_contract_class(&ClassHash(tx.class_hash().0.bytes().try_into().unwrap())) - .unwrap(); - - if tx.version() != TransactionVersion(2_u8.into()) { - let contract_class = match contract_class { - CompiledClass::Deprecated(cc) => cc.as_ref().clone(), - _ => unreachable!(), - }; - - let declare = Declare::new_with_tx_and_class_hash( - contract_class, - Address(Felt252::from_bytes_be(tx.sender_address().0.key().bytes())), - tx.max_fee().0, - Felt252::from_bytes_be(tx.version().0.bytes()), - tx.signature() - .0 - .iter() - .map(|f| Felt252::from_bytes_be(f.bytes())) - .collect(), - Felt252::from_bytes_be(tx.nonce().0.bytes()), - Felt252::from_bytes_be(tx_hash.0.bytes()), - ClassHash(tx.class_hash().0.bytes().try_into().unwrap()), - ) - .unwrap(); - declare.create_for_simulation(skip_validate, false, false, false, skip_nonce_check) - } else { - let contract_class = match contract_class { - CompiledClass::Casm(cc) => cc.as_ref().clone(), - _ => unreachable!(), - }; - - let compiled_class_hash = compute_casm_class_hash(&contract_class).unwrap(); - - let declare = DeclareV2::new_with_sierra_class_hash_and_tx_hash( - None, - Felt252::from_bytes_be(tx.class_hash().0.bytes()), - Some(contract_class), - compiled_class_hash, - Address(Felt252::from_bytes_be(tx.sender_address().0.key().bytes())), - tx.max_fee().0, - Felt252::from_bytes_be(tx.version().0.bytes()), - tx.signature() - .0 - .iter() - .map(|f| Felt252::from_bytes_be(f.bytes())) - .collect(), - Felt252::from_bytes_be(tx.nonce().0.bytes()), - Felt252::from_bytes_be(tx_hash.0.bytes()), - ) - .unwrap(); - declare.create_for_simulation(skip_validate, false, false, false, skip_nonce_check) - } - } - SNTransaction::L1Handler(tx) => L1Handler::from_sn_api_tx( - tx, - Felt252::from_bytes_be(tx_hash.0.bytes()), - Some(Felt252::from(u128::MAX)), - ) - .unwrap() - .create_for_simulation(skip_validate, false), - _ => unimplemented!(), - }; - - let trace = rpc_reader.0.get_transaction_trace(&tx_hash).unwrap(); - let receipt = rpc_reader.0.get_transaction_receipt(&tx_hash).unwrap(); - - let class_cache = Arc::new(PermanentContractClassCache::default()); - let mut state = CachedState::new(Arc::new(rpc_reader), class_cache); - - let block_context = BlockContext::new( - starknet_os_config, - DEFAULT_CONTRACT_STORAGE_COMMITMENT_TREE_HEIGHT, - DEFAULT_GLOBAL_STATE_COMMITMENT_TREE_HEIGHT, - DEFAULT_CAIRO_RESOURCE_FEE_WEIGHTS.clone(), - DEFAULT_INVOKE_TX_MAX_N_STEPS, - DEFAULT_VALIDATE_MAX_N_STEPS, - block_info, - Default::default(), - true, - ); - - ( - tx.execute( - &mut state, - &block_context, - u128::MAX, - #[cfg(feature = "cairo-native")] - None, - ) - .unwrap(), - trace, - receipt, - ) -} - -pub fn execute_tx( - tx_hash: &str, - network: RpcChain, - block_number: BlockNumber, -) -> ( - TransactionExecutionInfo, - TransactionTrace, - RpcTransactionReceipt, -) { - execute_tx_configurable(tx_hash, network, block_number, false, false) -} - -pub fn execute_tx_without_validate( - tx_hash: &str, - network: RpcChain, - block_number: BlockNumber, -) -> ( - TransactionExecutionInfo, - TransactionTrace, - RpcTransactionReceipt, -) { - execute_tx_configurable(tx_hash, network, block_number, true, true) -} - #[test] fn test_get_transaction_try_from() { let rpc_state = RpcState::new_infura(RpcChain::MainNet, BlockTag::Latest.into()).unwrap(); @@ -401,7 +146,7 @@ fn test_get_gas_price() { RpcChain::TestNet )] fn starknet_in_rust_test_case_tx(hash: &str, block_number: u64, chain: RpcChain) { - let (tx_info, trace, receipt) = execute_tx(hash, chain, BlockNumber(block_number)); + let (tx_info, trace, receipt) = execute_tx(hash, chain, BlockNumber(block_number)).unwrap(); let TransactionExecutionInfo { call_info, @@ -488,7 +233,8 @@ fn test_sorted_events( block_number: u64, expected_amount_of_events: usize, ) { - let (tx_info, _trace, _receipt) = execute_tx(tx_hash, chain, BlockNumber(block_number)); + let (tx_info, _trace, _receipt) = + execute_tx(tx_hash, chain, BlockNumber(block_number)).unwrap(); let events_len = tx_info.get_sorted_events().unwrap().len(); @@ -507,7 +253,7 @@ fn test_sorted_events( => ignore["broken on both due to a cairo-vm error"] )] fn starknet_in_rust_test_case_reverted_tx(hash: &str, block_number: u64, chain: RpcChain) { - let (tx_info, trace, receipt) = execute_tx(hash, chain, BlockNumber(block_number)); + let (tx_info, trace, receipt) = execute_tx(hash, chain, BlockNumber(block_number)).unwrap(); assert_eq!(tx_info.revert_error.is_some(), trace.revert_error.is_some()); @@ -527,9 +273,9 @@ fn starknet_in_rust_test_case_reverted_tx(hash: &str, block_number: u64, chain: RpcChain::MainNet )] fn test_validate_fee(hash: &str, block_number: u64, chain: RpcChain) { - let (tx_info, _trace, receipt) = execute_tx(hash, chain, BlockNumber(block_number)); + let (tx_info, _trace, receipt) = execute_tx(hash, chain, BlockNumber(block_number)).unwrap(); let (tx_info_without_fee, _trace, _receipt) = - execute_tx_without_validate(hash, chain, BlockNumber(block_number)); + execute_tx_without_validate(hash, chain, BlockNumber(block_number)).unwrap(); assert_eq!(tx_info.actual_fee, receipt.actual_fee); assert!(tx_info_without_fee.actual_fee < tx_info.actual_fee); @@ -548,7 +294,7 @@ fn test_validate_fee(hash: &str, block_number: u64, chain: RpcChain) { RpcChain::MainNet )] fn starknet_in_rust_test_case_declare_tx(hash: &str, block_number: u64, chain: RpcChain) { - let (tx_info, _trace, receipt) = execute_tx(hash, chain, BlockNumber(block_number)); + let (tx_info, _trace, receipt) = execute_tx(hash, chain, BlockNumber(block_number)).unwrap(); let TransactionExecutionInfo { call_info, actual_fee, @@ -577,7 +323,8 @@ fn starknet_in_rust_test_case_declare_tx(hash: &str, block_number: u64, chain: R )] fn starknet_in_rust_test_case_tx_skip_nonce_check(hash: &str, block_number: u64, chain: RpcChain) { let (tx_info, trace, receipt) = - execute_tx_configurable(hash, chain, BlockNumber(block_number), false, true); + execute_tx_configurable(hash, chain, BlockNumber(block_number), false, true).unwrap(); + let TransactionExecutionInfo { call_info, actual_fee, @@ -638,7 +385,7 @@ fn starknet_in_rust_test_case_tx_skip_nonce_check(hash: &str, block_number: u64, RpcChain::TestNet )] fn starknet_in_rust_check_fee_and_retdata(hash: &str, block_number: u64, chain: RpcChain) { - let (tx_info, trace, receipt) = execute_tx(hash, chain, BlockNumber(block_number)); + let (tx_info, trace, receipt) = execute_tx(hash, chain, BlockNumber(block_number)).unwrap(); let TransactionExecutionInfo { call_info,