From 2c984aad70e38aa578dd5beb00ee6908ad3952b0 Mon Sep 17 00:00:00 2001 From: marioiordanov Date: Tue, 21 Nov 2023 12:37:43 +0200 Subject: [PATCH] 0.5.0 spec (#230) * modified contract error in ApiError * remove pending transactions endpoint * add spec_version * added config file where the version of the rpc_spec is specified * add get_transaction_execution_and_finality_status, make TransactionFinalityStatus non optional, remove create_rejected method * added starknet_version and l1_gas_price to block header and entry to config file * added constants for builtins names * execution resources struct * map transaction execution info to execution resources * use builtins constants in vm_resource_fee_cost setting * use txn execution info when generating a receipt * add check for empty string passed as abi when deserializing sierra class * removed empty file * test functionality for reading json-rpc spec and testing it against existing in/out structures * remove not needed test files * anotate request inputs with deny_unknown_fields so if there are any additions to the requests in the spec, the deserialization will fail * move models/state to types/rpc/state * improve algorithm for generating request responses of spec * extract ThinStateDiff logic generation into converter * replace PatriciaKeyHex with PatriciaKey * add state diff to transaction trace * add instructions to modify the spec * modify tests for simulated transactions * remove println * generate only required fields * tests for generated requests/responses from the spec * edit the spec * comments about extra fields not present in 0.5.0 spec * add execution info to get_receipt params * add StarknetResponse - enum for covering all return values from json rpc methods * return StarknetResponse from each method of json-rpc * changes to match 0.5.0 spec * dependencies * clippy + fmt * lock file * clippy fix * marked spec 0.5.0 * Update crates/starknet-server/tests/test_simulate_transactions.rs Co-authored-by: FabijanC * remove comment * fmt * edit starknet_version * update starknet_version in config * added ordered event and ordered message to l1 * use thread_rng().gen_bool in do_for_boolean_primitive * improved generated_combined_schema readability * not needed serde attribute * fmt * added comment * added forgotten method mentioned in issue https://github.com/0xSpaceShard/starknet-devnet-rs/issues/248 * fix test * clippy * fmt --------- Co-authored-by: FabijanC --- .cargo/config.toml | 3 + Cargo.lock | 97 +- Cargo.toml | 4 + README.md | 2 +- crates/starknet-server/Cargo.toml | 4 + .../src/api/http/endpoints/mint_token.rs | 5 +- .../src/api/json_rpc/endpoints.rs | 222 +- .../starknet-server/src/api/json_rpc/error.rs | 113 +- .../starknet-server/src/api/json_rpc/mod.rs | 100 +- .../src/api/json_rpc/models.rs | 61 +- .../json_rpc/spec_reader/data_generator.rs | 226 ++ .../src/api/json_rpc/spec_reader/mod.rs | 312 ++ .../api/json_rpc/spec_reader/spec_modifier.rs | 123 + .../spec_reader/spec_schemas/all_of_schema.rs | 18 + .../spec_schemas/array_primitive.rs | 17 + .../spec_schemas/boolean_primitive.rs | 19 + .../spec_schemas/integer_primitive.rs | 19 + .../json_rpc/spec_reader/spec_schemas/mod.rs | 54 + .../spec_schemas/object_primitive.rs | 24 + .../spec_reader/spec_schemas/one_of_schema.rs | 18 + .../spec_reader/spec_schemas/ref_schema.rs | 22 + .../spec_schemas/string_primitive.rs | 25 + .../src/api/json_rpc/write_endpoints.rs | 34 +- crates/starknet-server/src/api/mod.rs | 1 - crates/starknet-server/src/api/models/mod.rs | 17 - .../spec/0.5.0/edit_spec_instructions.yaml | 41 + .../spec/0.5.0/starknet_api_openrpc.json | 3389 +++++++++++++++++ .../0.5.0/starknet_trace_api_openrpc.json | 422 ++ .../spec/0.5.0/starknet_write_api.json | 298 ++ crates/starknet-server/tests/common/data.rs | 1 - crates/starknet-server/tests/common/mod.rs | 1 - .../tests/test_estimate_fee.rs | 35 +- .../tests/test_simulate_transactions.rs | 40 +- crates/starknet/src/blocks/mod.rs | 7 +- crates/starknet/src/error.rs | 4 +- crates/starknet/src/starknet/estimations.rs | 2 +- crates/starknet/src/starknet/mod.rs | 48 +- crates/starknet/src/starknet/state_update.rs | 19 +- crates/starknet/src/state/state_diff.rs | 56 + crates/starknet/src/state/state_update.rs | 43 +- crates/starknet/src/transactions.rs | 50 +- crates/types/src/constants.rs | 10 + crates/types/src/lib.rs | 1 + crates/types/src/patricia_key.rs | 22 + crates/types/src/rpc.rs | 1 + crates/types/src/rpc/block.rs | 12 + crates/types/src/rpc/emitted_event.rs | 28 +- .../src/api/models => types/src/rpc}/state.rs | 11 +- crates/types/src/rpc/transaction_receipt.rs | 72 +- crates/types/src/rpc/transactions.rs | 118 +- .../broadcasted_declare_transaction_v1.rs | 1 + .../broadcasted_declare_transaction_v2.rs | 1 + .../broadcasted_deploy_account_transaction.rs | 1 + .../broadcasted_invoke_transaction.rs | 1 + .../transactions/declare_transaction_v0v1.rs | 1 + .../transactions/declare_transaction_v2.rs | 1 + crates/types/src/serde_helpers.rs | 24 +- .../types/test_data/rpc/declare_accepted.json | 11 - .../types/test_data/rpc/invoke_accepted.json | 25 - 59 files changed, 5887 insertions(+), 450 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 crates/starknet-server/src/api/json_rpc/spec_reader/data_generator.rs create mode 100644 crates/starknet-server/src/api/json_rpc/spec_reader/mod.rs create mode 100644 crates/starknet-server/src/api/json_rpc/spec_reader/spec_modifier.rs create mode 100644 crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/all_of_schema.rs create mode 100644 crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/array_primitive.rs create mode 100644 crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/boolean_primitive.rs create mode 100644 crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/integer_primitive.rs create mode 100644 crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/mod.rs create mode 100644 crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/object_primitive.rs create mode 100644 crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/one_of_schema.rs create mode 100644 crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/ref_schema.rs create mode 100644 crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/string_primitive.rs delete mode 100644 crates/starknet-server/src/api/models/mod.rs create mode 100644 crates/starknet-server/test_data/spec/0.5.0/edit_spec_instructions.yaml create mode 100644 crates/starknet-server/test_data/spec/0.5.0/starknet_api_openrpc.json create mode 100644 crates/starknet-server/test_data/spec/0.5.0/starknet_trace_api_openrpc.json create mode 100644 crates/starknet-server/test_data/spec/0.5.0/starknet_write_api.json delete mode 100644 crates/starknet-server/tests/common/data.rs create mode 100644 crates/types/src/constants.rs rename crates/{starknet-server/src/api/models => types/src/rpc}/state.rs (89%) delete mode 100644 crates/types/test_data/rpc/declare_accepted.json delete mode 100644 crates/types/test_data/rpc/invoke_accepted.json diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 000000000..1ca016190 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,3 @@ +[env] +RPC_SPEC_VERSION = "0.5.0" +STARKNET_VERSION = "0.12.3" \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 3c183d6ad..bd427703d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + [[package]] name = "adler" version = "1.0.2" @@ -415,6 +424,21 @@ dependencies = [ "tower-service", ] +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base64" version = "0.13.1" @@ -1500,6 +1524,16 @@ dependencies = [ "libc", ] +[[package]] +name = "error-chain" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +dependencies = [ + "backtrace", + "version_check", +] + [[package]] name = "eth-keystore" version = "0.5.0" @@ -1752,6 +1786,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + [[package]] name = "good_lp" version = "1.4.0" @@ -2596,6 +2636,15 @@ dependencies = [ "libc", ] +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.18.0" @@ -3017,6 +3066,17 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" +[[package]] +name = "regex_generate" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1d78a292128952da6c279dd679b00ab2713ef590c9b65df6eeecbcb4b480aac" +dependencies = [ + "error-chain", + "rand", + "regex-syntax 0.6.29", +] + [[package]] name = "relative-path" version = "1.8.0" @@ -3097,6 +3157,12 @@ dependencies = [ "rustc-hex", ] +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -3274,18 +3340,18 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" [[package]] name = "serde" -version = "1.0.189" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" +checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.189" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" +checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" dependencies = [ "proc-macro2", "quote", @@ -3375,6 +3441,19 @@ dependencies = [ "syn 2.0.28", ] +[[package]] +name = "serde_yaml" +version = "0.9.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c" +dependencies = [ + "indexmap 2.0.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "server" version = "0.1.0" @@ -3675,9 +3754,13 @@ dependencies = [ "futures", "hyper", "lazy_static", + "rand", + "rand_chacha", "random-number-generator", + "regex_generate", "serde", "serde_json", + "serde_yaml", "server", "starknet", "starknet-accounts", @@ -4280,6 +4363,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "unsafe-libyaml" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" + [[package]] name = "untrusted" version = "0.7.1" diff --git a/Cargo.toml b/Cargo.toml index 260effd79..c4b0afabf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,10 +45,14 @@ clap = { version = "4.3.2", features = ["derive"] } flate2 = { version = "1.0.26" } serde = { version = "1.0.171", features = ["derive"] } serde_json = { version = "1.0.81" } +serde_yaml = { version = "0.9.27" } thiserror = { version = "1.0.32" } anyhow = "1" tokio-graceful-shutdown = "0.13.0" indexmap = "2.0.0" +rand = "0.8.5" +rand_chacha = "0.3.1" +regex_generate = "0.2.3" # Starknet dependencies starknet_api = { version = "0.6.0-rc0", features = ["testing"] } diff --git a/README.md b/README.md index d51021eba..c3fa26399 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ This repository is work in progress, please be patient. Please check below the s ### TODO -- [ ] RPC v0.5.0 +- [x] RPC v0.5.0 ### TODO to reach feature parity with the Pythonic Devnet diff --git a/crates/starknet-server/Cargo.toml b/crates/starknet-server/Cargo.toml index a0669218d..7ca8d017e 100644 --- a/crates/starknet-server/Cargo.toml +++ b/crates/starknet-server/Cargo.toml @@ -54,3 +54,7 @@ starknet-rs-accounts = { workspace = true } starknet-rs-contract = { workspace = true } starknet-rs-signers = { workspace = true } starknet-core = { workspace = true, features = ["test_utils"] } +rand = { workspace = true } +rand_chacha = { workspace = true } +regex_generate = { workspace = true } +serde_yaml = { workspace = true } \ No newline at end of file diff --git a/crates/starknet-server/src/api/http/endpoints/mint_token.rs b/crates/starknet-server/src/api/http/endpoints/mint_token.rs index bf7f531d7..a1c4bd22f 100644 --- a/crates/starknet-server/src/api/http/endpoints/mint_token.rs +++ b/crates/starknet-server/src/api/http/endpoints/mint_token.rs @@ -31,7 +31,10 @@ fn get_balance(starknet: &Starknet, address: ContractAddress) -> Result StrictRpcResult { + Ok(StarknetResponse::SpecVersion(env!("RPC_SPEC_VERSION").to_string())) + } + /// starknet_getBlockWithTxHashes - pub(crate) async fn get_block_with_tx_hashes(&self, block_id: BlockId) -> RpcResult { + pub(crate) async fn get_block_with_tx_hashes(&self, block_id: BlockId) -> StrictRpcResult { let block = self.api.starknet.read().await.get_block(block_id.into()).map_err(|err| match err { Error::NoBlock => ApiError::BlockNotFound, unknown_error => ApiError::StarknetDevnetError(unknown_error), })?; - Ok(Block { + Ok(StarknetResponse::BlockWithTransactionHashes(Block { status: *block.status(), header: BlockHeader::from(&block), transactions: starknet_types::rpc::transactions::Transactions::Hashes( block.get_transactions().to_owned(), ), - }) + })) } /// starknet_getBlockWithTxs - pub(crate) async fn get_block_with_txs(&self, block_id: BlockId) -> RpcResult { - self.api.starknet.read().await.get_block_with_transactions(block_id.into()).map_err(|err| { - match err { - Error::NoBlock => ApiError::BlockNotFound, - Error::NoTransaction => ApiError::TransactionNotFound, - unknown_error => ApiError::StarknetDevnetError(unknown_error), - } - }) + pub(crate) async fn get_block_with_txs(&self, block_id: BlockId) -> StrictRpcResult { + let block = + self.api.starknet.read().await.get_block_with_transactions(block_id.into()).map_err( + |err| match err { + Error::NoBlock => ApiError::BlockNotFound, + Error::NoTransaction => ApiError::TransactionNotFound, + unknown_error => ApiError::StarknetDevnetError(unknown_error), + }, + )?; + + Ok(StarknetResponse::BlockWithFullTransactions(block)) } /// starknet_getStateUpdate - pub(crate) async fn get_state_update(&self, block_id: BlockId) -> RpcResult { + pub(crate) async fn get_state_update(&self, block_id: BlockId) -> StrictRpcResult { let state_update = self.api.starknet.read().await.block_state_update(block_id.into()).map_err(|err| { match err { @@ -64,61 +64,29 @@ impl JsonRpcHandler { } })?; - let state_diff = ThinStateDiff { - deployed_contracts: state_update - .deployed_contracts - .into_iter() - .map(|(address, class_hash)| DeployedContract { address, class_hash }) - .collect(), - declared_classes: state_update - .declared_classes - .into_iter() - .map(|(class_hash, compiled_class_hash)| ClassHashes { - class_hash, - compiled_class_hash, - }) - .collect(), - deprecated_declared_classes: state_update.cairo_0_declared_classes, - nonces: state_update - .nonces - .into_iter() - .map(|(address, nonce)| ContractNonce { contract_address: address, nonce }) - .collect(), - storage_diffs: state_update - .storage_updates - .into_iter() - .map(|(contract_address, updates)| StorageDiff { - address: contract_address, - storage_entries: updates - .into_iter() - .map(|(key, value)| StorageEntry { key: PatriciaKeyHex(key), value }) - .collect(), - }) - .collect(), - replaced_classes: vec![], - }; - - Ok(StateUpdate { + let state_diff = state_update.state_diff.into(); + + Ok(StarknetResponse::StateUpdate(StateUpdate { block_hash: state_update.block_hash, new_root: state_update.new_root, old_root: state_update.old_root, state_diff, - }) + })) } /// starknet_getStorageAt pub(crate) async fn get_storage_at( &self, contract_address: ContractAddress, - key: PatriciaKeyHex, + key: PatriciaKey, block_id: BlockId, - ) -> RpcResult { + ) -> StrictRpcResult { let felt = self .api .starknet .read() .await - .contract_storage_at_block(block_id.into(), contract_address, key.0) + .contract_storage_at_block(block_id.into(), contract_address, key) .map_err(|err| match err { Error::NoBlock => ApiError::BlockNotFound, Error::StateError(StateError::NoneStorage(_)) @@ -126,16 +94,39 @@ impl JsonRpcHandler { unknown_error => ApiError::StarknetDevnetError(unknown_error), })?; - Ok(felt) + Ok(StarknetResponse::StorageAt(felt)) } /// starknet_getTransactionByHash pub(crate) async fn get_transaction_by_hash( &self, transaction_hash: TransactionHash, - ) -> RpcResult { + ) -> StrictRpcResult { match self.api.starknet.read().await.get_transaction_by_hash(transaction_hash) { - Ok(transaction) => Ok(transaction.clone()), + Ok(transaction) => Ok(StarknetResponse::TransactionByHash(transaction.clone())), + Err(Error::NoTransaction) => Err(ApiError::TransactionNotFound), + Err(err) => Err(err.into()), + } + } + + /// starknet_getTransactionStatus + pub(crate) async fn get_transaction_status_by_hash( + &self, + transaction_hash: TransactionHash, + ) -> StrictRpcResult { + match self + .api + .starknet + .read() + .await + .get_transaction_execution_and_finality_status(transaction_hash) + { + Ok((execution_status, finality_status)) => { + Ok(StarknetResponse::TransactionStatusByHash(TransactionStatusOutput { + execution_status, + finality_status, + })) + } Err(Error::NoTransaction) => Err(ApiError::TransactionNotFound), Err(err) => Err(err.into()), } @@ -146,7 +137,7 @@ impl JsonRpcHandler { &self, block_id: BlockId, index: u64, - ) -> RpcResult { + ) -> StrictRpcResult { match self .api .starknet @@ -154,7 +145,9 @@ impl JsonRpcHandler { .await .get_transaction_by_block_id_and_index(block_id.into(), index) { - Ok(transaction) => Ok(transaction.clone()), + Ok(transaction) => { + Ok(StarknetResponse::TransactionByBlockAndIndex(transaction.clone())) + } Err(Error::InvalidTransactionIndexInBlock) => { Err(ApiError::InvalidTransactionIndexInBlock) } @@ -167,9 +160,11 @@ impl JsonRpcHandler { pub(crate) async fn get_transaction_receipt_by_hash( &self, transaction_hash: TransactionHash, - ) -> RpcResult { + ) -> StrictRpcResult { match self.api.starknet.read().await.get_transaction_receipt_by_hash(transaction_hash) { - Ok(receipt) => Ok(receipt), + Ok(receipt) => { + Ok(StarknetResponse::TransactionReceiptByTransactionHash(Box::new(receipt))) + } Err(Error::NoTransaction) => Err(ApiError::TransactionNotFound), Err(err) => Err(err.into()), } @@ -180,9 +175,9 @@ impl JsonRpcHandler { &self, block_id: BlockId, class_hash: ClassHash, - ) -> RpcResult { + ) -> StrictRpcResult { match self.api.starknet.read().await.get_class(block_id.into(), class_hash) { - Ok(contract_class) => Ok(contract_class.try_into()?), + Ok(contract_class) => Ok(StarknetResponse::ClassByHash(contract_class.try_into()?)), Err(Error::NoBlock) => Err(ApiError::BlockNotFound), Err(Error::StateError(_) | Error::NoStateAtBlock { block_number: _ }) => { Err(ApiError::ClassHashNotFound) @@ -196,9 +191,11 @@ impl JsonRpcHandler { &self, block_id: BlockId, contract_address: ContractAddress, - ) -> RpcResult { + ) -> StrictRpcResult { match self.api.starknet.read().await.get_class_at(block_id.into(), contract_address) { - Ok(contract_class) => Ok(contract_class.try_into()?), + Ok(contract_class) => { + Ok(StarknetResponse::ClassAtContractAddress(contract_class.try_into()?)) + } Err(Error::NoBlock) => Err(ApiError::BlockNotFound), Err( Error::ContractNotFound @@ -214,9 +211,9 @@ impl JsonRpcHandler { &self, block_id: BlockId, contract_address: ContractAddress, - ) -> RpcResult { + ) -> StrictRpcResult { match self.api.starknet.read().await.get_class_hash_at(block_id.into(), contract_address) { - Ok(class_hash) => Ok(class_hash), + Ok(class_hash) => Ok(StarknetResponse::ClassHashAtContractAddress(class_hash)), Err(Error::NoBlock) => Err(ApiError::BlockNotFound), Err(Error::ContractNotFound | Error::NoStateAtBlock { block_number: _ }) => { Err(ApiError::ContractNotFound) @@ -226,20 +223,16 @@ impl JsonRpcHandler { } /// starknet_getBlockTransactionCount - pub(crate) async fn get_block_txs_count(&self, block_id: BlockId) -> RpcResult { + pub(crate) async fn get_block_txs_count(&self, block_id: BlockId) -> StrictRpcResult { let num_trans_count = self.api.starknet.read().await.get_block_txs_count(block_id.into()); match num_trans_count { - Ok(count) => Ok(count), + Ok(count) => Ok(StarknetResponse::BlockTransactionCount(count)), Err(_) => Err(ApiError::NoBlocks), } } /// starknet_call - pub(crate) async fn call( - &self, - block_id: BlockId, - request: FunctionCall, - ) -> RpcResult> { + pub(crate) async fn call(&self, block_id: BlockId, request: FunctionCall) -> StrictRpcResult { let starknet = self.api.starknet.read().await; match starknet.call( @@ -248,10 +241,10 @@ impl JsonRpcHandler { request.entry_point_selector, request.calldata, ) { - Ok(result) => Ok(result), + Ok(result) => Ok(StarknetResponse::Call(result)), Err(Error::NoBlock) => Err(ApiError::BlockNotFound), Err(Error::ContractNotFound) => Err(ApiError::ContractNotFound), - Err(err) => Err(ApiError::ContractError { msg: err.to_string() }), + Err(err) => Err(ApiError::ContractError { error: err }), } } @@ -260,13 +253,13 @@ impl JsonRpcHandler { &self, block_id: BlockId, request: Vec, - ) -> RpcResult> { + ) -> StrictRpcResult { let starknet = self.api.starknet.read().await; match starknet.estimate_fee(block_id.into(), &request) { - Ok(result) => Ok(result), + Ok(result) => Ok(StarknetResponse::EsimateFee(result)), Err(Error::ContractNotFound) => Err(ApiError::ContractNotFound), Err(Error::NoBlock) => Err(ApiError::BlockNotFound), - Err(err) => Err(ApiError::ContractError { msg: err.to_string() }), + Err(err) => Err(ApiError::ContractError { error: err }), } } @@ -274,57 +267,52 @@ impl JsonRpcHandler { &self, block_id: BlockId, message: MsgFromL1, - ) -> RpcResult { + ) -> StrictRpcResult { match self.api.starknet.read().await.estimate_message_fee(block_id.into(), message) { - Ok(result) => Ok(result), + Ok(result) => Ok(StarknetResponse::EstimateMessageFee(result)), Err(Error::ContractNotFound) => Err(ApiError::ContractNotFound), Err(Error::NoBlock) => Err(ApiError::BlockNotFound), - Err(err) => Err(ApiError::ContractError { msg: err.to_string() }), + Err(err) => Err(ApiError::ContractError { error: err }), } } /// starknet_blockNumber - pub(crate) async fn block_number(&self) -> RpcResult { + pub(crate) async fn block_number(&self) -> StrictRpcResult { let block = self.api.starknet.read().await.get_latest_block().map_err(|err| match err { Error::NoBlock => ApiError::BlockNotFound, unknown_error => ApiError::StarknetDevnetError(unknown_error), })?; - Ok(block.block_number()) + Ok(StarknetResponse::BlockNumber(block.block_number())) } /// starknet_blockHashAndNumber - pub(crate) async fn block_hash_and_number(&self) -> RpcResult { + pub(crate) async fn block_hash_and_number(&self) -> StrictRpcResult { let block = self.api.starknet.read().await.get_latest_block().map_err(|err| match err { Error::NoBlock => ApiError::BlockNotFound, unknown_error => ApiError::StarknetDevnetError(unknown_error), })?; - Ok(BlockHashAndNumberOutput { + Ok(StarknetResponse::BlockHashAndNumber(BlockHashAndNumberOutput { block_hash: block.block_hash(), block_number: block.block_number(), - }) + })) } /// starknet_chainId - pub(crate) async fn chain_id(&self) -> RpcResult { + pub(crate) async fn chain_id(&self) -> StrictRpcResult { let chain_id = self.api.starknet.read().await.chain_id(); - Ok(chain_id.to_felt().to_prefixed_hex_str()) - } - - /// starknet_pendingTransactions - pub(crate) async fn pending_transactions(&self) -> RpcResult> { - Ok(vec![]) + Ok(StarknetResponse::ChainId(chain_id.to_felt().to_prefixed_hex_str())) } /// starknet_syncing - pub(crate) async fn syncing(&self) -> RpcResult { - Ok(SyncingOutput::False(false)) + pub(crate) async fn syncing(&self) -> StrictRpcResult { + Ok(StarknetResponse::Syncing(SyncingOutput::False(false))) } /// starknet_getEvents - pub(crate) async fn get_events(&self, filter: EventFilter) -> RpcResult { + pub(crate) async fn get_events(&self, filter: EventFilter) -> StrictRpcResult { let starknet = self.api.starknet.read().await; let page = filter @@ -342,10 +330,10 @@ impl JsonRpcHandler { Some(filter.chunk_size), )?; - Ok(EventsChunk { + Ok(StarknetResponse::Events(EventsChunk { events, continuation_token: if has_more_events { Some((page + 1).to_string()) } else { None }, - }) + })) } /// starknet_getNonce @@ -353,7 +341,7 @@ impl JsonRpcHandler { &self, block_id: BlockId, contract_address: ContractAddress, - ) -> RpcResult { + ) -> StrictRpcResult { let nonce = self .api .starknet @@ -368,7 +356,7 @@ impl JsonRpcHandler { unknown_error => ApiError::StarknetDevnetError(unknown_error), })?; - Ok(nonce) + Ok(StarknetResponse::ContractNonce(nonce)) } /// starknet_simulateTransactions @@ -377,13 +365,13 @@ impl JsonRpcHandler { block_id: BlockId, transactions: Vec, simulation_flags: Vec, - ) -> RpcResult> { + ) -> StrictRpcResult { let starknet = self.api.starknet.read().await; match starknet.simulate_transactions(block_id.into(), &transactions, simulation_flags) { - Ok(result) => Ok(result), + Ok(result) => Ok(StarknetResponse::SimulateTransactions(result)), Err(Error::ContractNotFound) => Err(ApiError::ContractNotFound), Err(Error::NoBlock) => Err(ApiError::BlockNotFound), - Err(err) => Err(ApiError::ContractError { msg: err.to_string() }), + Err(err) => Err(ApiError::ContractError { error: err }), } } } diff --git a/crates/starknet-server/src/api/json_rpc/error.rs b/crates/starknet-server/src/api/json_rpc/error.rs index 8f55ffa97..1b3a2246e 100644 --- a/crates/starknet-server/src/api/json_rpc/error.rs +++ b/crates/starknet-server/src/api/json_rpc/error.rs @@ -1,9 +1,10 @@ +use serde_json::json; use server::rpc_core::error::RpcError; use starknet_types; use thiserror::Error; use tracing::error; -use super::WILDCARD_RPC_ERROR_CODE; +use super::{StarknetResponse, WILDCARD_RPC_ERROR_CODE}; #[allow(unused)] #[derive(Error, Debug)] @@ -24,8 +25,8 @@ pub enum ApiError { InvalidTransactionIndexInBlock, #[error("Class hash not found")] ClassHashNotFound, - #[error("Contract error: {msg}")] - ContractError { msg: String }, + #[error("Contract error")] + ContractError { error: starknet_core::error::Error }, #[error("There are no blocks")] NoBlocks, #[error("Requested page size is too big")] @@ -54,76 +55,81 @@ pub enum ApiError { impl ApiError { pub(crate) fn api_error_to_rpc_error(self) -> RpcError { + let error_message = self.to_string(); match self { ApiError::RpcError(rpc_error) => rpc_error, - err @ ApiError::BlockNotFound => RpcError { + ApiError::BlockNotFound => RpcError { code: server::rpc_core::error::ErrorCode::ServerError(24), - message: err.to_string().into(), + message: error_message.into(), data: None, }, - err @ ApiError::ContractNotFound => RpcError { + ApiError::ContractNotFound => RpcError { code: server::rpc_core::error::ErrorCode::ServerError(20), - message: err.to_string().into(), + message: error_message.into(), data: None, }, - err @ ApiError::TransactionNotFound => RpcError { + ApiError::TransactionNotFound => RpcError { code: server::rpc_core::error::ErrorCode::ServerError(29), - message: err.to_string().into(), + message: error_message.into(), data: None, }, - err @ ApiError::InvalidTransactionIndexInBlock => RpcError { + ApiError::InvalidTransactionIndexInBlock => RpcError { code: server::rpc_core::error::ErrorCode::ServerError(27), - message: err.to_string().into(), + message: error_message.into(), data: None, }, - err @ ApiError::ClassHashNotFound => RpcError { + ApiError::ClassHashNotFound => RpcError { code: server::rpc_core::error::ErrorCode::ServerError(28), - message: err.to_string().into(), + message: error_message.into(), data: None, }, - ApiError::ContractError { msg } => RpcError { + ApiError::ContractError { error: inner_error } => RpcError { code: server::rpc_core::error::ErrorCode::ServerError(40), - message: msg.into(), - data: None, + message: error_message.into(), + data: Some(json!( + { + "revert_error": anyhow::format_err!(inner_error).root_cause().to_string() + } + )), }, - err @ ApiError::NoBlocks => RpcError { + ApiError::NoBlocks => RpcError { code: server::rpc_core::error::ErrorCode::ServerError(32), - message: err.to_string().into(), + message: error_message.into(), data: None, }, - err @ ApiError::RequestPageSizeTooBig => RpcError { + ApiError::RequestPageSizeTooBig => RpcError { code: server::rpc_core::error::ErrorCode::ServerError(31), - message: err.to_string().into(), + message: error_message.into(), data: None, }, - err @ ApiError::InvalidContinuationToken => RpcError { + ApiError::InvalidContinuationToken => RpcError { code: server::rpc_core::error::ErrorCode::ServerError(33), - message: err.to_string().into(), + message: error_message.into(), data: None, }, - err @ ApiError::TooManyKeysInFilter => RpcError { + ApiError::TooManyKeysInFilter => RpcError { code: server::rpc_core::error::ErrorCode::ServerError(34), - message: err.to_string().into(), + message: error_message.into(), data: None, }, - err @ ApiError::ClassAlreadyDeclared => RpcError { + ApiError::ClassAlreadyDeclared => RpcError { code: server::rpc_core::error::ErrorCode::ServerError(51), - message: err.to_string().into(), + message: error_message.into(), data: None, }, - err @ ApiError::InvalidContractClass => RpcError { + ApiError::InvalidContractClass => RpcError { code: server::rpc_core::error::ErrorCode::ServerError(50), - message: err.to_string().into(), + message: error_message.into(), data: None, }, - err @ ApiError::TypesError(_) => RpcError { + ApiError::TypesError(_) => RpcError { code: server::rpc_core::error::ErrorCode::ServerError(WILDCARD_RPC_ERROR_CODE), - message: err.to_string().into(), + message: error_message.into(), data: None, }, - err @ ApiError::OnlyLatestBlock => RpcError { + ApiError::OnlyLatestBlock => RpcError { code: server::rpc_core::error::ErrorCode::ServerError(24), - message: err.to_string().into(), + message: error_message.into(), data: None, }, ApiError::UnsupportedAction { msg } => RpcError { @@ -131,24 +137,24 @@ impl ApiError { message: msg.into(), data: None, }, - err @ ApiError::InsufficientMaxFee => RpcError { + ApiError::InsufficientMaxFee => RpcError { code: server::rpc_core::error::ErrorCode::ServerError(53), - message: err.to_string().into(), + message: error_message.into(), data: None, }, - err @ ApiError::InvalidTransactionNonce => RpcError { + ApiError::InvalidTransactionNonce => RpcError { code: server::rpc_core::error::ErrorCode::ServerError(52), - message: err.to_string().into(), + message: error_message.into(), data: None, }, - err @ ApiError::InsufficientAccountBalance => RpcError { + ApiError::InsufficientAccountBalance => RpcError { code: server::rpc_core::error::ErrorCode::ServerError(54), - message: err.to_string().into(), + message: error_message.into(), data: None, }, - err @ ApiError::ValidationFailure => RpcError { + ApiError::ValidationFailure => RpcError { code: server::rpc_core::error::ErrorCode::ServerError(55), - message: err.to_string().into(), + message: error_message.into(), data: None, }, ApiError::StarknetDevnetError( @@ -165,19 +171,19 @@ impl ApiError { } ApiError::StarknetDevnetError(error) => RpcError { code: server::rpc_core::error::ErrorCode::ServerError(WILDCARD_RPC_ERROR_CODE), - message: error.to_string().into(), + message: anyhow::format_err!(error).root_cause().to_string().into(), data: None, }, } } } -pub(crate) type RpcResult = Result; +pub(crate) type StrictRpcResult = Result; #[cfg(test)] mod tests { - - use crate::api::json_rpc::error::{ApiError, RpcResult}; + use super::StrictRpcResult; + use crate::api::json_rpc::error::ApiError; use crate::api::json_rpc::ToRpcResponseResult; #[test] @@ -247,11 +253,26 @@ mod tests { #[test] fn contract_error() { + fn test_error() -> starknet_core::error::Error { + starknet_core::error::Error::TransactionValidationError( + starknet_core::error::TransactionValidationError::ValidationFailure, + ) + } + let error_expected_message = anyhow::format_err!(test_error()).root_cause().to_string(); + error_expected_code_and_message( - ApiError::ContractError { msg: "Contract error".into() }, + ApiError::ContractError { error: test_error() }, 40, "Contract error", ); + + // check contract error data property + let error = ApiError::ContractError { error: test_error() }.api_error_to_rpc_error(); + + assert_eq!( + error.data.unwrap().get("revert_error").unwrap().as_str().unwrap(), + &error_expected_message + ); } #[test] @@ -327,7 +348,7 @@ mod tests { } fn error_expected_code_and_message(err: ApiError, expected_code: i64, expected_message: &str) { - let error_result = RpcResult::<()>::Err(err).to_rpc_result(); + let error_result = StrictRpcResult::Err(err).to_rpc_result(); match error_result { server::rpc_core::response::ResponseResult::Success(_) => panic!("Expected error"), server::rpc_core::response::ResponseResult::Error(err) => { diff --git a/crates/starknet-server/src/api/json_rpc/mod.rs b/crates/starknet-server/src/api/json_rpc/mod.rs index 879ebc8dd..9604f7a14 100644 --- a/crates/starknet-server/src/api/json_rpc/mod.rs +++ b/crates/starknet-server/src/api/json_rpc/mod.rs @@ -1,9 +1,10 @@ mod endpoints; pub mod error; mod models; +#[cfg(test)] +mod spec_reader; mod write_endpoints; -use error::RpcResult; use models::{ BlockAndClassHashInput, BlockAndContractAddressInput, BlockAndIndexInput, CallInput, EstimateFeeInput, EventsInput, GetStorageInput, TransactionHashInput, @@ -12,15 +13,30 @@ use serde::{Deserialize, Serialize}; use server::rpc_core::error::RpcError; use server::rpc_core::response::ResponseResult; use server::rpc_handler::RpcHandler; -use starknet_types::rpc::estimate_message_fee::EstimateMessageFeeRequestWrapper; +use starknet_rs_core::types::ContractClass as CodegenContractClass; +use starknet_types::felt::{ClassHash, Felt}; +use starknet_types::rpc::block::Block; +use starknet_types::rpc::estimate_message_fee::{ + EstimateMessageFeeRequestWrapper, FeeEstimateWrapper, +}; +use starknet_types::rpc::state::StateUpdate; +use starknet_types::rpc::transaction_receipt::TransactionReceipt; +use starknet_types::rpc::transactions::{EventsChunk, SimulatedTransaction, Transaction}; +use starknet_types::starknet_api::block::BlockNumber; use tracing::{error, info, trace}; +use self::error::StrictRpcResult; use self::models::{ - BlockIdInput, BroadcastedDeclareTransactionInput, BroadcastedDeployAccountTransactionInput, - BroadcastedInvokeTransactionInput, + BlockHashAndNumberOutput, BlockIdInput, BroadcastedDeclareTransactionInput, + BroadcastedDeployAccountTransactionInput, BroadcastedInvokeTransactionInput, + DeclareTransactionOutput, DeployAccountTransactionOutput, InvokeTransactionOutput, + SyncingOutput, TransactionStatusOutput, }; use super::Api; -use crate::api::json_rpc::models::SimulateTransactionsInput; +use crate::api::json_rpc::models::{ + BroadcastedDeclareTransactionEnumWrapper, BroadcastedDeployAccountTransactionEnumWrapper, + BroadcastedInvokeTransactionEnumWrapper, SimulateTransactionsInput, +}; use crate::api::serde_helpers::empty_params; /// Helper trait to easily convert results to rpc results @@ -42,7 +58,7 @@ pub fn to_rpc_result(val: T) -> ResponseResult { } } -impl ToRpcResponseResult for RpcResult { +impl ToRpcResponseResult for StrictRpcResult { fn to_rpc_result(self) -> ResponseResult { match self { Ok(data) => to_rpc_result(data), @@ -75,6 +91,7 @@ impl JsonRpcHandler { trace!(target: "JsonRpcHandler::execute", "executing starknet request"); match request { + StarknetRequest::SpecVersion => self.spec_version().to_rpc_result(), StarknetRequest::BlockWithTransactionHashes(block) => { self.get_block_with_tx_hashes(block.block_id).await.to_rpc_result() } @@ -87,6 +104,9 @@ impl JsonRpcHandler { StarknetRequest::StorageAt(GetStorageInput { contract_address, key, block_id }) => { self.get_storage_at(contract_address, key, block_id).await.to_rpc_result() } + StarknetRequest::TransactionStatusByHash(TransactionHashInput { transaction_hash }) => { + self.get_transaction_status_by_hash(transaction_hash).await.to_rpc_result() + } StarknetRequest::TransactionByHash(TransactionHashInput { transaction_hash }) => { self.get_transaction_by_hash(transaction_hash).await.to_rpc_result() } @@ -121,9 +141,6 @@ impl JsonRpcHandler { self.block_hash_and_number().await.to_rpc_result() } StarknetRequest::ChainId => self.chain_id().await.to_rpc_result(), - StarknetRequest::PendingTransactions => { - self.pending_transactions().await.to_rpc_result() - } StarknetRequest::Syncing => self.syncing().await.to_rpc_result(), StarknetRequest::Events(EventsInput { filter }) => { self.get_events(filter).await.to_rpc_result() @@ -134,16 +151,26 @@ impl JsonRpcHandler { }) => self.get_nonce(block_id, contract_address).await.to_rpc_result(), StarknetRequest::AddDeclareTransaction(BroadcastedDeclareTransactionInput { declare_transaction, - }) => self.add_declare_transaction(declare_transaction).await.to_rpc_result(), + }) => { + let BroadcastedDeclareTransactionEnumWrapper::Declare(broadcasted_transaction) = + declare_transaction; + self.add_declare_transaction(broadcasted_transaction).await.to_rpc_result() + } StarknetRequest::AddDeployAccountTransaction( BroadcastedDeployAccountTransactionInput { deploy_account_transaction }, - ) => self - .add_deploy_account_transaction(deploy_account_transaction) - .await - .to_rpc_result(), + ) => { + let BroadcastedDeployAccountTransactionEnumWrapper::DeployAccount( + broadcasted_transaction, + ) = deploy_account_transaction; + self.add_deploy_account_transaction(broadcasted_transaction).await.to_rpc_result() + } StarknetRequest::AddInvokeTransaction(BroadcastedInvokeTransactionInput { invoke_transaction, - }) => self.add_invoke_transaction(invoke_transaction).await.to_rpc_result(), + }) => { + let BroadcastedInvokeTransactionEnumWrapper::Invoke(broadcasted_transaction) = + invoke_transaction; + self.add_invoke_transaction(broadcasted_transaction).await.to_rpc_result() + } StarknetRequest::EstimateMessageFee(request) => self .estimate_message_fee(request.get_block_id(), request.get_raw_message().clone()) .await @@ -163,6 +190,8 @@ impl JsonRpcHandler { #[derive(Clone, Debug, Deserialize)] #[serde(tag = "method", content = "params")] pub enum StarknetRequest { + #[serde(rename = "starknet_specVersion", with = "empty_params")] + SpecVersion, #[serde(rename = "starknet_getBlockWithTxHashes")] BlockWithTransactionHashes(BlockIdInput), #[serde(rename = "starknet_getBlockWithTxs")] @@ -177,6 +206,8 @@ pub enum StarknetRequest { TransactionByBlockAndIndex(BlockAndIndexInput), #[serde(rename = "starknet_getTransactionReceipt")] TransactionReceiptByTransactionHash(TransactionHashInput), + #[serde(rename = "starknet_getTransactionStatus")] + TransactionStatusByHash(TransactionHashInput), #[serde(rename = "starknet_getClass")] ClassByHash(BlockAndClassHashInput), #[serde(rename = "starknet_getClassHashAt")] @@ -195,8 +226,6 @@ pub enum StarknetRequest { BlockHashAndNumber, #[serde(rename = "starknet_chainId", with = "empty_params")] ChainId, - #[serde(rename = "starknet_pendingTransactions", with = "empty_params")] - PendingTransactions, #[serde(rename = "starknet_syncing", with = "empty_params")] Syncing, #[serde(rename = "starknet_getEvents")] @@ -218,6 +247,7 @@ pub enum StarknetRequest { impl std::fmt::Display for StarknetRequest { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { + StarknetRequest::SpecVersion => write!(f, "starknet_specVersion"), StarknetRequest::BlockWithTransactionHashes(_) => { write!(f, "starknet_getBlockWithTxHashes") } @@ -225,6 +255,9 @@ impl std::fmt::Display for StarknetRequest { StarknetRequest::StateUpdate(_) => write!(f, "starknet_getStateUpdate"), StarknetRequest::StorageAt(_) => write!(f, "starknet_getStorageAt"), StarknetRequest::TransactionByHash(_) => write!(f, "starknet_getTransactionByHash"), + StarknetRequest::TransactionStatusByHash(_) => { + write!(f, "starknet_getTransactionStatus") + } StarknetRequest::TransactionByBlockAndIndex(_) => { write!(f, "starknet_getTransactionByBlockIdAndIndex") } @@ -242,7 +275,6 @@ impl std::fmt::Display for StarknetRequest { StarknetRequest::BlockNumber => write!(f, "starknet_blockNumber"), StarknetRequest::BlockHashAndNumber => write!(f, "starknet_blockHashAndNumber"), StarknetRequest::ChainId => write!(f, "starknet_chainId"), - StarknetRequest::PendingTransactions => write!(f, "starknet_pendingTransactions"), StarknetRequest::Syncing => write!(f, "starknet_syncing"), StarknetRequest::Events(_) => write!(f, "starknet_getEvents"), StarknetRequest::ContractNonce(_) => write!(f, "starknet_getNonce"), @@ -259,8 +291,40 @@ impl std::fmt::Display for StarknetRequest { } } +#[derive(Serialize, Deserialize, Debug)] +#[serde(untagged)] +pub(crate) enum StarknetResponse { + BlockWithTransactionHashes(Block), + BlockWithFullTransactions(Block), + StateUpdate(StateUpdate), + StorageAt(Felt), + TransactionByHash(Transaction), + TransactionByBlockAndIndex(Transaction), + TransactionReceiptByTransactionHash(Box), + TransactionStatusByHash(TransactionStatusOutput), + ClassByHash(CodegenContractClass), + ClassHashAtContractAddress(ClassHash), + ClassAtContractAddress(CodegenContractClass), + BlockTransactionCount(u64), + Call(Vec), + EsimateFee(Vec), + BlockNumber(BlockNumber), + BlockHashAndNumber(BlockHashAndNumberOutput), + ChainId(String), + Syncing(SyncingOutput), + Events(EventsChunk), + ContractNonce(Felt), + AddDeclareTransaction(DeclareTransactionOutput), + AddDeployAccountTransaction(DeployAccountTransactionOutput), + AddInvokeTransaction(InvokeTransactionOutput), + EstimateMessageFee(FeeEstimateWrapper), + SimulateTransactions(Vec), + SpecVersion(String), +} + #[cfg(test)] mod requests_tests { + use starknet_types::felt::Felt; use super::StarknetRequest; diff --git a/crates/starknet-server/src/api/json_rpc/models.rs b/crates/starknet-server/src/api/json_rpc/models.rs index e0f54d116..fcc01ea2f 100644 --- a/crates/starknet-server/src/api/json_rpc/models.rs +++ b/crates/starknet-server/src/api/json_rpc/models.rs @@ -1,6 +1,8 @@ use serde::{Deserialize, Serialize}; +use starknet_rs_core::types::{TransactionExecutionStatus, TransactionFinalityStatus}; use starknet_types::contract_address::ContractAddress; use starknet_types::felt::{BlockHash, ClassHash, TransactionHash}; +use starknet_types::patricia_key::PatriciaKey; use starknet_types::rpc::block::{BlockId, SyncStatus}; use starknet_types::rpc::transactions::broadcasted_deploy_account_transaction::BroadcastedDeployAccountTransaction; use starknet_types::rpc::transactions::broadcasted_invoke_transaction::BroadcastedInvokeTransaction; @@ -10,56 +12,63 @@ use starknet_types::rpc::transactions::{ }; use starknet_types::starknet_api::block::BlockNumber; -use crate::api::models::PatriciaKeyHex; - #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(deny_unknown_fields)] pub struct BlockIdInput { pub(crate) block_id: BlockId, } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(deny_unknown_fields)] pub struct TransactionHashInput { pub(crate) transaction_hash: TransactionHash, } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(deny_unknown_fields)] pub struct GetStorageInput { pub(crate) contract_address: ContractAddress, - pub(crate) key: PatriciaKeyHex, + pub(crate) key: PatriciaKey, pub(crate) block_id: BlockId, } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(deny_unknown_fields)] pub struct BlockAndIndexInput { pub(crate) block_id: BlockId, pub(crate) index: u64, } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(deny_unknown_fields)] pub struct BlockAndClassHashInput { pub(crate) block_id: BlockId, pub(crate) class_hash: ClassHash, } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(deny_unknown_fields)] pub struct BlockAndContractAddressInput { pub(crate) block_id: BlockId, pub(crate) contract_address: ContractAddress, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] pub struct CallInput { pub request: FunctionCall, pub block_id: BlockId, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] pub struct EstimateFeeInput { pub request: Vec, pub block_id: BlockId, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] pub struct BlockHashAndNumberOutput { pub block_hash: BlockHash, pub block_number: BlockNumber, @@ -77,45 +86,80 @@ pub struct EventsInput { pub filter: EventFilter, } +#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)] +#[serde(tag = "type")] +pub enum BroadcastedDeclareTransactionEnumWrapper { + #[serde(rename = "DECLARE")] + Declare(BroadcastedDeclareTransaction), +} + #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] pub struct BroadcastedDeclareTransactionInput { - pub declare_transaction: BroadcastedDeclareTransaction, + pub declare_transaction: BroadcastedDeclareTransactionEnumWrapper, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] pub struct DeclareTransactionOutput { pub transaction_hash: TransactionHash, pub class_hash: ClassHash, } +#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)] +#[serde(tag = "type")] +pub enum BroadcastedDeployAccountTransactionEnumWrapper { + #[serde(rename = "DEPLOY_ACCOUNT")] + DeployAccount(BroadcastedDeployAccountTransaction), +} + #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] pub struct BroadcastedDeployAccountTransactionInput { - pub deploy_account_transaction: BroadcastedDeployAccountTransaction, + pub deploy_account_transaction: BroadcastedDeployAccountTransactionEnumWrapper, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] pub struct DeployAccountTransactionOutput { pub transaction_hash: TransactionHash, pub contract_address: ContractAddress, } +#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)] +#[serde(tag = "type")] +pub enum BroadcastedInvokeTransactionEnumWrapper { + #[serde(rename = "INVOKE")] + Invoke(BroadcastedInvokeTransaction), +} + #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] pub struct BroadcastedInvokeTransactionInput { - pub invoke_transaction: BroadcastedInvokeTransaction, + pub invoke_transaction: BroadcastedInvokeTransactionEnumWrapper, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] pub struct InvokeTransactionOutput { pub transaction_hash: TransactionHash, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] pub struct SimulateTransactionsInput { pub block_id: BlockId, pub transactions: Vec, pub simulation_flags: Vec, } +#[derive(Debug, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct TransactionStatusOutput { + pub finality_status: TransactionFinalityStatus, + pub execution_status: TransactionExecutionStatus, +} + #[cfg(test)] mod tests { use starknet_types::contract_address::ContractAddress; @@ -128,7 +172,6 @@ mod tests { use starknet_types::starknet_api::block::BlockNumber; use super::{BlockIdInput, EstimateFeeInput, GetStorageInput}; - use crate::api::models::PatriciaKeyHex; #[test] fn errored_deserialization_of_estimate_fee_with_broadcasted_declare_transaction() { @@ -367,9 +410,7 @@ mod tests { )), contract_address: ContractAddress::new(Felt::from_prefixed_hex_str("0x02").unwrap()) .unwrap(), - key: PatriciaKeyHex( - PatriciaKey::new(Felt::from_prefixed_hex_str("0x03").unwrap()).unwrap(), - ), + key: PatriciaKey::new(Felt::from_prefixed_hex_str("0x03").unwrap()).unwrap(), }; assert_get_storage_input_correctness( diff --git a/crates/starknet-server/src/api/json_rpc/spec_reader/data_generator.rs b/crates/starknet-server/src/api/json_rpc/spec_reader/data_generator.rs new file mode 100644 index 000000000..c88fb56b7 --- /dev/null +++ b/crates/starknet-server/src/api/json_rpc/spec_reader/data_generator.rs @@ -0,0 +1,226 @@ +use std::collections::HashMap; +use std::time::{SystemTime, UNIX_EPOCH}; + +use rand::{Rng, SeedableRng}; +use serde_json::{Map, Value}; + +use super::spec_schemas::all_of_schema::AllOf; +use super::spec_schemas::array_primitive::ArrayPrimitive; +use super::spec_schemas::integer_primitive::IntegerPrimitive; +use super::spec_schemas::object_primitive::ObjectPrimitive; +use super::spec_schemas::one_of_schema::OneOf; +use super::spec_schemas::ref_schema::Reference; +use super::spec_schemas::string_primitive::StringPrimitive; +use super::spec_schemas::{Primitive, Schema}; + +const MAX_DEPTH: u8 = 5; + +pub(crate) trait Visitor { + fn do_for_boolean_primitive(&self) -> Result; + fn do_for_string_primitive( + &self, + element: &StringPrimitive, + ) -> Result; + fn do_for_integer_primitive( + &self, + element: &IntegerPrimitive, + ) -> Result; + fn do_for_array_primitive(&self, element: &ArrayPrimitive) + -> Result; + fn do_for_ref(&self, element: &Reference) -> Result; + fn do_for_one_of(&self, element: &OneOf) -> Result; + fn do_for_all_of(&self, element: &AllOf) -> Result; + fn do_for_object_primitive( + &self, + element: &ObjectPrimitive, + ) -> Result; +} + +pub(crate) trait Acceptor { + fn accept(&self, visitor: &impl Visitor) -> Result; +} + +pub(crate) struct RandDataGenerator<'a> { + schemas: &'a HashMap, + depth: u8, +} + +impl<'a> RandDataGenerator<'a> { + pub(crate) fn new(schemas: &'a HashMap, depth: u8) -> Self { + Self { schemas, depth } + } +} + +impl<'a> Visitor for RandDataGenerator<'a> { + fn do_for_boolean_primitive(&self) -> Result { + Ok(serde_json::Value::Bool(rand::thread_rng().gen_bool(0.5))) + } + + fn do_for_string_primitive( + &self, + element: &StringPrimitive, + ) -> Result { + if let Some(enums) = element.possible_enums.clone() { + let random_number = rand::thread_rng().gen_range(0..enums.len()); + return Ok(serde_json::Value::String(enums[random_number].clone())); + } + + if let Some(regex_pattern) = element.pattern.clone() { + let mut buffer: Vec = vec![]; + let duration = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + let u128_bytes = duration.as_nanos().to_be_bytes(); + let mut u64_bytes = [0; 8]; + u64_bytes.copy_from_slice(&u128_bytes[8..16]); + let seed = u64::from_be_bytes(u64_bytes); + + regex_generate::Generator::new( + ®ex_pattern, + rand_chacha::ChaCha8Rng::seed_from_u64(seed), + 100, + ) + .unwrap() + .generate(&mut buffer) + .unwrap(); + let random_string = String::from_utf8(buffer).unwrap(); + + return Ok(serde_json::Value::String(random_string)); + } + + Ok(serde_json::Value::String("".to_string())) + } + + fn do_for_integer_primitive( + &self, + element: &IntegerPrimitive, + ) -> Result { + let num = rand::thread_rng().gen_range(element.minimum.unwrap_or_default()..i32::MAX); + + Ok(serde_json::Value::Number(serde_json::Number::from(num))) + } + + fn do_for_array_primitive( + &self, + element: &ArrayPrimitive, + ) -> Result { + let mut array = vec![]; + if self.depth >= MAX_DEPTH { + return Ok(serde_json::Value::Array(array)); + } + + let number_of_elements = rand::thread_rng().gen_range(1..3); + + for _ in 0..number_of_elements { + let generated_value = + generate_schema_value(element.items.as_ref(), self.schemas, self.depth + 1)?; + + if !generated_value.is_null() { + array.push(generated_value); + } + } + + Ok(serde_json::Value::Array(array)) + } + + fn do_for_ref(&self, element: &Reference) -> Result { + let schema_name = element + .ref_field + .trim_start_matches("./") + .split("#/components/schemas/") + .filter(|entry| !entry.is_empty()) + .last() + .unwrap_or_default(); + + let schema = self + .schemas + .get(schema_name) + .ok_or(format!("Missing schema in components {}", schema_name))?; + + generate_schema_value(schema, self.schemas, self.depth) + } + + fn do_for_one_of(&self, element: &OneOf) -> Result { + let idx = rand::thread_rng().gen_range(0..element.one_of.len()); + let schema = element.one_of.get(idx).ok_or("OneOf schema doesnt have entry".to_string())?; + + generate_schema_value(schema, self.schemas, self.depth) + } + + fn do_for_all_of(&self, element: &AllOf) -> Result { + let mut accumulated_json_value = Map::new(); + + for one in element.all_of.iter() { + let generated_value = generate_schema_value(one, self.schemas, self.depth)?; + + if !generated_value.is_null() { + let single_value = generated_value + .as_object() + .ok_or("Expected to be an object".to_string())? + .clone(); + + accumulated_json_value.extend(single_value); + } + } + + if accumulated_json_value.is_empty() { + Ok(Value::Null) + } else { + Ok(serde_json::Value::Object(accumulated_json_value)) + } + } + + fn do_for_object_primitive( + &self, + element: &ObjectPrimitive, + ) -> Result { + if self.depth >= MAX_DEPTH { + return Ok(Value::Null); + } + let mut accumulated_json_value = Map::new(); + + for (key, inner_schema) in + element.properties.iter().filter(|(k, _)| match element.required.as_ref() { + Some(required_fields) => required_fields.contains(k), + None => true, + }) + { + let generated_value = + generate_schema_value(inner_schema, self.schemas, self.depth + 1)?; + + if !generated_value.is_null() { + accumulated_json_value.insert(key.to_string(), generated_value); + } + } + + if accumulated_json_value.is_empty() { + Ok(Value::Null) + } else { + Ok(Value::Object(accumulated_json_value)) + } + } +} + +pub(crate) fn generate_schema_value( + schema: &Schema, + schemas: &HashMap, + depth: u8, +) -> core::result::Result { + let generator = RandDataGenerator::new(schemas, depth); + + match schema { + Schema::Ref(schema_ref) => schema_ref.accept(&generator), + Schema::OneOf(one) => one.accept(&generator), + Schema::AllOf(all) => all.accept(&generator), + Schema::Primitive(Primitive::Integer(integer_primitive)) => { + integer_primitive.accept(&generator) + } + Schema::Primitive(Primitive::Number(number_primitive)) => { + number_primitive.accept(&generator) + } + Schema::Primitive(Primitive::String(string_primitive)) => { + string_primitive.accept(&generator) + } + Schema::Primitive(Primitive::Array(array)) => array.accept(&generator), + Schema::Primitive(Primitive::Boolean(boolean)) => boolean.accept(&generator), + Schema::Primitive(Primitive::Object(obj)) => obj.accept(&generator), + } +} diff --git a/crates/starknet-server/src/api/json_rpc/spec_reader/mod.rs b/crates/starknet-server/src/api/json_rpc/spec_reader/mod.rs new file mode 100644 index 000000000..bbfb73fce --- /dev/null +++ b/crates/starknet-server/src/api/json_rpc/spec_reader/mod.rs @@ -0,0 +1,312 @@ +use std::collections::HashMap; +use std::ffi::OsStr; +use std::fs; + +use serde::{Deserialize, Serialize}; +use serde_json::{Map, Value}; + +use self::data_generator::generate_schema_value; +use self::spec_schemas::Schema; + +mod data_generator; +mod spec_modifier; +mod spec_schemas; + +#[derive(Serialize, Deserialize)] +pub(crate) struct Spec { + openrpc: String, + methods: Vec, + components: Components, +} + +#[derive(Serialize, Deserialize)] +struct Method { + name: String, + params: Vec, + result: Result, +} + +#[derive(Serialize, Deserialize)] +struct Param { + name: String, + required: bool, + schema: Schema, +} + +#[derive(Serialize, Deserialize)] +struct Result { + name: String, + schema: Schema, +} + +#[derive(Serialize, Deserialize)] +struct Components { + schemas: HashMap, +} + +impl Spec { + fn load_from_dir(dir_path: &str) -> Vec { + let mut specs: Vec = Vec::new(); + + let mut instructions = Option::None; + + for path in fs::read_dir(dir_path).unwrap() { + let path = path.unwrap().path(); + + if let Some("yaml") = path.as_path().extension().and_then(OsStr::to_str) { + instructions = Some(spec_modifier::SpecModifier::load_from_path( + path.as_path().to_str().unwrap(), + )); + break; + } + } + + for path in fs::read_dir(dir_path).unwrap() { + let path = path.unwrap().path(); + + if let Some("yaml") = path.as_path().extension().and_then(OsStr::to_str) { + continue; + } + + let spec = Self::load_from_path(path.as_path().to_str().unwrap(), &instructions); + + specs.push(spec); + } + + specs + } + + fn load_from_path( + path: &str, + modify_spec_instructions: &Option, + ) -> Self { + let spec_str = fs::read_to_string(path).expect("Could not read the JSON-RPC spec file"); + + if let Some(instructions) = modify_spec_instructions { + // Remove some parts of the spec which were added due to some mistake + let json_obj_spec: serde_json::Value = serde_json::from_str(&spec_str) + .expect("Could not parse the JSON-RPC spec file to JSON object"); + + instructions.generate_spec(json_obj_spec) + } else { + let spec: Spec = + serde_json::from_str(&spec_str).expect("Could not parse the JSON-RPC spec"); + + spec + } + } +} + +fn generate_combined_schema(specs: &Vec) -> HashMap { + let mut combined_schema = HashMap::::new(); + + for spec in specs { + for (schema_name, schema) in &spec.components.schemas { + match schema.clone() { + Schema::Ref(reference) => { + // if reference to external file, then dont add it + let schema_parts = reference + .ref_field + .trim_start_matches("./") + .split("#/components/schemas/") + .filter(|entry| !entry.is_empty()) + .collect::>(); + + if schema_parts.len() == 1 { + // then it is not reference to external file + // only references to external files are not added + combined_schema.insert(schema_name.clone(), schema.clone()); + } + } + _ => { + combined_schema.insert(schema_name.clone(), schema.clone()); + } + } + } + } + + combined_schema +} + +fn generate_json_rpc_request( + method: &Method, + schemas: &HashMap, +) -> core::result::Result { + let mut request = HashMap::new(); + request.insert("jsonrpc", Value::String("2.0".to_string())); + request.insert("method", Value::String(method.name.clone())); + request.insert("id", Value::Number(serde_json::Number::from(1))); + + // Add the parameters to the request + let mut params = Map::new(); + + for param in method.params.iter() { + let param_value = generate_schema_value(¶m.schema, schemas, 0)?; + params.insert(param.name.clone(), param_value); + } + if !params.is_empty() { + request.insert("params", Value::Object(params)); + } else { + request.insert("params", Value::Array(vec![])); + } + + serde_json::to_value(&request) + .map_err(|err| format!("Could not serialize the JSON-RPC request: {}", err)) +} + +fn generate_json_rpc_response( + method: &Method, + schemas: &HashMap, +) -> core::result::Result { + generate_schema_value(&method.result.schema, schemas, 0) +} + +mod tests { + + use super::{generate_combined_schema, generate_json_rpc_response, Spec}; + use crate::api::json_rpc::spec_reader::generate_json_rpc_request; + use crate::api::json_rpc::{StarknetRequest, StarknetResponse}; + + #[test] + fn test_spec_methods() { + let specs = + Spec::load_from_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/test_data/spec/0.5.0")); + let combined_schema = generate_combined_schema(&specs); + let expected_failed_method_responses = vec![ + "starknet_getTransactionByBlockIdAndIndex", + "starknet_getTransactionByHash", + "starknet_getBlockWithTxs", + "starknet_getTransactionReceipt", + ]; + + let mut failed_method_responses = vec![]; + for _ in 0..100 { + for spec in specs.iter() { + // Iterate over the methods in the spec + for method in spec.methods.iter() { + if failed_method_responses.contains(&method.name) { + continue; + } + // Create a JSON-RPC request for each method + let request = generate_json_rpc_request(method, &combined_schema) + .expect("Could not generate the JSON-RPC request"); + + let sn_request = serde_json::from_value::(request.clone()); + + if sn_request.is_err() { + panic!("Failed method request: {}", method.name); + } + + let response = generate_json_rpc_response(method, &combined_schema) + .expect("Could not generate the JSON-RPC response"); + + let sn_response = serde_json::from_value::(response.clone()); + + if sn_response.is_err() { + failed_method_responses.push(method.name.clone()); + continue; + } + + let sn_response = sn_response.unwrap(); + let sn_request = sn_request.unwrap(); + + match sn_request { + StarknetRequest::BlockWithTransactionHashes(_) => { + assert!(matches!( + sn_response, + StarknetResponse::BlockWithTransactionHashes(_) + )); + } + StarknetRequest::BlockHashAndNumber => { + assert!(matches!(sn_response, StarknetResponse::BlockHashAndNumber(_))); + } + StarknetRequest::BlockNumber + | StarknetRequest::BlockTransactionCount(_) => { + assert!(matches!( + sn_response, + StarknetResponse::BlockNumber(_) + | StarknetResponse::BlockTransactionCount(_) + )); + } + StarknetRequest::Call(_) => { + assert!(matches!(sn_response, StarknetResponse::Call(_))); + } + StarknetRequest::ClassAtContractAddress(_) + | StarknetRequest::ClassByHash(_) => { + assert!(matches!( + sn_response, + StarknetResponse::ClassAtContractAddress(_) + | StarknetResponse::ClassByHash(_) + )); + } + StarknetRequest::EsimateFee(_) => { + assert!(matches!(sn_response, StarknetResponse::EsimateFee(_))); + } + StarknetRequest::EstimateMessageFee(_) => { + assert!(matches!(sn_response, StarknetResponse::EstimateMessageFee(_))); + } + StarknetRequest::Events(_) => { + assert!(matches!(sn_response, StarknetResponse::Events(_))); + } + StarknetRequest::SimulateTransactions(_) => { + assert!(matches!( + sn_response, + StarknetResponse::SimulateTransactions(_) + )); + } + StarknetRequest::StateUpdate(_) => { + assert!(matches!(sn_response, StarknetResponse::StateUpdate(_))); + } + StarknetRequest::Syncing => { + assert!(matches!(sn_response, StarknetResponse::Syncing(_))); + } + StarknetRequest::TransactionStatusByHash(_) => { + assert!(matches!( + sn_response, + StarknetResponse::TransactionStatusByHash(_) + )); + } + StarknetRequest::AddDeclareTransaction(_) => { + assert!(matches!( + sn_response, + StarknetResponse::AddDeclareTransaction(_) + )); + } + StarknetRequest::AddDeployAccountTransaction(_) => { + assert!(matches!( + sn_response, + StarknetResponse::AddDeployAccountTransaction(_) + )); + } + StarknetRequest::AddInvokeTransaction(_) => { + assert!(matches!( + sn_response, + StarknetResponse::AddInvokeTransaction(_) + )); + } + _ => { + // Remaining responses are not implemented, because + // multiple requests return the same response format either u64, Felt, + // etc. so its impossible to know which + // response variant is generated based on + // serde untagged deserialization. This is due to the fact that the + // first variant which complies with the response format is returned + } + } + } + } + } + + // TODO: there are some failed methods responses deserializations, because + // The implemented response variants have more fields than the json created from the + // generator Thus they diverge in some way from the spec, issue: https://github.com/0xSpaceShard/starknet-devnet-rs/issues/248 + println!("Methods diverging from the spec in some way {:?}", failed_method_responses); + assert_eq!( + failed_method_responses + .iter() + .filter(|&el| !expected_failed_method_responses.contains(&el.as_str())) + .count(), + 0 + ); + } +} diff --git a/crates/starknet-server/src/api/json_rpc/spec_reader/spec_modifier.rs b/crates/starknet-server/src/api/json_rpc/spec_reader/spec_modifier.rs new file mode 100644 index 000000000..5af007d48 --- /dev/null +++ b/crates/starknet-server/src/api/json_rpc/spec_reader/spec_modifier.rs @@ -0,0 +1,123 @@ +use std::fs; + +use serde::Deserialize; +use serde_json::Value; + +use super::Spec; + +/// Note: this structs is not all round solution for modifying the specification. +/// It has some limitations, if you need to modify the specification in a way that is not supported +/// Please use the remove_manually entry of the .yaml file +#[derive(Deserialize)] +pub(crate) struct SpecModifier { + clean: Vec, + replace: Vec, + add: Vec, + remove_from_array: Vec, +} + +#[derive(Deserialize)] +struct ReplacePropertyData { + path: String, + new_name: String, +} + +#[derive(Deserialize)] +struct AddPropertyData { + path: String, + new_entry: String, +} + +#[derive(Deserialize)] +struct RemoveArrayElement { + path: String, + index: usize, +} + +fn rename_property(json_obj: &mut Value, path_parts: &[&str], new_name: &str) { + if path_parts.len() == 1 { + if let Some(obj) = json_obj.as_object_mut() { + if let Some(value) = obj.remove(path_parts[0]) { + obj.insert(new_name.to_string(), value); + } + } + } else if let Some(next_obj) = json_obj.get_mut(path_parts[0]) { + rename_property(next_obj, &path_parts[1..], new_name); + } +} + +/// Deletes a property from a JSON object +fn delete_property(json_obj: &mut Value, path_parts: &[&str]) { + if path_parts.len() == 1 { + if let Some(obj) = json_obj.as_object_mut() { + obj.remove(path_parts[0]); + } + } else if let Some(next_obj) = json_obj.get_mut(path_parts[0]) { + delete_property(next_obj, &path_parts[1..]); + } +} + +/// add property to a JSON object +/// the new property comes in the form "key/value" +fn add_property(json_obj: &mut Value, path_parts: &[&str], new_entry: &str) { + if path_parts.is_empty() { + if let Some(obj) = json_obj.as_object_mut() { + let new_entry_parts = new_entry.split('/').collect::>(); + obj.insert( + new_entry_parts[0].to_string(), + serde_json::Value::String(new_entry_parts[1..].join("/")), + ); + } + } else if let Some(next_obj) = json_obj.get_mut(path_parts[0]) { + add_property(next_obj, &path_parts[1..], new_entry); + } +} + +fn remove_array_element(json_obj: &mut Value, path_parts: &[&str], index: usize) { + if path_parts.is_empty() { + if let Some(arr) = json_obj.as_array_mut() { + arr.remove(index); + } + } else if let Some(next_obj) = json_obj.get_mut(path_parts[0]) { + remove_array_element(next_obj, &path_parts[1..], index); + } +} + +impl SpecModifier { + pub(crate) fn load_from_path(path: &str) -> Self { + let yaml_str = fs::read_to_string(path).expect("Could not read YAML file"); + + let instructions: SpecModifier = + serde_yaml::from_str(&yaml_str).expect("Could not parse the YAML file"); + + instructions + } + + pub(crate) fn generate_spec(&self, mut json_obj_spec: Value) -> Spec { + for path_to_clean in self.clean.iter() { + let path_parts = path_to_clean.split('/').collect::>(); + delete_property(&mut json_obj_spec, &path_parts); + } + + for path_to_replace in self.replace.iter() { + let path_parts = path_to_replace.path.split('/').collect::>(); + rename_property(&mut json_obj_spec, &path_parts, &path_to_replace.new_name); + } + + for entry_to_add in self.add.iter() { + let path_parts = entry_to_add.path.split('/').collect::>(); + add_property(&mut json_obj_spec, &path_parts, &entry_to_add.new_entry); + } + + for array_element_to_remove in self.remove_from_array.iter() { + let path_parts = array_element_to_remove.path.split('/').collect::>(); + remove_array_element(&mut json_obj_spec, &path_parts, array_element_to_remove.index); + } + + // Parse the spec into a Spec struct + let spec: Spec = + serde_json::from_value(json_obj_spec).expect("Could not parse the JSON-RPC spec"); + + spec + } +} diff --git a/crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/all_of_schema.rs b/crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/all_of_schema.rs new file mode 100644 index 000000000..1e194c918 --- /dev/null +++ b/crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/all_of_schema.rs @@ -0,0 +1,18 @@ +use serde::{Deserialize, Serialize}; + +use super::{Common, Schema}; +use crate::api::json_rpc::spec_reader::data_generator::{Acceptor, Visitor}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct AllOf { + #[serde(flatten)] + pub common: Common, + pub all_of: Vec, +} + +impl Acceptor for AllOf { + fn accept(&self, visitor: &impl Visitor) -> Result { + visitor.do_for_all_of(self) + } +} diff --git a/crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/array_primitive.rs b/crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/array_primitive.rs new file mode 100644 index 000000000..7a38b6988 --- /dev/null +++ b/crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/array_primitive.rs @@ -0,0 +1,17 @@ +use serde::{Deserialize, Serialize}; + +use super::{Common, Schema}; +use crate::api::json_rpc::spec_reader::data_generator::{Acceptor, Visitor}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct ArrayPrimitive { + #[serde(flatten)] + pub common: Common, + pub items: Box, +} +impl Acceptor for ArrayPrimitive { + fn accept(&self, visitor: &impl Visitor) -> Result { + visitor.do_for_array_primitive(self) + } +} diff --git a/crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/boolean_primitive.rs b/crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/boolean_primitive.rs new file mode 100644 index 000000000..9bf4647c9 --- /dev/null +++ b/crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/boolean_primitive.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; + +use super::Common; +use crate::api::json_rpc::spec_reader::data_generator::{Acceptor, Visitor}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct BooleanPrimitive { + #[serde(flatten)] + pub common: Common, + #[serde(skip)] + pub generated_value: Option, +} + +impl Acceptor for BooleanPrimitive { + fn accept(&self, visitor: &impl Visitor) -> Result { + visitor.do_for_boolean_primitive() + } +} diff --git a/crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/integer_primitive.rs b/crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/integer_primitive.rs new file mode 100644 index 000000000..e22d63709 --- /dev/null +++ b/crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/integer_primitive.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; + +use super::Common; +use crate::api::json_rpc::spec_reader::data_generator::{Acceptor, Visitor}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct IntegerPrimitive { + #[serde(flatten)] + pub common: Common, + #[serde(skip_serializing_if = "Option::is_none")] + pub minimum: Option, +} + +impl Acceptor for IntegerPrimitive { + fn accept(&self, visitor: &impl Visitor) -> Result { + visitor.do_for_integer_primitive(self) + } +} diff --git a/crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/mod.rs b/crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/mod.rs new file mode 100644 index 000000000..d01b658aa --- /dev/null +++ b/crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/mod.rs @@ -0,0 +1,54 @@ +use serde::{Deserialize, Serialize}; + +use self::all_of_schema::AllOf; +use self::array_primitive::ArrayPrimitive; +use self::boolean_primitive::BooleanPrimitive; +use self::integer_primitive::IntegerPrimitive; +use self::object_primitive::ObjectPrimitive; +use self::one_of_schema::OneOf; +use self::ref_schema::Reference; +use self::string_primitive::StringPrimitive; + +pub(crate) mod all_of_schema; +pub(crate) mod array_primitive; +pub(crate) mod boolean_primitive; +pub(crate) mod integer_primitive; +pub(crate) mod object_primitive; +pub(crate) mod one_of_schema; +pub(crate) mod ref_schema; +pub(crate) mod string_primitive; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct Common { + #[serde(skip_serializing_if = "Option::is_none")] + pub title: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub additional_properties: Option, + #[serde(rename = "type")] + pub t: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(tag = "type", rename_all = "lowercase")] +pub enum Primitive { + Array(ArrayPrimitive), + Boolean(BooleanPrimitive), + Integer(IntegerPrimitive), + Number(IntegerPrimitive), + Object(ObjectPrimitive), + String(StringPrimitive), +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(untagged)] +pub enum Schema { + Ref(Reference), + OneOf(OneOf), + AllOf(AllOf), + Primitive(Primitive), +} diff --git a/crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/object_primitive.rs b/crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/object_primitive.rs new file mode 100644 index 000000000..578c3d7c5 --- /dev/null +++ b/crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/object_primitive.rs @@ -0,0 +1,24 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; + +use super::{Common, Schema}; +use crate::api::json_rpc::spec_reader::data_generator::{Acceptor, Visitor}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct ObjectPrimitive { + #[serde(flatten)] + pub common: Common, + #[serde(skip_serializing_if = "Option::is_none")] + pub summary: Option, + pub properties: HashMap, + #[serde(skip_serializing_if = "Option::is_none")] + pub required: Option>, +} + +impl Acceptor for ObjectPrimitive { + fn accept(&self, visitor: &impl Visitor) -> Result { + visitor.do_for_object_primitive(self) + } +} diff --git a/crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/one_of_schema.rs b/crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/one_of_schema.rs new file mode 100644 index 000000000..219dc110f --- /dev/null +++ b/crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/one_of_schema.rs @@ -0,0 +1,18 @@ +use serde::{Deserialize, Serialize}; + +use super::{Common, Schema}; +use crate::api::json_rpc::spec_reader::data_generator::{Acceptor, Visitor}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct OneOf { + #[serde(flatten)] + pub common: Common, + pub one_of: Vec, +} + +impl Acceptor for OneOf { + fn accept(&self, visitor: &impl Visitor) -> Result { + visitor.do_for_one_of(self) + } +} diff --git a/crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/ref_schema.rs b/crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/ref_schema.rs new file mode 100644 index 000000000..b8263028a --- /dev/null +++ b/crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/ref_schema.rs @@ -0,0 +1,22 @@ +use serde::{Deserialize, Serialize}; + +use super::Common; +use crate::api::json_rpc::spec_reader::data_generator::{Acceptor, Visitor}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct Reference { + #[serde(rename = "$comment")] + #[serde(skip_serializing_if = "Option::is_none")] + pub comment: Option, + #[serde(rename = "$ref")] + pub ref_field: String, + #[serde(flatten)] + pub common: Common, +} + +impl Acceptor for Reference { + fn accept(&self, visitor: &impl Visitor) -> Result { + visitor.do_for_ref(self) + } +} diff --git a/crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/string_primitive.rs b/crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/string_primitive.rs new file mode 100644 index 000000000..60092fedc --- /dev/null +++ b/crates/starknet-server/src/api/json_rpc/spec_reader/spec_schemas/string_primitive.rs @@ -0,0 +1,25 @@ +use serde::{Deserialize, Serialize}; + +use super::Common; +use crate::api::json_rpc::spec_reader::data_generator::{Acceptor, Visitor}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct StringPrimitive { + #[serde(flatten)] + pub common: Common, + #[serde(rename = "$comment")] + #[serde(skip_serializing_if = "Option::is_none")] + pub comment: Option, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "enum")] + pub possible_enums: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub pattern: Option, +} + +impl Acceptor for StringPrimitive { + fn accept(&self, visitor: &impl Visitor) -> Result { + visitor.do_for_string_primitive(self) + } +} diff --git a/crates/starknet-server/src/api/json_rpc/write_endpoints.rs b/crates/starknet-server/src/api/json_rpc/write_endpoints.rs index d9cb26036..82fb71578 100644 --- a/crates/starknet-server/src/api/json_rpc/write_endpoints.rs +++ b/crates/starknet-server/src/api/json_rpc/write_endpoints.rs @@ -2,18 +2,18 @@ use starknet_types::rpc::transactions::broadcasted_deploy_account_transaction::B use starknet_types::rpc::transactions::broadcasted_invoke_transaction::BroadcastedInvokeTransaction; use starknet_types::rpc::transactions::BroadcastedDeclareTransaction; -use super::error::ApiError; +use super::error::{ApiError, StrictRpcResult}; use super::models::{ DeclareTransactionOutput, DeployAccountTransactionOutput, InvokeTransactionOutput, }; -use crate::api::json_rpc::error::RpcResult; +use super::StarknetResponse; use crate::api::json_rpc::JsonRpcHandler; impl JsonRpcHandler { pub(crate) async fn add_declare_transaction( &self, request: BroadcastedDeclareTransaction, - ) -> RpcResult { + ) -> StrictRpcResult { let (transaction_hash, class_hash) = match request { BroadcastedDeclareTransaction::V1(broadcasted_declare_txn) => self .api @@ -29,13 +29,16 @@ impl JsonRpcHandler { .add_declare_transaction_v2(*broadcasted_declare_txn)?, }; - Ok(DeclareTransactionOutput { transaction_hash, class_hash }) + Ok(StarknetResponse::AddDeclareTransaction(DeclareTransactionOutput { + transaction_hash, + class_hash, + })) } pub(crate) async fn add_deploy_account_transaction( &self, request: BroadcastedDeployAccountTransaction, - ) -> RpcResult { + ) -> StrictRpcResult { let (transaction_hash, contract_address) = self.api.starknet.write().await.add_deploy_account_transaction(request).map_err( |err| match err { @@ -46,23 +49,28 @@ impl JsonRpcHandler { }, )?; - Ok(DeployAccountTransactionOutput { transaction_hash, contract_address }) + Ok(StarknetResponse::AddDeployAccountTransaction(DeployAccountTransactionOutput { + transaction_hash, + contract_address, + })) } pub(crate) async fn add_invoke_transaction( &self, request: BroadcastedInvokeTransaction, - ) -> RpcResult { + ) -> StrictRpcResult { let transaction_hash = self.api.starknet.write().await.add_invoke_transaction(request)?; - Ok(InvokeTransactionOutput { transaction_hash }) + Ok(StarknetResponse::AddInvokeTransaction(InvokeTransactionOutput { transaction_hash })) } } #[cfg(test)] mod tests { - use starknet_types::rpc::transactions::broadcasted_declare_transaction_v1::BroadcastedDeclareTransactionV1; - use starknet_types::rpc::transactions::broadcasted_deploy_account_transaction::BroadcastedDeployAccountTransaction; + + use crate::api::json_rpc::models::{ + BroadcastedDeclareTransactionEnumWrapper, BroadcastedDeployAccountTransactionEnumWrapper, + }; #[test] fn check_correct_deserialization_of_deploy_account_transaction_request() { @@ -80,18 +88,18 @@ mod tests { )) .unwrap(); - let _broadcasted_declare_transaction_v1: BroadcastedDeclareTransactionV1 = + let _broadcasted_declare_transaction_v1: BroadcastedDeclareTransactionEnumWrapper = serde_json::from_str(&json_string).unwrap(); } - fn test_deploy_account_transaction() -> BroadcastedDeployAccountTransaction { + fn test_deploy_account_transaction() -> BroadcastedDeployAccountTransactionEnumWrapper { let json_string = std::fs::read_to_string(concat!( env!("CARGO_MANIFEST_DIR"), "/test_data/rpc/deploy_account.json" )) .unwrap(); - let broadcasted_deploy_account_transaction: BroadcastedDeployAccountTransaction = + let broadcasted_deploy_account_transaction: BroadcastedDeployAccountTransactionEnumWrapper = serde_json::from_str(&json_string).unwrap(); broadcasted_deploy_account_transaction diff --git a/crates/starknet-server/src/api/mod.rs b/crates/starknet-server/src/api/mod.rs index f24660bc9..6f1d9b536 100644 --- a/crates/starknet-server/src/api/mod.rs +++ b/crates/starknet-server/src/api/mod.rs @@ -1,6 +1,5 @@ pub(crate) mod http; pub(crate) mod json_rpc; -pub(crate) mod models; pub(crate) mod serde_helpers; use std::sync::Arc; diff --git a/crates/starknet-server/src/api/models/mod.rs b/crates/starknet-server/src/api/models/mod.rs deleted file mode 100644 index a73d05d0f..000000000 --- a/crates/starknet-server/src/api/models/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -pub(crate) mod state; - -use serde::{Deserialize, Serialize}; -use starknet_types::patricia_key::PatriciaKey; -use starknet_types::serde_helpers::hex_string::{ - deserialize_to_prefixed_patricia_key, serialize_patricia_key_to_prefixed_hex, -}; - -/// Patricia key serialized/deserialized from/to prefixed hex string -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] -pub struct PatriciaKeyHex( - #[serde( - serialize_with = "serialize_patricia_key_to_prefixed_hex", - deserialize_with = "deserialize_to_prefixed_patricia_key" - )] - pub PatriciaKey, -); diff --git a/crates/starknet-server/test_data/spec/0.5.0/edit_spec_instructions.yaml b/crates/starknet-server/test_data/spec/0.5.0/edit_spec_instructions.yaml new file mode 100644 index 000000000..672c9e342 --- /dev/null +++ b/crates/starknet-server/test_data/spec/0.5.0/edit_spec_instructions.yaml @@ -0,0 +1,41 @@ +# Remove entries before processing the spec file +clean: + - components/schemas/COMMON_RECEIPT_PROPERTIES/properties/revert_reason/name + - components/schemas/PENDING_COMMON_RECEIPT_PROPERTIES/properties/revert_reason/name + +# Replace entries at "path" with the specified value in the spec file before processing it +replace: + - path: components/schemas/BLOCK_HEADER/properties/l1_gas_price/descritpion + new_name: description + - path: components/schemas/PENDING_BLOCK_HEADER/properties/l1_gas_price/descritpion + new_name: description + +add: + - path: components/schemas/INVOKE_TXN_V0 + new_entry: type/object + - path: components/schemas/DEPLOY_ACCOUNT_TXN_V1 + new_entry: type/object + - path: components/schemas/NUM_AS_HEX + new_entry: pattern/^0x[a-fA-F0-9]{1,10}$ + - path: components/schemas/CHAIN_ID + new_entry: pattern/^0x[a-fA-F0-9]{1,10}$ + - path: components/schemas/DEPRECATED_CONTRACT_CLASS/properties/program + new_entry: pattern/^$ + - path: components/schemas/MSG_TO_L1/properties/to_address + new_entry: $ref/#/components/schemas/ETH_ADDRESS + +remove_from_array: + - path: components/schemas/BROADCASTED_INVOKE_TXN/oneOf + index: 0 + - path: components/schemas/TXN_STATUS/enum + index: 1 + - path: components/schemas/TXN_STATUS/enum + index: 0 + - path: components/schemas/TRANSACTION_TRACE/oneOf + index: 3 + +remove_manually: + - path: components/schemas/TXN_STATUS + index: 0,1 + - path: components/schemas/BROADCASTED_DECLARE_TXN/oneOf + index: 0 \ No newline at end of file diff --git a/crates/starknet-server/test_data/spec/0.5.0/starknet_api_openrpc.json b/crates/starknet-server/test_data/spec/0.5.0/starknet_api_openrpc.json new file mode 100644 index 000000000..2a9ecdf61 --- /dev/null +++ b/crates/starknet-server/test_data/spec/0.5.0/starknet_api_openrpc.json @@ -0,0 +1,3389 @@ +{ + "openrpc": "1.0.0-rc1", + "info": { + "version": "0.5.0", + "title": "StarkNet Node API", + "license": {} + }, + "servers": [], + "methods": [ + { + "name": "starknet_specVersion", + "summary": "Returns the version of the Starknet JSON-RPC specification being used", + "params": [], + "result": { + "name": "result", + "description": "Semver of Starknet's JSON-RPC spec being used", + "required": true, + "schema": { + "title": "JSON-RPC spec version", + "type": "string" + } + } + }, + { + "name": "starknet_getBlockWithTxHashes", + "summary": "Get block information with transaction hashes given the block id", + "params": [ + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag", + "required": true, + "schema": { + "title": "Block id", + "$ref": "#/components/schemas/BLOCK_ID" + } + } + ], + "result": { + "name": "result", + "description": "The resulting block information with transaction hashes", + "schema": { + "title": "Starknet get block hash with tx hashes result", + "oneOf": [ + { + "title": "Block with transaction hashes", + "$ref": "#/components/schemas/BLOCK_WITH_TX_HASHES" + } + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getBlockWithTxs", + "summary": "Get block information with full transactions given the block id", + "params": [ + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag", + "required": true, + "schema": { + "title": "Block id", + "$ref": "#/components/schemas/BLOCK_ID" + } + } + ], + "result": { + "name": "result", + "description": "The resulting block information with full transactions", + "schema": { + "title": "Starknet get block with txs result", + "oneOf": [ + { + "title": "Block with transactions", + "$ref": "#/components/schemas/BLOCK_WITH_TXS" + } + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getStateUpdate", + "summary": "Get the information about the result of executing the requested block", + "params": [ + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag", + "required": true, + "schema": { + "title": "Block id", + "$ref": "#/components/schemas/BLOCK_ID" + } + } + ], + "result": { + "name": "result", + "description": "The information about the state update of the requested block", + "schema": { + "title": "Starknet get state update result", + "oneOf": [ + { + "title": "State update", + "$ref": "#/components/schemas/STATE_UPDATE" + } + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getStorageAt", + "summary": "Get the value of the storage at the given address and key", + "params": [ + { + "name": "contract_address", + "description": "The address of the contract to read from", + "summary": "The address of the contract to read from", + "required": true, + "schema": { + "title": "Address", + "$ref": "#/components/schemas/ADDRESS" + } + }, + { + "name": "key", + "description": "The key to the storage value for the given contract", + "summary": "The key to the storage value for the given contract", + "required": true, + "schema": { + "title": "Storage key", + "$ref": "#/components/schemas/STORAGE_KEY" + } + }, + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag", + "required": true, + "schema": { + "title": "Block id", + "$ref": "#/components/schemas/BLOCK_ID" + } + } + ], + "result": { + "name": "result", + "description": "The value at the given key for the given contract. 0 if no value is found", + "summary": "The value at the given key for the given contract.", + "schema": { + "title": "Field element", + "$ref": "#/components/schemas/FELT" + } + }, + "errors": [ + { + "$ref": "#/components/errors/CONTRACT_NOT_FOUND" + }, + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getTransactionStatus", + "summary": "Gets the transaction status (possibly reflecting that the tx is still in the mempool, or dropped from it)", + "paramStructure": "by-name", + "params": [ + { + "name": "transaction_hash", + "summary": "The hash of the requested transaction", + "required": true, + "schema": { + "title": "Transaction hash", + "$ref": "#/components/schemas/TXN_HASH" + } + } + ], + "result": { + "name": "result", + "schema": { + "title": "Transaction status", + "type": "object", + "properties": { + "finality_status": { + "title": "finality status", + "$ref": "#/components/schemas/TXN_STATUS" + }, + "execution_status": { + "title": "execution status", + "$ref": "#/components/schemas/TXN_EXECUTION_STATUS" + } + }, + "required": [ + "finality_status", + "execution_status" + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/TXN_HASH_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getTransactionByHash", + "summary": "Get the details and status of a submitted transaction", + "paramStructure": "by-name", + "params": [ + { + "name": "transaction_hash", + "summary": "The hash of the requested transaction", + "required": true, + "schema": { + "title": "Transaction hash", + "$ref": "#/components/schemas/TXN_HASH" + } + } + ], + "result": { + "name": "result", + "schema": { + "title": "Transaction", + "allOf": [ + { + "$ref": "#/components/schemas/TXN" + }, + { + "type": "object", + "properties": { + "transaction_hash": { + "title": "transaction hash", + "$ref": "#/components/schemas/TXN_HASH" + } + }, + "required": [ + "transaction_hash" + ] + } + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/TXN_HASH_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getTransactionByBlockIdAndIndex", + "summary": "Get the details of a transaction by a given block id and index", + "description": "Get the details of the transaction given by the identified block and index in that block. If no transaction is found, null is returned.", + "params": [ + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag", + "required": true, + "schema": { + "title": "Block id", + "$ref": "#/components/schemas/BLOCK_ID" + } + }, + { + "name": "index", + "summary": "The index in the block to search for the transaction", + "required": true, + "schema": { + "title": "Index", + "type": "integer", + "minimum": 0 + } + } + ], + "result": { + "name": "transactionResult", + "schema": { + "title": "Transaction", + "allOf": [ + { + "$ref": "#/components/schemas/TXN" + }, + { + "type": "object", + "properties": { + "transaction_hash": { + "title": "transaction hash", + "$ref": "#/components/schemas/TXN_HASH" + } + }, + "required": [ + "transaction_hash" + ] + } + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + }, + { + "$ref": "#/components/errors/INVALID_TXN_INDEX" + } + ] + }, + { + "name": "starknet_getTransactionReceipt", + "summary": "Get the transaction receipt by the transaction hash", + "paramStructure": "by-name", + "params": [ + { + "name": "transaction_hash", + "summary": "The hash of the requested transaction", + "required": true, + "schema": { + "title": "Transaction hash", + "$ref": "#/components/schemas/TXN_HASH" + } + } + ], + "result": { + "name": "result", + "schema": { + "oneOf": [ + { + "title": "Transaction receipt", + "$ref": "#/components/schemas/TXN_RECEIPT" + } + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/TXN_HASH_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getClass", + "summary": "Get the contract class definition in the given block associated with the given hash", + "params": [ + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag", + "required": true, + "schema": { + "title": "Block id", + "$ref": "#/components/schemas/BLOCK_ID" + } + }, + { + "name": "class_hash", + "description": "The hash of the requested contract class", + "required": true, + "schema": { + "title": "Field element", + "$ref": "#/components/schemas/FELT" + } + } + ], + "result": { + "name": "result", + "description": "The contract class, if found", + "schema": { + "title": "Starknet get class result", + "oneOf": [ + { + "title": "Deprecated contract class", + "$ref": "#/components/schemas/DEPRECATED_CONTRACT_CLASS" + }, + { + "title": "Contract class", + "$ref": "#/components/schemas/CONTRACT_CLASS" + } + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + }, + { + "$ref": "#/components/errors/CLASS_HASH_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getClassHashAt", + "summary": "Get the contract class hash in the given block for the contract deployed at the given address", + "params": [ + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag", + "required": true, + "schema": { + "title": "Block id", + "$ref": "#/components/schemas/BLOCK_ID" + } + }, + { + "name": "contract_address", + "description": "The address of the contract whose class hash will be returned", + "required": true, + "schema": { + "title": "Address", + "$ref": "#/components/schemas/ADDRESS" + } + } + ], + "result": { + "name": "result", + "description": "The class hash of the given contract", + "schema": { + "title": "Field element", + "$ref": "#/components/schemas/FELT" + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + }, + { + "$ref": "#/components/errors/CONTRACT_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getClassAt", + "summary": "Get the contract class definition in the given block at the given address", + "params": [ + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag", + "required": true, + "schema": { + "title": "Block id", + "$ref": "#/components/schemas/BLOCK_ID" + } + }, + { + "name": "contract_address", + "description": "The address of the contract whose class definition will be returned", + "required": true, + "schema": { + "title": "Address", + "$ref": "#/components/schemas/ADDRESS" + } + } + ], + "result": { + "name": "result", + "description": "The contract class", + "schema": { + "title": "Starknet get class at result", + "oneOf": [ + { + "title": "Deprecated contract class", + "$ref": "#/components/schemas/DEPRECATED_CONTRACT_CLASS" + }, + { + "title": "Contract class", + "$ref": "#/components/schemas/CONTRACT_CLASS" + } + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + }, + { + "$ref": "#/components/errors/CONTRACT_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getBlockTransactionCount", + "summary": "Get the number of transactions in a block given a block id", + "description": "Returns the number of transactions in the designated block.", + "params": [ + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag", + "required": true, + "schema": { + "title": "Block id", + "$ref": "#/components/schemas/BLOCK_ID" + } + } + ], + "result": { + "name": "result", + "description": "The number of transactions in the designated block", + "summary": "The number of transactions in the designated block", + "schema": { + "title": "Block transaction count", + "type": "integer", + "minimum": 0 + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + } + ] + }, + { + "name": "starknet_call", + "summary": "call a starknet function without creating a StarkNet transaction", + "description": "Calls a function in a contract and returns the return value. Using this call will not create a transaction; hence, will not change the state", + "params": [ + { + "name": "request", + "summary": "The details of the function call", + "schema": { + "title": "Function call", + "$ref": "#/components/schemas/FUNCTION_CALL" + }, + "required": true + }, + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag, for the block referencing the state or call the transaction on.", + "required": true, + "schema": { + "title": "Block id", + "$ref": "#/components/schemas/BLOCK_ID" + } + } + ], + "result": { + "name": "result", + "summary": "The function's return value", + "description": "The function's return value, as defined in the Cairo output", + "schema": { + "type": "array", + "title": "Field element", + "items": { + "$ref": "#/components/schemas/FELT" + } + } + }, + "errors": [ + { + "$ref": "#/components/errors/CONTRACT_NOT_FOUND" + }, + { + "$ref": "#/components/errors/CONTRACT_ERROR" + }, + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + } + ] + }, + { + "name": "starknet_estimateFee", + "summary": "estimate the fee for of StarkNet transactions", + "description": "estimates the resources required by transactions when applyed on a given state", + "params": [ + { + "name": "request", + "summary": "The transaction to estimate", + "schema": { + "type": "array", + "description": "a sequence of transactions to estimate, running each transaction on the state resulting from applying all the previous ones", + "title": "Transaction", + "items": { + "$ref": "#/components/schemas/BROADCASTED_TXN" + } + }, + "required": true + }, + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag, for the block referencing the state or call the transaction on.", + "required": true, + "schema": { + "title": "Block id", + "$ref": "#/components/schemas/BLOCK_ID" + } + } + ], + "result": { + "name": "result", + "description": "the fee estimations", + "schema": { + "title": "Estimation", + "type": "array", + "description": "a sequence of fee estimatione where the i'th estimate corresponds to the i'th transaction", + "items": { + "$ref": "#/components/schemas/FEE_ESTIMATE" + } + } + }, + "errors": [ + { + "$ref": "#/components/errors/CONTRACT_NOT_FOUND" + }, + { + "$ref": "#/components/errors/CONTRACT_ERROR" + }, + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + } + ] + }, + { + "name": "starknet_estimateMessageFee", + "summary": "estimate the L2 fee of a message sent on L1", + "description": "estimates the resources required by the l1_handler transaction induced by the message", + "params": [ + { + "name": "message", + "description": "the message's parameters", + "schema": { + "$ref": "#/components/schemas/MSG_FROM_L1" + }, + "required": true + }, + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag, for the block referencing the state or call the transaction on.", + "required": true, + "schema": { + "title": "Block id", + "$ref": "#/components/schemas/BLOCK_ID" + } + } + ], + "result": { + "name": "result", + "description": "the fee estimation", + "schema": { + "$ref": "#/components/schemas/FEE_ESTIMATE" + } + }, + "errors": [ + { + "$ref": "#/components/errors/CONTRACT_NOT_FOUND" + }, + { + "$ref": "#/components/errors/CONTRACT_ERROR" + }, + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + } + ] + }, + { + "name": "starknet_blockNumber", + "summary": "Get the most recent accepted block number", + "params": [], + "result": { + "name": "result", + "description": "The latest block number", + "schema": { + "title": "Block number", + "$ref": "#/components/schemas/BLOCK_NUMBER" + } + }, + "errors": [ + { + "$ref": "#/components/errors/NO_BLOCKS" + } + ] + }, + { + "name": "starknet_blockHashAndNumber", + "summary": "Get the most recent accepted block hash and number", + "params": [], + "result": { + "name": "result", + "description": "The latest block hash and number", + "schema": { + "title": "Starknet block hash and number result", + "type": "object", + "properties": { + "block_hash": { + "title": "Block hash", + "$ref": "#/components/schemas/BLOCK_HASH" + }, + "block_number": { + "title": "Block number", + "$ref": "#/components/schemas/BLOCK_NUMBER" + } + }, + "required": [ + "block_hash", + "block_number" + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/NO_BLOCKS" + } + ] + }, + { + "name": "starknet_chainId", + "summary": "Return the currently configured StarkNet chain id", + "params": [], + "result": { + "name": "result", + "description": "The chain id this node is connected to", + "schema": { + "title": "Chain id", + "$ref": "#/components/schemas/CHAIN_ID" + } + } + }, + { + "name": "starknet_syncing", + "summary": "Returns an object about the sync status, or false if the node is not synching", + "params": [], + "result": { + "name": "syncing", + "summary": "The state of the synchronization, or false if the node is not synchronizing", + "description": "The status of the node, if it is currently synchronizing state. FALSE otherwise", + "schema": { + "title": "SyncingStatus", + "oneOf": [ + { + "type": "boolean", + "title": "False", + "description": "only legal value is FALSE here" + }, + { + "title": "Sync status", + "$ref": "#/components/schemas/SYNC_STATUS" + } + ] + } + } + }, + { + "name": "starknet_getEvents", + "summary": "Returns all events matching the given filter", + "description": "Returns all event objects matching the conditions in the provided filter", + "params": [ + { + "name": "filter", + "summary": "The conditions used to filter the returned events", + "required": true, + "schema": { + "title": "Events request", + "allOf": [ + { + "title": "Event filter", + "$ref": "#/components/schemas/EVENT_FILTER" + }, + { + "title": "Result page request", + "$ref": "#/components/schemas/RESULT_PAGE_REQUEST" + } + ] + } + } + ], + "result": { + "name": "events", + "description": "All the event objects matching the filter", + "schema": { + "title": "Events chunk", + "$ref": "#/components/schemas/EVENTS_CHUNK" + } + }, + "errors": [ + { + "$ref": "#/components/errors/PAGE_SIZE_TOO_BIG" + }, + { + "$ref": "#/components/errors/INVALID_CONTINUATION_TOKEN" + }, + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + }, + { + "$ref": "#/components/errors/TOO_MANY_KEYS_IN_FILTER" + } + ] + }, + { + "name": "starknet_getNonce", + "summary": "Get the nonce associated with the given address in the given block", + "params": [ + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag", + "required": true, + "schema": { + "title": "Block id", + "$ref": "#/components/schemas/BLOCK_ID" + } + }, + { + "name": "contract_address", + "description": "The address of the contract whose nonce we're seeking", + "required": true, + "schema": { + "title": "Address", + "$ref": "#/components/schemas/ADDRESS" + } + } + ], + "result": { + "name": "result", + "description": "The contract's nonce at the requested state", + "schema": { + "title": "Field element", + "$ref": "#/components/schemas/FELT" + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + }, + { + "$ref": "#/components/errors/CONTRACT_NOT_FOUND" + } + ] + } + ], + "components": { + "contentDescriptors": {}, + "schemas": { + "EVENTS_CHUNK": { + "title": "Events chunk", + "type": "object", + "properties": { + "events": { + "type": "array", + "title": "Matching Events", + "items": { + "$ref": "#/components/schemas/EMITTED_EVENT" + } + }, + "continuation_token": { + "title": "Continuation token", + "description": "Use this token in a subsequent query to obtain the next page. Should not appear if there are no more pages.", + "type": "string" + } + }, + "required": [ + "events" + ] + }, + "RESULT_PAGE_REQUEST": { + "title": "Result page request", + "type": "object", + "properties": { + "continuation_token": { + "title": "Continuation token", + "description": "The token returned from the previous query. If no token is provided the first page is returned.", + "type": "string" + }, + "chunk_size": { + "title": "Chunk size", + "type": "integer", + "minimum": 1 + } + }, + "required": [ + "chunk_size" + ] + }, + "EMITTED_EVENT": { + "title": "Emitted event", + "description": "Event information decorated with metadata on where it was emitted / An event emitted as a result of transaction execution", + "allOf": [ + { + "title": "Event", + "description": "The event information", + "$ref": "#/components/schemas/EVENT" + }, + { + "title": "Event context", + "description": "The event emission information", + "type": "object", + "properties": { + "block_hash": { + "title": "Block hash", + "description": "The hash of the block in which the event was emitted", + "$ref": "#/components/schemas/BLOCK_HASH" + }, + "block_number": { + "title": "Block number", + "description": "The number of the block in which the event was emitted", + "$ref": "#/components/schemas/BLOCK_NUMBER" + }, + "transaction_hash": { + "title": "Transaction hash", + "description": "The transaction that emitted the event", + "$ref": "#/components/schemas/TXN_HASH" + } + }, + "required": [ + "block_hash", + "block_number", + "transaction_hash" + ] + } + ] + }, + "EVENT": { + "title": "Event", + "description": "A StarkNet event", + "allOf": [ + { + "title": "Event emitter", + "type": "object", + "properties": { + "from_address": { + "title": "From address", + "$ref": "#/components/schemas/ADDRESS" + } + }, + "required": [ + "from_address" + ] + }, + { + "title": "Event content", + "$ref": "#/components/schemas/EVENT_CONTENT" + } + ] + }, + "EVENT_CONTENT": { + "title": "Event content", + "description": "The content of an event", + "type": "object", + "properties": { + "keys": { + "type": "array", + "title": "Keys", + "items": { + "$ref": "#/components/schemas/FELT" + } + }, + "data": { + "type": "array", + "title": "Data", + "items": { + "$ref": "#/components/schemas/FELT" + } + } + }, + "required": [ + "keys", + "data" + ] + }, + "EVENT_FILTER": { + "title": "Event filter", + "description": "An event filter/query", + "type": "object", + "properties": { + "from_block": { + "title": "from block", + "$ref": "#/components/schemas/BLOCK_ID" + }, + "to_block": { + "title": "to block", + "$ref": "#/components/schemas/BLOCK_ID" + }, + "address": { + "title": "from contract", + "$ref": "#/components/schemas/ADDRESS" + }, + "keys": { + "title": "Keys", + "description": "The values used to filter the events", + "type": "array", + "items": { + "title": "Keys", + "description": "Per key (by position), designate the possible values to be matched for events to be returned. Empty array designates 'any' value", + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + } + } + } + }, + "required": [] + }, + "BLOCK_ID": { + "title": "Block id", + "description": "Block hash, number or tag", + "oneOf": [ + { + "title": "Block hash", + "type": "object", + "properties": { + "block_hash": { + "title": "Block hash", + "$ref": "#/components/schemas/BLOCK_HASH" + } + }, + "required": [ + "block_hash" + ] + }, + { + "title": "Block number", + "type": "object", + "properties": { + "block_number": { + "title": "Block number", + "$ref": "#/components/schemas/BLOCK_NUMBER" + } + }, + "required": [ + "block_number" + ] + }, + { + "title": "Block tag", + "$ref": "#/components/schemas/BLOCK_TAG" + } + ] + }, + "BLOCK_TAG": { + "title": "Block tag", + "type": "string", + "description": "A tag specifying a dynamic reference to a block", + "enum": [ + "latest", + "pending" + ] + }, + "SYNC_STATUS": { + "title": "Sync status", + "type": "object", + "description": "An object describing the node synchronization status", + "properties": { + "starting_block_hash": { + "title": "Starting block hash", + "description": "The hash of the block from which the sync started", + "$ref": "#/components/schemas/BLOCK_HASH" + }, + "starting_block_num": { + "title": "Starting block number", + "description": "The number (height) of the block from which the sync started", + "$ref": "#/components/schemas/BLOCK_NUMBER" + }, + "current_block_hash": { + "title": "Current block hash", + "description": "The hash of the current block being synchronized", + "$ref": "#/components/schemas/BLOCK_HASH" + }, + "current_block_num": { + "title": "Current block number", + "description": "The number (height) of the current block being synchronized", + "$ref": "#/components/schemas/BLOCK_NUMBER" + }, + "highest_block_hash": { + "title": "Highest block hash", + "description": "The hash of the estimated highest block to be synchronized", + "$ref": "#/components/schemas/BLOCK_HASH" + }, + "highest_block_num": { + "title": "Highest block number", + "description": "The number (height) of the estimated highest block to be synchronized", + "$ref": "#/components/schemas/BLOCK_NUMBER" + } + }, + "required": [ + "starting_block_hash", + "starting_block_num", + "current_block_hash", + "current_block_num", + "highest_block_hash", + "highest_block_num" + ] + }, + "NUM_AS_HEX": { + "title": "Number as hex", + "description": "An integer number in hex format (0x...)", + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$" + }, + "CHAIN_ID": { + "title": "Chain id", + "description": "StarkNet chain id, given in hex representation.", + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$" + }, + "STATE_DIFF": { + "description": "The change in state applied in this block, given as a mapping of addresses to the new values and/or new contracts", + "type": "object", + "properties": { + "storage_diffs": { + "title": "Storage diffs", + "type": "array", + "items": { + "description": "The changes in the storage per contract address", + "$ref": "#/components/schemas/CONTRACT_STORAGE_DIFF_ITEM" + } + }, + "deprecated_declared_classes": { + "title": "Deprecated declared classes", + "type": "array", + "items": { + "description": "The hash of the declared class", + "$ref": "#/components/schemas/FELT" + } + }, + "declared_classes": { + "title": "Declared classes", + "type": "array", + "items": { + "title": "New classes", + "type": "object", + "description": "The declared class hash and compiled class hash", + "properties": { + "class_hash": { + "title": "Class hash", + "description": "The hash of the declared class", + "$ref": "#/components/schemas/FELT" + }, + "compiled_class_hash": { + "title": "Compiled class hash", + "description": "The Cairo assembly hash corresponding to the declared class", + "$ref": "#/components/schemas/FELT" + } + } + } + }, + "deployed_contracts": { + "title": "Deployed contracts", + "type": "array", + "items": { + "description": "A new contract deployed as part of the state update", + "$ref": "#/components/schemas/DEPLOYED_CONTRACT_ITEM" + } + }, + "replaced_classes": { + "title": "Replaced classes", + "type": "array", + "items": { + "description": "The list of contracts whose class was replaced", + "title": "Replaced class", + "type": "object", + "properties": { + "contract_address": { + "title": "Contract address", + "description": "The address of the contract whose class was replaced", + "$ref": "#/components/schemas/ADDRESS" + }, + "class_hash": { + "title": "Class hash", + "description": "The new class hash", + "$ref": "#/components/schemas/FELT" + } + } + } + }, + "nonces": { + "title": "Nonces", + "type": "array", + "items": { + "title": "Nonce update", + "description": "The updated nonce per contract address", + "type": "object", + "properties": { + "contract_address": { + "title": "Contract address", + "description": "The address of the contract", + "$ref": "#/components/schemas/ADDRESS" + }, + "nonce": { + "title": "Nonce", + "description": "The nonce for the given address at the end of the block", + "$ref": "#/components/schemas/FELT" + } + } + } + } + }, + "required": [ + "storage_diffs", + "deprecated_declared_classes", + "declared_classes", + "replaced_classes", + "deployed_contracts", + "nonces" + ] + }, + "PENDING_STATE_UPDATE": { + "title": "Pending state update", + "description": "Pending state update", + "type": "object", + "properties": { + "old_root": { + "title": "Old root", + "description": "The previous global state root", + "$ref": "#/components/schemas/FELT" + }, + "state_diff": { + "title": "State diff", + "$ref": "#/components/schemas/STATE_DIFF" + } + }, + "required": [ + "old_root", + "state_diff" + ], + "additionalProperties": false + }, + "STATE_UPDATE": { + "title": "State update", + "type": "object", + "properties": { + "block_hash": { + "title": "Block hash", + "$ref": "#/components/schemas/BLOCK_HASH" + }, + "old_root": { + "title": "Old root", + "description": "The previous global state root", + "$ref": "#/components/schemas/FELT" + }, + "new_root": { + "title": "New root", + "description": "The new global state root", + "$ref": "#/components/schemas/FELT" + }, + "state_diff": { + "title": "State diff", + "$ref": "#/components/schemas/STATE_DIFF" + } + }, + "required": [ + "state_diff", + "block_hash", + "old_root", + "new_root" + ] + }, + "ADDRESS": { + "title": "Address", + "$ref": "#/components/schemas/FELT" + }, + "STORAGE_KEY": { + "type": "string", + "title": "Storage key", + "$comment": "A storage key, represented as a string of hex digits", + "description": "A storage key. Represented as up to 62 hex digits, 3 bits, and 5 leading zeroes.", + "pattern": "^0x0[0-7]{1}[a-fA-F0-9]{0,62}$" + }, + "ETH_ADDRESS": { + "title": "Ethereum address", + "type": "string", + "$comment": "An ethereum address", + "description": "an ethereum address represented as 40 hex digits", + "pattern": "^0x[a-fA-F0-9]{40}$" + }, + "TXN_HASH": { + "$ref": "#/components/schemas/FELT", + "description": "The transaction hash, as assigned in StarkNet", + "title": "Transaction hash" + }, + "FELT": { + "type": "string", + "title": "Field element", + "description": "A field element. represented by at most 63 hex digits", + "pattern": "^0x(0|[a-fA-F1-9]{1}[a-fA-F0-9]{0,61})$" + }, + "FELT_U128": { + "type": "string", + "title": "Field element", + "description": "A field element. represented by at most 63 hex digits", + "pattern": "^0x(0|[a-fA-F1-9]{1}[a-fA-F0-9]{0,16})$" + }, + "BLOCK_NUMBER": { + "title": "Block number", + "description": "The block's number (its height)", + "type": "integer", + "minimum": 0 + }, + "BLOCK_HASH": { + "title": "Block hash", + "$ref": "#/components/schemas/FELT" + }, + "BLOCK_BODY_WITH_TX_HASHES": { + "title": "Block body with transaction hashes", + "type": "object", + "properties": { + "transactions": { + "title": "Transaction hashes", + "description": "The hashes of the transactions included in this block", + "type": "array", + "items": { + "description": "The hash of a single transaction", + "$ref": "#/components/schemas/TXN_HASH" + } + } + }, + "required": [ + "transactions" + ] + }, + "BLOCK_BODY_WITH_TXS": { + "title": "Block body with transactions", + "type": "object", + "properties": { + "transactions": { + "title": "Transactions", + "description": "The transactions in this block", + "type": "array", + "items": { + "title": "transactions in block", + "type": "object", + "allOf": [ + { + "title": "transaction", + "$ref": "#/components/schemas/TXN" + }, + { + "type": "object", + "properties": { + "transaction_hash": { + "title": "transaction hash", + "$ref": "#/components/schemas/TXN_HASH" + } + }, + "required": [ + "transaction_hash" + ] + } + ] + } + } + }, + "required": [ + "transactions" + ] + }, + "BLOCK_HEADER": { + "title": "Block header", + "type": "object", + "properties": { + "block_hash": { + "title": "Block hash", + "$ref": "#/components/schemas/BLOCK_HASH" + }, + "parent_hash": { + "title": "Parent hash", + "description": "The hash of this block's parent", + "$ref": "#/components/schemas/BLOCK_HASH" + }, + "block_number": { + "title": "Block number", + "description": "The block number (its height)", + "$ref": "#/components/schemas/BLOCK_NUMBER" + }, + "new_root": { + "title": "New root", + "description": "The new global state root", + "$ref": "#/components/schemas/FELT" + }, + "timestamp": { + "title": "Timestamp", + "description": "The time in which the block was created, encoded in Unix time", + "type": "integer", + "minimum": 0 + }, + "sequencer_address": { + "title": "Sequencer address", + "description": "The StarkNet identity of the sequencer submitting this block", + "$ref": "#/components/schemas/FELT" + }, + "l1_gas_price": { + "title": "L1 gas price", + "descritpion": "The price of l1 gas in the block", + "$ref": "#/components/schemas/RESOURCE_PRICE" + }, + "starknet_version": { + "title": "Starknet version", + "description": "Semver of the current Starknet protocol", + "type": "string" + } + }, + "required": [ + "block_hash", + "parent_hash", + "block_number", + "new_root", + "timestamp", + "sequencer_address", + "l1_gas_price", + "starknet_version" + ] + }, + "PENDING_BLOCK_HEADER": { + "title": "Pending block header", + "type": "object", + "properties": { + "parent_hash": { + "title": "Parent hash", + "description": "The hash of this block's parent", + "$ref": "#/components/schemas/BLOCK_HASH" + }, + "timestamp": { + "title": "Timestamp", + "description": "The time in which the block was created, encoded in Unix time", + "type": "integer", + "minimum": 0 + }, + "sequencer_address": { + "title": "Sequencer address", + "description": "The StarkNet identity of the sequencer submitting this block", + "$ref": "#/components/schemas/FELT" + }, + "l1_gas_price": { + "title": "L1 gas price", + "descritpion": "The price of l1 gas in the block", + "$ref": "#/components/schemas/RESOURCE_PRICE" + }, + "starknet_version": { + "title": "Starknet version", + "description": "Semver of the current Starknet protocol", + "type": "string" + } + }, + "required": [ + "parent_hash", + "timestamp", + "sequencer_address", + "l1_gas_price", + "starknet_version" + ], + "additionalProperties": false + }, + "BLOCK_WITH_TX_HASHES": { + "title": "Block with transaction hashes", + "description": "The block object", + "allOf": [ + { + "title": "Block status", + "type": "object", + "properties": { + "status": { + "title": "Status", + "$ref": "#/components/schemas/BLOCK_STATUS" + } + }, + "required": [ + "status" + ] + }, + { + "title": "Block header", + "$ref": "#/components/schemas/BLOCK_HEADER" + }, + { + "title": "Block body with transaction hashes", + "$ref": "#/components/schemas/BLOCK_BODY_WITH_TX_HASHES" + } + ] + }, + "BLOCK_WITH_TXS": { + "title": "Block with transactions", + "description": "The block object", + "allOf": [ + { + "title": "block with txs", + "type": "object", + "properties": { + "status": { + "title": "Status", + "$ref": "#/components/schemas/BLOCK_STATUS" + } + }, + "required": [ + "status" + ] + }, + { + "title": "Block header", + "$ref": "#/components/schemas/BLOCK_HEADER" + }, + { + "title": "Block body with transactions", + "$ref": "#/components/schemas/BLOCK_BODY_WITH_TXS" + } + ] + }, + "PENDING_BLOCK_WITH_TX_HASHES": { + "title": "Pending block with transaction hashes", + "description": "The dynamic block being constructed by the sequencer. Note that this object will be deprecated upon decentralization.", + "allOf": [ + { + "title": "Block body with transactions hashes", + "$ref": "#/components/schemas/BLOCK_BODY_WITH_TX_HASHES" + }, + { + "title": "Pending block header", + "$ref": "#/components/schemas/PENDING_BLOCK_HEADER" + } + ], + "additionalProperties": false + }, + "PENDING_BLOCK_WITH_TXS": { + "title": "Pending block with transactions", + "description": "The dynamic block being constructed by the sequencer. Note that this object will be deprecated upon decentralization.", + "allOf": [ + { + "title": "Block body with transactions", + "$ref": "#/components/schemas/BLOCK_BODY_WITH_TXS" + }, + { + "title": "Pending block header", + "$ref": "#/components/schemas/PENDING_BLOCK_HEADER" + } + ], + "additionalProperties": false + }, + "DEPLOYED_CONTRACT_ITEM": { + "title": "Deployed contract item", + "type": "object", + "properties": { + "address": { + "title": "Address", + "description": "The address of the contract", + "$ref": "#/components/schemas/FELT" + }, + "class_hash": { + "title": "Class hash", + "description": "The hash of the contract code", + "$ref": "#/components/schemas/FELT" + } + }, + "required": [ + "address", + "class_hash" + ] + }, + "CONTRACT_STORAGE_DIFF_ITEM": { + "title": "Contract storage diff item", + "type": "object", + "properties": { + "address": { + "title": "Address", + "description": "The contract address for which the storage changed", + "$ref": "#/components/schemas/FELT" + }, + "storage_entries": { + "title": "Storage entries", + "description": "The changes in the storage of the contract", + "type": "array", + "items": { + "title": "Storage diff item", + "type": "object", + "properties": { + "key": { + "title": "Key", + "description": "The key of the changed value", + "$ref": "#/components/schemas/FELT" + }, + "value": { + "title": "Value", + "description": "The new value applied to the given address", + "$ref": "#/components/schemas/FELT" + } + } + } + } + }, + "required": [ + "address", + "storage_entries" + ] + }, + "TXN": { + "title": "Transaction", + "description": "The transaction schema, as it appears inside a block", + "oneOf": [ + { + "title": "Invoke transaction", + "$ref": "#/components/schemas/INVOKE_TXN" + }, + { + "title": "L1 handler transaction", + "$ref": "#/components/schemas/L1_HANDLER_TXN" + }, + { + "title": "Declare transaction", + "$ref": "#/components/schemas/DECLARE_TXN" + }, + { + "title": "Deploy transaction", + "$ref": "#/components/schemas/DEPLOY_TXN" + }, + { + "title": "Deploy account transaction", + "$ref": "#/components/schemas/DEPLOY_ACCOUNT_TXN" + } + ] + }, + "SIGNATURE": { + "title": "Signature", + "description": "A transaction signature", + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + } + }, + "DECLARE_TXN": { + "title": "Declare transaction", + "oneOf": [ + { + "title": "Declare transaction V0", + "$ref": "#/components/schemas/DECLARE_TXN_V0" + }, + { + "title": "Declare transaction V1", + "$ref": "#/components/schemas/DECLARE_TXN_V1" + }, + { + "title": "Declare transaction V2", + "$ref": "#/components/schemas/DECLARE_TXN_V2" + } + ] + }, + "DECLARE_TXN_V0": { + "title": "Declare Contract Transaction V0", + "description": "Declare Contract Transaction V0", + "allOf": [ + { + "type": "object", + "title": "Declare txn v0", + "properties": { + "type": { + "title": "Declare", + "type": "string", + "enum": [ + "DECLARE" + ] + }, + "sender_address": { + "title": "Sender address", + "description": "The address of the account contract sending the declaration transaction", + "$ref": "#/components/schemas/ADDRESS" + }, + "max_fee": { + "title": "Max fee", + "$ref": "#/components/schemas/FELT_U128", + "description": "The maximal fee that can be charged for including the transaction" + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "type": "string", + "enum": [ + "0x0" + ] + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "class_hash": { + "title": "Class hash", + "description": "The hash of the declared class", + "$ref": "#/components/schemas/FELT" + } + }, + "required": [ + "type", + "sender_address", + "max_fee", + "version", + "signature", + "class_hash" + ] + } + ] + }, + "DECLARE_TXN_V1": { + "title": "Declare Contract Transaction V1", + "description": "Declare Contract Transaction V1", + "allOf": [ + { + "type": "object", + "title": "Declare txn v1", + "properties": { + "type": { + "title": "Declare", + "type": "string", + "enum": [ + "DECLARE" + ] + }, + "sender_address": { + "title": "Sender address", + "description": "The address of the account contract sending the declaration transaction", + "$ref": "#/components/schemas/ADDRESS" + }, + "max_fee": { + "title": "Max fee", + "$ref": "#/components/schemas/FELT_U128", + "description": "The maximal fee that can be charged for including the transaction" + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "type": "string", + "enum": [ + "0x1" + ] + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "nonce": { + "title": "Nonce", + "$ref": "#/components/schemas/FELT" + }, + "class_hash": { + "title": "Class hash", + "description": "The hash of the declared class", + "$ref": "#/components/schemas/FELT" + } + }, + "required": [ + "type", + "sender_address", + "max_fee", + "version", + "signature", + "nonce", + "class_hash" + ] + } + ] + }, + "DECLARE_TXN_V2": { + "title": "Declare Transaction V2", + "description": "Declare Contract Transaction V2", + "allOf": [ + { + "type": "object", + "title": "Declare txn v2", + "properties": { + "type": { + "title": "Declare", + "type": "string", + "enum": [ + "DECLARE" + ] + }, + "sender_address": { + "title": "Sender address", + "description": "The address of the account contract sending the declaration transaction", + "$ref": "#/components/schemas/ADDRESS" + }, + "compiled_class_hash": { + "title": "Compiled class hash", + "description": "The hash of the Cairo assembly resulting from the Sierra compilation", + "$ref": "#/components/schemas/FELT" + }, + "max_fee": { + "title": "Max fee", + "$ref": "#/components/schemas/FELT_U128", + "description": "The maximal fee that can be charged for including the transaction" + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "type": "string", + "enum": [ + "0x2" + ] + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "nonce": { + "title": "Nonce", + "$ref": "#/components/schemas/FELT" + }, + "class_hash": { + "title": "Class hash", + "description": "The hash of the declared class", + "$ref": "#/components/schemas/FELT" + } + }, + "required": [ + "type", + "sender_address", + "compiled_class_hash", + "max_fee", + "version", + "signature", + "nonce", + "class_hash" + ] + } + ] + }, + "BROADCASTED_TXN": { + "oneOf": [ + { + "$ref": "#/components/schemas/BROADCASTED_INVOKE_TXN" + }, + { + "$ref": "#/components/schemas/BROADCASTED_DECLARE_TXN" + }, + { + "$ref": "#/components/schemas/BROADCASTED_DEPLOY_ACCOUNT_TXN" + } + ] + }, + "BROADCASTED_INVOKE_TXN": { + "title": "Broadcasted invoke transaction", + "oneOf": [ + { + "title": "Broadcasted invoke transaction V0", + "$ref": "#/components/schemas/INVOKE_TXN_V0" + }, + { + "title": "Broadcasted invoke transaction V1", + "$ref": "#/components/schemas/INVOKE_TXN_V1" + } + ] + }, + "BROADCASTED_DEPLOY_ACCOUNT_TXN": { + "title": "Broadcasted deploy account transaction", + "$ref": "#/components/schemas/DEPLOY_ACCOUNT_TXN" + }, + "BROADCASTED_DECLARE_TXN": { + "title": "Broadcasted declare transaction", + "oneOf": [ + { + "title": "Broadcasted declare transaction V1", + "$ref": "#/components/schemas/BROADCASTED_DECLARE_TXN_V1" + }, + { + "title": "Broadcasted declare transaction V2", + "$ref": "#/components/schemas/BROADCASTED_DECLARE_TXN_V2" + } + ] + }, + "BROADCASTED_DECLARE_TXN_V1": { + "title": "Broadcasted declare contract transaction V1", + "allOf": [ + { + "type": "object", + "title": "Declare txn v1", + "properties": { + "type": { + "title": "Declare", + "type": "string", + "enum": [ + "DECLARE" + ] + }, + "sender_address": { + "title": "Sender address", + "description": "The address of the account contract sending the declaration transaction", + "$ref": "#/components/schemas/ADDRESS" + }, + "max_fee": { + "title": "Max fee", + "$ref": "#/components/schemas/FELT_U128", + "description": "The maximal fee that can be charged for including the transaction" + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "$ref": "#/components/schemas/NUM_AS_HEX" + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "nonce": { + "title": "Nonce", + "$ref": "#/components/schemas/FELT" + }, + "contract_class": { + "title": "Contract class", + "description": "The class to be declared", + "$ref": "#/components/schemas/DEPRECATED_CONTRACT_CLASS" + } + }, + "required": [ + "type", + "sender_address", + "max_fee", + "version", + "signature", + "nonce", + "contract_class" + ] + } + ] + }, + "BROADCASTED_DECLARE_TXN_V2": { + "title": "Broadcasted declare Transaction V2", + "description": "Broadcasted declare Contract Transaction V2", + "allOf": [ + { + "type": "object", + "title": "Declare txn v2", + "properties": { + "type": { + "title": "Declare", + "type": "string", + "enum": [ + "DECLARE" + ] + }, + "sender_address": { + "title": "Sender address", + "description": "The address of the account contract sending the declaration transaction", + "$ref": "#/components/schemas/ADDRESS" + }, + "compiled_class_hash": { + "title": "Compiled class hash", + "description": "The hash of the Cairo assembly resulting from the Sierra compilation", + "$ref": "#/components/schemas/FELT" + }, + "max_fee": { + "title": "Max fee", + "$ref": "#/components/schemas/FELT_U128", + "description": "The maximal fee that can be charged for including the transaction" + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "$ref": "#/components/schemas/NUM_AS_HEX" + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "nonce": { + "title": "Nonce", + "$ref": "#/components/schemas/FELT" + }, + "contract_class": { + "title": "Contract class", + "description": "The class to be declared", + "$ref": "#/components/schemas/CONTRACT_CLASS" + } + }, + "required": [ + "type", + "sender_address", + "compiled_class_hash", + "max_fee", + "version", + "signature", + "nonce", + "contract_class" + ] + } + ] + }, + "DEPLOY_ACCOUNT_TXN": { + "title": "Deploy account transaction", + "description": "deploys a new account contract", + "oneOf": [ + { + "title": "Deploy account V1", + "$ref": "#/components/schemas/DEPLOY_ACCOUNT_TXN_V1" + } + ] + }, + "DEPLOY_ACCOUNT_TXN_V1": { + "title": "Deploy account transaction", + "description": "Deploys an account contract, charges fee from the pre-funded account addresses", + "properties": { + "type": { + "title": "Deploy account", + "type": "string", + "enum": [ + "DEPLOY_ACCOUNT" + ] + }, + "max_fee": { + "title": "Max fee", + "$ref": "#/components/schemas/FELT_U128", + "description": "The maximal fee that can be charged for including the transaction" + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "$ref": "#/components/schemas/NUM_AS_HEX" + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "nonce": { + "title": "Nonce", + "$ref": "#/components/schemas/FELT" + }, + "contract_address_salt": { + "title": "Contract address salt", + "description": "The salt for the address of the deployed contract", + "$ref": "#/components/schemas/FELT" + }, + "constructor_calldata": { + "type": "array", + "description": "The parameters passed to the constructor", + "title": "Constructor calldata", + "items": { + "$ref": "#/components/schemas/FELT" + } + }, + "class_hash": { + "title": "Class hash", + "description": "The hash of the deployed contract's class", + "$ref": "#/components/schemas/FELT" + } + }, + "required": [ + "max_fee", + "version", + "signature", + "nonce", + "type", + "contract_address_salt", + "constructor_calldata", + "class_hash" + ] + }, + "DEPLOY_TXN": { + "title": "Deploy Contract Transaction", + "description": "The structure of a deploy transaction. Note that this transaction type is deprecated and will no longer be supported in future versions", + "allOf": [ + { + "type": "object", + "title": "Deploy txn", + "properties": { + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "$ref": "#/components/schemas/NUM_AS_HEX" + }, + "type": { + "title": "Deploy", + "type": "string", + "enum": [ + "DEPLOY" + ] + }, + "contract_address_salt": { + "description": "The salt for the address of the deployed contract", + "title": "Contract address salt", + "$ref": "#/components/schemas/FELT" + }, + "constructor_calldata": { + "type": "array", + "title": "Constructor calldata", + "description": "The parameters passed to the constructor", + "items": { + "$ref": "#/components/schemas/FELT" + } + }, + "class_hash": { + "title": "Class hash", + "description": "The hash of the deployed contract's class", + "$ref": "#/components/schemas/FELT" + } + }, + "required": [ + "version", + "type", + "constructor_calldata", + "contract_address_salt", + "class_hash" + ] + } + ] + }, + "INVOKE_TXN_V0": { + "title": "Invoke transaction V0", + "description": "invokes a specific function in the desired contract (not necessarily an account)", + "properties": { + "type": { + "title": "Type", + "type": "string", + "enum": [ + "INVOKE" + ] + }, + "max_fee": { + "title": "Max fee", + "$ref": "#/components/schemas/FELT_U128", + "description": "The maximal fee that can be charged for including the transaction" + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "type": "string", + "enum": [ + "0x0" + ] + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "contract_address": { + "title": "Contract address", + "$ref": "#/components/schemas/ADDRESS" + }, + "entry_point_selector": { + "title": "Entry point selector", + "$ref": "#/components/schemas/FELT" + }, + "calldata": { + "title": "Calldata", + "type": "array", + "description": "The parameters passed to the function", + "items": { + "$ref": "#/components/schemas/FELT" + } + } + }, + "required": [ + "type", + "contract_address", + "entry_point_selector", + "calldata", + "max_fee", + "version", + "signature" + ] + }, + "INVOKE_TXN_V1": { + "title": "Invoke transaction V1", + "description": "initiates a transaction from a given account", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "title": "Type", + "type": "string", + "enum": [ + "INVOKE" + ] + }, + "sender_address": { + "title": "sender address", + "$ref": "#/components/schemas/ADDRESS" + }, + "calldata": { + "type": "array", + "title": "calldata", + "description": "The data expected by the account's `execute` function (in most usecases, this includes the called contract address and a function selector)", + "items": { + "$ref": "#/components/schemas/FELT" + } + }, + "max_fee": { + "title": "Max fee", + "$ref": "#/components/schemas/FELT_U128", + "description": "The maximal fee that can be charged for including the transaction" + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "$ref": "#/components/schemas/NUM_AS_HEX" + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "nonce": { + "title": "Nonce", + "$ref": "#/components/schemas/FELT" + } + }, + "required": [ + "type", + "sender_address", + "calldata", + "max_fee", + "version", + "signature", + "nonce" + ] + } + ] + }, + "INVOKE_TXN": { + "title": "Invoke transaction", + "description": "Initiate a transaction from an account", + "oneOf": [ + { + "title": "Invoke transaction V0", + "$ref": "#/components/schemas/INVOKE_TXN_V0" + }, + { + "title": "Invoke transaction V1", + "$ref": "#/components/schemas/INVOKE_TXN_V1" + } + ] + }, + "L1_HANDLER_TXN": { + "title": "L1 Handler transaction", + "allOf": [ + { + "type": "object", + "title": "L1 handler transaction", + "description": "a call to an l1_handler on an L2 contract induced by a message from L1", + "properties": { + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "$ref": "#/components/schemas/NUM_AS_HEX" + }, + "type": { + "title": "type", + "type": "string", + "enum": [ + "L1_HANDLER" + ] + }, + "nonce": { + "title": "Nonce", + "description": "The L1->L2 message nonce field of the SN Core L1 contract at the time the transaction was sent", + "$ref": "#/components/schemas/NUM_AS_HEX" + } + }, + "required": [ + "version", + "type", + "nonce" + ] + }, + { + "title": "Function call", + "$ref": "#/components/schemas/FUNCTION_CALL" + } + ] + }, + "COMMON_RECEIPT_PROPERTIES": { + "title": "Common receipt properties", + "description": "Common properties for a transaction receipt", + "type": "object", + "properties": { + "transaction_hash": { + "title": "Transaction hash", + "$ref": "#/components/schemas/TXN_HASH", + "description": "The hash identifying the transaction" + }, + "actual_fee": { + "title": "Actual fee", + "$ref": "#/components/schemas/FELT_U128", + "description": "The fee that was charged by the sequencer" + }, + "execution_status": { + "title": "Execution status", + "$ref": "#/components/schemas/TXN_EXECUTION_STATUS" + }, + "finality_status": { + "title": "Finality status", + "$ref": "#/components/schemas/TXN_FINALITY_STATUS" + }, + "block_hash": { + "title": "Block hash", + "$ref": "#/components/schemas/BLOCK_HASH" + }, + "block_number": { + "title": "Block number", + "$ref": "#/components/schemas/BLOCK_NUMBER" + }, + "messages_sent": { + "type": "array", + "title": "Messages sent", + "items": { + "$ref": "#/components/schemas/MSG_TO_L1" + } + }, + "revert_reason": { + "title": "Revert reason", + "name": "revert reason", + "description": "the revert reason for the failed execution", + "type": "string" + }, + "events": { + "description": "The events emitted as part of this transaction", + "title": "Events", + "type": "array", + "items": { + "$ref": "#/components/schemas/EVENT" + } + }, + "execution_resources": { + "title": "Execution resources", + "description": "The resources consumed by the transaction", + "$ref": "#/components/schemas/EXECUTION_RESOURCES" + } + }, + "required": [ + "transaction_hash", + "actual_fee", + "finality_status", + "execution_status", + "block_hash", + "block_number", + "messages_sent", + "events", + "execution_resources" + ] + }, + "INVOKE_TXN_RECEIPT": { + "title": "Invoke Transaction Receipt", + "allOf": [ + { + "title": "Type", + "type": "object", + "properties": { + "type": { + "title": "Type", + "type": "string", + "enum": [ + "INVOKE" + ] + } + }, + "required": [ + "type" + ] + }, + { + "title": "Common receipt properties", + "$ref": "#/components/schemas/COMMON_RECEIPT_PROPERTIES" + } + ] + }, + "PENDING_INVOKE_TXN_RECEIPT": { + "title": "Invoke Transaction Receipt", + "allOf": [ + { + "title": "Type", + "type": "object", + "properties": { + "type": { + "title": "Type", + "type": "string", + "enum": [ + "INVOKE" + ] + } + }, + "required": [ + "type" + ] + }, + { + "title": "Common receipt properties", + "$ref": "#/components/schemas/PENDING_COMMON_RECEIPT_PROPERTIES" + } + ] + }, + "DECLARE_TXN_RECEIPT": { + "title": "Declare Transaction Receipt", + "allOf": [ + { + "title": "Declare txn receipt", + "type": "object", + "properties": { + "type": { + "title": "Declare", + "type": "string", + "enum": [ + "DECLARE" + ] + } + }, + "required": [ + "type" + ] + }, + { + "title": "Common receipt properties", + "$ref": "#/components/schemas/COMMON_RECEIPT_PROPERTIES" + } + ] + }, + "PENDING_DECLARE_TXN_RECEIPT": { + "title": "Declare Transaction Receipt", + "allOf": [ + { + "title": "Declare txn receipt", + "type": "object", + "properties": { + "type": { + "title": "Declare", + "type": "string", + "enum": [ + "DECLARE" + ] + } + }, + "required": [ + "type" + ] + }, + { + "title": "Common receipt properties", + "$ref": "#/components/schemas/PENDING_COMMON_RECEIPT_PROPERTIES" + } + ] + }, + "DEPLOY_ACCOUNT_TXN_RECEIPT": { + "title": "Deploy Account Transaction Receipt", + "allOf": [ + { + "title": "Common receipt properties", + "$ref": "#/components/schemas/COMMON_RECEIPT_PROPERTIES" + }, + { + "title": "DeployAccount txn receipt", + "type": "object", + "properties": { + "type": { + "title": "Deploy account", + "type": "string", + "enum": [ + "DEPLOY_ACCOUNT" + ] + }, + "contract_address": { + "title": "Contract address", + "description": "The address of the deployed contract", + "$ref": "#/components/schemas/FELT" + } + }, + "required": [ + "type", + "contract_address" + ] + } + ] + }, + "PENDING_DEPLOY_ACCOUNT_TXN_RECEIPT": { + "title": "Deploy Account Transaction Receipt", + "allOf": [ + { + "title": "Common receipt properties", + "$ref": "#/components/schemas/PENDING_COMMON_RECEIPT_PROPERTIES" + }, + { + "title": "DeployAccount txn receipt", + "type": "object", + "properties": { + "type": { + "title": "Deploy account", + "type": "string", + "enum": [ + "DEPLOY_ACCOUNT" + ] + }, + "contract_address": { + "title": "Contract address", + "description": "The address of the deployed contract", + "$ref": "#/components/schemas/FELT" + } + }, + "required": [ + "type", + "contract_address" + ] + } + ] + }, + "DEPLOY_TXN_RECEIPT": { + "title": "Deploy Transaction Receipt", + "allOf": [ + { + "title": "Common receipt properties", + "$ref": "#/components/schemas/COMMON_RECEIPT_PROPERTIES" + }, + { + "title": "Deploy txn receipt", + "type": "object", + "properties": { + "type": { + "title": "Deploy", + "type": "string", + "enum": [ + "DEPLOY" + ] + }, + "contract_address": { + "title": "Contract address", + "description": "The address of the deployed contract", + "$ref": "#/components/schemas/FELT" + } + }, + "required": [ + "type", + "contract_address" + ] + } + ] + }, + "L1_HANDLER_TXN_RECEIPT": { + "title": "L1 Handler Transaction Receipt", + "description": "receipt for l1 handler transaction", + "allOf": [ + { + "title": "Transaction type", + "type": "object", + "properties": { + "type": { + "title": "type", + "type": "string", + "enum": [ + "L1_HANDLER" + ] + }, + "message_hash": { + "title": "Message hash", + "description": "The message hash as it appears on the L1 core contract", + "$ref": "#/components/schemas/NUM_AS_HEX" + } + }, + "required": [ + "type", + "message_hash" + ] + }, + { + "title": "Common receipt properties", + "$ref": "#/components/schemas/COMMON_RECEIPT_PROPERTIES" + } + ] + }, + "PENDING_L1_HANDLER_TXN_RECEIPT": { + "title": "L1 Handler Transaction Receipt", + "description": "receipt for l1 handler transaction", + "allOf": [ + { + "title": "Transaction type", + "type": "object", + "properties": { + "type": { + "title": "type", + "type": "string", + "enum": [ + "L1_HANDLER" + ] + }, + "message_hash": { + "title": "Message hash", + "description": "The message hash as it appears on the L1 core contract", + "$ref": "#/components/schemas/NUM_AS_HEX" + } + }, + "required": [ + "type", + "message_hash" + ] + }, + { + "title": "Common receipt properties", + "$ref": "#/components/schemas/PENDING_COMMON_RECEIPT_PROPERTIES" + } + ] + }, + "TXN_RECEIPT": { + "title": "Transaction Receipt", + "oneOf": [ + { + "title": "Invoke transaction receipt", + "$ref": "#/components/schemas/INVOKE_TXN_RECEIPT" + }, + { + "title": "L1 handler transaction receipt", + "$ref": "#/components/schemas/L1_HANDLER_TXN_RECEIPT" + }, + { + "title": "Declare transaction receipt", + "$ref": "#/components/schemas/DECLARE_TXN_RECEIPT" + }, + { + "title": "Deploy transaction receipt", + "$ref": "#/components/schemas/DEPLOY_TXN_RECEIPT" + }, + { + "title": "Deploy account transaction receipt", + "$ref": "#/components/schemas/DEPLOY_ACCOUNT_TXN_RECEIPT" + } + ] + }, + "PENDING_TXN_RECEIPT": { + "title": "Transaction Receipt", + "oneOf": [ + { + "title": "Pending Invoke transaction receipt", + "$ref": "#/components/schemas/PENDING_INVOKE_TXN_RECEIPT" + }, + { + "title": "Pending L1 handler transaction receipt", + "$ref": "#/components/schemas/PENDING_L1_HANDLER_TXN_RECEIPT" + }, + { + "title": "Pending Declare transaction receipt", + "$ref": "#/components/schemas/PENDING_DECLARE_TXN_RECEIPT" + }, + { + "title": "Pending Deploy account transaction receipt", + "$ref": "#/components/schemas/PENDING_DEPLOY_ACCOUNT_TXN_RECEIPT" + } + ] + }, + "PENDING_COMMON_RECEIPT_PROPERTIES": { + "title": "Pending common receipt properties", + "description": "Common properties for a pending transaction receipt", + "type": "object", + "properties": { + "transaction_hash": { + "title": "Transaction hash", + "$ref": "#/components/schemas/TXN_HASH", + "description": "The hash identifying the transaction" + }, + "actual_fee": { + "title": "Actual fee", + "$ref": "#/components/schemas/FELT", + "description": "The fee that was charged by the sequencer" + }, + "type": { + "title": "Transaction type", + "$ref": "#/components/schemas/TXN_TYPE" + }, + "messages_sent": { + "type": "array", + "title": "Messages sent", + "items": { + "$ref": "#/components/schemas/MSG_TO_L1" + } + }, + "events": { + "description": "The events emitted as part of this transaction", + "title": "Events", + "type": "array", + "items": { + "$ref": "#/components/schemas/EVENT" + } + }, + "revert_reason": { + "title": "Revert reason", + "name": "revert reason", + "description": "the revert reason for the failed execution", + "type": "string" + }, + "finality_status": { + "title": "Finality status", + "type": "string", + "enum": [ + "ACCEPTED_ON_L2" + ], + "description": "The finality status of the transaction" + }, + "execution_status": { + "title": "Execution status", + "$ref": "#/components/schemas/TXN_EXECUTION_STATUS" + }, + "execution_resources": { + "title": "Execution resources", + "description": "The resources consumed by the transaction", + "$ref": "#/components/schemas/EXECUTION_RESOURCES" + } + }, + "required": [ + "transaction_hash", + "actual_fee", + "type", + "messages_sent", + "events", + "finality_status", + "execution_status", + "execution_resources" + ] + }, + "MSG_TO_L1": { + "title": "Message to L1", + "type": "object", + "properties": { + "from_address": { + "description": "The address of the L2 contract sending the message", + "$ref": "#/components/schemas/FELT" + }, + "to_address": { + "title": "To address", + "description": "The target L1 address the message is sent to", + "$ref": "#/components/schemas/FELT" + }, + "payload": { + "description": "The payload of the message", + "title": "Payload", + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + } + } + }, + "required": [ + "from_address", + "to_address", + "payload" + ] + }, + "MSG_FROM_L1": { + "title": "Message from L1", + "type": "object", + "properties": { + "from_address": { + "description": "The address of the L1 contract sending the message", + "$ref": "#/components/schemas/ETH_ADDRESS" + }, + "to_address": { + "title": "To address", + "description": "The target L2 address the message is sent to", + "$ref": "#/components/schemas/ADDRESS" + }, + "entry_point_selector": { + "title": "Selector", + "description": "The selector of the l1_handler in invoke in the target contract", + "$ref": "#/components/schemas/FELT" + }, + "payload": { + "description": "The payload of the message", + "title": "Payload", + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + } + } + }, + "required": [ + "from_address", + "to_address", + "payload", + "entry_point_selector" + ] + }, + "TXN_STATUS": { + "title": "Transaction status", + "type": "string", + "enum": [ + "RECEIVED", + "REJECTED", + "ACCEPTED_ON_L2", + "ACCEPTED_ON_L1" + ], + "description": "The finality status of the transaction, including the case the txn is still in the mempool or failed validation during the block construction phase" + }, + "TXN_FINALITY_STATUS": { + "title": "Finality status", + "type": "string", + "enum": [ + "ACCEPTED_ON_L2", + "ACCEPTED_ON_L1" + ], + "description": "The finality status of the transaction" + }, + "TXN_EXECUTION_STATUS": { + "title": "Execution status", + "type": "string", + "enum": [ + "SUCCEEDED", + "REVERTED" + ], + "description": "The execution status of the transaction" + }, + "TXN_TYPE": { + "title": "Transaction type", + "type": "string", + "enum": [ + "DECLARE", + "DEPLOY", + "DEPLOY_ACCOUNT", + "INVOKE", + "L1_HANDLER" + ], + "description": "The type of the transaction" + }, + "BLOCK_STATUS": { + "title": "Block status", + "type": "string", + "enum": [ + "PENDING", + "ACCEPTED_ON_L2", + "ACCEPTED_ON_L1", + "REJECTED" + ], + "description": "The status of the block" + }, + "FUNCTION_CALL": { + "title": "Function call", + "type": "object", + "description": "Function call information", + "properties": { + "contract_address": { + "title": "Contract address", + "$ref": "#/components/schemas/ADDRESS" + }, + "entry_point_selector": { + "title": "Entry point selector", + "$ref": "#/components/schemas/FELT" + }, + "calldata": { + "title": "Calldata", + "type": "array", + "description": "The parameters passed to the function", + "items": { + "$ref": "#/components/schemas/FELT" + } + } + }, + "required": [ + "contract_address", + "entry_point_selector", + "calldata" + ] + }, + "CONTRACT_CLASS": { + "title": "Contract class", + "type": "object", + "properties": { + "sierra_program": { + "title": "Sierra program", + "type": "array", + "description": "The list of Sierra instructions of which the program consists", + "items": { + "$ref": "#/components/schemas/FELT" + } + }, + "contract_class_version": { + "title": "Contract class version", + "type": "string", + "description": "The version of the contract class object. Currently, the Starknet OS supports version 0.1.0" + }, + "entry_points_by_type": { + "title": "Entry points by type", + "type": "object", + "properties": { + "CONSTRUCTOR": { + "type": "array", + "title": "Constructor", + "items": { + "$ref": "#/components/schemas/SIERRA_ENTRY_POINT" + } + }, + "EXTERNAL": { + "title": "External", + "type": "array", + "items": { + "$ref": "#/components/schemas/SIERRA_ENTRY_POINT" + } + }, + "L1_HANDLER": { + "title": "L1 handler", + "type": "array", + "items": { + "$ref": "#/components/schemas/SIERRA_ENTRY_POINT" + } + } + }, + "required": [ + "CONSTRUCTOR", + "EXTERNAL", + "L1_HANDLER" + ] + }, + "abi": { + "title": "ABI", + "type": "string", + "description": "The class ABI, as supplied by the user declaring the class" + } + }, + "required": [ + "sierra_program", + "contract_class_version", + "entry_points_by_type", + "abi" + ] + }, + "DEPRECATED_CONTRACT_CLASS": { + "title": "Deprecated contract class", + "description": "The definition of a StarkNet contract class", + "type": "object", + "properties": { + "program": { + "type": "string", + "title": "Program", + "description": "A base64 representation of the compressed program code", + "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$" + }, + "entry_points_by_type": { + "type": "object", + "title": "Deprecated entry points by type", + "properties": { + "CONSTRUCTOR": { + "type": "array", + "title": "Deprecated constructor", + "items": { + "$ref": "#/components/schemas/DEPRECATED_CAIRO_ENTRY_POINT" + } + }, + "EXTERNAL": { + "type": "array", + "title": "Deprecated external", + "items": { + "$ref": "#/components/schemas/DEPRECATED_CAIRO_ENTRY_POINT" + } + }, + "L1_HANDLER": { + "type": "array", + "title": "Deprecated L1 handler", + "items": { + "$ref": "#/components/schemas/DEPRECATED_CAIRO_ENTRY_POINT" + } + } + } + }, + "abi": { + "title": "Contract ABI", + "$ref": "#/components/schemas/CONTRACT_ABI" + } + }, + "required": [ + "program", + "entry_points_by_type", + "abi" + ] + }, + "DEPRECATED_CAIRO_ENTRY_POINT": { + "title": "Deprecated Cairo entry point", + "type": "object", + "properties": { + "offset": { + "title": "Offset", + "description": "The offset of the entry point in the program", + "$ref": "#/components/schemas/NUM_AS_HEX" + }, + "selector": { + "title": "Selector", + "description": "A unique identifier of the entry point (function) in the program", + "$ref": "#/components/schemas/FELT" + } + }, + "required": [ + "offset", + "selector" + ] + }, + "SIERRA_ENTRY_POINT": { + "title": "Sierra entry point", + "type": "object", + "properties": { + "selector": { + "title": "Selector", + "description": "A unique identifier of the entry point (function) in the program", + "$ref": "#/components/schemas/FELT" + }, + "function_idx": { + "title": "Function index", + "description": "The index of the function in the program", + "type": "integer" + } + }, + "required": [ + "selector", + "function_idx" + ] + }, + "CONTRACT_ABI": { + "title": "Contract ABI", + "type": "array", + "items": { + "$ref": "#/components/schemas/CONTRACT_ABI_ENTRY" + } + }, + "CONTRACT_ABI_ENTRY": { + "title": "Contract ABI entry", + "oneOf": [ + { + "title": "Function ABI entry", + "$ref": "#/components/schemas/FUNCTION_ABI_ENTRY" + }, + { + "title": "Event ABI entry", + "$ref": "#/components/schemas/EVENT_ABI_ENTRY" + }, + { + "title": "Struct ABI entry", + "$ref": "#/components/schemas/STRUCT_ABI_ENTRY" + } + ] + }, + "STRUCT_ABI_TYPE": { + "title": "Struct ABI type", + "type": "string", + "enum": [ + "struct" + ] + }, + "EVENT_ABI_TYPE": { + "title": "Event ABI type", + "type": "string", + "enum": [ + "event" + ] + }, + "FUNCTION_ABI_TYPE": { + "title": "Function ABI type", + "type": "string", + "enum": [ + "function", + "l1_handler", + "constructor" + ] + }, + "STRUCT_ABI_ENTRY": { + "title": "Struct ABI entry", + "type": "object", + "properties": { + "type": { + "title": "Struct ABI type", + "$ref": "#/components/schemas/STRUCT_ABI_TYPE" + }, + "name": { + "title": "Struct name", + "description": "The struct name", + "type": "string" + }, + "size": { + "title": "Size", + "type": "integer", + "minimum": 1 + }, + "members": { + "type": "array", + "title": "Members", + "items": { + "$ref": "#/components/schemas/STRUCT_MEMBER" + } + } + }, + "required": [ + "type", + "name", + "size", + "members" + ] + }, + "STRUCT_MEMBER": { + "title": "Struct member", + "allOf": [ + { + "title": "Typed parameter", + "$ref": "#/components/schemas/TYPED_PARAMETER" + }, + { + "type": "object", + "title": "Offset", + "properties": { + "offset": { + "title": "Offset", + "description": "offset of this property within the struct", + "type": "integer" + } + } + } + ] + }, + "EVENT_ABI_ENTRY": { + "title": "Event ABI entry", + "type": "object", + "properties": { + "type": { + "title": "Event ABI type", + "$ref": "#/components/schemas/EVENT_ABI_TYPE" + }, + "name": { + "title": "Event name", + "description": "The event name", + "type": "string" + }, + "keys": { + "type": "array", + "title": "Typed parameter", + "items": { + "$ref": "#/components/schemas/TYPED_PARAMETER" + } + }, + "data": { + "type": "array", + "title": "Typed parameter", + "items": { + "$ref": "#/components/schemas/TYPED_PARAMETER" + } + } + }, + "required": [ + "type", + "name", + "keys", + "data" + ] + }, + "FUNCTION_STATE_MUTABILITY": { + "title": "Function state mutability type", + "type": "string", + "enum": [ + "view" + ] + }, + "FUNCTION_ABI_ENTRY": { + "title": "Function ABI entry", + "type": "object", + "properties": { + "type": { + "title": "Function ABI type", + "$ref": "#/components/schemas/FUNCTION_ABI_TYPE" + }, + "name": { + "title": "Function name", + "description": "The function name", + "type": "string" + }, + "inputs": { + "type": "array", + "title": "Typed parameter", + "items": { + "$ref": "#/components/schemas/TYPED_PARAMETER" + } + }, + "outputs": { + "type": "array", + "title": "Typed parameter", + "items": { + "$ref": "#/components/schemas/TYPED_PARAMETER" + } + }, + "stateMutability": { + "title": "Function state mutability", + "$ref": "#/components/schemas/FUNCTION_STATE_MUTABILITY" + } + }, + "required": [ + "type", + "name", + "inputs", + "outputs" + ] + }, + "TYPED_PARAMETER": { + "title": "Typed parameter", + "type": "object", + "properties": { + "name": { + "title": "Parameter name", + "description": "The parameter's name", + "type": "string" + }, + "type": { + "title": "Parameter type", + "description": "The parameter's type", + "type": "string" + } + }, + "required": [ + "name", + "type" + ] + }, + "FEE_ESTIMATE": { + "title": "Fee estimation", + "type": "object", + "properties": { + "gas_consumed": { + "title": "Gas consumed", + "description": "The Ethereum gas cost of the transaction (see https://docs.starknet.io/docs/Fees/fee-mechanism for more info)", + "$ref": "#/components/schemas/NUM_AS_HEX" + }, + "gas_price": { + "title": "Gas price", + "description": "The gas price (in gwei) that was used in the cost estimation", + "$ref": "#/components/schemas/NUM_AS_HEX" + }, + "overall_fee": { + "title": "Overall fee", + "description": "The estimated fee for the transaction (in gwei), product of gas_consumed and gas_price", + "$ref": "#/components/schemas/NUM_AS_HEX" + } + }, + "required": [ + "gas_consumed", + "gas_price", + "overall_fee" + ] + }, + "DA_MODE": { + "title": "DA mode", + "type": "string", + "description": "Specifies a storage domain in Starknet. Each domain has different gurantess regarding availability", + "enum": [ + "L1", + "L2" + ] + }, + "RESOURCE_LIMITS": { + "type": "object", + "properties": { + "max_amount": { + "title": "max amount", + "description": "the max amount of the resource that can be used in the tx", + "$ref": "#/components/schemas/NUM_AS_HEX" + }, + "max_price_per_unit": { + "title": "max price", + "description": "the max price per unit of this resource for this tx", + "$ref": "#/components/schemas/NUM_AS_HEX" + } + }, + "required": [ + "max_amount", + "max_price_per_unit" + ] + }, + "RESOURCE_PRICE": { + "type": "object", + "properties": { + "price_in_strk": { + "title": "price in strk", + "description": "the price of one unit of the given resource, denominated in strk", + "$ref": "#/components/schemas/NUM_AS_HEX" + }, + "price_in_wei": { + "title": "price in wei", + "description": "the price of one unit of the given resource, denominated in wei", + "$ref": "#/components/schemas/NUM_AS_HEX" + } + }, + "required": [ + "price_in_wei" + ] + }, + "EXECUTION_RESOURCES": { + "title": "Execution resources", + "description": "The resources consumed by the transaction", + "type": "object", + "properties": { + "steps": { + "title": "Steps", + "description": "The number of Cairo steps used", + "$ref": "#/components/schemas/NUM_AS_HEX" + }, + "memory_holes": { + "title": "Memory holes", + "description": "The number of unused memory cells (each cell is roughly equivalent to a step)", + "$ref": "#/components/schemas/NUM_AS_HEX" + }, + "range_check_builtin_applications": { + "title": "Range check applications", + "description": "The number of RANGE_CHECK builtin instances", + "$ref": "#/components/schemas/NUM_AS_HEX" + }, + "pedersen_builtin_applications": { + "title": "Pedersen applications", + "description": "The number of Pedersen builtin instances", + "$ref": "#/components/schemas/NUM_AS_HEX" + }, + "poseidon_builtin_applications": { + "title": "Poseidon applications", + "description": "The number of Poseidon builtin instances", + "$ref": "#/components/schemas/NUM_AS_HEX" + }, + "ec_op_builtin_applications": { + "title": "EC_OP applications", + "description": "the number of EC_OP builtin instances", + "$ref": "#/components/schemas/NUM_AS_HEX" + }, + "ecdsa_builtin_applications": { + "title": "ECDSA applications", + "description": "the number of ECDSA builtin instances", + "$ref": "#/components/schemas/NUM_AS_HEX" + }, + "bitwise_builtin_applications": { + "title": "BITWISE applications", + "description": "the number of BITWISE builtin instances", + "$ref": "#/components/schemas/NUM_AS_HEX" + }, + "keccak_builtin_applications": { + "title": "Keccak applications", + "description": "The number of KECCAK builtin instances", + "$ref": "#/components/schemas/NUM_AS_HEX" + } + }, + "required": [ + "steps", + "memory_holes", + "range_check_builtin_applications", + "pedersen_builtin_applications", + "poseidon_builtin_applications", + "ec_op_builtin_applications", + "ecdsa_builtin_applications", + "bitwise_builtin_applications", + "keccak_builtin_applications" + ] + } + }, + "errors": { + "FAILED_TO_RECEIVE_TXN": { + "code": 1, + "message": "Failed to write transaction" + }, + "CONTRACT_NOT_FOUND": { + "code": 20, + "message": "Contract not found" + }, + "BLOCK_NOT_FOUND": { + "code": 24, + "message": "Block not found" + }, + "INVALID_TXN_INDEX": { + "code": 27, + "message": "Invalid transaction index in a block" + }, + "CLASS_HASH_NOT_FOUND": { + "code": 28, + "message": "Class hash not found" + }, + "TXN_HASH_NOT_FOUND": { + "code": 29, + "message": "Transaction hash not found" + }, + "PAGE_SIZE_TOO_BIG": { + "code": 31, + "message": "Requested page size is too big" + }, + "NO_BLOCKS": { + "code": 32, + "message": "There are no blocks" + }, + "INVALID_CONTINUATION_TOKEN": { + "code": 33, + "message": "The supplied continuation token is invalid or unknown" + }, + "TOO_MANY_KEYS_IN_FILTER": { + "code": 34, + "message": "Too many keys provided in a filter" + }, + "CONTRACT_ERROR": { + "code": 40, + "message": "Contract error", + "data": { + "type": "object", + "description": "More data about the execution failure", + "properties": { + "revert_error": { + "title": "revert error", + "description": "a string encoding the execution trace up to the point of failure", + "type": "string" + } + }, + "required": "revert_error" + } + } + } + } +} \ No newline at end of file diff --git a/crates/starknet-server/test_data/spec/0.5.0/starknet_trace_api_openrpc.json b/crates/starknet-server/test_data/spec/0.5.0/starknet_trace_api_openrpc.json new file mode 100644 index 000000000..13c669fb4 --- /dev/null +++ b/crates/starknet-server/test_data/spec/0.5.0/starknet_trace_api_openrpc.json @@ -0,0 +1,422 @@ +{ + "openrpc": "1.0.0-rc1", + "info": { + "version": "0.5.0", + "title": "StarkNet Trace API", + "license": {} + }, + "servers": [], + "methods": [ + { + "name": "starknet_simulateTransactions", + "summary": "simulate a given sequence of transactions on the requested state, and generate the execution traces. If one of the transactions is reverted, raises CONTRACT_ERROR.", + "params": [ + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag, for the block referencing the state or call the transaction on.", + "required": true, + "schema": { + "$ref": "#/components/schemas/BLOCK_ID" + } + }, + { + "name": "transactions", + "description": "The transactions to simulate", + "required": true, + "schema": { + "type": "array", + "description": "a sequence of transactions to simulate, running each transaction on the state resulting from applying all the previous ones", + "items": { + "$ref": "#/components/schemas/BROADCASTED_TXN" + } + } + }, + { + "name": "simulation_flags", + "description": "describes what parts of the transaction should be executed", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SIMULATION_FLAG" + } + } + } + ], + "result": { + "name": "simulated_transactions", + "description": "The execution trace and consuemd resources of the required transactions", + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "transaction_trace": { + "title": "the transaction's trace", + "$ref": "#/components/schemas/TRANSACTION_TRACE" + }, + "fee_estimation": { + "title": "the transaction's resources and fee", + "$ref": "#/components/schemas/FEE_ESTIMATE" + } + } + } + } + }, + "errors": [ + { + "$ref": "#/components/errors/CONTRACT_NOT_FOUND" + }, + { + "$ref": "#/components/errors/CONTRACT_ERROR" + }, + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + } + ] + } + ], + "components": { + "contentDescriptors": {}, + "schemas": { + "TRANSACTION_TRACE": { + "oneOf": [ + { + "name": "INVOKE_TXN_TRACE", + "type": "object", + "description": "the execution trace of an invoke transaction", + "properties": { + "validate_invocation": { + "$ref": "#/components/schemas/FUNCTION_INVOCATION" + }, + "execute_invocation": { + "description": "the trace of the __execute__ call or constructor call, depending on the transaction type (none for declare transactions)", + "oneOf": [ + { + "$ref": "#/components/schemas/FUNCTION_INVOCATION" + }, + { + "type": "object", + "properties": { + "revert_reason": { + "name": "revert reason", + "description": "the revert reason for the failed execution", + "type": "string" + } + } + } + ] + }, + "fee_transfer_invocation": { + "$ref": "#/components/schemas/FUNCTION_INVOCATION" + }, + "state_diff": { + "title": "state_diff", + "description": "the state diffs induced by the transaction", + "$ref": "#/components/schemas/STATE_DIFF" + }, + "type": { + "title": "Type", + "type": "string", + "enum": [ + "INVOKE" + ] + } + }, + "required": [ + "validate_invocation", + "execute_invocation", + "fee_transfer_invocation", + "state_diff", + "type" + ] + }, + { + "name": "DECLARE_TXN_TRACE", + "type": "object", + "description": "the execution trace of a declare transaction", + "properties": { + "validate_invocation": { + "$ref": "#/components/schemas/FUNCTION_INVOCATION" + }, + "fee_transfer_invocation": { + "$ref": "#/components/schemas/FUNCTION_INVOCATION" + }, + "state_diff": { + "title": "state_diff", + "description": "the state diffs induced by the transaction", + "$ref": "#/components/schemas/STATE_DIFF" + }, + "type": { + "title": "Type", + "type": "string", + "enum": [ + "DECLARE" + ] + } + }, + "required": [ + "validate_invocation", + "fee_transfer_invocation", + "state_diff", + "type" + ] + }, + { + "name": "DEPLOY_ACCOUNT_TXN_TRACE", + "type": "object", + "description": "the execution trace of a deploy account transaction", + "properties": { + "validate_invocation": { + "$ref": "#/components/schemas/FUNCTION_INVOCATION" + }, + "constructor_invocation": { + "description": "the trace of the __execute__ call or constructor call, depending on the transaction type (none for declare transactions)", + "$ref": "#/components/schemas/FUNCTION_INVOCATION" + }, + "fee_transfer_invocation": { + "$ref": "#/components/schemas/FUNCTION_INVOCATION" + }, + "state_diff": { + "title": "state_diff", + "description": "the state diffs induced by the transaction", + "$ref": "#/components/schemas/STATE_DIFF" + }, + "type": { + "title": "Type", + "type": "string", + "enum": [ + "DEPLOY_ACCOUNT" + ] + } + }, + "required": [ + "validate_invocation", + "constructor_invocation", + "fee_transfer_invocation", + "state_diff", + "type" + ] + }, + { + "name": "L1_HANDLER_TXN_TRACE", + "type": "object", + "description": "the execution trace of an L1 handler transaction", + "properties": { + "function_invocation": { + "description": "the trace of the __execute__ call or constructor call, depending on the transaction type (none for declare transactions)", + "$ref": "#/components/schemas/FUNCTION_INVOCATION" + }, + "type": { + "title": "Type", + "type": "string", + "enum": [ + "L1_HANDLER" + ] + } + }, + "required": [ + "function_invocation", + "type" + ] + } + ] + }, + "SIMULATION_FLAG": { + "type": "string", + "enum": [ + "SKIP_VALIDATE", + "SKIP_FEE_CHARGE" + ], + "description": "Flags that indicate how to simulate a given transaction. By default, the sequencer behavior is replicated locally (enough funds are expected to be in the account, and fee will be deducted from the balance before the simulation of the next transaction). To skip the fee charge, use the SKIP_FEE_CHARGE flag." + }, + "NESTED_CALL": { + "$ref": "#/components/schemas/FUNCTION_INVOCATION" + }, + "FUNCTION_INVOCATION": { + "allOf": [ + { + "$ref": "#/components/schemas/FUNCTION_CALL" + }, + { + "type": "object", + "properties": { + "caller_address": { + "title": "Caller Address", + "description": "The address of the invoking contract. 0 for the root invocation", + "$ref": "#/components/schemas/FELT" + }, + "class_hash": { + "title": "Class hash", + "description": "The hash of the class being called", + "$ref": "#/components/schemas/FELT" + }, + "entry_point_type": { + "$ref": "#/components/schemas/ENTRY_POINT_TYPE" + }, + "call_type": { + "$ref": "#/components/schemas/CALL_TYPE" + }, + "result": { + "title": "Invocation Result", + "description": "The value returned from the function invocation", + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + } + }, + "calls": { + "title": "Nested Calls", + "description": "The calls made by this invocation", + "type": "array", + "items": { + "$ref": "#/components/schemas/NESTED_CALL" + } + }, + "events": { + "title": "Invocation Events", + "description": "The events emitted in this invocation", + "type": "array", + "items": { + "$ref": "#/components/schemas/ORDERED_EVENT" + } + }, + "messages": { + "title": "L1 Messages", + "description": "The messages sent by this invocation to L1", + "type": "array", + "items": { + "$ref": "#/components/schemas/ORDERED_MESSAGE" + } + } + }, + "required": [ + "caller_address", + "class_hash", + "entry_point_type", + "call_type", + "result", + "calls", + "events", + "messages" + ] + } + ] + }, + "ENTRY_POINT_TYPE": { + "type": "string", + "enum": [ + "EXTERNAL", + "L1_HANDLER", + "CONSTRUCTOR" + ] + }, + "CALL_TYPE": { + "type": "string", + "enum": [ + "LIBRARY_CALL", + "CALL" + ] + }, + "ORDERED_EVENT": { + "type": "object", + "title": "orderedEvent", + "description": "an event alongside its order within the transaction", + "allOf": [ + { + "type": "object", + "properties": { + "order": { + "title": "order", + "description": "the order of the event within the transaction", + "type": "number" + } + } + }, + { + "$ref": "#/components/schemas/EVENT" + } + ] + }, + "ORDERED_MESSAGE": { + "type": "object", + "title": "orderedMessage", + "description": "a message alongside its order within the transaction", + "allOf": [ + { + "type": "object", + "properties": { + "order": { + "title": "order", + "description": "the order of the message within the transaction", + "type": "number" + } + } + }, + { + "$ref": "#/components/schemas/MSG_TO_L1" + } + ] + }, + "FELT": { + "$ref": "./starknet_api_openrpc.json#/components/schemas/FELT" + }, + "FUNCTION_CALL": { + "$ref": "./starknet_api_openrpc.json#/components/schemas/FUNCTION_CALL" + }, + "EVENT": { + "$ref": "./starknet_api_openrpc.json#/components/schemas/EVENT_CONTENT" + }, + "MSG_TO_L1": { + "$ref": "./starknet_api_openrpc.json#/components/schemas/MSG_TO_L1" + }, + "BLOCK_ID": { + "$ref": "./starknet_api_openrpc.json#/components/schemas/BLOCK_ID" + }, + "FEE_ESTIMATE": { + "$ref": "./starknet_api_openrpc.json#/components/schemas/FEE_ESTIMATE" + }, + "BROADCASTED_TXN": { + "$ref": "./starknet_api_openrpc.json#/components/schemas/BROADCASTED_TXN" + }, + "STATE_DIFF": { + "$ref": "./starknet_api_openrpc.json#/components/schemas/STATE_DIFF" + } + }, + "errors": { + "NO_TRACE_AVAILABLE": { + "code": 10, + "message": "No trace available for transaction", + "data": { + "type": "object", + "description": "Extra information on why trace is not available. Either it wasn't executed yet (RECEIVED), or the transaction failed (REJECTED)", + "properties": { + "status": { + "type": "string", + "enum": [ + "RECEIVED", + "REJECTED" + ] + } + } + } + }, + "CONTRACT_NOT_FOUND": { + "code": 20, + "message": "Contract not found" + }, + "INVALID_TXN_HASH": { + "code": 25, + "message": "Invalid transaction hash" + }, + "BLOCK_NOT_FOUND": { + "code": 24, + "message": "Invalid block hash" + }, + "CONTRACT_ERROR": { + "code": 40, + "message": "Contract error" + } + } + } +} \ No newline at end of file diff --git a/crates/starknet-server/test_data/spec/0.5.0/starknet_write_api.json b/crates/starknet-server/test_data/spec/0.5.0/starknet_write_api.json new file mode 100644 index 000000000..b633a9e67 --- /dev/null +++ b/crates/starknet-server/test_data/spec/0.5.0/starknet_write_api.json @@ -0,0 +1,298 @@ +{ + "openrpc": "1.0.0-rc1", + "info": { + "version": "0.5.0", + "title": "StarkNet Node Write API", + "license": {} + }, + "servers": [], + "methods": [ + { + "name": "starknet_addInvokeTransaction", + "summary": "Submit a new transaction to be added to the chain", + "params": [ + { + "name": "invoke_transaction", + "description": "The information needed to invoke the function (or account, for version 1 transactions)", + "required": true, + "schema": { + "$ref": "#/components/schemas/BROADCASTED_INVOKE_TXN" + } + } + ], + "result": { + "name": "result", + "description": "The result of the transaction submission", + "schema": { + "type": "object", + "properties": { + "transaction_hash": { + "title": "The hash of the invoke transaction", + "$ref": "#/components/schemas/TXN_HASH" + } + }, + "required": [ + "transaction_hash" + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/INSUFFICIENT_ACCOUNT_BALANCE" + }, + { + "$ref": "#/components/errors/INSUFFICIENT_MAX_FEE" + }, + { + "$ref": "#/components/errors/INVALID_TRANSACTION_NONCE" + }, + { + "$ref": "#/components/errors/VALIDATION_FAILURE" + }, + { + "$ref": "#/components/errors/NON_ACCOUNT" + }, + { + "$ref": "#/components/errors/DUPLICATE_TX" + }, + { + "$ref": "#/components/errors/UNSUPPORTED_TX_VERSION" + }, + { + "$ref": "#/components/errors/UNEXPECTED_ERROR" + } + ] + }, + { + "name": "starknet_addDeclareTransaction", + "summary": "Submit a new class declaration transaction", + "params": [ + { + "name": "declare_transaction", + "description": "Declare transaction required to declare a new class on Starknet", + "required": true, + "schema": { + "title": "Declare transaction", + "$ref": "#/components/schemas/BROADCASTED_DECLARE_TXN" + } + } + ], + "result": { + "name": "result", + "description": "The result of the transaction submission", + "schema": { + "type": "object", + "properties": { + "transaction_hash": { + "title": "The hash of the declare transaction", + "$ref": "#/components/schemas/TXN_HASH" + }, + "class_hash": { + "title": "The hash of the declared class", + "$ref": "#/components/schemas/FELT" + } + }, + "required": [ + "transaction_hash", + "class_hash" + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/CLASS_ALREADY_DECLARED" + }, + { + "$ref": "#/components/errors/COMPILATION_FAILED" + }, + { + "$ref": "#/components/errors/COMPILED_CLASS_HASH_MISMATCH" + }, + { + "$ref": "#/components/errors/INSUFFICIENT_ACCOUNT_BALANCE" + }, + { + "$ref": "#/components/errors/INSUFFICIENT_MAX_FEE" + }, + { + "$ref": "#/components/errors/INVALID_TRANSACTION_NONCE" + }, + { + "$ref": "#/components/errors/VALIDATION_FAILURE" + }, + { + "$ref": "#/components/errors/NON_ACCOUNT" + }, + { + "$ref": "#/components/errors/DUPLICATE_TX" + }, + { + "$ref": "#/components/errors/CONTRACT_CLASS_SIZE_IS_TOO_LARGE" + }, + { + "$ref": "#/components/errors/UNSUPPORTED_TX_VERSION" + }, + { + "$ref": "#/components/errors/UNSUPPORTED_CONTRACT_CLASS_VERSION" + }, + { + "$ref": "#/components/errors/UNEXPECTED_ERROR" + } + ] + }, + { + "name": "starknet_addDeployAccountTransaction", + "summary": "Submit a new deploy account transaction", + "params": [ + { + "name": "deploy_account_transaction", + "description": "The deploy account transaction", + "required": true, + "schema": { + "$ref": "#/components/schemas/BROADCASTED_DEPLOY_ACCOUNT_TXN" + } + } + ], + "result": { + "name": "result", + "description": "The result of the transaction submission", + "schema": { + "type": "object", + "properties": { + "transaction_hash": { + "title": "The hash of the deploy transaction", + "$ref": "#/components/schemas/TXN_HASH" + }, + "contract_address": { + "title": "The address of the new contract", + "$ref": "#/components/schemas/FELT" + } + }, + "required": [ + "transaction_hash", + "contract_address" + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/INSUFFICIENT_ACCOUNT_BALANCE" + }, + { + "$ref": "#/components/errors/INSUFFICIENT_MAX_FEE" + }, + { + "$ref": "#/components/errors/INVALID_TRANSACTION_NONCE" + }, + { + "$ref": "#/components/errors/VALIDATION_FAILURE" + }, + { + "$ref": "#/components/errors/NON_ACCOUNT" + }, + { + "$ref": "#/components/errors/CLASS_HASH_NOT_FOUND" + }, + { + "$ref": "#/components/errors/DUPLICATE_TX" + }, + { + "$ref": "#/components/errors/UNSUPPORTED_TX_VERSION" + }, + { + "$ref": "#/components/errors/UNEXPECTED_ERROR" + } + ] + } + ], + "components": { + "contentDescriptors": {}, + "schemas": { + "NUM_AS_HEX": { + "title": "An integer number in hex format (0x...)", + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$" + }, + "SIGNATURE": { + "$ref": "./starknet_api_openrpc.json#/components/schemas/SIGNATURE" + }, + "FELT": { + "$ref": "./starknet_api_openrpc.json#/components/schemas/FELT" + }, + "TXN_HASH": { + "$ref": "./starknet_api_openrpc.json#/components/schemas/TXN_HASH" + }, + "BROADCASTED_INVOKE_TXN": { + "$ref": "./starknet_api_openrpc.json#/components/schemas/BROADCASTED_INVOKE_TXN" + }, + "DECLARE_TXN": { + "$ref": "./starknet_api_openrpc.json#/components/schemas/DECLARE_TXN" + }, + "BROADCASTED_DEPLOY_ACCOUNT_TXN": { + "$ref": "./starknet_api_openrpc.json#/components/schemas/BROADCASTED_DEPLOY_ACCOUNT_TXN" + }, + "FUNCTION_CALL": { + "$ref": "./starknet_api_openrpc.json#/components/schemas/FUNCTION_CALL" + } + }, + "errors": { + "CLASS_HASH_NOT_FOUND": { + "code": 28, + "message": "Class hash not found" + }, + "CLASS_ALREADY_DECLARED": { + "code": 51, + "message": "Class already declared" + }, + "INVALID_TRANSACTION_NONCE": { + "code": 52, + "message": "Invalid transaction nonce" + }, + "INSUFFICIENT_MAX_FEE": { + "code": 53, + "message": "Max fee is smaller than the minimal transaction cost (validation plus fee transfer)" + }, + "INSUFFICIENT_ACCOUNT_BALANCE": { + "code": 54, + "message": "Account balance is smaller than the transaction's max_fee" + }, + "VALIDATION_FAILURE": { + "code": 55, + "message": "Account validation failed" + }, + "COMPILATION_FAILED": { + "code": 56, + "message": "Compilation failed" + }, + "CONTRACT_CLASS_SIZE_IS_TOO_LARGE": { + "code": 57, + "message": "Contract class size it too large" + }, + "NON_ACCOUNT": { + "code": 58, + "message": "Sender address in not an account contract" + }, + "DUPLICATE_TX": { + "code": 59, + "message": "A transaction with the same hash already exists in the mempool" + }, + "COMPILED_CLASS_HASH_MISMATCH": { + "code": 60, + "message": "the compiled class hash did not match the one supplied in the transaction" + }, + "UNSUPPORTED_TX_VERSION": { + "code": 61, + "message": "the transaction version is not supported" + }, + "UNSUPPORTED_CONTRACT_CLASS_VERSION": { + "code": 62, + "message": "the contract class version is not supported" + }, + "UNEXPECTED_ERROR": { + "code": 63, + "message": "An unexpected error occurred", + "data": "string" + } + } + } +} diff --git a/crates/starknet-server/tests/common/data.rs b/crates/starknet-server/tests/common/data.rs deleted file mode 100644 index 8b1378917..000000000 --- a/crates/starknet-server/tests/common/data.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/starknet-server/tests/common/mod.rs b/crates/starknet-server/tests/common/mod.rs index b462f31f2..20fcc9f49 100644 --- a/crates/starknet-server/tests/common/mod.rs +++ b/crates/starknet-server/tests/common/mod.rs @@ -1,4 +1,3 @@ pub mod constants; -pub mod data; pub mod devnet; pub mod utils; diff --git a/crates/starknet-server/tests/test_estimate_fee.rs b/crates/starknet-server/tests/test_estimate_fee.rs index 120c429e3..26dd27f16 100644 --- a/crates/starknet-server/tests/test_estimate_fee.rs +++ b/crates/starknet-server/tests/test_estimate_fee.rs @@ -3,13 +3,14 @@ pub mod common; mod estimate_fee_tests { use std::sync::Arc; + use serde_json::json; use starknet_core::constants::{ CAIRO_0_ACCOUNT_CONTRACT_HASH, QUERY_VERSION_BASE, UDC_CONTRACT_ADDRESS, }; use starknet_core::utils::exported_test_utils::dummy_cairo_0_contract_class; use starknet_rs_accounts::{ - Account, AccountError, AccountFactory, AccountFactoryError, Call, ExecutionEncoding, - OpenZeppelinAccountFactory, SingleOwnerAccount, + Account, AccountError, AccountFactory, AccountFactoryError, Call, ConnectedAccount, + ExecutionEncoding, OpenZeppelinAccountFactory, SingleOwnerAccount, }; use starknet_rs_contract::ContractFactory; use starknet_rs_core::types::contract::legacy::LegacyContractClass; @@ -403,15 +404,27 @@ mod estimate_fee_tests { calldata: vec![cairo_short_string_to_felt(panic_reason).unwrap()], }]; - match account.execute(calls).estimate_fee().await { - Err(AccountError::Provider(ProviderError::StarknetError( - StarknetErrorWithMessage { - code: MaybeUnknownErrorCode::Known(StarknetError::ContractError), - message, - }, - ))) => assert!(message.contains(panic_reason)), - other => panic!("Unexpected result: {other:?}"), - } + let prepared = account + .execute(calls.clone()) + .nonce(account.get_nonce().await.unwrap()) + .max_fee(FieldElement::ZERO) + .prepared() + .unwrap() + .get_invoke_request(true) + .await + .unwrap(); + + let params = json!({ + "block_id": "latest", + "request": [ + serde_json::to_value(prepared).unwrap() + ] + }); + + let result = devnet.send_custom_rpc("starknet_estimateFee", params).await; + let revert_error = result["error"]["data"]["revert_error"].as_str().unwrap(); + + assert!(revert_error.contains(panic_reason)); } #[tokio::test] diff --git a/crates/starknet-server/tests/test_simulate_transactions.rs b/crates/starknet-server/tests/test_simulate_transactions.rs index 05cc6bc9e..c77b27d64 100644 --- a/crates/starknet-server/tests/test_simulate_transactions.rs +++ b/crates/starknet-server/tests/test_simulate_transactions.rs @@ -44,17 +44,27 @@ mod estimate_fee_tests { resp_no_flags: &serde_json::Value, resp_skip_validation: &serde_json::Value, expected_contract_adddress: &str, + should_skip_fee_invocation: bool, ) { let no_flags_trace = &resp_no_flags["transaction_trace"]; assert_eq!( no_flags_trace["validate_invocation"]["contract_address"].as_str().unwrap(), expected_contract_adddress ); - assert!(no_flags_trace["fee_transfer_invocation"].as_object().is_none()); + assert!(no_flags_trace["state_diff"].as_object().is_some()); let skip_validation_trace = &resp_skip_validation["transaction_trace"]; assert!(skip_validation_trace["validate_invocation"].as_object().is_none()); - assert!(skip_validation_trace["fee_transfer_invocation"].as_object().is_none()); + assert!(skip_validation_trace["state_diff"].as_object().is_some()); + + assert_eq!( + skip_validation_trace["fee_transfer_invocation"].as_object().is_none(), + should_skip_fee_invocation + ); + assert_eq!( + no_flags_trace["fee_transfer_invocation"].as_object().is_none(), + should_skip_fee_invocation + ); assert_fee_in_resp_greater(resp_no_flags, resp_skip_validation); } @@ -122,7 +132,12 @@ mod estimate_fee_tests { .send_custom_rpc("starknet_simulateTransactions", params_skip_validation) .await["result"][0]; - assert_difference_if_validation(resp_no_flags, resp_skip_validation, &sender_address_hex); + assert_difference_if_validation( + resp_no_flags, + resp_skip_validation, + &sender_address_hex, + max_fee == FieldElement::ZERO, + ); } #[tokio::test] @@ -189,7 +204,12 @@ mod estimate_fee_tests { .send_custom_rpc("starknet_simulateTransactions", params_skip_validation) .await["result"][0]; - assert_difference_if_validation(resp_no_flags, resp_skip_validation, &sender_address_hex); + assert_difference_if_validation( + resp_no_flags, + resp_skip_validation, + &sender_address_hex, + max_fee == FieldElement::ZERO, + ); } #[tokio::test] @@ -379,11 +399,12 @@ mod estimate_fee_tests { }; let params_no_flags = get_params(&[]); + let resp_no_flags = &devnet .send_custom_rpc("starknet_simulateTransactions", params_no_flags) .await["result"][0]; assert_eq!( - resp_no_flags["transaction_trace"]["execution_invocation"]["contract_address"], + resp_no_flags["transaction_trace"]["execute_invocation"]["contract_address"], sender_address_hex ); @@ -392,10 +413,15 @@ mod estimate_fee_tests { .send_custom_rpc("starknet_simulateTransactions", params_skip_validation) .await["result"][0]; assert_eq!( - resp_skip_validation["transaction_trace"]["execution_invocation"]["contract_address"], + resp_skip_validation["transaction_trace"]["execute_invocation"]["contract_address"], sender_address_hex ); - assert_difference_if_validation(resp_no_flags, resp_skip_validation, &sender_address_hex); + assert_difference_if_validation( + resp_no_flags, + resp_skip_validation, + &sender_address_hex, + max_fee == FieldElement::ZERO, + ); } } diff --git a/crates/starknet/src/blocks/mod.rs b/crates/starknet/src/blocks/mod.rs index 17d536e41..d865c5a25 100644 --- a/crates/starknet/src/blocks/mod.rs +++ b/crates/starknet/src/blocks/mod.rs @@ -6,7 +6,7 @@ use starknet_api::stark_felt; use starknet_rs_core::types::BlockId; use starknet_types::contract_address::ContractAddress; use starknet_types::felt::{BlockHash, Felt, TransactionHash}; -use starknet_types::rpc::block::BlockHeader as TypesBlockHeader; +use starknet_types::rpc::block::{BlockHeader as TypesBlockHeader, ResourcePrice}; use starknet_types::traits::HashProducer; use crate::error::{DevnetResult, Error}; @@ -157,6 +157,11 @@ impl From<&StarknetBlock> for TypesBlockHeader { sequencer_address: value.sequencer_address(), new_root: value.new_root(), timestamp: value.timestamp(), + starknet_version: env!("STARKNET_VERSION").to_string(), + l1_gas_price: ResourcePrice { + price_in_strk: None, + price_in_wei: value.header.gas_price.0.into(), + }, } } } diff --git a/crates/starknet/src/error.rs b/crates/starknet/src/error.rs index 255c3593e..cfe281c5d 100644 --- a/crates/starknet/src/error.rs +++ b/crates/starknet/src/error.rs @@ -14,8 +14,8 @@ pub enum Error { BlockifierStateError(#[from] blockifier::state::errors::StateError), #[error(transparent)] BlockifierTransactionError(#[from] blockifier::transaction::errors::TransactionExecutionError), - #[error("Failed execution during fee estimation: {msg}")] - EstimationExecutionError { msg: String }, + #[error("{revert_error}")] + ExecutionError { revert_error: String }, #[error("Types error")] TypesError(#[from] starknet_types::error::Error), #[error("I/O error")] diff --git a/crates/starknet/src/starknet/estimations.rs b/crates/starknet/src/starknet/estimations.rs index 289c7c471..8c64c152f 100644 --- a/crates/starknet/src/starknet/estimations.rs +++ b/crates/starknet/src/starknet/estimations.rs @@ -86,7 +86,7 @@ fn estimate_transaction_fee( )?; if let Some(revert_error) = transaction_execution_info.revert_error { - return Err(Error::EstimationExecutionError { msg: revert_error }); + return Err(Error::ExecutionError { revert_error }); } let (l1_gas_usage, vm_resources) = diff --git a/crates/starknet/src/starknet/mod.rs b/crates/starknet/src/starknet/mod.rs index 4baafd29f..c4a68f27b 100644 --- a/crates/starknet/src/starknet/mod.rs +++ b/crates/starknet/src/starknet/mod.rs @@ -7,11 +7,18 @@ use blockifier::transaction::objects::TransactionExecutionInfo; use blockifier::transaction::transactions::ExecutableTransaction; use starknet_api::block::{BlockNumber, BlockStatus, BlockTimestamp, GasPrice}; use starknet_api::transaction::Fee; -use starknet_rs_core::types::{BlockId, MsgFromL1, TransactionFinalityStatus}; +use starknet_rs_core::types::{ + BlockId, MsgFromL1, TransactionExecutionStatus, TransactionFinalityStatus, +}; use starknet_rs_core::utils::get_selector_from_name; use starknet_rs_ff::FieldElement; use starknet_rs_signers::Signer; use starknet_types::chain_id::ChainId; +use starknet_types::constants::{ + BITWISE_BUILTIN_NAME, EC_OP_BUILTIN_NAME, HASH_BUILTIN_NAME, KECCAK_BUILTIN_NAME, N_STEPS, + OUTPUT_BUILTIN_NAME, POSEIDON_BUILTIN_NAME, RANGE_CHECK_BUILTIN_NAME, + SEGMENT_ARENA_BUILTIN_NAME, SIGNATURE_BUILTIN_NAME, +}; use starknet_types::contract_address::ContractAddress; use starknet_types::contract_class::ContractClass; use starknet_types::contract_storage_key::ContractStorageKey; @@ -20,6 +27,7 @@ use starknet_types::felt::{ClassHash, Felt, TransactionHash}; use starknet_types::patricia_key::PatriciaKey; use starknet_types::rpc::block::{Block, BlockHeader}; use starknet_types::rpc::estimate_message_fee::FeeEstimateWrapper; +use starknet_types::rpc::state::ThinStateDiff; use starknet_types::rpc::transaction_receipt::TransactionReceipt; use starknet_types::rpc::transactions::broadcasted_declare_transaction_v1::BroadcastedDeclareTransactionV1; use starknet_types::rpc::transactions::broadcasted_declare_transaction_v2::BroadcastedDeclareTransactionV2; @@ -335,16 +343,16 @@ impl Starknet { strk_fee_token_address: contract_address!("0x1002"), }, vm_resource_fee_cost: std::sync::Arc::new(HashMap::from([ - ("n_steps".to_string(), N_STEPS_FEE_WEIGHT), - ("output_builtin".to_string(), 0.0), - ("pedersen_builtin".to_string(), N_STEPS_FEE_WEIGHT * 32.0), - ("range_check_builtin".to_string(), N_STEPS_FEE_WEIGHT * 16.0), - ("ecdsa_builtin".to_string(), N_STEPS_FEE_WEIGHT * 2048.0), - ("bitwise_builtin".to_string(), N_STEPS_FEE_WEIGHT * 64.0), - ("ec_op_builtin".to_string(), N_STEPS_FEE_WEIGHT * 1024.0), - ("poseidon_builtin".to_string(), N_STEPS_FEE_WEIGHT * 32.0), - ("segment_arena_builtin".to_string(), N_STEPS_FEE_WEIGHT * 10.0), - ("keccak_builtin".to_string(), N_STEPS_FEE_WEIGHT * 2048.0), // 2**11 + (N_STEPS.to_string(), N_STEPS_FEE_WEIGHT), + (OUTPUT_BUILTIN_NAME.to_string(), 0.0), + (HASH_BUILTIN_NAME.to_string(), N_STEPS_FEE_WEIGHT * 32.0), + (RANGE_CHECK_BUILTIN_NAME.to_string(), N_STEPS_FEE_WEIGHT * 16.0), + (SIGNATURE_BUILTIN_NAME.to_string(), N_STEPS_FEE_WEIGHT * 2048.0), + (BITWISE_BUILTIN_NAME.to_string(), N_STEPS_FEE_WEIGHT * 64.0), + (EC_OP_BUILTIN_NAME.to_string(), N_STEPS_FEE_WEIGHT * 1024.0), + (POSEIDON_BUILTIN_NAME.to_string(), N_STEPS_FEE_WEIGHT * 32.0), + (SEGMENT_ARENA_BUILTIN_NAME.to_string(), N_STEPS_FEE_WEIGHT * 10.0), + (KECCAK_BUILTIN_NAME.to_string(), N_STEPS_FEE_WEIGHT * 2048.0), // 2**11 ])), gas_prices: blockifier::block_context::GasPrices { eth_l1_gas_price: gas_price as u128, @@ -677,6 +685,15 @@ impl Starknet { transaction_to_map.get_receipt() } + pub fn get_transaction_execution_and_finality_status( + &self, + transaction_hash: TransactionHash, + ) -> DevnetResult<(TransactionExecutionStatus, TransactionFinalityStatus)> { + let transaction = self.transactions.get(&transaction_hash).ok_or(Error::NoTransaction)?; + + Ok((transaction.execution_result.status(), transaction.finality_status)) + } + pub fn simulate_transactions( &self, block_id: BlockId, @@ -709,6 +726,9 @@ impl Starknet { !skip_fee_charge, !skip_validate, )?; + + let state_diff: ThinStateDiff = state.extract_state_diff_from_pending_state()?.into(); + let address_to_class_hash_map = &state.state.state.address_to_class_hash; let validate_invocation = @@ -736,6 +756,7 @@ impl Starknet { TransactionTrace::Declare(DeclareTransactionTrace { validate_invocation, fee_transfer_invocation, + state_diff, }) } BroadcastedTransaction::DeployAccount(_) => { @@ -752,12 +773,15 @@ impl Starknet { None }, fee_transfer_invocation, + state_diff, }) } BroadcastedTransaction::Invoke(_) => { TransactionTrace::Invoke(InvokeTransactionTrace { + fee_transfer_invocation, validate_invocation, - execution_invocation: match tx_execution_info.execute_call_info { + state_diff, + execute_invocation: match tx_execution_info.execute_call_info { Some(call_info) => match call_info.execution.failed { false => ExecutionInvocation::Succeeded( FunctionInvocation::try_from_call_info( diff --git a/crates/starknet/src/starknet/state_update.rs b/crates/starknet/src/starknet/state_update.rs index 3bd1a9db1..93863e44e 100644 --- a/crates/starknet/src/starknet/state_update.rs +++ b/crates/starknet/src/starknet/state_update.rs @@ -12,7 +12,7 @@ pub fn state_update_by_block_id( let state_diff = starknet.blocks.num_to_state_diff.get(&block.block_number()).cloned().unwrap_or_default(); - StateUpdate::new(block.block_hash(), state_diff) + Ok(StateUpdate::new(block.block_hash(), state_diff)) } #[cfg(test)] @@ -23,6 +23,7 @@ mod tests { use starknet_types::contract_address::ContractAddress; use starknet_types::contract_class::{compute_casm_class_hash, Cairo0Json, ContractClass}; use starknet_types::felt::Felt; + use starknet_types::rpc::state::ThinStateDiff; use starknet_types::rpc::transactions::broadcasted_declare_transaction_v2::BroadcastedDeclareTransactionV2; use starknet_types::traits::HashProducer; @@ -30,7 +31,6 @@ mod tests { use crate::constants::{self, DEVNET_DEFAULT_CHAIN_ID}; use crate::starknet::{predeployed, Starknet}; use crate::state::state_diff::StateDiff; - use crate::state::state_update::StateUpdate; use crate::traits::{Accounted, Deployed, HashIdentifiedMut}; use crate::utils::test_utils::{dummy_cairo_1_contract_class, dummy_felt}; @@ -68,24 +68,25 @@ mod tests { )) .unwrap(); - let expected_state_diff = StateDiff { + let state_diff: ThinStateDiff = state_update.state_diff.into(); + + let expected_state_diff: ThinStateDiff = StateDiff { declared_contracts: vec![compiled_class_hash], class_hash_to_compiled_class_hash: vec![(sierra_class_hash, compiled_class_hash)] .into_iter() .collect(), ..Default::default() - }; - - let expected_state_update = StateUpdate::new(Felt::default(), expected_state_diff).unwrap(); + } + .into(); // check only 3 of the 4 fields, because the inner property has changes to the storage of // the ERC20 contract which are hard to be tested correctly, it depends on the fee // calculation of starknet_in_rust_library assert_eq!( - state_update.cairo_0_declared_classes, - expected_state_update.cairo_0_declared_classes + state_diff.deprecated_declared_classes, + expected_state_diff.deprecated_declared_classes ); - assert_eq!(state_update.declared_classes, expected_state_update.declared_classes); + assert_eq!(state_diff.declared_classes, expected_state_diff.declared_classes); } /// Initializes starknet with account_without_validations diff --git a/crates/starknet/src/state/state_diff.rs b/crates/starknet/src/state/state_diff.rs index 296bac17b..af06690e1 100644 --- a/crates/starknet/src/state/state_diff.rs +++ b/crates/starknet/src/state/state_diff.rs @@ -5,6 +5,9 @@ use blockifier::state::state_api::State; use starknet_types::contract_address::ContractAddress; use starknet_types::felt::{ClassHash, Felt}; use starknet_types::patricia_key::{PatriciaKey, StorageKey}; +use starknet_types::rpc::state::{ + ClassHashes, ContractNonce, DeployedContract, StorageDiff, StorageEntry, ThinStateDiff, +}; use super::DevnetState; use crate::error::DevnetResult; @@ -118,6 +121,59 @@ impl StateDiff { } } +impl From for ThinStateDiff { + fn from(value: StateDiff) -> Self { + let declared_classes: Vec<(Felt, Felt)> = + value.class_hash_to_compiled_class_hash.into_iter().collect(); + + // cairo 0 declarations + let cairo_0_declared_classes: Vec = value.cairo_0_declared_contracts; + + // storage updates (contract address -> [(storage_entry, value)]) + let storage_updates: Vec<(ContractAddress, Vec<(PatriciaKey, Felt)>)> = value + .storage_updates + .into_iter() + .map(|(address, entries)| (address, entries.into_iter().collect())) + .collect(); + + // contract nonces + let nonces: Vec<(ContractAddress, Felt)> = value.address_to_nonce.into_iter().collect(); + + // deployed contracts (address -> class hash) + let deployed_contracts: Vec<(ContractAddress, Felt)> = + value.address_to_class_hash.into_iter().collect(); + + ThinStateDiff { + deployed_contracts: deployed_contracts + .into_iter() + .map(|(address, class_hash)| DeployedContract { address, class_hash }) + .collect(), + declared_classes: declared_classes + .into_iter() + .map(|(class_hash, compiled_class_hash)| ClassHashes { + class_hash, + compiled_class_hash, + }) + .collect(), + deprecated_declared_classes: cairo_0_declared_classes, + nonces: nonces + .into_iter() + .map(|(address, nonce)| ContractNonce { contract_address: address, nonce }) + .collect(), + storage_diffs: storage_updates + .into_iter() + .map(|(contract_address, updates)| StorageDiff { + address: contract_address, + storage_entries: updates + .into_iter() + .map(|(key, value)| StorageEntry { key, value }) + .collect(), + }) + .collect(), + replaced_classes: vec![], + } + } +} #[cfg(test)] mod tests { diff --git a/crates/starknet/src/state/state_update.rs b/crates/starknet/src/state/state_update.rs index cbef96fc0..e1acf7c0b 100644 --- a/crates/starknet/src/state/state_update.rs +++ b/crates/starknet/src/state/state_update.rs @@ -1,52 +1,17 @@ -use starknet_types::contract_address::ContractAddress; -use starknet_types::felt::{ClassHash, Felt}; -use starknet_types::patricia_key::PatriciaKey; +use starknet_types::felt::Felt; use super::state_diff::StateDiff; -use crate::error::DevnetResult; pub struct StateUpdate { pub block_hash: Felt, pub new_root: Felt, pub old_root: Felt, - pub declared_classes: Vec<(ClassHash, ClassHash)>, - pub cairo_0_declared_classes: Vec, - pub storage_updates: Vec<(ContractAddress, Vec<(PatriciaKey, Felt)>)>, - pub nonces: Vec<(ContractAddress, Felt)>, - pub deployed_contracts: Vec<(ContractAddress, ClassHash)>, + pub state_diff: StateDiff, } impl StateUpdate { - pub fn new(block_hash: Felt, state_diff: StateDiff) -> DevnetResult { - // declared classes (class hash, compiled class hash) that are not cairo 0 - let declared_classes = state_diff.class_hash_to_compiled_class_hash.into_iter().collect(); - - // cairo 0 declarations - let cairo_0_declared_classes: Vec = state_diff.cairo_0_declared_contracts; - - // storage updates (contract address -> [(storage_entry, value)]) - let storage_updates = state_diff - .storage_updates - .into_iter() - .map(|(address, entries)| (address, entries.into_iter().collect())) - .collect(); - - // contract nonces - let nonces = state_diff.address_to_nonce.into_iter().collect(); - - // deployed contracts (address -> class hash) - let deployed_contracts = state_diff.address_to_class_hash.into_iter().collect(); - + pub fn new(block_hash: Felt, state_diff: StateDiff) -> Self { // TODO new and old root are not computed, they are not part of the MVP - Ok(Self { - block_hash, - new_root: Felt::default(), - old_root: Felt::default(), - declared_classes, - cairo_0_declared_classes, - storage_updates, - nonces, - deployed_contracts, - }) + Self { block_hash, new_root: Felt::default(), old_root: Felt::default(), state_diff } } } diff --git a/crates/starknet/src/transactions.rs b/crates/starknet/src/transactions.rs index 379e0867c..c20ddeba7 100644 --- a/crates/starknet/src/transactions.rs +++ b/crates/starknet/src/transactions.rs @@ -5,7 +5,7 @@ use starknet_api::block::BlockNumber; use starknet_rs_core::types::{ExecutionResult, TransactionFinalityStatus}; use starknet_rs_core::utils::get_selector_from_name; use starknet_types::contract_address::ContractAddress; -use starknet_types::emitted_event::Event; +use starknet_types::emitted_event::{Event, OrderedEvent}; use starknet_types::felt::{BlockHash, Felt, TransactionHash}; use starknet_types::rpc::transaction_receipt::{DeployTransactionReceipt, TransactionReceipt}; use starknet_types::rpc::transactions::{Transaction, TransactionType}; @@ -87,19 +87,16 @@ impl StarknetTransaction { fn get_blockifier_events_recursively( call_info: &blockifier::execution::call_info::CallInfo, - ) -> Vec<(usize, Event)> { - let mut events: Vec<(usize, Event)> = vec![]; - - events.extend(call_info.execution.events.iter().map(|e| { - ( - e.order, - Event { - from_address: call_info.call.storage_address.into(), - data: e.event.data.0.iter().map(|d| (*d).into()).collect(), - keys: e.event.keys.iter().map(|k| k.0.into()).collect(), - }, - ) - })); + ) -> Vec { + let mut events: Vec = vec![]; + + events.extend( + call_info + .execution + .events + .iter() + .map(|e| OrderedEvent::new(e, call_info.call.storage_address.into())), + ); call_info.inner_calls.iter().for_each(|call| { events.extend(get_blockifier_events_recursively(call)); @@ -108,22 +105,16 @@ impl StarknetTransaction { events } - if let Some(validate_call_info) = self.execution_info.validate_call_info.as_ref() { - let mut not_sorted_events = get_blockifier_events_recursively(validate_call_info); - not_sorted_events.sort_by_key(|(order, _)| *order); - events.extend(not_sorted_events.into_iter().map(|(_, e)| e)); - } - - if let Some(execution_call_info) = self.execution_info.execute_call_info.as_ref() { - let mut not_sorted_events = get_blockifier_events_recursively(execution_call_info); - not_sorted_events.sort_by_key(|(order, _)| *order); - events.extend(not_sorted_events.into_iter().map(|(_, e)| e)); - } + let call_infos = vec![ + self.execution_info.validate_call_info.as_ref(), + self.execution_info.execute_call_info.as_ref(), + self.execution_info.fee_transfer_call_info.as_ref(), + ]; - if let Some(fee_transfer_call_info) = self.execution_info.fee_transfer_call_info.as_ref() { - let mut not_sorted_events = get_blockifier_events_recursively(fee_transfer_call_info); - not_sorted_events.sort_by_key(|(order, _)| *order); - events.extend(not_sorted_events.into_iter().map(|(_, e)| e)); + for inner_call_info in call_infos.into_iter().flatten() { + let mut not_sorted_events = get_blockifier_events_recursively(inner_call_info); + not_sorted_events.sort_by_key(|event| event.order); + events.extend(not_sorted_events.into_iter().map(|e| e.event)); } events @@ -166,6 +157,7 @@ impl StarknetTransaction { &self.execution_result, self.finality_status, self.execution_info.actual_fee, + &self.execution_info, ); match &self.inner { diff --git a/crates/types/src/constants.rs b/crates/types/src/constants.rs new file mode 100644 index 000000000..5eaf55539 --- /dev/null +++ b/crates/types/src/constants.rs @@ -0,0 +1,10 @@ +pub const OUTPUT_BUILTIN_NAME: &str = "output_builtin"; +pub const HASH_BUILTIN_NAME: &str = "pedersen_builtin"; +pub const RANGE_CHECK_BUILTIN_NAME: &str = "range_check_builtin"; +pub const SIGNATURE_BUILTIN_NAME: &str = "ecdsa_builtin"; +pub const BITWISE_BUILTIN_NAME: &str = "bitwise_builtin"; +pub const EC_OP_BUILTIN_NAME: &str = "ec_op_builtin"; +pub const KECCAK_BUILTIN_NAME: &str = "keccak_builtin"; +pub const POSEIDON_BUILTIN_NAME: &str = "poseidon_builtin"; +pub const SEGMENT_ARENA_BUILTIN_NAME: &str = "segment_arena_builtin"; +pub const N_STEPS: &str = "n_steps"; diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 44165c7b5..d2e7155ec 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -1,4 +1,5 @@ pub mod chain_id; +pub mod constants; pub mod contract_storage_key; pub mod error; pub mod patricia_key; diff --git a/crates/types/src/patricia_key.rs b/crates/types/src/patricia_key.rs index 4b60189dc..ac8b56964 100644 --- a/crates/types/src/patricia_key.rs +++ b/crates/types/src/patricia_key.rs @@ -1,7 +1,11 @@ +use serde::{Deserialize, Deserializer, Serialize}; use starknet_api::core::{CONTRACT_ADDRESS_DOMAIN_SIZE, PATRICIA_KEY_UPPER_BOUND}; use crate::error::{DevnetResult, Error}; use crate::felt::Felt; +use crate::serde_helpers::hex_string::{ + deserialize_to_prefixed_patricia_key, serialize_patricia_key_to_prefixed_hex, +}; #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] pub struct PatriciaKey(pub(crate) Felt); @@ -22,6 +26,24 @@ impl PatriciaKey { } } +impl Serialize for PatriciaKey { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serialize_patricia_key_to_prefixed_hex(self, serializer) + } +} + +impl<'de> Deserialize<'de> for PatriciaKey { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserialize_to_prefixed_patricia_key(deserializer) + } +} + impl From for PatriciaKey { fn from(value: starknet_api::core::PatriciaKey) -> Self { Self(value.into()) diff --git a/crates/types/src/rpc.rs b/crates/types/src/rpc.rs index 395a68ef2..2cb0ce12e 100644 --- a/crates/types/src/rpc.rs +++ b/crates/types/src/rpc.rs @@ -6,5 +6,6 @@ pub mod estimate_message_fee; pub mod eth_address; pub mod felt; mod macro_utils; +pub mod state; pub mod transaction_receipt; pub mod transactions; diff --git a/crates/types/src/rpc/block.rs b/crates/types/src/rpc/block.rs index 344685a8f..696fbd619 100644 --- a/crates/types/src/rpc/block.rs +++ b/crates/types/src/rpc/block.rs @@ -77,6 +77,7 @@ impl From for BlockId { } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] pub struct Block { pub status: BlockStatus, #[serde(flatten)] @@ -92,6 +93,17 @@ pub struct BlockHeader { pub sequencer_address: ContractAddress, pub new_root: GlobalRootHex, pub timestamp: BlockTimestamp, + pub starknet_version: String, + pub l1_gas_price: ResourcePrice, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +pub struct ResourcePrice { + // for now this will be always None, this field is introduced in 0.5.0 + // but current version of blockifier doesnt return this value + #[serde(skip_serializing_if = "Option::is_none")] + pub price_in_strk: Option, + pub price_in_wei: Felt, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] diff --git a/crates/types/src/rpc/emitted_event.rs b/crates/types/src/rpc/emitted_event.rs index b9a2f3cbf..faf492454 100644 --- a/crates/types/src/rpc/emitted_event.rs +++ b/crates/types/src/rpc/emitted_event.rs @@ -4,7 +4,8 @@ use starknet_api::block::BlockNumber; use crate::contract_address::ContractAddress; use crate::felt::{BlockHash, Felt, TransactionHash}; -#[derive(Serialize, Clone, Debug)] +#[derive(Serialize, Clone, Debug, Deserialize)] +#[serde(deny_unknown_fields)] pub struct EmittedEvent { pub transaction_hash: TransactionHash, pub block_hash: BlockHash, @@ -14,8 +15,33 @@ pub struct EmittedEvent { } #[derive(Serialize, Clone, Debug, PartialEq, Eq, Deserialize)] +#[serde(deny_unknown_fields)] pub struct Event { pub from_address: ContractAddress, pub keys: Vec, pub data: Vec, } + +#[derive(Serialize, Clone, Debug, PartialEq, Eq, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct OrderedEvent { + pub order: usize, + #[serde(flatten)] + pub event: Event, +} + +impl OrderedEvent { + pub fn new( + event: &blockifier::execution::call_info::OrderedEvent, + from_adress: ContractAddress, + ) -> Self { + Self { + order: event.order, + event: Event { + from_address: from_adress, + keys: event.event.keys.iter().map(|k| k.0.into()).collect(), + data: event.event.data.0.iter().map(|d| (*d).into()).collect(), + }, + } + } +} diff --git a/crates/starknet-server/src/api/models/state.rs b/crates/types/src/rpc/state.rs similarity index 89% rename from crates/starknet-server/src/api/models/state.rs rename to crates/types/src/rpc/state.rs index 611dadde7..7f65dc01c 100644 --- a/crates/starknet-server/src/api/models/state.rs +++ b/crates/types/src/rpc/state.rs @@ -1,13 +1,14 @@ use serde::{Deserialize, Serialize}; -use starknet_types::contract_address::ContractAddress; -use starknet_types::felt::{BlockHash, ClassHash, Felt, Nonce}; -use starknet_types::rpc::block::GlobalRootHex; -use super::PatriciaKeyHex; +use super::block::GlobalRootHex; +use crate::contract_address::ContractAddress; +use crate::felt::{BlockHash, ClassHash, Felt, Nonce}; +use crate::patricia_key::PatriciaKey; pub type CompiledClassHashHex = Felt; #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] pub struct StateUpdate { pub block_hash: BlockHash, pub new_root: GlobalRootHex, @@ -43,7 +44,7 @@ pub struct StorageDiff { /// A storage entry in a contract. #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] pub struct StorageEntry { - pub key: PatriciaKeyHex, + pub key: PatriciaKey, pub value: Felt, } diff --git a/crates/types/src/rpc/transaction_receipt.rs b/crates/types/src/rpc/transaction_receipt.rs index 23d81fd04..225f7b5e5 100644 --- a/crates/types/src/rpc/transaction_receipt.rs +++ b/crates/types/src/rpc/transaction_receipt.rs @@ -17,6 +17,7 @@ pub enum TransactionReceipt { } #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct DeployTransactionReceipt { #[serde(flatten)] pub common: CommonTransactionReceipt, @@ -32,6 +33,7 @@ pub struct MaybePendingProperties { } #[derive(Debug, Clone, Serialize, Deserialize)] +//#[serde(deny_unknown_fields)] pub struct CommonTransactionReceipt { pub r#type: TransactionType, pub transaction_hash: TransactionHash, @@ -42,6 +44,21 @@ pub struct CommonTransactionReceipt { pub finality_status: TransactionFinalityStatus, #[serde(flatten)] pub maybe_pending_properties: MaybePendingProperties, + pub execution_resources: ExecutionResources, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +#[serde(deny_unknown_fields)] +pub struct ExecutionResources { + pub steps: Felt, + pub memory_holes: Felt, + pub range_check_builtin_applications: Felt, + pub pedersen_builtin_applications: Felt, + pub poseidon_builtin_applications: Felt, + pub ec_op_builtin_applications: Felt, + pub ecdsa_builtin_applications: Felt, + pub bitwise_builtin_applications: Felt, + pub keccak_builtin_applications: Felt, } impl PartialEq for CommonTransactionReceipt { @@ -59,6 +76,7 @@ impl PartialEq for CommonTransactionReceipt { && self.r#type == other.r#type && self.maybe_pending_properties == other.maybe_pending_properties && self.output == other.output + && self.execution_resources == other.execution_resources && identical_execution_result } } @@ -66,6 +84,7 @@ impl PartialEq for CommonTransactionReceipt { impl Eq for CommonTransactionReceipt {} #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct TransactionOutput { pub actual_fee: Fee, pub messages_sent: Vec, @@ -76,44 +95,33 @@ pub type L2ToL1Payload = Vec; /// An L2 to L1 message. #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] pub struct MessageToL1 { pub from_address: ContractAddress, pub to_address: EthAddress, pub payload: L2ToL1Payload, } -#[cfg(test)] -mod tests { - use starknet_rs_core::types::MaybePendingTransactionReceipt; - - use crate::rpc::transaction_receipt::TransactionReceipt; - - #[test] - fn test_invoke_accepted_serialization() { - let receipt_path = - concat!(env!("CARGO_MANIFEST_DIR"), "/test_data/rpc/invoke_accepted.json"); - let receipt = std::fs::read_to_string(receipt_path).unwrap(); - - let _: TransactionReceipt = serde_json::from_str(&receipt).unwrap(); - } - - #[test] - fn test_invoke_accepted_conversion() { - let receipt_path = - concat!(env!("CARGO_MANIFEST_DIR"), "/test_data/rpc/invoke_accepted.json"); - let receipt = std::fs::read_to_string(receipt_path).unwrap(); - - let receipt: TransactionReceipt = serde_json::from_str(&receipt).unwrap(); - let serialized_receipt = serde_json::to_value(receipt).unwrap(); - let _: MaybePendingTransactionReceipt = serde_json::from_value(serialized_receipt).unwrap(); - } - - #[test] - fn test_declare_accepted_serialization() { - let receipt_path = - concat!(env!("CARGO_MANIFEST_DIR"), "/test_data/rpc/declare_accepted.json"); - let receipt = std::fs::read_to_string(receipt_path).unwrap(); +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct OrderedMessageToL1 { + pub order: usize, + #[serde(flatten)] + pub message: MessageToL1, +} - let _: TransactionReceipt = serde_json::from_str(&receipt).unwrap(); +impl OrderedMessageToL1 { + pub fn new( + msg: blockifier::execution::call_info::OrderedL2ToL1Message, + from_address: ContractAddress, + ) -> Self { + Self { + order: msg.order, + message: MessageToL1 { + from_address, + to_address: msg.message.to_address, + payload: msg.message.payload.0.into_iter().map(Felt::from).collect(), + }, + } } } diff --git a/crates/types/src/rpc/transactions.rs b/crates/types/src/rpc/transactions.rs index 94cc6a61d..0d9a34246 100644 --- a/crates/types/src/rpc/transactions.rs +++ b/crates/types/src/rpc/transactions.rs @@ -1,6 +1,8 @@ use std::collections::HashMap; +use blockifier::execution::call_info::CallInfo; use blockifier::transaction::account_transaction::AccountTransaction; +use blockifier::transaction::objects::TransactionExecutionInfo; use broadcasted_declare_transaction_v1::BroadcastedDeclareTransactionV1; use broadcasted_declare_transaction_v2::BroadcastedDeclareTransactionV2; use broadcasted_deploy_account_transaction::BroadcastedDeployAccountTransaction; @@ -17,9 +19,14 @@ use starknet_api::transaction::Fee; use starknet_rs_core::types::{BlockId, ExecutionResult, TransactionFinalityStatus}; use super::estimate_message_fee::FeeEstimateWrapper; -use super::transaction_receipt::MessageToL1; +use super::state::ThinStateDiff; +use super::transaction_receipt::{ExecutionResources, OrderedMessageToL1}; +use crate::constants::{ + BITWISE_BUILTIN_NAME, EC_OP_BUILTIN_NAME, HASH_BUILTIN_NAME, KECCAK_BUILTIN_NAME, N_STEPS, + POSEIDON_BUILTIN_NAME, RANGE_CHECK_BUILTIN_NAME, SIGNATURE_BUILTIN_NAME, +}; use crate::contract_address::ContractAddress; -use crate::emitted_event::Event; +use crate::emitted_event::{Event, OrderedEvent}; use crate::error::{ConversionError, DevnetResult}; use crate::felt::{ BlockHash, Calldata, EntryPointSelector, Felt, Nonce, TransactionHash, TransactionSignature, @@ -98,6 +105,7 @@ impl Transaction { } } + #[allow(clippy::too_many_arguments)] pub fn create_common_receipt( &self, transaction_events: &[Event], @@ -106,9 +114,60 @@ impl Transaction { execution_result: &ExecutionResult, finality_status: TransactionFinalityStatus, actual_fee: Fee, + execution_info: &TransactionExecutionInfo, ) -> CommonTransactionReceipt { let r#type = self.get_type(); + fn get_memory_holes_from_call_info(call_info: &Option) -> usize { + if let Some(call) = call_info { call.vm_resources.n_memory_holes } else { 0 } + } + + fn get_resource_from_execution_info( + execution_info: &TransactionExecutionInfo, + resource_name: &str, + ) -> Felt { + let resource = + execution_info.actual_resources.0.get(resource_name).cloned().unwrap_or_default(); + Felt::from(resource as u128) + } + + let total_memory_holes = get_memory_holes_from_call_info(&execution_info.execute_call_info) + + get_memory_holes_from_call_info(&execution_info.validate_call_info) + + get_memory_holes_from_call_info(&execution_info.fee_transfer_call_info); + + let execution_resources = ExecutionResources { + steps: get_resource_from_execution_info(execution_info, N_STEPS), + memory_holes: Felt::from(total_memory_holes as u128), + range_check_builtin_applications: get_resource_from_execution_info( + execution_info, + RANGE_CHECK_BUILTIN_NAME, + ), + pedersen_builtin_applications: get_resource_from_execution_info( + execution_info, + HASH_BUILTIN_NAME, + ), + poseidon_builtin_applications: get_resource_from_execution_info( + execution_info, + POSEIDON_BUILTIN_NAME, + ), + ec_op_builtin_applications: get_resource_from_execution_info( + execution_info, + EC_OP_BUILTIN_NAME, + ), + ecdsa_builtin_applications: get_resource_from_execution_info( + execution_info, + SIGNATURE_BUILTIN_NAME, + ), + bitwise_builtin_applications: get_resource_from_execution_info( + execution_info, + BITWISE_BUILTIN_NAME, + ), + keccak_builtin_applications: get_resource_from_execution_info( + execution_info, + KECCAK_BUILTIN_NAME, + ), + }; + let output = TransactionOutput { actual_fee, messages_sent: Vec::new(), // TODO wrong @@ -125,6 +184,7 @@ impl Transaction { execution_status: execution_result.clone(), finality_status, maybe_pending_properties, + execution_resources, } } } @@ -206,7 +266,7 @@ pub struct EventFilter { pub chunk_size: usize, } -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct EventsChunk { pub events: Vec, #[serde(skip_serializing_if = "Option::is_none")] @@ -214,6 +274,7 @@ pub struct EventsChunk { } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] pub struct FunctionCall { pub contract_address: ContractAddress, pub entry_point_selector: EntryPointSelector, @@ -292,7 +353,7 @@ pub enum SimulationFlag { SkipFeeCharge, } -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub enum CallType { #[serde(rename = "LIBRARY_CALL")] LibraryCall, @@ -300,7 +361,7 @@ pub enum CallType { Call, } -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct FunctionInvocation { #[serde(flatten)] function_call: FunctionCall, @@ -310,15 +371,18 @@ pub struct FunctionInvocation { call_type: CallType, result: Vec, calls: Vec, - events: Vec, - messages: Vec, + events: Vec, + messages: Vec, } -#[derive(Debug, Clone, Serialize)] -#[serde(untagged)] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type")] pub enum TransactionTrace { + #[serde(rename = "INVOKE")] Invoke(InvokeTransactionTrace), + #[serde(rename = "DECLARE")] Declare(DeclareTransactionTrace), + #[serde(rename = "DEPLOY_ACCOUNT")] DeployAccount(DeployAccountTransactionTrace), } @@ -327,33 +391,41 @@ pub struct Reversion { pub revert_reason: String, } -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum ExecutionInvocation { Succeeded(FunctionInvocation), Reverted(Reversion), } -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct InvokeTransactionTrace { pub validate_invocation: Option, - pub execution_invocation: ExecutionInvocation, + pub execute_invocation: ExecutionInvocation, + pub fee_transfer_invocation: Option, + pub state_diff: ThinStateDiff, } -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct DeclareTransactionTrace { pub validate_invocation: Option, pub fee_transfer_invocation: Option, + pub state_diff: ThinStateDiff, } -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct DeployAccountTransactionTrace { pub validate_invocation: Option, pub constructor_invocation: Option, pub fee_transfer_invocation: Option, + pub state_diff: ThinStateDiff, } -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct SimulatedTransaction { pub transaction_trace: TransactionTrace, pub fee_estimation: FeeEstimateWrapper, @@ -378,28 +450,20 @@ impl FunctionInvocation { call_info.execution.l2_to_l1_messages.sort_by_key(|msg| msg.order); - let messages: Vec = call_info + let messages: Vec = call_info .execution .l2_to_l1_messages .into_iter() - .map(|msg| MessageToL1 { - from_address: call_info.call.caller_address.into(), - to_address: msg.message.to_address, - payload: msg.message.payload.0.into_iter().map(Felt::from).collect(), - }) + .map(|msg| OrderedMessageToL1::new(msg, call_info.call.caller_address.into())) .collect(); call_info.execution.events.sort_by_key(|event| event.order); - let events: Vec = call_info + let events: Vec = call_info .execution .events .into_iter() - .map(|event| Event { - from_address: call_info.call.storage_address.into(), - keys: event.event.keys.into_iter().map(|key| Felt::from(key.0)).collect(), - data: event.event.data.0.into_iter().map(Felt::from).collect(), - }) + .map(|event| OrderedEvent::new(&event, call_info.call.storage_address.into())) .collect(); let function_call = FunctionCall { diff --git a/crates/types/src/rpc/transactions/broadcasted_declare_transaction_v1.rs b/crates/types/src/rpc/transactions/broadcasted_declare_transaction_v1.rs index af6dfdbac..1cc084ec1 100644 --- a/crates/types/src/rpc/transactions/broadcasted_declare_transaction_v1.rs +++ b/crates/types/src/rpc/transactions/broadcasted_declare_transaction_v1.rs @@ -23,6 +23,7 @@ use crate::rpc::transactions::BroadcastedTransactionCommon; use crate::traits::HashProducer; #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] pub struct BroadcastedDeclareTransactionV1 { #[serde(flatten)] pub common: BroadcastedTransactionCommon, diff --git a/crates/types/src/rpc/transactions/broadcasted_declare_transaction_v2.rs b/crates/types/src/rpc/transactions/broadcasted_declare_transaction_v2.rs index 0e0952cce..7a9cd330f 100644 --- a/crates/types/src/rpc/transactions/broadcasted_declare_transaction_v2.rs +++ b/crates/types/src/rpc/transactions/broadcasted_declare_transaction_v2.rs @@ -18,6 +18,7 @@ use crate::rpc::transactions::BroadcastedTransactionCommon; use crate::serde_helpers::rpc_sierra_contract_class_to_sierra_contract_class::deserialize_to_sierra_contract_class; #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] pub struct BroadcastedDeclareTransactionV2 { #[serde(flatten)] pub common: BroadcastedTransactionCommon, diff --git a/crates/types/src/rpc/transactions/broadcasted_deploy_account_transaction.rs b/crates/types/src/rpc/transactions/broadcasted_deploy_account_transaction.rs index f8ad2719a..6c9ef2d35 100644 --- a/crates/types/src/rpc/transactions/broadcasted_deploy_account_transaction.rs +++ b/crates/types/src/rpc/transactions/broadcasted_deploy_account_transaction.rs @@ -24,6 +24,7 @@ const PREFIX_DEPLOY_ACCOUNT: FieldElement = FieldElement::from_mont([ ]); #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] pub struct BroadcastedDeployAccountTransaction { #[serde(flatten)] pub common: BroadcastedTransactionCommon, diff --git a/crates/types/src/rpc/transactions/broadcasted_invoke_transaction.rs b/crates/types/src/rpc/transactions/broadcasted_invoke_transaction.rs index 76430e17d..066d1b844 100644 --- a/crates/types/src/rpc/transactions/broadcasted_invoke_transaction.rs +++ b/crates/types/src/rpc/transactions/broadcasted_invoke_transaction.rs @@ -24,6 +24,7 @@ const PREFIX_INVOKE: FieldElement = FieldElement::from_mont([ ]); #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] pub struct BroadcastedInvokeTransaction { #[serde(flatten)] pub common: BroadcastedTransactionCommon, diff --git a/crates/types/src/rpc/transactions/declare_transaction_v0v1.rs b/crates/types/src/rpc/transactions/declare_transaction_v0v1.rs index 8e88b2a65..b5f8b99f6 100644 --- a/crates/types/src/rpc/transactions/declare_transaction_v0v1.rs +++ b/crates/types/src/rpc/transactions/declare_transaction_v0v1.rs @@ -13,6 +13,7 @@ use crate::traits::HashProducer; #[serde(deny_unknown_fields)] pub struct DeclareTransactionV0V1 { pub class_hash: ClassHash, + // TODO: in spec RPC response the contract class is missing pub contract_class: Cairo0ContractClass, pub sender_address: ContractAddress, pub nonce: Nonce, diff --git a/crates/types/src/rpc/transactions/declare_transaction_v2.rs b/crates/types/src/rpc/transactions/declare_transaction_v2.rs index 341f92930..de86dc32d 100644 --- a/crates/types/src/rpc/transactions/declare_transaction_v2.rs +++ b/crates/types/src/rpc/transactions/declare_transaction_v2.rs @@ -12,6 +12,7 @@ use crate::felt::{ pub struct DeclareTransactionV2 { pub class_hash: ClassHash, pub compiled_class_hash: CompiledClassHash, + // TODO: in spec RPC response the contract class is missing pub contract_class: SierraContractClass, pub sender_address: ContractAddress, pub nonce: Nonce, diff --git a/crates/types/src/serde_helpers.rs b/crates/types/src/serde_helpers.rs index 61b67251e..5e007a7e6 100644 --- a/crates/types/src/serde_helpers.rs +++ b/crates/types/src/serde_helpers.rs @@ -9,15 +9,23 @@ pub mod rpc_sierra_contract_class_to_sierra_contract_class { { let mut json_obj = serde_json::Value::deserialize(deserializer)?; // Take the inner part of the string value which is expected to be a JSON array and replace - // it + // it with the deserialized value. + // If for some reason the abi field is empty string, remove it from collection if let Some(serde_json::Value::String(abi_string)) = json_obj.get("abi") { - let arr: serde_json::Value = - serde_json::from_str(abi_string).map_err(serde::de::Error::custom)?; - - json_obj - .as_object_mut() - .ok_or(serde::de::Error::custom("Expected to be an object"))? - .insert("abi".to_string(), arr); + if !abi_string.is_empty() { + let arr: serde_json::Value = + serde_json::from_str(abi_string).map_err(serde::de::Error::custom)?; + + json_obj + .as_object_mut() + .ok_or(serde::de::Error::custom("Expected to be an object"))? + .insert("abi".to_string(), arr); + } else { + json_obj + .as_object_mut() + .ok_or(serde::de::Error::custom("Expected to be an object"))? + .remove("abi"); + } }; serde_json::from_value(json_obj).map_err(serde::de::Error::custom) diff --git a/crates/types/test_data/rpc/declare_accepted.json b/crates/types/test_data/rpc/declare_accepted.json deleted file mode 100644 index e8e50f162..000000000 --- a/crates/types/test_data/rpc/declare_accepted.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "type": "DECLARE", - "transaction_hash": "0x21933cb48e59c74caa4575a78e89e6046d043505e5600fd88af7f051d3610ca", - "actual_fee": "0x0", - "finality_status": "ACCEPTED_ON_L1", - "block_hash": "0x2f1335f3dab9f2773598dfdfc3cb0b650ee5bb768b894f629b6e090c189464f", - "block_number": 827466, - "messages_sent": [], - "events": [], - "execution_status": "SUCCEEDED" -} \ No newline at end of file diff --git a/crates/types/test_data/rpc/invoke_accepted.json b/crates/types/test_data/rpc/invoke_accepted.json deleted file mode 100644 index f6823aa02..000000000 --- a/crates/types/test_data/rpc/invoke_accepted.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "type": "INVOKE", - "transaction_hash": "0x5b08d06a7f6422881d6461175f325844d179ca9018dbab5e92dc34e5c176ff1", - "actual_fee": "0x2c39df8c1bd1", - "finality_status": "ACCEPTED_ON_L1", - "block_hash": "0x34687c6aeb4e2a67bc5e2ced6f761c7e425f73557f389dff90873c24f6b50b3", - "block_number": 234495, - "messages_sent": [], - "events": [ - { - "from_address": "0xdfb9e350368c57d9cf5b4a58e5de15f6a97923144bfd6e1b790e266a05a585", - "keys": [ - "0x5ad857f66a5b55f1301ff1ed7e098ac6d4433148f0b72ebc4a2945ab85ad53" - ], - "data": [ - "0x5b08d06a7f6422881d6461175f325844d179ca9018dbab5e92dc34e5c176ff1", - "0x3", - "0x1", - "0xac08578f2", - "0x0" - ] - } - ], - "execution_status": "SUCCEEDED" -} \ No newline at end of file