1use enum_helper_macros::{AllVariantsSerdeRenames, VariantName};
2use serde::de::DeserializeOwned;
3use serde::{Deserialize, Serialize};
4use serde_json::json;
5use starknet_types::messaging::{MessageToL1, MessageToL2};
6use starknet_types::rpc::estimate_message_fee::EstimateMessageFeeRequest;
7use starknet_types::rpc::gas_modification::GasModificationRequest;
8use tracing::error;
9
10use crate::api::account_helpers::{BalanceQuery, PredeployedAccountsQuery};
11use crate::api::error::StrictRpcResult;
12use crate::api::models::{
13 AbortingBlocks, AcceptOnL1Request, AccountAddressInput, BlockAndClassHashInput,
14 BlockAndContractAddressInput, BlockAndIndexInput, BlockIdInput,
15 BroadcastedDeclareTransactionInput, BroadcastedDeployAccountTransactionInput,
16 BroadcastedInvokeTransactionInput, CallInput, ClassHashInput, DumpPath, EstimateFeeInput,
17 EventsInput, EventsSubscriptionInput, FlushParameters, GetStorageInput, GetStorageProofInput,
18 IncreaseTime, JsonRpcResponse, L1TransactionHashInput, LoadPath, MintTokensRequest,
19 PostmanLoadL1MessagingContract, RestartParameters, SetTime, SimulateTransactionsInput,
20 SubscriptionBlockIdInput, SubscriptionIdInput, TransactionHashInput,
21 TransactionReceiptSubscriptionInput, TransactionSubscriptionInput,
22};
23use crate::api::serde_helpers::{empty_params, optional_params};
24use crate::rpc_core::error::RpcError;
25use crate::rpc_core::request::RpcMethodCall;
26use crate::rpc_core::response::ResponseResult;
27
28pub trait ToRpcResponseResult {
30 fn to_rpc_result(self) -> ResponseResult;
31}
32
33pub const WILDCARD_RPC_ERROR_CODE: i64 = -1;
35
36pub fn to_rpc_result<T: Serialize>(val: T) -> ResponseResult {
38 match serde_json::to_value(val) {
39 Ok(success) => ResponseResult::Success(success),
40 Err(err) => {
41 error!("Failed serialize rpc response: {:?}", err);
42 ResponseResult::error(RpcError::internal_error())
43 }
44 }
45}
46
47impl ToRpcResponseResult for StrictRpcResult {
48 fn to_rpc_result(self) -> ResponseResult {
49 match self {
50 Ok(JsonRpcResponse::Empty) => to_rpc_result(json!({})),
51 Ok(JsonRpcResponse::Devnet(data)) => to_rpc_result(data),
52 Ok(JsonRpcResponse::Starknet(data)) => to_rpc_result(data),
53 Err(err) => err.api_error_to_rpc_error().into(),
54 }
55 }
56}
57
58#[derive(Deserialize, AllVariantsSerdeRenames, VariantName)]
59#[cfg_attr(test, derive(Debug))]
60#[serde(tag = "method", content = "params")]
61pub enum StarknetSpecRequest {
62 #[serde(rename = "starknet_specVersion", with = "empty_params")]
63 SpecVersion,
64 #[serde(rename = "starknet_getBlockWithTxHashes")]
65 BlockWithTransactionHashes(BlockIdInput),
66 #[serde(rename = "starknet_getBlockWithTxs")]
67 BlockWithFullTransactions(BlockIdInput),
68 #[serde(rename = "starknet_getBlockWithReceipts")]
69 BlockWithReceipts(BlockIdInput),
70 #[serde(rename = "starknet_getStateUpdate")]
71 StateUpdate(BlockIdInput),
72 #[serde(rename = "starknet_getStorageAt")]
73 StorageAt(GetStorageInput),
74 #[serde(rename = "starknet_getStorageProof")]
75 StorageProof(GetStorageProofInput),
76 #[serde(rename = "starknet_getTransactionByHash")]
77 TransactionByHash(TransactionHashInput),
78 #[serde(rename = "starknet_getTransactionByBlockIdAndIndex")]
79 TransactionByBlockAndIndex(BlockAndIndexInput),
80 #[serde(rename = "starknet_getTransactionReceipt")]
81 TransactionReceiptByTransactionHash(TransactionHashInput),
82 #[serde(rename = "starknet_getTransactionStatus")]
83 TransactionStatusByHash(TransactionHashInput),
84 #[serde(rename = "starknet_getMessagesStatus")]
85 MessagesStatusByL1Hash(L1TransactionHashInput),
86 #[serde(rename = "starknet_getClass")]
87 ClassByHash(BlockAndClassHashInput),
88 #[serde(rename = "starknet_getCompiledCasm")]
89 CompiledCasmByClassHash(ClassHashInput),
90 #[serde(rename = "starknet_getClassHashAt")]
91 ClassHashAtContractAddress(BlockAndContractAddressInput),
92 #[serde(rename = "starknet_getClassAt")]
93 ClassAtContractAddress(BlockAndContractAddressInput),
94 #[serde(rename = "starknet_getBlockTransactionCount")]
95 BlockTransactionCount(BlockIdInput),
96 #[serde(rename = "starknet_call")]
97 Call(CallInput),
98 #[serde(rename = "starknet_estimateFee")]
99 EstimateFee(EstimateFeeInput),
100 #[serde(rename = "starknet_blockNumber", with = "empty_params")]
101 BlockNumber,
102 #[serde(rename = "starknet_blockHashAndNumber", with = "empty_params")]
103 BlockHashAndNumber,
104 #[serde(rename = "starknet_chainId", with = "empty_params")]
105 ChainId,
106 #[serde(rename = "starknet_syncing", with = "empty_params")]
107 Syncing,
108 #[serde(rename = "starknet_getEvents")]
109 Events(EventsInput),
110 #[serde(rename = "starknet_getNonce")]
111 ContractNonce(BlockAndContractAddressInput),
112 #[serde(rename = "starknet_addDeclareTransaction")]
113 AddDeclareTransaction(BroadcastedDeclareTransactionInput),
114 #[serde(rename = "starknet_addDeployAccountTransaction")]
115 AddDeployAccountTransaction(BroadcastedDeployAccountTransactionInput),
116 #[serde(rename = "starknet_addInvokeTransaction")]
117 AddInvokeTransaction(BroadcastedInvokeTransactionInput),
118 #[serde(rename = "starknet_estimateMessageFee")]
119 EstimateMessageFee(EstimateMessageFeeRequest),
120 #[serde(rename = "starknet_simulateTransactions")]
121 SimulateTransactions(SimulateTransactionsInput),
122 #[serde(rename = "starknet_traceTransaction")]
123 TraceTransaction(TransactionHashInput),
124 #[serde(rename = "starknet_traceBlockTransactions")]
125 BlockTransactionTraces(BlockIdInput),
126}
127
128#[derive(Deserialize, AllVariantsSerdeRenames, VariantName)]
129#[cfg_attr(test, derive(Debug))]
130#[serde(tag = "method", content = "params")]
131pub enum DevnetSpecRequest {
132 #[serde(rename = "devnet_impersonateAccount")]
133 ImpersonateAccount(AccountAddressInput),
134 #[serde(rename = "devnet_stopImpersonateAccount")]
135 StopImpersonateAccount(AccountAddressInput),
136 #[serde(rename = "devnet_autoImpersonate", with = "empty_params")]
137 AutoImpersonate,
138 #[serde(rename = "devnet_stopAutoImpersonate", with = "empty_params")]
139 StopAutoImpersonate,
140 #[serde(rename = "devnet_dump", with = "optional_params")]
141 Dump(Option<DumpPath>),
142 #[serde(rename = "devnet_load")]
143 Load(LoadPath),
144 #[serde(rename = "devnet_postmanLoad")]
145 PostmanLoadL1MessagingContract(PostmanLoadL1MessagingContract),
146 #[serde(rename = "devnet_postmanFlush", with = "optional_params")]
147 PostmanFlush(Option<FlushParameters>),
148 #[serde(rename = "devnet_postmanSendMessageToL2")]
149 PostmanSendMessageToL2(MessageToL2),
150 #[serde(rename = "devnet_postmanConsumeMessageFromL2")]
151 PostmanConsumeMessageFromL2(MessageToL1),
152 #[serde(rename = "devnet_createBlock", with = "empty_params")]
153 CreateBlock,
154 #[serde(rename = "devnet_abortBlocks")]
155 AbortBlocks(AbortingBlocks),
156 #[serde(rename = "devnet_acceptOnL1")]
157 AcceptOnL1(AcceptOnL1Request),
158 #[serde(rename = "devnet_setGasPrice")]
159 SetGasPrice(GasModificationRequest),
160 #[serde(rename = "devnet_restart", with = "optional_params")]
161 Restart(Option<RestartParameters>),
162 #[serde(rename = "devnet_setTime")]
163 SetTime(SetTime),
164 #[serde(rename = "devnet_increaseTime")]
165 IncreaseTime(IncreaseTime),
166 #[serde(rename = "devnet_getPredeployedAccounts", with = "optional_params")]
167 PredeployedAccounts(Option<PredeployedAccountsQuery>),
168 #[serde(rename = "devnet_getAccountBalance")]
169 AccountBalance(BalanceQuery),
170 #[serde(rename = "devnet_mint")]
171 Mint(MintTokensRequest),
172 #[serde(rename = "devnet_getConfig", with = "empty_params")]
173 DevnetConfig,
174}
175
176#[cfg_attr(test, derive(Debug))]
177pub enum JsonRpcRequest {
178 StarknetSpecRequest(StarknetSpecRequest),
179 DevnetSpecRequest(DevnetSpecRequest),
180 }
182
183impl<'de> Deserialize<'de> for JsonRpcRequest {
184 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
185 where
186 D: serde::Deserializer<'de>,
187 {
188 let raw_req = serde_json::Value::deserialize(deserializer)?;
189
190 let method = raw_req.get("method").and_then(|m| m.as_str()).unwrap_or("<missing>");
191
192 match method {
193 method if method.starts_with("starknet_") => Ok(Self::StarknetSpecRequest(
194 serde_json::from_value(raw_req).map_err(serde::de::Error::custom)?,
195 )),
196 method if method.starts_with("devnet_") => Ok(Self::DevnetSpecRequest(
197 serde_json::from_value(raw_req).map_err(serde::de::Error::custom)?,
198 )),
199 invalid => Err(serde::de::Error::custom(format!("Invalid method: {invalid}"))),
200 }
201 }
202}
203
204impl StarknetSpecRequest {
205 pub fn requires_notifying(&self) -> bool {
206 #![warn(clippy::wildcard_enum_match_arm)]
207 match self {
208 Self::AddDeclareTransaction(_)
209 | Self::AddDeployAccountTransaction(_)
210 | Self::AddInvokeTransaction(_) => true,
211 Self::SpecVersion
212 | Self::BlockWithTransactionHashes(_)
213 | Self::BlockWithFullTransactions(_)
214 | Self::BlockWithReceipts(_)
215 | Self::StateUpdate(_)
216 | Self::StorageAt(_)
217 | Self::TransactionByHash(_)
218 | Self::TransactionByBlockAndIndex(_)
219 | Self::TransactionReceiptByTransactionHash(_)
220 | Self::TransactionStatusByHash(_)
221 | Self::MessagesStatusByL1Hash(_)
222 | Self::ClassByHash(_)
223 | Self::CompiledCasmByClassHash(_)
224 | Self::ClassHashAtContractAddress(_)
225 | Self::ClassAtContractAddress(_)
226 | Self::BlockTransactionCount(_)
227 | Self::Call(_)
228 | Self::EstimateFee(_)
229 | Self::BlockNumber
230 | Self::BlockHashAndNumber
231 | Self::ChainId
232 | Self::Syncing
233 | Self::Events(_)
234 | Self::ContractNonce(_)
235 | Self::EstimateMessageFee(_)
236 | Self::SimulateTransactions(_)
237 | Self::TraceTransaction(_)
238 | Self::BlockTransactionTraces(_)
239 | Self::StorageProof(_) => false,
240 }
241 }
242
243 pub fn is_forwardable_to_origin(&self) -> bool {
244 #[warn(clippy::wildcard_enum_match_arm)]
245 match self {
246 Self::BlockWithTransactionHashes(_)
247 | Self::BlockWithFullTransactions(_)
248 | Self::BlockWithReceipts(_)
249 | Self::StateUpdate(_)
250 | Self::StorageAt(_)
251 | Self::TransactionByHash(_)
252 | Self::TransactionByBlockAndIndex(_)
253 | Self::TransactionReceiptByTransactionHash(_)
254 | Self::TransactionStatusByHash(_)
255 | Self::ClassByHash(_)
256 | Self::ClassHashAtContractAddress(_)
257 | Self::ClassAtContractAddress(_)
258 | Self::BlockTransactionCount(_)
259 | Self::Call(_)
260 | Self::EstimateFee(_)
261 | Self::BlockNumber
262 | Self::BlockHashAndNumber
263 | Self::Events(_)
264 | Self::ContractNonce(_)
265 | Self::EstimateMessageFee(_)
266 | Self::SimulateTransactions(_)
267 | Self::TraceTransaction(_)
268 | Self::MessagesStatusByL1Hash(_)
269 | Self::CompiledCasmByClassHash(_)
270 | Self::StorageProof(_)
271 | Self::BlockTransactionTraces(_) => true,
272 Self::SpecVersion
273 | Self::ChainId
274 | Self::Syncing
275 | Self::AddDeclareTransaction(_)
276 | Self::AddDeployAccountTransaction(_)
277 | Self::AddInvokeTransaction(_) => false,
278 }
279 }
280
281 pub fn is_dumpable(&self) -> bool {
282 #[warn(clippy::wildcard_enum_match_arm)]
283 match self {
284 Self::AddDeclareTransaction(_)
285 | Self::AddDeployAccountTransaction(_)
286 | Self::AddInvokeTransaction(_) => true,
287 Self::SpecVersion
288 | Self::BlockWithTransactionHashes(_)
289 | Self::BlockWithFullTransactions(_)
290 | Self::BlockWithReceipts(_)
291 | Self::StateUpdate(_)
292 | Self::StorageAt(_)
293 | Self::TransactionByHash(_)
294 | Self::TransactionByBlockAndIndex(_)
295 | Self::TransactionReceiptByTransactionHash(_)
296 | Self::TransactionStatusByHash(_)
297 | Self::ClassByHash(_)
298 | Self::ClassHashAtContractAddress(_)
299 | Self::ClassAtContractAddress(_)
300 | Self::BlockTransactionCount(_)
301 | Self::Call(_)
302 | Self::EstimateFee(_)
303 | Self::BlockNumber
304 | Self::BlockHashAndNumber
305 | Self::ChainId
306 | Self::Syncing
307 | Self::Events(_)
308 | Self::ContractNonce(_)
309 | Self::EstimateMessageFee(_)
310 | Self::SimulateTransactions(_)
311 | Self::TraceTransaction(_)
312 | Self::BlockTransactionTraces(_)
313 | Self::MessagesStatusByL1Hash(_)
314 | Self::CompiledCasmByClassHash(_)
315 | Self::StorageProof(_) => false,
316 }
317 }
318}
319
320impl DevnetSpecRequest {
321 pub fn requires_notifying(&self) -> bool {
322 #![warn(clippy::wildcard_enum_match_arm)]
323 match self {
324 Self::PostmanFlush(_)
325 | Self::PostmanSendMessageToL2(_)
326 | Self::CreateBlock
327 | Self::AbortBlocks(_)
328 | Self::AcceptOnL1(_)
329 | Self::SetTime(_)
330 | Self::IncreaseTime(_)
331 | Self::Mint(_) => true,
332 Self::ImpersonateAccount(_)
333 | Self::StopImpersonateAccount(_)
334 | Self::AutoImpersonate
335 | Self::StopAutoImpersonate
336 | Self::Dump(_)
337 | Self::Load(_)
338 | Self::PostmanLoadL1MessagingContract(_)
339 | Self::PostmanConsumeMessageFromL2(_)
340 | Self::SetGasPrice(_)
341 | Self::Restart(_)
342 | Self::PredeployedAccounts(_)
343 | Self::AccountBalance(_)
344 | Self::DevnetConfig => false,
345 }
346 }
347
348 pub fn is_dumpable(&self) -> bool {
350 #[warn(clippy::wildcard_enum_match_arm)]
351 match self {
352 Self::ImpersonateAccount(_)
353 | Self::StopImpersonateAccount(_)
354 | Self::AutoImpersonate
355 | Self::StopAutoImpersonate
356 | Self::PostmanLoadL1MessagingContract(_)
357 | Self::PostmanSendMessageToL2(_)
358 | Self::PostmanConsumeMessageFromL2(_)
359 | Self::CreateBlock
360 | Self::AbortBlocks(_)
361 | Self::AcceptOnL1(_)
362 | Self::SetGasPrice(_)
363 | Self::SetTime(_)
364 | Self::IncreaseTime(_)
365 | Self::Mint(_) => true,
366 Self::Dump(_)
367 | Self::Load(_)
368 | Self::PostmanFlush(_)
369 | Self::Restart(_)
370 | Self::PredeployedAccounts(_)
371 | Self::AccountBalance(_)
372 | Self::DevnetConfig => false,
373 }
374 }
375}
376
377impl JsonRpcRequest {
378 pub fn requires_notifying(&self) -> bool {
379 #![warn(clippy::wildcard_enum_match_arm)]
380 match self {
381 Self::StarknetSpecRequest(req) => req.requires_notifying(),
382 Self::DevnetSpecRequest(req) => req.requires_notifying(),
383 }
384 }
385
386 pub fn is_forwardable_to_origin(&self) -> bool {
388 #[warn(clippy::wildcard_enum_match_arm)]
389 match self {
390 Self::StarknetSpecRequest(req) => req.is_forwardable_to_origin(),
391 Self::DevnetSpecRequest(_) => false,
392 }
393 }
394
395 pub fn is_dumpable(&self) -> bool {
396 #[warn(clippy::wildcard_enum_match_arm)]
397 match self {
398 Self::StarknetSpecRequest(req) => req.is_dumpable(),
399 Self::DevnetSpecRequest(req) => req.is_dumpable(),
400 }
401 }
402
403 pub fn all_variants_serde_renames() -> Vec<String> {
404 let mut all_variants = vec![];
405 for variants in [
406 StarknetSpecRequest::all_variants_serde_renames(),
407 DevnetSpecRequest::all_variants_serde_renames(),
408 ] {
409 all_variants.extend(variants);
410 }
411 all_variants
412 }
413}
414
415pub enum JsonRpcWsRequest {
416 OneTimeRequest(Box<JsonRpcRequest>),
417 SubscriptionRequest(JsonRpcSubscriptionRequest),
418}
419
420impl<'de> Deserialize<'de> for JsonRpcWsRequest {
421 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
422 where
423 D: serde::Deserializer<'de>,
424 {
425 let raw_req = serde_json::Value::deserialize(deserializer)?;
426
427 let method = raw_req.get("method").and_then(|m| m.as_str()).unwrap_or("<missing>");
428
429 if method.starts_with("starknet_subscribe") || method == "starknet_unsubscribe" {
430 Ok(Self::SubscriptionRequest(
431 serde_json::from_value(raw_req).map_err(serde::de::Error::custom)?,
432 ))
433 } else {
434 Ok(Self::OneTimeRequest(
435 serde_json::from_value(raw_req).map_err(serde::de::Error::custom)?,
436 ))
437 }
438 }
439}
440
441#[derive(Deserialize, AllVariantsSerdeRenames, VariantName)]
442#[cfg_attr(test, derive(Debug))]
443#[serde(tag = "method", content = "params")]
444pub enum JsonRpcSubscriptionRequest {
445 #[serde(rename = "starknet_subscribeNewHeads", with = "optional_params")]
446 NewHeads(Option<SubscriptionBlockIdInput>),
447 #[serde(rename = "starknet_subscribeTransactionStatus")]
448 TransactionStatus(TransactionHashInput),
449 #[serde(rename = "starknet_subscribeEvents")]
450 Events(Option<EventsSubscriptionInput>),
451 #[serde(rename = "starknet_subscribeNewTransactions", with = "optional_params")]
452 NewTransactions(Option<TransactionSubscriptionInput>),
453 #[serde(rename = "starknet_subscribeNewTransactionReceipts", with = "optional_params")]
454 NewTransactionReceipts(Option<TransactionReceiptSubscriptionInput>),
455 #[serde(rename = "starknet_unsubscribe")]
456 Unsubscribe(SubscriptionIdInput),
457}
458
459pub fn to_json_rpc_request<D>(call: &RpcMethodCall) -> Result<D, RpcError>
460where
461 D: DeserializeOwned,
462{
463 let params: serde_json::Value = call.params.clone().into();
464 let deserializable_call = json!({
465 "method": call.method,
466 "params": params
467 });
468
469 serde_json::from_value::<D>(deserializable_call).map_err(|err| {
470 let err = err.to_string();
471 if err.contains("Invalid method") || err.contains(&format!("unknown variant `{}`", call.method)) {
474 error!(target: "rpc", method = ?call.method, "failed to deserialize RPC call: unknown method");
475 RpcError::method_not_found()
476 } else {
477 error!(target: "rpc", method = ?call.method, ?err, "failed to deserialize RPC call: invalid params");
478 RpcError::invalid_params(err)
479 }
480 })
481}
482
483impl std::fmt::Display for JsonRpcRequest {
484 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
485 let variant_name = match self {
486 Self::StarknetSpecRequest(req) => req.variant_name(),
487 Self::DevnetSpecRequest(req) => req.variant_name(),
488 };
489 write!(f, "{}", variant_name)
490 }
491}
492
493#[cfg(test)]
494mod requests_tests {
495
496 use serde_json::json;
497 use starknet_types::felt::felt_from_prefixed_hex;
498
499 use super::{JsonRpcRequest, StarknetSpecRequest};
500 use crate::rpc_core::request::RpcMethodCall;
501 use crate::test_utils::{EXPECTED_INVALID_BLOCK_ID_MSG, assert_contains};
502
503 #[test]
504 fn deserialize_get_block_with_transaction_hashes_request() {
505 let json_str =
506 r#"{"method":"starknet_getBlockWithTxHashes","params":{"block_id":"latest"}}"#;
507 assert_deserialization_succeeds(json_str);
508 assert_deserialization_succeeds(&json_str.replace("latest", "pre_confirmed"));
509
510 assert_deserialization_fails(&json_str.replace("latest", "0x134134"), "Invalid block ID");
511 }
512
513 #[test]
514 fn deserialize_get_block_with_transactions_request() {
515 let json_str = r#"{"method":"starknet_getBlockWithTxs","params":{"block_id":"latest"}}"#;
516 assert_deserialization_succeeds(json_str);
517 assert_deserialization_succeeds(&json_str.replace("latest", "pre_confirmed"));
518
519 assert_deserialization_fails(
520 json_str.replace("latest", "0x134134").as_str(),
521 EXPECTED_INVALID_BLOCK_ID_MSG,
522 );
523 }
524
525 #[test]
526 fn deserialize_get_state_update_request() {
527 let json_str = r#"{"method":"starknet_getStateUpdate","params":{"block_id":"latest"}}"#;
528 assert_deserialization_succeeds(json_str);
529 assert_deserialization_succeeds(&json_str.replace("latest", "pre_confirmed"));
530
531 assert_deserialization_fails(
532 &json_str.replace("latest", "0x134134"),
533 EXPECTED_INVALID_BLOCK_ID_MSG,
534 );
535 }
536
537 #[test]
538 fn deserialize_get_storage_at_request() {
539 let json_str = r#"{"method":"starknet_getStorageAt","params":{"contract_address":"0x134134","key":"0x134134","block_id":"latest"}}"#;
540 assert_deserialization_succeeds(json_str);
541
542 assert_deserialization_fails(
543 &json_str.replace(r#""contract_address":"0x134134""#, r#""contract_address":"123""#),
544 "Missing prefix 0x in 123",
545 );
546
547 assert_deserialization_fails(
548 &json_str.replace(r#""contract_address":"0x134134""#, r#""contract_address": 123"#),
549 "invalid type: number, expected a string",
550 );
551 }
552
553 #[test]
554 fn deserialize_get_transaction_by_hash_request() {
555 let json_str = r#"{"method":"starknet_getTransactionByHash","params":{"transaction_hash":"0x134134"}}"#;
556
557 let request = serde_json::from_str::<JsonRpcRequest>(json_str).unwrap();
558
559 match request {
560 JsonRpcRequest::StarknetSpecRequest(StarknetSpecRequest::TransactionByHash(input)) => {
561 assert!(input.transaction_hash == felt_from_prefixed_hex("0x134134").unwrap());
562 }
563 _ => panic!("Wrong request type"),
564 }
565
566 assert_deserialization_fails(
568 r#"{"method":"starknet_getTransactionByHash","params":"0x134134"}"#,
569 "invalid type: string \"0x134134\", expected struct",
570 );
571 assert_deserialization_fails(
573 r#"{"method":"starknet_getTransactionByHash","params":{"transaction_hash":"134134"}}"#,
574 "expected hex string to be prefixed by '0x'",
575 );
576 assert_deserialization_fails(
578 r#"{"method":"starknet_getTransactionByHash","params":{"transaction_hash":"0x004134134134134134134134134134134134134134134134134134134134134134"}}"#,
579 "expected hex string to be prefixed by '0x'",
580 );
581 }
582
583 #[test]
584 fn deserialize_get_transaction_by_block_and_index_request() {
585 let json_str = r#"{"method":"starknet_getTransactionByBlockIdAndIndex","params":{"block_id":"latest","index":0}}"#;
586 assert_deserialization_succeeds(json_str);
587
588 assert_deserialization_fails(
589 json_str.replace('0', "\"0x1\"").as_str(),
590 "invalid type: string \"0x1\", expected u64",
591 );
592 }
593
594 #[test]
595 fn deserialize_get_transaction_receipt_request() {
596 let json_str = r#"{"method":"starknet_getTransactionReceipt","params":{"transaction_hash":"0xAAABB"}}"#;
597 assert_deserialization_succeeds(json_str);
598
599 assert_deserialization_fails(
600 json_str.replace("0x", "").as_str(),
601 "expected hex string to be prefixed by '0x'",
602 );
603 }
604
605 #[test]
606 fn deserialize_get_class_request() {
607 let json_str = r#"{"method":"starknet_getClass","params":{"block_id":"latest","class_hash":"0xAAABB"}}"#;
608 assert_deserialization_succeeds(json_str);
609
610 assert_deserialization_fails(
611 json_str.replace("0x", "").as_str(),
612 "expected hex string to be prefixed by '0x'",
613 );
614 }
615
616 #[test]
617 fn deserialize_get_class_hash_at_request() {
618 let json_str = r#"{"method":"starknet_getClassHashAt","params":{"block_id":"latest","contract_address":"0xAAABB"}}"#;
619 assert_deserialization_succeeds(json_str);
620
621 assert_deserialization_fails(
622 json_str.replace("0x", "").as_str(),
623 "Error converting from hex string",
624 );
625 }
626
627 #[test]
628 fn deserialize_get_class_at_request() {
629 let json_str = r#"{"method":"starknet_getClassAt","params":{"block_id":"latest","contract_address":"0xAAABB"}}"#;
630 assert_deserialization_succeeds(json_str);
631
632 assert_deserialization_fails(json_str.replace("0x", "").as_str(), "Missing prefix 0x");
633 }
634
635 #[test]
636 fn deserialize_get_block_transaction_count_request() {
637 let json_str =
638 r#"{"method":"starknet_getBlockTransactionCount","params":{"block_id":"latest"}}"#;
639 assert_deserialization_succeeds(json_str);
640
641 assert_deserialization_fails(
642 json_str.replace("latest", "0x134134").as_str(),
643 EXPECTED_INVALID_BLOCK_ID_MSG,
644 );
645 }
646
647 #[test]
648 fn deserialize_call_request() {
649 let json_str = r#"{
650 "method":"starknet_call",
651 "params":{
652 "block_id":"latest",
653 "request":{
654 "contract_address":"0xAAABB",
655 "entry_point_selector":"0x134134",
656 "calldata":["0x134134"]
657 }
658 }
659 }"#;
660
661 assert_deserialization_succeeds(json_str);
662
663 assert_deserialization_fails(
664 json_str.replace("starknet_call", "starknet_Call").as_str(),
665 "unknown variant `starknet_Call`",
666 );
667
668 assert_deserialization_fails(
669 json_str
670 .replace(r#""contract_address":"0xAAABB""#, r#""contract_address":"123""#)
671 .as_str(),
672 "Error converting from hex string",
673 );
674 assert_deserialization_fails(
675 json_str
676 .replace(
677 r#""entry_point_selector":"0x134134""#,
678 r#""entry_point_selector":"134134""#,
679 )
680 .as_str(),
681 "expected hex string to be prefixed by '0x'",
682 );
683 assert_deserialization_fails(
684 json_str.replace(r#""calldata":["0x134134"]"#, r#""calldata":["123"]"#).as_str(),
685 "expected hex string to be prefixed by '0x'",
686 );
687 assert_deserialization_fails(
688 json_str.replace(r#""calldata":["0x134134"]"#, r#""calldata":[123]"#).as_str(),
689 "invalid type: number, expected a 32 byte array ([u8;32]) or a hexadecimal string",
690 );
691 }
692
693 #[test]
694 fn deserialize_deploy_account_fee_estimation_request() {
695 let json_str = r#"{
696 "method":"starknet_estimateFee",
697 "params":{
698 "block_id":"latest",
699 "simulation_flags": [],
700 "request":[
701 {
702 "type":"DEPLOY_ACCOUNT",
703 "resource_bounds": {
704 "l1_gas": {
705 "max_amount": "0x1",
706 "max_price_per_unit": "0x2"
707 },
708 "l1_data_gas": {
709 "max_amount": "0x1",
710 "max_price_per_unit": "0x2"
711 },
712 "l2_gas": {
713 "max_amount": "0x1",
714 "max_price_per_unit": "0x2"
715 }
716 },
717 "tip": "0xabc",
718 "paymaster_data": [],
719 "version": "0x100000000000000000000000000000003",
720 "signature": ["0xFF", "0xAA"],
721 "nonce": "0x0",
722 "contract_address_salt": "0x01",
723 "class_hash": "0x01",
724 "constructor_calldata": ["0x01"],
725 "nonce_data_availability_mode": "L1",
726 "fee_data_availability_mode": "L1"
727 }
728 ]
729 }
730 }"#;
731
732 assert_deserialization_succeeds(json_str);
733
734 assert_deserialization_fails(
735 json_str.replace("estimateFee", "estimate_fee").as_str(),
736 "unknown variant `starknet_estimate_fee`",
737 );
738 }
739
740 fn sample_declare_v3_body() -> serde_json::Value {
741 json!({
742 "type":"DECLARE",
743 "version": "0x3",
744 "signature": [
745 "0x2216f8f4d9abc06e130d2a05b13db61850f0a1d21891c7297b98fd6cc51920d",
746 "0x6aadfb198bbffa8425801a2342f5c6d804745912114d5976f53031cd789bb6d"
747 ],
748 "resource_bounds": {
749 "l1_gas": {
750 "max_amount": "0x1",
751 "max_price_per_unit": "0x2"
752 },
753 "l1_data_gas": {
754 "max_amount": "0x1",
755 "max_price_per_unit": "0x2"
756 },
757 "l2_gas": {
758 "max_amount": "0x1",
759 "max_price_per_unit": "0x2"
760 }
761 },
762 "tip": "0xabc",
763 "paymaster_data": [],
764 "account_deployment_data": [],
765 "nonce": "0x0",
766 "compiled_class_hash":"0x63b33a5f2f46b1445d04c06d7832c48c48ad087ce0803b71f2b8d96353716ca",
767 "sender_address":"0x34ba56f92265f0868c57d3fe72ecab144fc96f97954bbbc4252cef8e8a979ba",
768 "contract_class": {
769 "sierra_program": ["0xAA", "0xBB"],
770 "entry_points_by_type": {
771 "EXTERNAL": [{"function_idx":0,"selector":"0x362398bec32bc0ebb411203221a35a0301193a96f317ebe5e40be9f60d15320"},{"function_idx":1,"selector":"0x39e11d48192e4333233c7eb19d10ad67c362bb28580c604d67884c85da39695"}],
772 "L1_HANDLER": [],
773 "CONSTRUCTOR": [{"function_idx":2,"selector":"0x28ffe4ff0f226a9107253e17a904099aa4f63a02a5621de0576e5aa71bc5194"}]
774 },
775 "abi": "[{\"type\": \"function\", \"name\": \"constructor\", \"inputs\": [{\"name\": \"initial_balance\", \"type\": \"core::felt252\"}], \"outputs\": [], \"state_mutability\": \"external\"}, {\"type\": \"function\", \"name\": \"increase_balance\", \"inputs\": [{\"name\": \"amount1\", \"type\": \"core::felt252\"}, {\"name\": \"amount2\", \"type\": \"core::felt252\"}], \"outputs\": [], \"state_mutability\": \"external\"}, {\"type\": \"function\", \"name\": \"get_balance\", \"inputs\": [], \"outputs\": [{\"type\": \"core::felt252\"}], \"state_mutability\": \"view\"}]",
776 "contract_class_version": "0.1.0"
777 },
778 "nonce_data_availability_mode": "L1",
779 "fee_data_availability_mode": "L1"
780 })
781 }
782
783 fn create_declare_request(tx: serde_json::Value) -> serde_json::Value {
784 json!({
785 "method":"starknet_addDeclareTransaction",
786 "params":{
787 "declare_transaction": tx
788 }
789 })
790 }
791
792 fn create_estimate_request(requests: &[serde_json::Value]) -> serde_json::Value {
793 json!({
794 "method": "starknet_estimateFee",
795 "params": {
796 "block_id": "latest",
797 "simulation_flags": [],
798 "request": requests
799 }
800 })
801 }
802
803 #[test]
804 fn deserialize_declare_v3_fee_estimation_request() {
805 assert_deserialization_succeeds(
806 &create_estimate_request(&[sample_declare_v3_body()]).to_string(),
807 );
808 assert_deserialization_succeeds(
809 &create_estimate_request(&[sample_declare_v3_body()]).to_string().replace(
810 r#""version":"0x3""#,
811 r#""version":"0x100000000000000000000000000000003""#,
812 ),
813 );
814 }
815
816 #[test]
817 fn deserialize_get_events_request() {
818 let json_str = r#"{
819 "method":"starknet_getEvents",
820 "params":{
821 "filter":{
822 "chunk_size": 1,
823 "address":"0xAAABB",
824 "keys":[["0xFF"], ["0xAA"]],
825 "from_block": "latest",
826 "to_block": "pre_confirmed",
827 "continuation_token": "0x11"
828 }
829 }
830 }"#;
831
832 assert_deserialization_succeeds(json_str);
833 assert_deserialization_succeeds(
834 json_str.replace(r#""to_block": "pre_confirmed","#, "").as_str(),
835 );
836
837 assert_deserialization_fails(
838 json_str.replace(r#""chunk_size": 1,"#, "").as_str(),
839 "missing field `chunk_size`",
840 );
841 }
842
843 #[test]
844 fn deserialize_get_nonce_request() {
845 let json_str = r#"{
846 "method":"starknet_getNonce",
847 "params":{
848 "block_id":"latest",
849 "contract_address":"0xAAABB"
850 }
851 }"#;
852
853 assert_deserialization_succeeds(json_str);
854 assert_deserialization_fails(
855 json_str.replace(r#""block_id":"latest","#, "").as_str(),
856 "missing field `block_id`",
857 );
858 }
859
860 #[test]
861 fn deserialize_add_deploy_account_transaction_request() {
862 let json_str = r#"{
863 "method":"starknet_addDeployAccountTransaction",
864 "params":{
865 "deploy_account_transaction":{
866 "type":"DEPLOY_ACCOUNT",
867 "resource_bounds": {
868 "l1_gas": {
869 "max_amount": "0x1",
870 "max_price_per_unit": "0x2"
871 },
872 "l1_data_gas": {
873 "max_amount": "0x1",
874 "max_price_per_unit": "0x2"
875 },
876 "l2_gas": {
877 "max_amount": "0x1",
878 "max_price_per_unit": "0x2"
879 }
880 },
881 "tip": "0xabc",
882 "paymaster_data": [],
883 "version": "0x3",
884 "signature": ["0xFF", "0xAA"],
885 "nonce": "0x0",
886 "contract_address_salt": "0x01",
887 "class_hash": "0x01",
888 "constructor_calldata": ["0x01"],
889 "nonce_data_availability_mode": "L1",
890 "fee_data_availability_mode": "L1"
891 }
892 }
893 }"#;
894
895 assert_deserialization_succeeds(json_str);
896 assert_deserialization_fails(
897 json_str.replace(r#""class_hash": "0x01","#, "").as_str(),
898 "missing field `class_hash`",
899 );
900 }
901
902 #[test]
903 fn deserialize_add_declare_transaction_v3_request() {
904 assert_deserialization_succeeds(
905 &create_declare_request(sample_declare_v3_body()).to_string(),
906 );
907
908 assert_deserialization_fails(
909 &create_declare_request(sample_declare_v3_body())
910 .to_string()
911 .replace(r#""version":"0x3""#, r#""version":"0x123""#),
912 "Invalid version of declare transaction: \"0x123\"",
913 );
914
915 assert_deserialization_fails(
916 &create_estimate_request(&[sample_declare_v3_body()])
917 .to_string()
918 .replace("resource_bounds", "resourceBounds"),
919 "Invalid declare transaction v3: missing field `resource_bounds`",
920 );
921
922 assert_deserialization_fails(
923 &create_declare_request(sample_declare_v3_body())
924 .to_string()
925 .replace(r#""nonce":"0x0""#, r#""nonce":123"#),
926 "Invalid declare transaction v3: invalid type: integer `123`",
927 );
928 }
929
930 #[test]
931 fn deseralize_chain_id_request() {
932 for body in [
933 json!({
934 "method": "starknet_chainId",
935 "params": {}
936 }),
937 json!({
938 "method": "starknet_chainId",
939 "params": []
940 }),
941 json!({
942 "method": "starknet_chainId",
943 }),
944 ] {
945 assert_deserialization_succeeds(body.to_string().as_str())
946 }
947 }
948
949 #[test]
950 fn deserialize_spec_version_request() {
951 for body in [
952 json!({
953 "method": "starknet_specVersion",
954 "params": {}
955 }),
956 json!({
957 "method": "starknet_specVersion",
958 "params": []
959 }),
960 json!({
961 "method": "starknet_specVersion",
962 }),
963 ] {
964 assert_deserialization_succeeds(body.to_string().as_str())
965 }
966 }
967
968 #[test]
969 fn deserialize_devnet_methods_with_optional_body() {
970 for mut body in [
971 json!({
972 "method": "devnet_dump",
973 "params": {}
974 }),
975 json!({
976 "method":"devnet_dump",
977 }),
978 json!({
979 "method":"devnet_dump",
980 "params": {"path": "path"}
981 }),
982 json!({
983 "method":"devnet_getPredeployedAccounts",
984 "params": {"with_balance": true}
985 }),
986 json!({
987 "method":"devnet_getPredeployedAccounts",
988 }),
989 json!({
990 "method":"devnet_getPredeployedAccounts",
991 "params": {}
992 }),
993 json!({
994 "method":"devnet_postmanFlush",
995 "params": {"dry_run": true}
996 }),
997 json!({
998 "method":"devnet_postmanFlush",
999 }),
1000 json!({
1001 "method":"devnet_postmanFlush",
1002 "params": {}
1003 }),
1004 ] {
1005 let mut json_rpc_object = json!({
1006 "jsonrpc": "2.0",
1007 "id": 1,
1008 });
1009
1010 json_rpc_object.as_object_mut().unwrap().append(body.as_object_mut().unwrap());
1011
1012 let RpcMethodCall { method, params, .. } =
1013 serde_json::from_value(json_rpc_object).unwrap();
1014 let params: serde_json::Value = params.into();
1015 let deserializable_call = json!({
1016 "method": &method,
1017 "params": params
1018 });
1019
1020 assert_deserialization_succeeds(deserializable_call.to_string().as_str())
1021 }
1022 }
1023
1024 fn assert_deserialization_succeeds(json_str: &str) {
1025 serde_json::from_str::<JsonRpcRequest>(json_str).unwrap();
1026 }
1027
1028 fn assert_deserialization_fails(json_str: &str, expected_msg: &str) {
1029 match serde_json::from_str::<JsonRpcRequest>(json_str) {
1030 Err(err) => assert_contains(&err.to_string(), expected_msg).unwrap(),
1031 other => panic!("Invalid result: {other:?}"),
1032 }
1033 }
1034}