#[cfg(test)]
#[path = "serde_utils_test.rs"]
mod serde_utils_test;
use serde::de::{Deserialize, Visitor};
use serde::ser::{Serialize, SerializeTuple};
use serde::Deserializer;
use serde_json::Value;
use crate::deprecated_contract_class::ContractClassAbiEntry;
use crate::transaction::{
DeclareTransaction,
DeployAccountTransaction,
InvokeTransaction,
Transaction,
};
pub type PrefixedBytesAsHex<const N: usize> = BytesAsHex<N, true>;
#[derive(Debug, Eq, PartialEq)]
pub struct BytesAsHex<const N: usize, const PREFIXED: bool>(pub(crate) [u8; N]);
impl<'de, const N: usize, const PREFIXED: bool> Deserialize<'de> for BytesAsHex<N, PREFIXED> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct ByteArrayVisitor<const N: usize, const PREFIXED: bool>;
impl<'de, const N: usize, const PREFIXED: bool> Visitor<'de> for ByteArrayVisitor<N, PREFIXED> {
type Value = BytesAsHex<N, PREFIXED>;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("a byte array")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let mut res = [0u8; N];
let mut i = 0;
while let Some(value) = seq.next_element()? {
res[i] = value;
i += 1;
}
Ok(BytesAsHex(res))
}
}
if deserializer.is_human_readable() {
let s = String::deserialize(deserializer)?;
bytes_from_hex_str::<N, PREFIXED>(s.as_str())
.map_err(serde::de::Error::custom)
.map(BytesAsHex)
} else {
deserializer.deserialize_tuple(N, ByteArrayVisitor)
}
}
}
impl<const N: usize, const PREFIXED: bool> Serialize for BytesAsHex<N, PREFIXED> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if serializer.is_human_readable() {
let hex_str = hex_str_from_bytes::<N, PREFIXED>(self.0);
serializer.serialize_str(&hex_str)
} else {
let mut seq = serializer.serialize_tuple(N)?;
for element in &self.0[..] {
seq.serialize_element(element)?;
}
seq.end()
}
}
}
#[derive(thiserror::Error, Clone, Debug, PartialEq)]
pub enum InnerDeserializationError {
#[error(transparent)]
FromHex(#[from] hex::FromHexError),
#[error("Missing prefix 0x in {hex_str}")]
MissingPrefix { hex_str: String },
#[error("Bad input - expected #bytes: {expected_byte_count}, string found: {string_found}.")]
BadInput { expected_byte_count: usize, string_found: String },
}
pub fn bytes_from_hex_str<const N: usize, const PREFIXED: bool>(
hex_str: &str,
) -> Result<[u8; N], InnerDeserializationError> {
let hex_str = if PREFIXED {
hex_str
.strip_prefix("0x")
.ok_or(InnerDeserializationError::MissingPrefix { hex_str: hex_str.into() })?
} else {
hex_str
};
if hex_str.len() > 2 * N {
let mut err_str = "0x".to_owned();
err_str.push_str(hex_str);
return Err(InnerDeserializationError::BadInput {
expected_byte_count: N,
string_found: err_str,
});
}
let to_add = 2 * N - hex_str.len();
let padded_str = vec!["0"; to_add].join("") + hex_str;
Ok(hex::decode(padded_str)?.try_into().expect("Unexpected length of deserialized hex bytes."))
}
pub fn hex_str_from_bytes<const N: usize, const PREFIXED: bool>(bytes: [u8; N]) -> String {
let hex_str = hex::encode(bytes);
let mut hex_str = hex_str.trim_start_matches('0');
hex_str = if hex_str.is_empty() { "0" } else { hex_str };
if PREFIXED { format!("0x{hex_str}") } else { hex_str.to_string() }
}
pub fn deserialize_optional_contract_class_abi_entry_vector<'de, D>(
deserializer: D,
) -> Result<Option<Vec<ContractClassAbiEntry>>, D::Error>
where
D: Deserializer<'de>,
{
let result: Result<Option<Vec<ContractClassAbiEntry>>, _> = Option::deserialize(deserializer);
match result {
Ok(value) => Ok(value),
Err(_) => Ok(None),
}
}
fn upper_case_resource_bounds_names(raw_transaction: &mut Value) {
let resource_bounds = raw_transaction
.get_mut("resource_bounds")
.expect("tx should contain resource_bounds field")
.as_object_mut()
.expect("resource_bounds should be an object");
if let Some(l1_gas_value) = resource_bounds.remove("l1_gas") {
resource_bounds.insert("L1_GAS".to_string(), l1_gas_value);
let l2_gas_value = resource_bounds
.remove("l2_gas")
.expect("If tx contains l1_gas, it should contain l2_gas");
resource_bounds.insert("L2_GAS".to_string(), l2_gas_value);
}
if let Some(l1_data_gas_value) = resource_bounds.remove("l1_data_gas") {
resource_bounds.insert("L1_DATA_GAS".to_string(), l1_data_gas_value);
}
}
pub fn deserialize_transaction_json_to_starknet_api_tx(
mut raw_transaction: Value,
) -> serde_json::Result<Transaction> {
let tx_type: String = serde_json::from_value(raw_transaction["type"].clone())?;
let tx_version: String = serde_json::from_value(raw_transaction["version"].clone())?;
let raw_resourcebounds = &raw_transaction["resource_bounds"];
if !raw_resourcebounds.is_null()
&& !raw_resourcebounds["l1_data_gas"].is_null()
&& raw_resourcebounds["l1_data_gas"]["max_amount"] == "0x0"
&& !raw_resourcebounds["l2_gas"].is_null()
&& raw_resourcebounds["l2_gas"]["max_amount"] == "0x0"
{
raw_transaction["resource_bounds"]
.as_object_mut()
.expect("should be map of resource bounds")
.remove("l1_data_gas");
}
match (tx_type.as_str(), tx_version.as_str()) {
("INVOKE", "0x0") => {
Ok(Transaction::Invoke(InvokeTransaction::V0(serde_json::from_value(raw_transaction)?)))
}
("INVOKE", "0x1") => {
Ok(Transaction::Invoke(InvokeTransaction::V1(serde_json::from_value(raw_transaction)?)))
}
("INVOKE", "0x3") => {
upper_case_resource_bounds_names(&mut raw_transaction);
Ok(Transaction::Invoke(InvokeTransaction::V3(serde_json::from_value(raw_transaction)?)))
}
("DEPLOY_ACCOUNT", "0x1") => Ok(Transaction::DeployAccount(DeployAccountTransaction::V1(
serde_json::from_value(raw_transaction)?,
))),
("DEPLOY_ACCOUNT", "0x3") => {
upper_case_resource_bounds_names(&mut raw_transaction);
Ok(Transaction::DeployAccount(DeployAccountTransaction::V3(serde_json::from_value(
raw_transaction,
)?)))
}
("DECLARE", "0x0") => Ok(Transaction::Declare(DeclareTransaction::V0(
serde_json::from_value(raw_transaction)?,
))),
("DECLARE", "0x1") => Ok(Transaction::Declare(DeclareTransaction::V1(
serde_json::from_value(raw_transaction)?,
))),
("DECLARE", "0x2") => Ok(Transaction::Declare(DeclareTransaction::V2(
serde_json::from_value(raw_transaction)?,
))),
("DECLARE", "0x3") => {
upper_case_resource_bounds_names(&mut raw_transaction);
Ok(Transaction::Declare(DeclareTransaction::V3(serde_json::from_value(
raw_transaction,
)?)))
}
("L1_HANDLER", _) => Ok(Transaction::L1Handler(serde_json::from_value(raw_transaction)?)),
(tx_type, tx_version) => Err(serde::de::Error::custom(format!(
"unimplemented transaction type: {tx_type} version: {tx_version}"
))),
}
}