Skip to content

Commit 3428d34

Browse files
starknet_api: do not panic on malformed resource_bounds in tx json deserializer (#14412)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 6cfff03 commit 3428d34

2 files changed

Lines changed: 34 additions & 16 deletions

File tree

crates/starknet_api/src/serde_utils.rs

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -160,23 +160,18 @@ where
160160
/// In old transactions, the resource bounds names are lowercase.
161161
/// Need to convert to uppercase for deserialization to work.
162162
fn upper_case_resource_bounds_names(raw_transaction: &mut Value) {
163-
let resource_bounds = raw_transaction
164-
.get_mut("resource_bounds")
165-
.expect("tx should contain resource_bounds field")
166-
.as_object_mut()
167-
.expect("resource_bounds should be an object");
168-
169-
if let Some(l1_gas_value) = resource_bounds.remove("l1_gas") {
170-
resource_bounds.insert("L1_GAS".to_string(), l1_gas_value);
171-
172-
let l2_gas_value = resource_bounds
173-
.remove("l2_gas")
174-
.expect("If tx contains l1_gas, it should contain l2_gas");
175-
resource_bounds.insert("L2_GAS".to_string(), l2_gas_value);
176-
}
163+
let Some(resource_bounds) =
164+
raw_transaction.get_mut("resource_bounds").and_then(Value::as_object_mut)
165+
else {
166+
return;
167+
};
177168

178-
if let Some(l1_data_gas_value) = resource_bounds.remove("l1_data_gas") {
179-
resource_bounds.insert("L1_DATA_GAS".to_string(), l1_data_gas_value);
169+
for (lower_case_name, upper_case_name) in
170+
[("l1_gas", "L1_GAS"), ("l2_gas", "L2_GAS"), ("l1_data_gas", "L1_DATA_GAS")]
171+
{
172+
if let Some(resource_bounds_value) = resource_bounds.remove(lower_case_name) {
173+
resource_bounds.insert(upper_case_name.to_string(), resource_bounds_value);
174+
}
180175
}
181176
}
182177

crates/starknet_api/src/serde_utils_test.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::deprecated_contract_class::{
1010
use crate::serde_utils::{
1111
bytes_from_hex_str,
1212
deserialize_optional_contract_class_abi_entry_vector,
13+
deserialize_transaction_json_to_starknet_api_tx,
1314
hex_str_from_bytes,
1415
BytesAsHex,
1516
InnerDeserializationError,
@@ -186,3 +187,25 @@ fn deserialize_optional_contract_class_abi_entry_vector_none() {
186187
let res: DummyContractClass = serde_json::from_str(json).unwrap();
187188
assert_eq!(res, DummyContractClass { abi: None });
188189
}
190+
191+
/// V3 transaction JSON may arrive from untrusted sources; a missing or malformed
192+
/// `resource_bounds` field must surface as a deserialization error, not a panic.
193+
#[test]
194+
fn deserialize_transaction_json_does_not_panic_on_malformed_resource_bounds() {
195+
// Missing resource_bounds field.
196+
let raw_transaction = serde_json::json!({"type": "INVOKE", "version": "0x3"});
197+
assert!(deserialize_transaction_json_to_starknet_api_tx(raw_transaction).is_err());
198+
199+
// resource_bounds is not an object.
200+
let raw_transaction =
201+
serde_json::json!({"type": "INVOKE", "version": "0x3", "resource_bounds": 5});
202+
assert!(deserialize_transaction_json_to_starknet_api_tx(raw_transaction).is_err());
203+
204+
// l1_gas without l2_gas.
205+
let raw_transaction = serde_json::json!({
206+
"type": "DECLARE",
207+
"version": "0x3",
208+
"resource_bounds": {"l1_gas": {"max_amount": "0x0", "max_price_per_unit": "0x0"}}
209+
});
210+
assert!(deserialize_transaction_json_to_starknet_api_tx(raw_transaction).is_err());
211+
}

0 commit comments

Comments
 (0)