Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"class_hash": "0x1fcd2dcd811e049eaa730b87038a68f39220a04986b43fe287d690da0df01b8",
"constructor_calldata": [
"0x0",
"0x1",
"0x2",
"0x3",
"0x4",
"0x5",
"0x6",
"0x7",
"0x8",
"0x9"
],
"contract_address_salt": "0x2",
"fee_data_availability_mode": 0,
"nonce": "0x0",
"nonce_data_availability_mode": 0,
"paymaster_data": [],
"resource_bounds": {
"L1_GAS": {
"max_amount": "0xffffffffffffffff",
"max_price_per_unit": "0xffffffffffffffffffffffffffffffff"
},
"L2_GAS": {
"max_amount": "0x0",
"max_price_per_unit": "0x0"
}
},
"sender_address": "0x1b34d819720bd84c89bdfb476bc2c4d0de9a41b766efabd20fa292280e4c6d9",
"signature": [],
"tip": "0x0",
"transaction_hash": "0x60bc493f32b6a2ebc2f6fd67324e72c5104ca26afa9a1f61726caa1094f340b",
"version": "0x4",
"type": "DEPLOY_ACCOUNT"
}
121 changes: 83 additions & 38 deletions crates/apollo_starknet_client/src/reader/objects/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ impl From<DeployTransaction> for starknet_api::transaction::DeployTransaction {

/// Feeder Gateway format for deploy account transactions (see [`Transaction`]).
///
/// Supports multiple transaction versions (V1/V3) through optional fields.
/// Supports multiple transaction versions (V1/V3/V4) through optional fields.
// TODO(shahak, 01/11/2023): Add serde tests for v3 transactions.
#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
#[serde(deny_unknown_fields)]
Expand Down Expand Up @@ -416,6 +416,9 @@ impl TryFrom<IntermediateDeployAccountTransaction>
if deploy_account_tx.version == TransactionVersion::THREE {
return Ok(Self::V3(deploy_account_tx.try_into()?));
}
if deploy_account_tx.version == TransactionVersion::FOUR {
return Ok(Self::V4(deploy_account_tx.try_into()?));
}
Err(ReaderClientError::BadTransaction {
tx_hash: deploy_account_tx.transaction_hash,
msg: format!("DeployAccount version {:?} is not supported.", deploy_account_tx.version),
Expand Down Expand Up @@ -453,48 +456,90 @@ impl TryFrom<IntermediateDeployAccountTransaction>
fn try_from(
deploy_account_tx: IntermediateDeployAccountTransaction,
) -> Result<Self, ReaderClientError> {
deploy_account_v3_shaped_fields(deploy_account_tx)
}
}

/// V4 has the same field set as V3; only the version felt and the address derivation differ.
impl TryFrom<IntermediateDeployAccountTransaction>
for starknet_api::transaction::DeployAccountTransactionV4
{
type Error = ReaderClientError;

fn try_from(
deploy_account_tx: IntermediateDeployAccountTransaction,
) -> Result<Self, ReaderClientError> {
let starknet_api::transaction::DeployAccountTransactionV3 {
resource_bounds,
tip,
signature,
nonce,
class_hash,
contract_address_salt,
constructor_calldata,
nonce_data_availability_mode,
fee_data_availability_mode,
paymaster_data,
} = deploy_account_v3_shaped_fields(deploy_account_tx)?;
Ok(Self {
resource_bounds: deploy_account_tx.resource_bounds.ok_or(
ReaderClientError::BadTransaction {
tx_hash: deploy_account_tx.transaction_hash,
msg: "DeployAccount V3 must contain resource_bounds field.".to_string(),
},
)?,
tip: deploy_account_tx.tip.ok_or(ReaderClientError::BadTransaction {
tx_hash: deploy_account_tx.transaction_hash,
msg: "DeployAccount V3 must contain tip field.".to_string(),
})?,
signature: deploy_account_tx.signature,
nonce: deploy_account_tx.nonce,
class_hash: deploy_account_tx.class_hash,
contract_address_salt: deploy_account_tx.contract_address_salt,
constructor_calldata: deploy_account_tx.constructor_calldata,
nonce_data_availability_mode: deploy_account_tx
.nonce_data_availability_mode
.ok_or(ReaderClientError::BadTransaction {
tx_hash: deploy_account_tx.transaction_hash,
msg: "DeployAccount V3 must contain nonce_data_availability_mode field."
.to_string(),
})?
.into(),
fee_data_availability_mode: deploy_account_tx
.fee_data_availability_mode
.ok_or(ReaderClientError::BadTransaction {
tx_hash: deploy_account_tx.transaction_hash,
msg: "DeployAccount V3 must contain fee_data_availability_mode field."
.to_string(),
})?
.into(),
paymaster_data: deploy_account_tx.paymaster_data.ok_or(
ReaderClientError::BadTransaction {
tx_hash: deploy_account_tx.transaction_hash,
msg: "DeployAccount V3 must contain paymaster_data field.".to_string(),
},
)?,
resource_bounds,
tip,
signature,
nonce,
class_hash,
contract_address_salt,
constructor_calldata,
nonce_data_availability_mode,
fee_data_availability_mode,
paymaster_data,
})
}
}

fn deploy_account_v3_shaped_fields(
deploy_account_tx: IntermediateDeployAccountTransaction,
) -> Result<starknet_api::transaction::DeployAccountTransactionV3, ReaderClientError> {
Ok(starknet_api::transaction::DeployAccountTransactionV3 {
resource_bounds: deploy_account_tx.resource_bounds.ok_or(
ReaderClientError::BadTransaction {
tx_hash: deploy_account_tx.transaction_hash,
msg: "DeployAccount V3/V4 must contain resource_bounds field.".to_string(),
},
)?,
tip: deploy_account_tx.tip.ok_or(ReaderClientError::BadTransaction {
tx_hash: deploy_account_tx.transaction_hash,
msg: "DeployAccount V3/V4 must contain tip field.".to_string(),
})?,
signature: deploy_account_tx.signature,
nonce: deploy_account_tx.nonce,
class_hash: deploy_account_tx.class_hash,
contract_address_salt: deploy_account_tx.contract_address_salt,
constructor_calldata: deploy_account_tx.constructor_calldata,
nonce_data_availability_mode: deploy_account_tx
.nonce_data_availability_mode
.ok_or(ReaderClientError::BadTransaction {
tx_hash: deploy_account_tx.transaction_hash,
msg: "DeployAccount V3/V4 must contain nonce_data_availability_mode field."
.to_string(),
})?
.into(),
fee_data_availability_mode: deploy_account_tx
.fee_data_availability_mode
.ok_or(ReaderClientError::BadTransaction {
tx_hash: deploy_account_tx.transaction_hash,
msg: "DeployAccount V3/V4 must contain fee_data_availability_mode field."
.to_string(),
})?
.into(),
paymaster_data: deploy_account_tx.paymaster_data.ok_or(
ReaderClientError::BadTransaction {
tx_hash: deploy_account_tx.transaction_hash,
msg: "DeployAccount V3/V4 must contain paymaster_data field.".to_string(),
},
)?,
})
}

/// Feeder Gateway format for invoke transactions (see [`Transaction`]).
///
/// Supports multiple transaction versions (V0/V1/V3) through optional fields.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use assert_matches::assert_matches;
use starknet_api::transaction::DeployAccountTransaction;

use super::{Transaction, TransactionReceipt};
use crate::test_utils::read_resource::read_resource_file;
Expand Down Expand Up @@ -57,6 +58,7 @@ fn load_transaction_succeeds() {
"reader/declare_v0.json",
"reader/declare_v3.json",
"reader/deploy_account_v3.json",
"reader/deploy_account_v4.json",
] {
let res = serde_json::from_str::<Transaction>(&read_resource_file(file_name));
assert!(res.is_ok(), "filename: {file_name}, error: {res:?}");
Expand All @@ -70,6 +72,7 @@ fn load_transaction_unknown_field_fails() {
"reader/invoke_v0.json",
"reader/declare_v0.json",
"reader/deploy_account_v3.json",
"reader/deploy_account_v4.json",
] {
let mut json_value: serde_json::Value =
serde_json::from_str(&read_resource_file(file_name)).unwrap();
Expand All @@ -90,6 +93,7 @@ fn load_transaction_wrong_type_fails() {
("reader/invoke_v0.json", "DECLARE"),
("reader/declare_v0.json", "DEPLOY"),
("reader/deploy_account_v3.json", "INVOKE_FUNCTION"),
("reader/deploy_account_v4.json", "DECLARE"),
] {
let mut json_value: serde_json::Value =
serde_json::from_str(&read_resource_file(file_name)).unwrap();
Expand Down Expand Up @@ -119,3 +123,56 @@ fn load_transaction_receipt_succeeds() {
);
}
}

#[test]
fn deploy_account_v4_converts_to_starknet_api_v4() {
let client_tx: Transaction =
serde_json::from_str(&read_resource_file("reader/deploy_account_v4.json")).unwrap();
let Transaction::DeployAccount(deploy_account_tx) = client_tx.clone() else {
panic!("Expected a deploy account transaction.");
};

let api_tx = DeployAccountTransaction::try_from(deploy_account_tx).unwrap();
let DeployAccountTransaction::V4(v4_tx) = &api_tx else {
panic!("Expected a V4 deploy account transaction, got {api_tx:?}.");
};

// V4 carries the v3 field set verbatim; the version felt is the only difference.
let v3_client_tx: Transaction =
serde_json::from_str(&read_resource_file("reader/deploy_account_v3.json")).unwrap();
let Transaction::DeployAccount(v3_deploy_account_tx) = v3_client_tx else {
panic!("Expected a deploy account transaction.");
};
let DeployAccountTransaction::V3(v3_tx) =
DeployAccountTransaction::try_from(v3_deploy_account_tx).unwrap()
else {
panic!("Expected a V3 deploy account transaction.");
};
assert_eq!(v4_tx.resource_bounds, v3_tx.resource_bounds);
assert_eq!(v4_tx.constructor_calldata, v3_tx.constructor_calldata);
assert_eq!(v4_tx.class_hash, v3_tx.class_hash);
assert_eq!(v4_tx.contract_address_salt, v3_tx.contract_address_salt);

// Round-trip: the client form serializes back to the same JSON.
let round_tripped: serde_json::Value = serde_json::to_value(&client_tx).unwrap();
let original: serde_json::Value =
serde_json::from_str(&read_resource_file("reader/deploy_account_v4.json")).unwrap();
assert_eq!(round_tripped, original);
}

#[test]
fn version_0x4_is_deploy_account_only() {
for file_name in ["reader/invoke_v3.json", "reader/declare_v3.json"] {
let mut json_value: serde_json::Value =
serde_json::from_str(&read_resource_file(file_name)).unwrap();
json_value
.as_object_mut()
.unwrap()
.insert("version".to_string(), serde_json::Value::String("0x4".to_string()));
let client_tx: Transaction = serde_json::from_value(json_value).unwrap();
assert!(
starknet_api::transaction::Transaction::try_from(client_tx).is_err(),
"filename: {file_name}"
);
}
}
Loading