starknet_devnet_server/api/models/
mod.rs

1mod json_rpc_request;
2mod json_rpc_response;
3
4pub use json_rpc_request::{
5    DevnetSpecRequest, JsonRpcRequest, JsonRpcSubscriptionRequest, JsonRpcWsRequest,
6    StarknetSpecRequest, ToRpcResponseResult, WILDCARD_RPC_ERROR_CODE, to_json_rpc_request,
7};
8pub use json_rpc_response::{DevnetResponse, JsonRpcResponse, StarknetResponse};
9use serde::{Deserialize, Serialize};
10use starknet_rs_core::types::{Felt, Hash256, TransactionExecutionStatus};
11use starknet_types::contract_address::ContractAddress;
12use starknet_types::felt::{BlockHash, ClassHash, TransactionHash};
13use starknet_types::num_bigint::BigUint;
14use starknet_types::patricia_key::PatriciaKey;
15use starknet_types::rpc::block::{BlockId, SubscriptionBlockId};
16use starknet_types::rpc::messaging::{MessageToL1, MessageToL2};
17use starknet_types::rpc::transaction_receipt::FeeUnit;
18use starknet_types::rpc::transactions::{
19    BroadcastedDeclareTransaction, BroadcastedDeployAccountTransaction,
20    BroadcastedInvokeTransaction, BroadcastedTransaction, EventFilter, FunctionCall,
21    SimulationFlag, TransactionFinalityStatus,
22};
23use starknet_types::serde_helpers::dec_string::deserialize_biguint;
24use starknet_types::starknet_api::block::BlockNumber;
25
26use crate::rpc_core::request::RpcMethodCall;
27use crate::subscribe::{TransactionFinalityStatusWithoutL1, TransactionStatusWithoutL1};
28
29#[derive(Deserialize, Clone, Debug)]
30#[serde(deny_unknown_fields)]
31pub struct BlockIdInput {
32    pub block_id: BlockId,
33}
34
35#[derive(Deserialize, Clone, Debug)]
36#[serde(deny_unknown_fields)]
37pub struct TransactionHashInput {
38    pub transaction_hash: TransactionHash,
39}
40
41#[derive(Deserialize, Clone, Debug)]
42#[serde(deny_unknown_fields)]
43pub struct ClassHashInput {
44    pub class_hash: ClassHash,
45}
46
47#[derive(Deserialize, Clone, Debug)]
48#[serde(deny_unknown_fields)]
49#[cfg_attr(test, derive(PartialEq, Eq))]
50pub struct GetStorageInput {
51    pub contract_address: ContractAddress,
52    pub key: PatriciaKey,
53    pub block_id: BlockId,
54}
55
56#[derive(Deserialize, Clone, Debug)]
57pub struct ContractStorage {
58    pub contract_address: ContractAddress,
59    pub storage_keys: Vec<Felt>,
60}
61
62#[derive(Deserialize, Clone, Debug)]
63#[serde(deny_unknown_fields)]
64pub struct GetStorageProofInput {
65    pub block_id: BlockId,
66    pub class_hashes: Option<Vec<Felt>>,
67    pub contract_addresses: Option<Vec<ContractAddress>>,
68    pub contracts_storage_keys: Option<Vec<ContractStorage>>,
69}
70
71#[derive(Deserialize, Clone, Debug)]
72#[serde(deny_unknown_fields)]
73pub struct BlockAndIndexInput {
74    pub block_id: BlockId,
75    pub index: u64,
76}
77
78#[derive(Deserialize, Clone, Debug)]
79#[serde(deny_unknown_fields)]
80pub struct BlockAndClassHashInput {
81    pub block_id: BlockId,
82    pub class_hash: ClassHash,
83}
84
85#[derive(Deserialize, Clone, Debug)]
86#[serde(deny_unknown_fields)]
87pub struct BlockAndContractAddressInput {
88    pub block_id: BlockId,
89    pub contract_address: ContractAddress,
90}
91
92#[derive(Deserialize, Clone, Debug)]
93#[serde(deny_unknown_fields)]
94pub struct AccountAddressInput {
95    pub account_address: ContractAddress,
96}
97
98#[derive(Debug, Clone, Deserialize)]
99#[cfg_attr(test, derive(PartialEq, Eq))]
100#[serde(deny_unknown_fields)]
101pub struct CallInput {
102    pub request: FunctionCall,
103    pub block_id: BlockId,
104}
105
106#[derive(Debug, Clone, Deserialize)]
107#[serde(deny_unknown_fields)]
108pub struct EstimateFeeInput {
109    pub request: Vec<BroadcastedTransaction>,
110    pub simulation_flags: Vec<SimulationFlag>,
111    pub block_id: BlockId,
112}
113
114#[derive(Debug, Clone, Serialize)]
115#[cfg_attr(test, derive(Deserialize))]
116#[serde(deny_unknown_fields)]
117pub struct BlockHashAndNumberOutput {
118    pub block_hash: BlockHash,
119    pub block_number: BlockNumber,
120}
121
122#[derive(Debug, Clone, Serialize)]
123#[cfg_attr(test, derive(Deserialize))]
124#[serde(untagged)]
125pub enum SyncingOutput {
126    False(bool), // if it seems redundant, check the spec
127}
128
129#[derive(Debug, Clone, Deserialize)]
130pub struct EventsInput {
131    pub filter: EventFilter,
132}
133
134#[derive(Deserialize, Debug, Clone)]
135#[serde(tag = "type")]
136pub enum BroadcastedDeclareTransactionEnumWrapper {
137    #[serde(rename = "DECLARE")]
138    Declare(BroadcastedDeclareTransaction),
139}
140
141#[derive(Debug, Clone, Deserialize)]
142#[serde(deny_unknown_fields)]
143pub struct BroadcastedDeclareTransactionInput {
144    pub declare_transaction: BroadcastedDeclareTransactionEnumWrapper,
145}
146
147#[derive(Debug, Clone, Serialize)]
148#[cfg_attr(test, derive(Deserialize))]
149#[serde(deny_unknown_fields)]
150pub struct DeclareTransactionOutput {
151    pub transaction_hash: TransactionHash,
152    pub class_hash: ClassHash,
153}
154
155#[derive(Deserialize, Debug, Clone)]
156#[serde(tag = "type")]
157pub enum BroadcastedDeployAccountTransactionEnumWrapper {
158    #[serde(rename = "DEPLOY_ACCOUNT")]
159    DeployAccount(BroadcastedDeployAccountTransaction),
160}
161
162#[derive(Debug, Clone, Deserialize)]
163#[serde(deny_unknown_fields)]
164pub struct BroadcastedDeployAccountTransactionInput {
165    pub deploy_account_transaction: BroadcastedDeployAccountTransactionEnumWrapper,
166}
167
168#[derive(Debug, Clone, Serialize)]
169#[cfg_attr(test, derive(Deserialize))]
170#[serde(deny_unknown_fields)]
171pub struct DeployAccountTransactionOutput {
172    pub transaction_hash: TransactionHash,
173    pub contract_address: ContractAddress,
174}
175
176#[derive(Deserialize, Debug, Clone)]
177#[serde(tag = "type", rename_all = "SCREAMING_SNAKE_CASE")]
178pub enum BroadcastedInvokeTransactionEnumWrapper {
179    Invoke(BroadcastedInvokeTransaction),
180}
181
182#[derive(Debug, Clone, Deserialize)]
183#[serde(deny_unknown_fields)]
184pub struct BroadcastedInvokeTransactionInput {
185    pub invoke_transaction: BroadcastedInvokeTransactionEnumWrapper,
186}
187
188#[derive(Debug, Clone, Serialize)]
189#[cfg_attr(test, derive(Deserialize))]
190#[serde(deny_unknown_fields)]
191pub struct TransactionHashOutput {
192    pub transaction_hash: TransactionHash,
193}
194
195#[derive(Debug, Clone, Deserialize)]
196#[serde(deny_unknown_fields)]
197pub struct SimulateTransactionsInput {
198    pub block_id: BlockId,
199    pub transactions: Vec<BroadcastedTransaction>,
200    pub simulation_flags: Vec<SimulationFlag>,
201}
202
203#[derive(Debug, Serialize)]
204#[cfg_attr(test, derive(Deserialize))]
205#[serde(deny_unknown_fields)]
206pub struct TransactionStatusOutput {
207    pub finality_status: TransactionFinalityStatus,
208    pub execution_status: TransactionExecutionStatus,
209}
210
211#[derive(Debug, Serialize, Deserialize)]
212#[serde(deny_unknown_fields)]
213pub struct L1TransactionHashInput {
214    pub transaction_hash: Hash256,
215}
216
217#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
218pub struct SubscriptionId(u64);
219
220impl From<u64> for SubscriptionId {
221    fn from(value: u64) -> Self {
222        Self(value)
223    }
224}
225
226impl Serialize for SubscriptionId {
227    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
228    where
229        S: serde::Serializer,
230    {
231        serializer.serialize_str(&self.0.to_string())
232    }
233}
234
235/// Custom deserialization is needed, because subscriber initially received stringified u64 value.
236impl<'de> Deserialize<'de> for SubscriptionId {
237    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
238    where
239        D: serde::Deserializer<'de>,
240    {
241        let u64_as_string = String::deserialize(deserializer)?;
242        let subscription_id = u64_as_string.parse::<u64>().map_err(|_| {
243            serde::de::Error::invalid_type(serde::de::Unexpected::Str(&u64_as_string), &"u64")
244        })?;
245
246        Ok(SubscriptionId(subscription_id))
247    }
248}
249
250#[derive(Deserialize, Clone, Debug)]
251#[serde(deny_unknown_fields)]
252pub struct SubscriptionIdInput {
253    pub subscription_id: SubscriptionId,
254}
255
256#[derive(Deserialize, Clone, Debug)]
257#[serde(deny_unknown_fields)]
258pub struct SubscriptionBlockIdInput {
259    pub block_id: SubscriptionBlockId,
260}
261
262#[derive(Deserialize, Clone, Debug)]
263#[serde(deny_unknown_fields)]
264pub struct EventsSubscriptionInput {
265    pub block_id: Option<SubscriptionBlockId>,
266    pub from_address: Option<ContractAddress>,
267    pub keys: Option<Vec<Vec<Felt>>>,
268    pub finality_status: Option<TransactionFinalityStatus>,
269}
270
271#[derive(Deserialize, Clone, Debug)]
272#[serde(deny_unknown_fields)]
273pub struct TransactionSubscriptionInput {
274    pub sender_address: Option<Vec<ContractAddress>>,
275    pub finality_status: Option<Vec<TransactionStatusWithoutL1>>,
276}
277
278#[derive(Deserialize, Clone, Debug)]
279#[serde(deny_unknown_fields)]
280pub struct TransactionReceiptSubscriptionInput {
281    pub sender_address: Option<Vec<ContractAddress>>,
282    pub finality_status: Option<Vec<TransactionFinalityStatusWithoutL1>>,
283}
284
285#[derive(Deserialize)]
286#[serde(deny_unknown_fields)]
287#[cfg_attr(test, derive(Debug))]
288pub struct DumpPath {
289    pub path: String,
290}
291
292#[derive(Deserialize)]
293#[serde(deny_unknown_fields)]
294#[cfg_attr(test, derive(Debug))]
295pub struct LoadPath {
296    pub path: String,
297}
298
299#[derive(Deserialize)]
300#[serde(deny_unknown_fields)]
301#[cfg_attr(test, derive(Debug))]
302pub struct PostmanLoadL1MessagingContract {
303    pub network_url: String,
304    #[serde(alias = "address")]
305    pub messaging_contract_address: Option<String>,
306    pub deployer_account_private_key: Option<String>,
307}
308
309#[derive(Serialize)]
310pub struct MessageHash {
311    pub message_hash: Hash256,
312}
313
314// Implemented as type alias so JSON returned doesn't have extra key
315pub type DumpResponseBody = Option<Vec<RpcMethodCall>>;
316
317#[derive(Serialize)]
318pub struct CreatedBlock {
319    pub block_hash: BlockHash,
320}
321
322#[derive(Deserialize)]
323#[serde(deny_unknown_fields)]
324#[cfg_attr(test, derive(Debug))]
325pub struct AbortingBlocks {
326    pub(crate) starting_block_id: BlockId,
327}
328
329#[derive(Serialize)]
330pub struct AbortedBlocks {
331    pub(crate) aborted: Vec<BlockHash>,
332}
333
334#[derive(Deserialize)]
335#[serde(deny_unknown_fields)]
336#[cfg_attr(test, derive(Debug))]
337pub struct AcceptOnL1Request {
338    pub(crate) starting_block_id: BlockId,
339}
340
341#[derive(Serialize)]
342pub struct AcceptedOnL1Blocks {
343    pub(crate) accepted: Vec<BlockHash>,
344}
345
346#[derive(Deserialize)]
347#[serde(deny_unknown_fields)]
348#[cfg_attr(test, derive(Debug))]
349pub struct IncreaseTime {
350    pub time: u64,
351}
352
353#[derive(Deserialize)]
354#[serde(deny_unknown_fields)]
355#[cfg_attr(test, derive(Debug))]
356pub struct SetTime {
357    pub time: u64,
358    pub generate_block: Option<bool>,
359}
360
361#[derive(Serialize)]
362pub struct SetTimeResponse {
363    pub block_timestamp: u64,
364    pub block_hash: Option<BlockHash>,
365}
366
367#[derive(Serialize)]
368pub struct IncreaseTimeResponse {
369    pub timestamp_increased_by: u64,
370    pub block_hash: BlockHash,
371}
372
373#[derive(Serialize)]
374pub struct SerializableAccount {
375    pub initial_balance: String,
376    pub address: ContractAddress,
377    pub public_key: Felt,
378    pub private_key: Felt,
379    pub balance: Option<AccountBalancesResponse>,
380}
381
382#[derive(Serialize)]
383pub struct AccountBalancesResponse {
384    pub eth: AccountBalanceResponse,
385    pub strk: AccountBalanceResponse,
386}
387
388#[derive(Serialize)]
389pub struct AccountBalanceResponse {
390    pub amount: String,
391    pub unit: FeeUnit,
392}
393
394#[derive(Serialize)]
395pub struct FeeToken {
396    symbol: String,
397    address: ContractAddress,
398}
399
400#[derive(Deserialize)]
401#[serde(deny_unknown_fields)]
402#[cfg_attr(test, derive(Debug))]
403pub struct MintTokensRequest {
404    pub address: ContractAddress,
405    #[serde(deserialize_with = "deserialize_biguint")]
406    pub amount: BigUint,
407    #[serde(skip_serializing_if = "Option::is_none")]
408    pub unit: Option<FeeUnit>,
409}
410
411#[derive(Serialize)]
412pub struct MintTokensResponse {
413    /// decimal repr
414    pub new_balance: String,
415    pub unit: FeeUnit,
416    pub tx_hash: TransactionHash,
417}
418
419#[derive(Serialize)]
420pub struct ForkStatus {
421    #[serde(skip_serializing_if = "Option::is_none")]
422    pub url: Option<String>,
423    #[serde(skip_serializing_if = "Option::is_none")]
424    pub block: Option<u64>,
425}
426
427#[derive(Serialize, Deserialize)]
428pub struct FlushedMessages {
429    pub messages_to_l1: Vec<MessageToL1>,
430    pub messages_to_l2: Vec<MessageToL2>,
431    pub generated_l2_transactions: Vec<TransactionHash>,
432    pub l1_provider: String,
433}
434
435#[derive(Serialize, Deserialize)]
436#[serde(deny_unknown_fields)]
437#[cfg_attr(test, derive(Debug))]
438pub struct FlushParameters {
439    pub dry_run: bool,
440}
441
442#[derive(Serialize, Deserialize)]
443pub struct MessagingLoadAddress {
444    pub messaging_contract_address: String,
445}
446
447#[derive(Serialize, Deserialize, Default)]
448#[serde(deny_unknown_fields)]
449#[cfg_attr(test, derive(Debug))]
450pub struct RestartParameters {
451    pub restart_l1_to_l2_messaging: bool,
452}
453#[cfg(test)]
454mod tests {
455    use starknet_rs_core::types::Felt;
456    use starknet_types::contract_address::ContractAddress;
457    use starknet_types::felt::felt_from_prefixed_hex;
458    use starknet_types::patricia_key::PatriciaKey;
459    use starknet_types::rpc::block::{BlockId, BlockTag};
460    use starknet_types::rpc::transactions::{
461        BroadcastedDeclareTransaction, BroadcastedTransaction,
462    };
463
464    use super::{BlockIdInput, EstimateFeeInput, GetStorageInput};
465    use crate::test_utils::{EXPECTED_INVALID_BLOCK_ID_MSG, assert_contains};
466
467    #[test]
468    fn errored_deserialization_of_estimate_fee_with_broadcasted_declare_transaction() {
469        // Errored json struct that passed DECLARE V3, but contract class is of type V1
470        let json_str = r#"{
471            "request": [{
472                "type": "DECLARE",
473                "version": "0x3",
474                "signature": ["0xFF", "0xAA"],
475                "nonce": "0x0",
476                "sender_address": "0x0001",
477                "resource_bounds": {
478                    "l1_gas": {
479                        "max_amount": "0x1",
480                        "max_price_per_unit": "0x2"
481                    },
482                    "l1_data_gas": {
483                        "max_amount": "0x1",
484                        "max_price_per_unit": "0x2"
485                    },
486                    "l2_gas": {
487                        "max_amount": "0x1",
488                        "max_price_per_unit": "0x2"
489                    }
490                },
491                "compiled_class_hash": "0x01",
492                "tip": "0xabc",
493                "paymaster_data": [],
494                "account_deployment_data": [],
495                "contract_class": {
496                    "abi": [{
497                        "inputs": [],
498                        "name": "getPublicKey",
499                        "outputs": [
500                            {
501                                "name": "publicKey",
502                                "type": "felt"
503                            }
504                        ],
505                        "stateMutability": "view",
506                        "type": "function"
507                    },
508                    {
509                        "inputs": [],
510                        "name": "setPublicKey",
511                        "outputs": [
512                            {
513                                "name": "publicKey",
514                                "type": "felt"
515                            }
516                        ],
517                        "type": "function"
518                    }],
519                    "program": "",
520                    "entry_points_by_type": {}
521                },
522                "nonce_data_availability_mode": "L1",
523                "fee_data_availability_mode": "L1"
524            }],
525            "block_id": {
526                "block_number": 1
527            }
528        }"#;
529
530        match serde_json::from_str::<EstimateFeeInput>(json_str) {
531            Err(err) => assert_contains(
532                &err.to_string(),
533                // error indicative of expecting a cairo1 class artifact
534                "Invalid declare transaction v3: missing field `state_mutability`",
535            )
536            .unwrap(),
537            other => panic!("Invalid result: {other:?}"),
538        }
539    }
540
541    #[test]
542    fn deserialize_estimate_fee_input() {
543        let json_str = r#"{
544            "request": [
545                {
546                    "type": "DECLARE",
547                    "version": "0x3",
548                    "signature": ["0xFF", "0xAA"],
549                    "nonce": "0x0",
550                    "sender_address": "0x0001",
551                    "resource_bounds": {
552                        "l1_gas": {
553                            "max_amount": "0x1",
554                            "max_price_per_unit": "0x2"
555                        },
556                        "l1_data_gas": {
557                            "max_amount": "0x1",
558                            "max_price_per_unit": "0x2"
559                        },
560                        "l2_gas": {
561                            "max_amount": "0x1",
562                            "max_price_per_unit": "0x2"
563                        }
564                    },
565                    "compiled_class_hash": "0x01",
566                    "tip": "0xabc",
567                    "paymaster_data": [],
568                    "account_deployment_data": [],
569                    "contract_class": {
570                        "sierra_program": ["0xAA", "0xBB"],
571                        "contract_class_version": "1.0",
572                        "entry_points_by_type": {
573                            "EXTERNAL": [
574                                {
575                                    "selector": "0x3c118a68e16e12e97ed25cb4901c12f4d3162818669cc44c391d8049924c14",
576                                    "function_idx": 4
577                                },
578                                {
579                                    "selector": "0xe7510edcf6e9f1b70f7bd1f488767b50f0363422f3c563160ab77adf62467b",
580                                    "function_idx": 7
581                                }
582                            ],
583                            "L1_HANDLER": [
584                                {
585                                    "selector": "0x39edbbb129ad752107a94d40c3873cae369a46fd2fc578d075679aa67e85d12",
586                                    "function_idx": 11
587                                }
588                            ],
589                            "CONSTRUCTOR": [
590                                {
591                                    "selector": "0x28ffe4ff0f226a9107253e17a904099aa4f63a02a5621de0576e5aa71bc5194",
592                                    "function_idx": 12
593                                }
594                            ]
595                        },
596                        "abi": [
597                            {
598                                "type": "constructor",
599                                "name": "constructor",
600                                "inputs": [
601                                    {
602                                        "name": "arg1",
603                                        "type": "core::felt252"
604                                    },
605                                    {
606                                        "name": "arg2",
607                                        "type": "core::felt252"
608                                    }
609                                ]
610                            }
611                        ]
612                    },
613                    "nonce_data_availability_mode": "L1",
614                    "fee_data_availability_mode": "L1"
615                },
616                {
617                    "type": "INVOKE",
618                    "resource_bounds": {
619                        "l1_gas": {
620                            "max_amount": "0x1",
621                            "max_price_per_unit": "0x2"
622                        },
623                        "l1_data_gas": {
624                            "max_amount": "0x1",
625                            "max_price_per_unit": "0x2"
626                        },
627                        "l2_gas": {
628                            "max_amount": "0x1",
629                            "max_price_per_unit": "0x2"
630                        }
631                    },
632                    "tip": "0xabc",
633                    "paymaster_data": [],
634                    "account_deployment_data": [],
635                    "version": "0x100000000000000000000000000000003",
636                    "signature": ["0x2"],
637                    "nonce": "0x1",
638                    "sender_address": "0x3",
639                    "calldata": [
640                        "0x1",
641                        "0x2",
642                        "0x3"
643                    ],
644                    "nonce_data_availability_mode": "L1",
645                    "fee_data_availability_mode": "L1"
646                },
647                {
648                    "type":"DEPLOY_ACCOUNT",
649                    "resource_bounds": {
650                        "l1_gas": {
651                            "max_amount": "0x1",
652                            "max_price_per_unit": "0x2"
653                        },
654                        "l1_data_gas": {
655                            "max_amount": "0x1",
656                            "max_price_per_unit": "0x2"
657                        },
658                        "l2_gas": {
659                            "max_amount": "0x1",
660                            "max_price_per_unit": "0x2"
661                        }
662                    },
663                    "tip": "0xabc",
664                    "paymaster_data": [],
665                    "version": "0x100000000000000000000000000000003",
666                    "signature": ["0xFF", "0xAA"],
667                    "nonce": "0x0",
668                    "contract_address_salt": "0x01",
669                    "class_hash": "0x01",
670                    "constructor_calldata": ["0x01"],
671                    "nonce_data_availability_mode": "L1",
672                    "fee_data_availability_mode": "L1"
673                }
674            ],
675            "block_id": {
676                "block_number": 1
677            },
678            "simulation_flags": []
679        }"#;
680
681        let estimate_fee_input = serde_json::from_str::<super::EstimateFeeInput>(json_str).unwrap();
682        assert_eq!(estimate_fee_input.block_id, BlockId::Number(1));
683        assert_eq!(estimate_fee_input.request.len(), 3);
684        assert!(matches!(
685            estimate_fee_input.request[0],
686            BroadcastedTransaction::Declare(BroadcastedDeclareTransaction::V3(_))
687        ));
688        assert!(matches!(estimate_fee_input.request[1], BroadcastedTransaction::Invoke(_)));
689        assert!(matches!(estimate_fee_input.request[2], BroadcastedTransaction::DeployAccount(_)));
690    }
691
692    #[test]
693    fn deserialize_call_input() {
694        let json_str = r#"{"request": {"contract_address": "0x01", "entry_point_selector": "0x02", "calldata": ["0x03"]}, "block_id": {"block_number": 1}}"#;
695        let call_input = serde_json::from_str::<super::CallInput>(json_str).unwrap();
696
697        assert_eq!(
698            call_input,
699            super::CallInput {
700                request: super::FunctionCall {
701                    contract_address: ContractAddress::new(Felt::ONE).unwrap(),
702                    entry_point_selector: Felt::TWO,
703                    calldata: vec![Felt::THREE],
704                },
705                block_id: BlockId::Number(1),
706            }
707        );
708    }
709
710    #[test]
711    fn deserialize_get_storage_input() {
712        fn assert_get_storage_input_correctness(
713            should_be_correct: bool,
714            expected_storage_input: GetStorageInput,
715            json_str: &str,
716        ) {
717            let is_correct =
718                if let Ok(get_storage_input) = serde_json::from_str::<GetStorageInput>(json_str) {
719                    get_storage_input == expected_storage_input
720                } else {
721                    false
722                };
723
724            assert_eq!(should_be_correct, is_correct);
725        }
726
727        let expected_storage_input = GetStorageInput {
728            block_id: BlockId::Hash(Felt::ONE),
729            contract_address: ContractAddress::new(Felt::TWO).unwrap(),
730            key: PatriciaKey::new(Felt::THREE).unwrap(),
731        };
732
733        assert_get_storage_input_correctness(
734            true,
735            expected_storage_input.clone(),
736            r#"{"block_id": {"block_hash": "0x01"}, "contract_address": "0x02", "key": "0x03"}"#,
737        );
738
739        // Incorrect contract_address key
740        assert_get_storage_input_correctness(
741            false,
742            expected_storage_input.clone(),
743            r#"{"block_id": {"block_hash": "0x01"}, "contract_address_mock": "0x02", "key": "0x03"}"#,
744        );
745
746        // Incorrect key
747        assert_get_storage_input_correctness(
748            false,
749            expected_storage_input,
750            r#"{"block_id": {"block_hash": "0x01"}, "contract_address": "0x02", "keyy": "0x03"}"#,
751        );
752    }
753
754    // unit tests for TransactionHashInput deserialization
755    #[test]
756    fn deserialize_transaction_hash_input() {
757        assert_transaction_hash_correctness(true, "0x01", r#"{"transaction_hash": "0x01"}"#);
758
759        // Incorrect transaction_hash key
760        assert_transaction_hash_correctness(false, "0x01", r#"{"transaction_hashh": "0x01"}"#);
761
762        // Incorrect transaction_hash value
763        assert_transaction_hash_correctness(false, "0x02", r#"{"transaction_hash": "0x01"}"#);
764
765        // Incorrect transaction_hash format, should be prefixed with 0x
766        assert_transaction_hash_correctness(false, "0x02", r#"{"transaction_hash": "01"}"#);
767    }
768    #[test]
769    fn deserialize_block_id_tag_variants() {
770        assert_block_id_tag_correctness(true, BlockTag::Latest, r#"{"block_id": "latest"}"#);
771        assert_block_id_tag_correctness(
772            true,
773            BlockTag::PreConfirmed,
774            r#"{"block_id": "pre_confirmed"}"#,
775        );
776
777        // Incorrect tag
778        assert_block_id_tag_correctness(false, BlockTag::Latest, r#"{"block_id": "latestx"}"#);
779        assert_block_id_tag_correctness(false, BlockTag::Latest, r#"{"block_id": "pending"}"#);
780        assert_block_id_tag_correctness(
781            false,
782            BlockTag::PreConfirmed,
783            r#"{"block_id": "pre_confirmed_d"}"#,
784        );
785
786        // Incorrect key
787        assert_block_id_tag_correctness(false, BlockTag::Latest, r#"{"block": "latest"}"#);
788        assert_block_id_tag_correctness(false, BlockTag::PreConfirmed, r#"{"block": "pending"}"#);
789        assert_block_id_tag_correctness(
790            false,
791            BlockTag::PreConfirmed,
792            r#"{"block": "pre_confirmed"}"#,
793        );
794    }
795
796    #[test]
797    fn deserialize_block_id_block_hash_variants() {
798        assert_block_id_block_hash_correctness(
799            true,
800            "0x01",
801            r#"{"block_id": {"block_hash": "0x01"}}"#,
802        );
803
804        // BlockId's key is block instead of block_id
805        assert_block_id_block_hash_correctness(
806            false,
807            "0x01",
808            r#"{"block": {"block_hash": "0x01"}}"#,
809        );
810
811        // Incorrect block_hash key
812        assert_block_id_block_hash_correctness(
813            false,
814            "0x01",
815            r#"{"block_id": {"block_hasha": "0x01"}}"#,
816        );
817
818        // Incorrect block_hash value
819        assert_block_id_block_hash_correctness(
820            false,
821            "0x02",
822            r#"{"block_id": {"block_hash": "0x01"}}"#,
823        );
824
825        // Block hash hex value is more than 64 chars
826        assert_block_id_block_hash_correctness(
827            false,
828            "0x01",
829            r#"{"block_id": {"block_hash": "0x004134134134134134134134134134134134134134134134134134134134134134"}}"#,
830        );
831
832        // Block hash hex doesn't start with 0x
833        assert_block_id_block_hash_correctness(
834            false,
835            "0x01",
836            r#"{"block_id": {"block_hash": "01"}}"#,
837        );
838    }
839
840    #[test]
841    fn deserialize_block_id_block_number_variants() {
842        assert_block_id_block_number_correctness(true, 10, r#"{"block_id": {"block_number": 10}}"#);
843
844        // BlockId's key is block instead of block_id
845        assert_block_id_block_number_correctness(false, 10, r#"{"block": {"block_number": 10}}"#);
846
847        // Incorrect block_number key
848        assert_block_id_block_number_correctness(
849            false,
850            10,
851            r#"{"block_id": {"block_number_mock": 10}}"#,
852        );
853
854        // Incorrect block_number value
855        assert_block_id_block_number_correctness(
856            false,
857            10,
858            r#"{"block_id": {"block_number": "0x01"}}"#,
859        );
860    }
861
862    #[test]
863    fn assert_error_message_for_failed_block_id_deserialization() {
864        for json_str in [
865            r#"{"block_id": {"block_number": 10, "block_hash": "0x1"}}"#,
866            r#"{"block_id": {"block_number": "123"}}"#,
867            r#"{"block_id": {"block_number": -123}}"#,
868            r#"{"block_id": {"invalid_key": ""}}"#,
869            r#"{"block_id": {"block_hash": 123}}"#,
870            r#"{"block_id": {"block_hash": ""}}"#,
871        ] {
872            match serde_json::from_str::<BlockIdInput>(json_str) {
873                Err(e) => assert_contains(&e.to_string(), EXPECTED_INVALID_BLOCK_ID_MSG).unwrap(),
874                other => panic!("Invalid result: {other:?}"),
875            }
876        }
877    }
878
879    fn assert_block_id_tag_correctness(
880        should_be_correct: bool,
881        expected_tag: BlockTag,
882        json_str_block_id: &str,
883    ) {
884        let is_correct =
885            serde_json::from_str::<BlockIdInput>(json_str_block_id)
886                .map(|BlockIdInput { block_id }| matches!(block_id, BlockId::Tag(generated_tag) if generated_tag == expected_tag))
887                .unwrap_or(false);
888
889        assert_eq!(should_be_correct, is_correct);
890    }
891
892    fn assert_block_id_block_number_correctness(
893        should_be_correct: bool,
894        expected_block_number: u64,
895        json_str_block_id: &str,
896    ) {
897        let is_correct =
898            serde_json::from_str::<BlockIdInput>(json_str_block_id)
899                .map(
900                    |BlockIdInput { block_id }|
901                    matches!(block_id,
902                    BlockId::Number(generated_block_number) if generated_block_number == expected_block_number)
903            ).unwrap_or(false);
904
905        assert_eq!(should_be_correct, is_correct);
906    }
907
908    fn assert_block_id_block_hash_correctness(
909        should_be_correct: bool,
910        expected_block_hash: &str,
911        json_str_block_id: &str,
912    ) {
913        let is_correct =
914            serde_json::from_str::<BlockIdInput>(json_str_block_id)
915                .map(|BlockIdInput { block_id }| matches!(block_id, BlockId::Hash(generated_block_hash) if generated_block_hash == felt_from_prefixed_hex(expected_block_hash).unwrap()))
916        .unwrap_or(false);
917
918        assert_eq!(should_be_correct, is_correct)
919    }
920
921    fn assert_transaction_hash_correctness(
922        should_be_correct: bool,
923        expected_transaction_hash: &str,
924        json_str_transaction_hash: &str,
925    ) {
926        let is_correct = if let Ok(transaction_hash_input) =
927            serde_json::from_str::<super::TransactionHashInput>(json_str_transaction_hash)
928        {
929            transaction_hash_input.transaction_hash
930                == felt_from_prefixed_hex(expected_transaction_hash).unwrap()
931        } else {
932            false
933        };
934
935        assert_eq!(should_be_correct, is_correct);
936    }
937}