From 159b89bf00b97d13a43ebed82837b5ba90c5ce27 Mon Sep 17 00:00:00 2001 From: ron-starkware Date: Wed, 15 Jul 2026 10:48:54 +0300 Subject: [PATCH] starknet_api: add DeployAccount v4 transaction type (Blake2 address derivation) --- .../apollo_batcher/src/cende_client_types.rs | 62 ++++++--- .../src/cende/central_objects.rs | 74 +++++++--- .../src/cende/central_objects_test.rs | 7 +- .../src/stateless_transaction_validator.rs | 7 + .../src/deprecated_gateway_transaction.rs | 5 + .../src/converters/consensus_test.rs | 4 + .../src/converters/rpc_transaction.rs | 33 ++++- .../src/converters/transaction.rs | 116 ++++++++++++++++ .../proto/p2p/proto/consensus/consensus.proto | 1 + .../proto/p2p/proto/mempool/transaction.proto | 1 + .../proto/p2p/proto/sync/transaction.proto | 1 + .../src/proto/p2p/proto/transaction.proto | 15 +++ .../src/protobuf/protoc_output.rs | 38 +++++- crates/apollo_rpc/src/v0_8/transaction.rs | 7 + .../src/serialization/serializers.rs | 15 +++ crates/apollo_test_utils/src/lib.rs | 28 ++++ .../src/transaction_converter.rs | 13 ++ .../src/transaction/transactions.rs | 12 ++ .../src/transaction/transactions_test.rs | 1 + .../src/executable_transaction.rs | 13 +- crates/starknet_api/src/rpc_transaction.rs | 127 +++++++++++++++++- .../starknet_api/src/rpc_transaction_test.rs | 4 +- crates/starknet_api/src/serde_utils.rs | 3 + .../src/test_utils/deploy_account.rs | 16 ++- crates/starknet_api/src/transaction.rs | 78 ++++++++++- crates/starknet_api/src/transaction_hash.rs | 41 ++++++ .../execution/implementation.rs | 7 + 27 files changed, 663 insertions(+), 66 deletions(-) diff --git a/crates/apollo_batcher/src/cende_client_types.rs b/crates/apollo_batcher/src/cende_client_types.rs index 42b476017c9..67666a7dc2d 100644 --- a/crates/apollo_batcher/src/cende_client_types.rs +++ b/crates/apollo_batcher/src/cende_client_types.rs @@ -464,27 +464,47 @@ impl From for CendePreconfirmedTransaction { deploy_account_transaction, ) => { let version = deploy_account_transaction.version(); - let InternalRpcDeployAccountTransaction { - tx: RpcDeployAccountTransaction::V3(tx), - contract_address, - } = deploy_account_transaction; - CendePreconfirmedTransaction::DeployAccount(IntermediateDeployAccountTransaction { - resource_bounds: Some(tx.resource_bounds.into()), - tip: Some(tx.tip), - signature: tx.signature, - nonce: tx.nonce, - class_hash: tx.class_hash, - contract_address_salt: tx.contract_address_salt, - constructor_calldata: tx.constructor_calldata, - nonce_data_availability_mode: Some(tx.nonce_data_availability_mode.into()), - fee_data_availability_mode: Some(tx.fee_data_availability_mode.into()), - paymaster_data: Some(tx.paymaster_data), - sender_address: contract_address, - transaction_hash: tx_hash, - version, - // Irrelevant for V3 deploy account transactions. - max_fee: None, - }) + let InternalRpcDeployAccountTransaction { tx, contract_address } = + deploy_account_transaction; + // V3 and V4 share a field set; only the version and the derivation of + // `contract_address` differ. + let intermediate_deploy_account_transaction = match tx { + RpcDeployAccountTransaction::V3(tx) => IntermediateDeployAccountTransaction { + resource_bounds: Some(tx.resource_bounds.into()), + tip: Some(tx.tip), + signature: tx.signature, + nonce: tx.nonce, + class_hash: tx.class_hash, + contract_address_salt: tx.contract_address_salt, + constructor_calldata: tx.constructor_calldata, + nonce_data_availability_mode: Some(tx.nonce_data_availability_mode.into()), + fee_data_availability_mode: Some(tx.fee_data_availability_mode.into()), + paymaster_data: Some(tx.paymaster_data), + sender_address: contract_address, + transaction_hash: tx_hash, + version, + // Irrelevant for V3 deploy account transactions. + max_fee: None, + }, + RpcDeployAccountTransaction::V4(tx) => IntermediateDeployAccountTransaction { + resource_bounds: Some(tx.resource_bounds.into()), + tip: Some(tx.tip), + signature: tx.signature, + nonce: tx.nonce, + class_hash: tx.class_hash, + contract_address_salt: tx.contract_address_salt, + constructor_calldata: tx.constructor_calldata, + nonce_data_availability_mode: Some(tx.nonce_data_availability_mode.into()), + fee_data_availability_mode: Some(tx.fee_data_availability_mode.into()), + paymaster_data: Some(tx.paymaster_data), + sender_address: contract_address, + transaction_hash: tx_hash, + version, + // Irrelevant for V4 deploy account transactions. + max_fee: None, + }, + }; + CendePreconfirmedTransaction::DeployAccount(intermediate_deploy_account_transaction) } starknet_api::rpc_transaction::InternalRpcTransactionWithoutTxHash::Invoke( invoke_transaction, diff --git a/crates/apollo_consensus_orchestrator/src/cende/central_objects.rs b/crates/apollo_consensus_orchestrator/src/cende/central_objects.rs index da21cb5335a..1ce74b58d25 100644 --- a/crates/apollo_consensus_orchestrator/src/cende/central_objects.rs +++ b/crates/apollo_consensus_orchestrator/src/cende/central_objects.rs @@ -265,28 +265,62 @@ struct CentralDeployAccountTransactionV3 { hash_value: TransactionHash, } +#[derive(serde::Deserialize, Debug, PartialEq, Serialize)] +struct CentralDeployAccountTransactionV4 { + resource_bounds: CentralResourceBounds, + tip: Tip, + signature: TransactionSignature, + nonce: Nonce, + class_hash: ClassHash, + contract_address_salt: ContractAddressSalt, + sender_address: ContractAddress, + constructor_calldata: Calldata, + nonce_data_availability_mode: u32, + fee_data_availability_mode: u32, + paymaster_data: PaymasterData, + hash_value: TransactionHash, +} + impl From<(InternalRpcDeployAccountTransaction, TransactionHash)> - for CentralDeployAccountTransactionV3 + for CentralDeployAccountTransaction { fn from( (tx, hash_value): (InternalRpcDeployAccountTransaction, TransactionHash), - ) -> CentralDeployAccountTransactionV3 { + ) -> CentralDeployAccountTransaction { let sender_address = tx.contract_address; - let RpcDeployAccountTransaction::V3(tx) = tx.tx; - - CentralDeployAccountTransactionV3 { - resource_bounds: tx.resource_bounds.into(), - tip: tx.tip, - signature: tx.signature, - nonce: tx.nonce, - class_hash: tx.class_hash, - contract_address_salt: tx.contract_address_salt, - constructor_calldata: tx.constructor_calldata, - nonce_data_availability_mode: tx.nonce_data_availability_mode.into(), - fee_data_availability_mode: tx.fee_data_availability_mode.into(), - paymaster_data: tx.paymaster_data, - hash_value, - sender_address, + match tx.tx { + RpcDeployAccountTransaction::V3(tx) => { + CentralDeployAccountTransaction::V3(CentralDeployAccountTransactionV3 { + resource_bounds: tx.resource_bounds.into(), + tip: tx.tip, + signature: tx.signature, + nonce: tx.nonce, + class_hash: tx.class_hash, + contract_address_salt: tx.contract_address_salt, + constructor_calldata: tx.constructor_calldata, + nonce_data_availability_mode: tx.nonce_data_availability_mode.into(), + fee_data_availability_mode: tx.fee_data_availability_mode.into(), + paymaster_data: tx.paymaster_data, + hash_value, + sender_address, + }) + } + RpcDeployAccountTransaction::V4(tx) => { + CentralDeployAccountTransaction::V4(CentralDeployAccountTransactionV4 { + resource_bounds: tx.resource_bounds.into(), + tip: tx.tip, + signature: tx.signature, + nonce: tx.nonce, + class_hash: tx.class_hash, + contract_address_salt: tx.contract_address_salt, + constructor_calldata: tx.constructor_calldata, + nonce_data_availability_mode: tx.nonce_data_availability_mode.into(), + fee_data_availability_mode: tx.fee_data_availability_mode.into(), + paymaster_data: tx.paymaster_data, + hash_value, + sender_address, + }) + } } } } @@ -296,6 +330,8 @@ impl From<(InternalRpcDeployAccountTransaction, TransactionHash)> enum CentralDeployAccountTransaction { #[serde(rename = "0x3")] V3(CentralDeployAccountTransactionV3), + #[serde(rename = "0x4")] + V4(CentralDeployAccountTransactionV4), } fn into_string_tuple(val: SierraVersion) -> (String, String, String) { @@ -413,9 +449,9 @@ impl TryFrom<(InternalConsensusTransaction, Option<&SierraContractClass>)> for C ))) } InternalRpcTransactionWithoutTxHash::DeployAccount(deploy_tx) => { - Ok(CentralTransaction::DeployAccount(CentralDeployAccountTransaction::V3( + Ok(CentralTransaction::DeployAccount( (deploy_tx, rpc_transaction.tx_hash).into(), - ))) + )) } InternalRpcTransactionWithoutTxHash::Declare(declare_tx) => { let sierra = sierra diff --git a/crates/apollo_consensus_orchestrator/src/cende/central_objects_test.rs b/crates/apollo_consensus_orchestrator/src/cende/central_objects_test.rs index 7d5a95e42b3..466c91f7b81 100644 --- a/crates/apollo_consensus_orchestrator/src/cende/central_objects_test.rs +++ b/crates/apollo_consensus_orchestrator/src/cende/central_objects_test.rs @@ -155,7 +155,6 @@ use super::{ CentralCompiledClassHashesForMigration, CentralCompressedStateDiff, CentralDeclareTransaction, - CentralDeployAccountTransaction, CentralFeeMarketInfo, CentralFeeProposalInfo, CentralInvokeTransaction, @@ -357,9 +356,7 @@ fn central_deploy_account_tx() -> CentralTransactionWritten { TransactionHash(felt!("0x429cb4dc45610a80a96800ab350a11ff50e2d69e25c7723c002934e66b5a282")); CentralTransactionWritten { - tx: CentralTransaction::DeployAccount(CentralDeployAccountTransaction::V3( - (deploy_account_tx, tx_hash).into(), - )), + tx: CentralTransaction::DeployAccount((deploy_account_tx, tx_hash).into()), time_created: 1734601616, } } @@ -1233,7 +1230,7 @@ fn test_deploy_account_tx_size_of() { // + internal_deploy_account_tx.tip.dynamic_size() // + deploy_account_tx.contract_address.dynamic_size(); - assert_eq!(deploy_account_tx.size_bytes(), 528); + assert_eq!(deploy_account_tx.size_bytes(), 544); } #[test] diff --git a/crates/apollo_gateway/src/stateless_transaction_validator.rs b/crates/apollo_gateway/src/stateless_transaction_validator.rs index 298be7b65d4..7e7ce046a93 100644 --- a/crates/apollo_gateway/src/stateless_transaction_validator.rs +++ b/crates/apollo_gateway/src/stateless_transaction_validator.rs @@ -129,6 +129,9 @@ impl StatelessTransactionValidator { RpcTransaction::DeployAccount(RpcDeployAccountTransaction::V3(tx)) => { &tx.paymaster_data } + RpcTransaction::DeployAccount(RpcDeployAccountTransaction::V4(tx)) => { + &tx.paymaster_data + } RpcTransaction::Declare(RpcDeclareTransaction::V3(tx)) => &tx.paymaster_data, RpcTransaction::Invoke(RpcInvokeTransaction::V3(tx)) => &tx.paymaster_data, }; @@ -165,6 +168,10 @@ impl StatelessTransactionValidator { tx.constructor_calldata.0.len() } + RpcTransaction::DeployAccount(RpcDeployAccountTransaction::V4(tx)) => { + tx.constructor_calldata.0.len() + } + RpcTransaction::Invoke(RpcInvokeTransaction::V3(tx)) => { tx.calldata.0.len() + tx.proof_facts.0.len() } diff --git a/crates/apollo_http_server/src/deprecated_gateway_transaction.rs b/crates/apollo_http_server/src/deprecated_gateway_transaction.rs index 7a2c796f218..9ae5c34fcf9 100644 --- a/crates/apollo_http_server/src/deprecated_gateway_transaction.rs +++ b/crates/apollo_http_server/src/deprecated_gateway_transaction.rs @@ -84,6 +84,11 @@ impl From for DeprecatedGatewayTransactionV3 { DeprecatedGatewayDeployAccountTransaction::V3(deploy_account_tx.into()), ) } + // TODO(Ron): add a V4 variant to the deprecated gateway dialect when v4 ingestion is + // enabled. + RpcTransaction::DeployAccount(RpcDeployAccountTransaction::V4(_)) => { + panic!("Deploy account v4 is not supported by the deprecated gateway dialect.") + } RpcTransaction::Invoke(RpcInvokeTransaction::V3(invoke_tx)) => { DeprecatedGatewayTransactionV3::Invoke(DeprecatedGatewayInvokeTransaction::V3( invoke_tx.into(), diff --git a/crates/apollo_protobuf/src/converters/consensus_test.rs b/crates/apollo_protobuf/src/converters/consensus_test.rs index 7847e47e51c..f6b400abed0 100644 --- a/crates/apollo_protobuf/src/converters/consensus_test.rs +++ b/crates/apollo_protobuf/src/converters/consensus_test.rs @@ -6,6 +6,7 @@ use starknet_api::rpc_transaction::{ RpcDeclareTransactionV3, RpcDeployAccountTransaction, RpcDeployAccountTransactionV3, + RpcDeployAccountTransactionV4, RpcInvokeTransaction, RpcInvokeTransactionV3, RpcTransaction, @@ -39,6 +40,9 @@ fn add_gas_values_to_transaction(transactions: &mut [ConsensusTransaction]) { })) | RpcTransaction::DeployAccount(RpcDeployAccountTransaction::V3( RpcDeployAccountTransactionV3 { resource_bounds, .. }, + )) + | RpcTransaction::DeployAccount(RpcDeployAccountTransaction::V4( + RpcDeployAccountTransactionV4 { resource_bounds, .. }, )) => { resource_bounds.l2_gas.max_amount = GasAmount(1); } diff --git a/crates/apollo_protobuf/src/converters/rpc_transaction.rs b/crates/apollo_protobuf/src/converters/rpc_transaction.rs index b08e7e08c26..9c659753844 100644 --- a/crates/apollo_protobuf/src/converters/rpc_transaction.rs +++ b/crates/apollo_protobuf/src/converters/rpc_transaction.rs @@ -10,13 +10,18 @@ use starknet_api::rpc_transaction::{ RpcDeclareTransactionV3, RpcDeployAccountTransaction, RpcDeployAccountTransactionV3, + RpcDeployAccountTransactionV4, RpcInvokeTransaction, RpcInvokeTransactionV3, RpcTransaction, }; use starknet_api::state::SierraContractClass; use starknet_api::transaction::fields::{AllResourceBounds, Proof, ValidResourceBounds}; -use starknet_api::transaction::{DeployAccountTransactionV3, InvokeTransactionV3}; +use starknet_api::transaction::{ + DeployAccountTransactionV3, + DeployAccountTransactionV4, + InvokeTransactionV3, +}; use super::common::missing; use super::ProtobufConversionError; @@ -40,6 +45,9 @@ impl TryFrom for RpcTransaction { protobuf::mempool_transaction::Txn::DeployAccountV3(txn) => { RpcTransaction::DeployAccount(RpcDeployAccountTransaction::V3(txn.try_into()?)) } + protobuf::mempool_transaction::Txn::DeployAccountV4(txn) => { + RpcTransaction::DeployAccount(RpcDeployAccountTransaction::V4(txn.try_into()?)) + } protobuf::mempool_transaction::Txn::InvokeV3(txn) => { RpcTransaction::Invoke(RpcInvokeTransaction::V3(txn.try_into()?)) } @@ -64,6 +72,13 @@ impl From for protobuf::MempoolTransaction { transaction_hash: None, } } + RpcTransaction::DeployAccount(RpcDeployAccountTransaction::V4(txn)) => { + protobuf::MempoolTransaction { + txn: Some(protobuf::mempool_transaction::Txn::DeployAccountV4(txn.into())), + // TODO(alonl): Consider removing transaction hash from protobuf + transaction_hash: None, + } + } RpcTransaction::Invoke(RpcInvokeTransaction::V3(txn)) => { protobuf::MempoolTransaction { txn: Some(protobuf::mempool_transaction::Txn::InvokeV3(txn.into())), @@ -112,6 +127,22 @@ impl From for protobuf::DeployAccountV3 { } } +impl TryFrom for RpcDeployAccountTransactionV4 { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::DeployAccountV4) -> Result { + let snapi_deploy_account: DeployAccountTransactionV4 = value.try_into()?; + // This conversion can fail only if the resource_bounds are not AllResources. + snapi_deploy_account.try_into().map_err(|_| DEPRECATED_RESOURCE_BOUNDS_ERROR) + } +} + +impl From for protobuf::DeployAccountV4 { + fn from(value: RpcDeployAccountTransactionV4) -> Self { + let snapi_deploy_account: DeployAccountTransactionV4 = value.into(); + snapi_deploy_account.into() + } +} + impl TryFrom for RpcInvokeTransactionV3 { type Error = ProtobufConversionError; fn try_from(mut value: protobuf::InvokeV3WithProof) -> Result { diff --git a/crates/apollo_protobuf/src/converters/transaction.rs b/crates/apollo_protobuf/src/converters/transaction.rs index edcbabbdf6d..9fb7b16e3ef 100644 --- a/crates/apollo_protobuf/src/converters/transaction.rs +++ b/crates/apollo_protobuf/src/converters/transaction.rs @@ -35,6 +35,7 @@ use starknet_api::transaction::{ DeployAccountTransaction, DeployAccountTransactionV1, DeployAccountTransactionV3, + DeployAccountTransactionV4, DeployTransaction, FullTransaction, InvokeTransaction, @@ -167,6 +168,11 @@ impl TryFrom for (Transaction, TransactionHash) { DeployAccountTransactionV3::try_from(deploy_account_v3)?, )) } + protobuf::transaction_in_block::Txn::DeployAccountV4(deploy_account_v4) => { + Transaction::DeployAccount(DeployAccountTransaction::V4( + DeployAccountTransactionV4::try_from(deploy_account_v4)?, + )) + } protobuf::transaction_in_block::Txn::InvokeV0(invoke_v0) => Transaction::Invoke( InvokeTransaction::V0(InvokeTransactionV0::try_from(invoke_v0)?), ), @@ -229,6 +235,12 @@ impl From<(Transaction, TransactionHash)> for protobuf::TransactionInBlock { )), transaction_hash: tx_hash, }, + DeployAccountTransaction::V4(deploy_account_v4) => protobuf::TransactionInBlock { + txn: Some(protobuf::transaction_in_block::Txn::DeployAccountV4( + deploy_account_v4.into(), + )), + transaction_hash: tx_hash, + }, }, Transaction::Invoke(invoke) => match invoke { InvokeTransaction::V0(invoke_v0) => protobuf::TransactionInBlock { @@ -414,6 +426,98 @@ impl From for protobuf::DeployAccountV3 { } } +impl TryFrom for DeployAccountTransactionV4 { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::DeployAccountV4) -> Result { + let resource_bounds = ValidResourceBounds::try_from( + value.resource_bounds.ok_or(missing("DeployAccountV4::resource_bounds"))?, + )?; + + let tip = Tip(value.tip); + + let signature = TransactionSignature( + value + .signature + .ok_or(missing("DeployAccountV4::signature"))? + .parts + .into_iter() + .map(Felt::try_from) + .collect::, _>>()? + .into(), + ); + + let nonce = Nonce(value.nonce.ok_or(missing("DeployAccountV4::nonce"))?.try_into()?); + + let class_hash = + ClassHash(value.class_hash.ok_or(missing("DeployAccountV4::class_hash"))?.try_into()?); + + let contract_address_salt = ContractAddressSalt( + value.address_salt.ok_or(missing("DeployAccountV4::address_salt"))?.try_into()?, + ); + + let constructor_calldata = + value.calldata.into_iter().map(Felt::try_from).collect::, _>>()?; + + let constructor_calldata = Calldata(constructor_calldata.into()); + + let nonce_data_availability_mode = + enum_int_to_volition_domain(value.nonce_data_availability_mode)?; + + let fee_data_availability_mode = + enum_int_to_volition_domain(value.fee_data_availability_mode)?; + + let paymaster_data = PaymasterData( + value.paymaster_data.into_iter().map(Felt::try_from).collect::, _>>()?, + ); + + Ok(Self { + resource_bounds, + tip, + signature, + nonce, + class_hash, + contract_address_salt, + constructor_calldata, + nonce_data_availability_mode, + fee_data_availability_mode, + paymaster_data, + }) + } +} + +impl From for protobuf::DeployAccountV4 { + fn from(value: DeployAccountTransactionV4) -> Self { + Self { + resource_bounds: Some(protobuf::ResourceBounds::from(value.resource_bounds)), + tip: value.tip.0, + signature: Some(protobuf::AccountSignature { + parts: value.signature.0.iter().map(|stark_felt| (*stark_felt).into()).collect(), + }), + nonce: Some(value.nonce.0.into()), + class_hash: Some(value.class_hash.0.into()), + address_salt: Some(value.contract_address_salt.0.into()), + calldata: value + .constructor_calldata + .0 + .iter() + .map(|calldata| (*calldata).into()) + .collect(), + nonce_data_availability_mode: volition_domain_to_enum_int( + value.nonce_data_availability_mode, + ), + fee_data_availability_mode: volition_domain_to_enum_int( + value.fee_data_availability_mode, + ), + paymaster_data: value + .paymaster_data + .0 + .iter() + .map(|paymaster_data| (*paymaster_data).into()) + .collect(), + } + } +} + impl TryFrom for ValidResourceBounds { type Error = ProtobufConversionError; fn try_from(value: protobuf::ResourceBounds) -> Result { @@ -1010,6 +1114,12 @@ impl From for protobuf::ConsensusTransaction { txn: Some(protobuf::consensus_transaction::Txn::DeployAccountV3(txn.into())), transaction_hash: None, }, + ConsensusTransaction::RpcTransaction(RpcTransaction::DeployAccount( + RpcDeployAccountTransaction::V4(txn), + )) => protobuf::ConsensusTransaction { + txn: Some(protobuf::consensus_transaction::Txn::DeployAccountV4(txn.into())), + transaction_hash: None, + }, ConsensusTransaction::RpcTransaction(RpcTransaction::Invoke( RpcInvokeTransaction::V3(txn), )) => protobuf::ConsensusTransaction { @@ -1039,6 +1149,11 @@ impl TryFrom for ConsensusTransaction { RpcDeployAccountTransaction::V3(txn.try_into()?), )) } + protobuf::consensus_transaction::Txn::DeployAccountV4(txn) => { + ConsensusTransaction::RpcTransaction(RpcTransaction::DeployAccount( + RpcDeployAccountTransaction::V4(txn.try_into()?), + )) + } protobuf::consensus_transaction::Txn::InvokeV3(txn) => { ConsensusTransaction::RpcTransaction(RpcTransaction::Invoke( RpcInvokeTransaction::V3(txn.try_into()?), @@ -1093,6 +1208,7 @@ pub fn set_price_unit_based_on_transaction( Some(protobuf::transaction_in_block::Txn::Deploy(_)) => protobuf::PriceUnit::Wei, Some(protobuf::transaction_in_block::Txn::DeployAccountV1(_)) => protobuf::PriceUnit::Wei, Some(protobuf::transaction_in_block::Txn::DeployAccountV3(_)) => protobuf::PriceUnit::Fri, + Some(protobuf::transaction_in_block::Txn::DeployAccountV4(_)) => protobuf::PriceUnit::Fri, Some(protobuf::transaction_in_block::Txn::InvokeV1(_)) => protobuf::PriceUnit::Wei, Some(protobuf::transaction_in_block::Txn::InvokeV3(_)) => protobuf::PriceUnit::Fri, Some(protobuf::transaction_in_block::Txn::L1Handler(_)) => protobuf::PriceUnit::Wei, diff --git a/crates/apollo_protobuf/src/proto/p2p/proto/consensus/consensus.proto b/crates/apollo_protobuf/src/proto/p2p/proto/consensus/consensus.proto index 13f2e90b580..18a224e9fd0 100644 --- a/crates/apollo_protobuf/src/proto/p2p/proto/consensus/consensus.proto +++ b/crates/apollo_protobuf/src/proto/p2p/proto/consensus/consensus.proto @@ -13,6 +13,7 @@ message ConsensusTransaction { DeployAccountV3 deploy_account_v3 = 2; InvokeV3WithProof invoke_v3 = 3; L1HandlerV0 l1_handler = 4; + DeployAccountV4 deploy_account_v4 = 6; } Hash transaction_hash = 5; } diff --git a/crates/apollo_protobuf/src/proto/p2p/proto/mempool/transaction.proto b/crates/apollo_protobuf/src/proto/p2p/proto/mempool/transaction.proto index df6b442ccbf..e5643f72761 100644 --- a/crates/apollo_protobuf/src/proto/p2p/proto/mempool/transaction.proto +++ b/crates/apollo_protobuf/src/proto/p2p/proto/mempool/transaction.proto @@ -14,6 +14,7 @@ message MempoolTransaction { DeclareV3WithClass declare_v3 = 1; DeployAccountV3 deploy_account_v3 = 2; InvokeV3WithProof invoke_v3 = 3; + DeployAccountV4 deploy_account_v4 = 5; } Hash transaction_hash = 4; } diff --git a/crates/apollo_protobuf/src/proto/p2p/proto/sync/transaction.proto b/crates/apollo_protobuf/src/proto/p2p/proto/sync/transaction.proto index b798dfb109d..176cebe6713 100644 --- a/crates/apollo_protobuf/src/proto/p2p/proto/sync/transaction.proto +++ b/crates/apollo_protobuf/src/proto/p2p/proto/sync/transaction.proto @@ -103,6 +103,7 @@ message TransactionInBlock { InvokeV1 invoke_v1 = 9; InvokeV3 invoke_v3 = 10; L1HandlerV0 l1_handler = 11; + DeployAccountV4 deploy_account_v4 = 13; } Hash transaction_hash = 12; } diff --git a/crates/apollo_protobuf/src/proto/p2p/proto/transaction.proto b/crates/apollo_protobuf/src/proto/p2p/proto/transaction.proto index 00748edc299..bb87409cc38 100644 --- a/crates/apollo_protobuf/src/proto/p2p/proto/transaction.proto +++ b/crates/apollo_protobuf/src/proto/p2p/proto/transaction.proto @@ -84,3 +84,18 @@ message DeployAccountV3 { VolitionDomain nonce_data_availability_mode = 9; VolitionDomain fee_data_availability_mode = 10; } + +// A v3-shaped deploy account transaction whose contract address is derived with Blake2 instead of +// Pedersen. +message DeployAccountV4 { + AccountSignature signature = 1; + Hash class_hash = 2; + Felt252 nonce = 3; + Felt252 address_salt = 4; + repeated Felt252 calldata = 5; + ResourceBounds resource_bounds = 6; + uint64 tip = 7; + repeated Felt252 paymaster_data = 8; + VolitionDomain nonce_data_availability_mode = 9; + VolitionDomain fee_data_availability_mode = 10; +} diff --git a/crates/apollo_protobuf/src/protobuf/protoc_output.rs b/crates/apollo_protobuf/src/protobuf/protoc_output.rs index 9b4c42191d7..b6a7dc6f910 100644 --- a/crates/apollo_protobuf/src/protobuf/protoc_output.rs +++ b/crates/apollo_protobuf/src/protobuf/protoc_output.rs @@ -342,6 +342,32 @@ pub struct DeployAccountV3 { #[prost(enumeration = "VolitionDomain", tag = "10")] pub fee_data_availability_mode: i32, } +/// A v3-shaped deploy account transaction whose contract address is derived with Blake2 instead of +/// Pedersen. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DeployAccountV4 { + #[prost(message, optional, tag = "1")] + pub signature: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub class_hash: ::core::option::Option, + #[prost(message, optional, tag = "3")] + pub nonce: ::core::option::Option, + #[prost(message, optional, tag = "4")] + pub address_salt: ::core::option::Option, + #[prost(message, repeated, tag = "5")] + pub calldata: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag = "6")] + pub resource_bounds: ::core::option::Option, + #[prost(uint64, tag = "7")] + pub tip: u64, + #[prost(message, repeated, tag = "8")] + pub paymaster_data: ::prost::alloc::vec::Vec, + #[prost(enumeration = "VolitionDomain", tag = "9")] + pub nonce_data_availability_mode: i32, + #[prost(enumeration = "VolitionDomain", tag = "10")] + pub fee_data_availability_mode: i32, +} /// Contains all transaction types that can be in a new block: /// - User transactions (same types as MempoolTransaction: Declare, DeployAccount, Invoke) /// - L1Handler transactions (messages from L1, not propagated via mempool) @@ -350,7 +376,7 @@ pub struct DeployAccountV3 { pub struct ConsensusTransaction { #[prost(message, optional, tag = "5")] pub transaction_hash: ::core::option::Option, - #[prost(oneof = "consensus_transaction::Txn", tags = "1, 2, 3, 4")] + #[prost(oneof = "consensus_transaction::Txn", tags = "1, 2, 3, 4, 6")] pub txn: ::core::option::Option, } /// Nested message and enum types in `ConsensusTransaction`. @@ -366,6 +392,8 @@ pub mod consensus_transaction { InvokeV3(super::InvokeV3WithProof), #[prost(message, tag = "4")] L1Handler(super::L1HandlerV0), + #[prost(message, tag = "6")] + DeployAccountV4(super::DeployAccountV4), } } #[allow(clippy::derive_partial_eq_without_eq)] @@ -865,7 +893,7 @@ pub struct TransactionInBlock { pub transaction_hash: ::core::option::Option, #[prost( oneof = "transaction_in_block::Txn", - tags = "1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11" + tags = "1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13" )] pub txn: ::core::option::Option, } @@ -1003,6 +1031,8 @@ pub mod transaction_in_block { InvokeV3(super::InvokeV3), #[prost(message, tag = "11")] L1Handler(super::L1HandlerV0), + #[prost(message, tag = "13")] + DeployAccountV4(super::DeployAccountV4), } } /// Doesn't contain L1Handler, as those don't need to be propagated and can be downloaded from L1. @@ -1011,7 +1041,7 @@ pub mod transaction_in_block { pub struct MempoolTransaction { #[prost(message, optional, tag = "4")] pub transaction_hash: ::core::option::Option, - #[prost(oneof = "mempool_transaction::Txn", tags = "1, 2, 3")] + #[prost(oneof = "mempool_transaction::Txn", tags = "1, 2, 3, 5")] pub txn: ::core::option::Option, } /// Nested message and enum types in `MempoolTransaction`. @@ -1025,6 +1055,8 @@ pub mod mempool_transaction { DeployAccountV3(super::DeployAccountV3), #[prost(message, tag = "3")] InvokeV3(super::InvokeV3WithProof), + #[prost(message, tag = "5")] + DeployAccountV4(super::DeployAccountV4), } } #[allow(clippy::derive_partial_eq_without_eq)] diff --git a/crates/apollo_rpc/src/v0_8/transaction.rs b/crates/apollo_rpc/src/v0_8/transaction.rs index e7a068089f7..1e5a27f1c81 100644 --- a/crates/apollo_rpc/src/v0_8/transaction.rs +++ b/crates/apollo_rpc/src/v0_8/transaction.rs @@ -300,6 +300,13 @@ impl TryFrom for DeployAcco tx: starknet_api::transaction::DeployAccountTransaction, ) -> Result { match tx { + // TODO(Ron): serve v4 once the JSON-RPC spec adds it (requires a new spec version + // module; v0_8 predates deploy account v4). + starknet_api::transaction::DeployAccountTransaction::V4(_) => { + Err(internal_server_error( + "Deploy account v4 transactions are not supported in JSON-RPC v0.8.", + )) + } starknet_api::transaction::DeployAccountTransaction::V1( starknet_api::transaction::DeployAccountTransactionV1 { max_fee, diff --git a/crates/apollo_storage/src/serialization/serializers.rs b/crates/apollo_storage/src/serialization/serializers.rs index 0cbe98b0094..ec1aba23341 100644 --- a/crates/apollo_storage/src/serialization/serializers.rs +++ b/crates/apollo_storage/src/serialization/serializers.rs @@ -102,6 +102,7 @@ use starknet_api::transaction::{ DeployAccountTransactionOutput, DeployAccountTransactionV1, DeployAccountTransactionV3, + DeployAccountTransactionV4, DeployTransaction, DeployTransactionOutput, Event, @@ -252,6 +253,7 @@ auto_storage_serde! { pub enum DeployAccountTransaction { V1(DeployAccountTransactionV1) = 0, V3(DeployAccountTransactionV3) = 1, + V4(DeployAccountTransactionV4) = 2, } pub struct DeprecatedEntryPoint { pub selector: EntryPointSelector, @@ -1263,6 +1265,19 @@ auto_storage_serde_conditionally_compressed! { pub paymaster_data: PaymasterData, } + pub struct DeployAccountTransactionV4 { + pub resource_bounds: ValidResourceBounds, + pub tip: Tip, + pub signature: TransactionSignature, + pub nonce: Nonce, + pub class_hash: ClassHash, + pub contract_address_salt: ContractAddressSalt, + pub constructor_calldata: Calldata, + pub nonce_data_availability_mode: DataAvailabilityMode, + pub fee_data_availability_mode: DataAvailabilityMode, + pub paymaster_data: PaymasterData, + } + pub struct DeployTransaction { pub version: TransactionVersion, pub class_hash: ClassHash, diff --git a/crates/apollo_test_utils/src/lib.rs b/crates/apollo_test_utils/src/lib.rs index e8932d27cf4..3389dc7217f 100644 --- a/crates/apollo_test_utils/src/lib.rs +++ b/crates/apollo_test_utils/src/lib.rs @@ -108,6 +108,7 @@ use starknet_api::rpc_transaction::{ RpcDeclareTransactionV3, RpcDeployAccountTransaction, RpcDeployAccountTransactionV3, + RpcDeployAccountTransactionV4, RpcInvokeTransaction, RpcInvokeTransactionV3, RpcTransaction, @@ -146,6 +147,7 @@ use starknet_api::transaction::{ DeployAccountTransactionOutput, DeployAccountTransactionV1, DeployAccountTransactionV3, + DeployAccountTransactionV4, DeployTransaction, DeployTransactionOutput, Event, @@ -608,6 +610,7 @@ auto_impl_get_test_instance! { pub enum DeployAccountTransaction { V1(DeployAccountTransactionV1) = 0, V3(DeployAccountTransactionV3) = 1, + V4(DeployAccountTransactionV4) = 2, } pub struct DeployAccountTransactionOutput { pub actual_fee: Fee, @@ -637,6 +640,18 @@ auto_impl_get_test_instance! { pub fee_data_availability_mode: DataAvailabilityMode, pub paymaster_data: PaymasterData, } + pub struct DeployAccountTransactionV4 { + pub resource_bounds: ValidResourceBounds, + pub tip: Tip, + pub signature: TransactionSignature, + pub nonce: Nonce, + pub class_hash: ClassHash, + pub contract_address_salt: ContractAddressSalt, + pub constructor_calldata: Calldata, + pub nonce_data_availability_mode: DataAvailabilityMode, + pub fee_data_availability_mode: DataAvailabilityMode, + pub paymaster_data: PaymasterData, + } pub struct DeployTransaction { pub version: TransactionVersion, pub class_hash: ClassHash, @@ -863,6 +878,7 @@ auto_impl_get_test_instance! { } pub enum RpcDeployAccountTransaction { V3(RpcDeployAccountTransactionV3) = 0, + V4(RpcDeployAccountTransactionV4) = 1, } pub struct RpcDeployAccountTransactionV3 { pub resource_bounds: AllResourceBounds, @@ -876,6 +892,18 @@ auto_impl_get_test_instance! { pub fee_data_availability_mode: DataAvailabilityMode, pub paymaster_data: PaymasterData, } + pub struct RpcDeployAccountTransactionV4 { + pub resource_bounds: AllResourceBounds, + pub tip: Tip, + pub signature: TransactionSignature, + pub nonce: Nonce, + pub class_hash: ClassHash, + pub contract_address_salt: ContractAddressSalt, + pub constructor_calldata: Calldata, + pub nonce_data_availability_mode: DataAvailabilityMode, + pub fee_data_availability_mode: DataAvailabilityMode, + pub paymaster_data: PaymasterData, + } pub struct RpcEntryPointByType { pub constructor: Vec, pub external: Vec, diff --git a/crates/apollo_transaction_converter/src/transaction_converter.rs b/crates/apollo_transaction_converter/src/transaction_converter.rs index fe8ea277c16..866f6445bc8 100644 --- a/crates/apollo_transaction_converter/src/transaction_converter.rs +++ b/crates/apollo_transaction_converter/src/transaction_converter.rs @@ -407,6 +407,19 @@ impl TransactionConverter { None, ) } + RpcTransaction::DeployAccount(RpcDeployAccountTransaction::V4(tx)) => { + let contract_address = + tx.calculate_contract_address(AddressDerivationHash::Blake2)?; + ( + InternalRpcTransactionWithoutTxHash::DeployAccount( + InternalRpcDeployAccountTransaction { + tx: RpcDeployAccountTransaction::V4(tx), + contract_address, + }, + ), + None, + ) + } }; let tx_hash = tx_without_hash.calculate_transaction_hash(&self.chain_id)?; Ok((InternalRpcTransaction { tx: tx_without_hash, tx_hash }, proof_data)) diff --git a/crates/blockifier/src/transaction/transactions.rs b/crates/blockifier/src/transaction/transactions.rs index d89766d8e04..c48d619b956 100644 --- a/crates/blockifier/src/transaction/transactions.rs +++ b/crates/blockifier/src/transaction/transactions.rs @@ -291,6 +291,18 @@ impl TransactionInfoCreatorInner for DeployAccountTransaction { proof_facts: ProofFacts::default(), }) } + starknet_api::transaction::DeployAccountTransaction::V4(tx) => { + TransactionInfo::Current(CurrentTransactionInfo { + common_fields, + resource_bounds: tx.resource_bounds, + tip: tx.tip, + nonce_data_availability_mode: tx.nonce_data_availability_mode, + fee_data_availability_mode: tx.fee_data_availability_mode, + paymaster_data: tx.paymaster_data.clone(), + account_deployment_data: AccountDeploymentData::default(), + proof_facts: ProofFacts::default(), + }) + } } } } diff --git a/crates/blockifier/src/transaction/transactions_test.rs b/crates/blockifier/src/transaction/transactions_test.rs index f248d6d5724..b04582b6c87 100644 --- a/crates/blockifier/src/transaction/transactions_test.rs +++ b/crates/blockifier/src/transaction/transactions_test.rs @@ -2323,6 +2323,7 @@ fn test_deploy_account_tx( match tx { starknet_api::transaction::DeployAccountTransaction::V1(ref mut tx) => tx.nonce = nonce, starknet_api::transaction::DeployAccountTransaction::V3(ref mut tx) => tx.nonce = nonce, + starknet_api::transaction::DeployAccountTransaction::V4(ref mut tx) => tx.nonce = nonce, } } let deploy_account = AccountTransaction::new_with_default_flags(tx); diff --git a/crates/starknet_api/src/executable_transaction.rs b/crates/starknet_api/src/executable_transaction.rs index aa048d67808..a258e57f1e7 100644 --- a/crates/starknet_api/src/executable_transaction.rs +++ b/crates/starknet_api/src/executable_transaction.rs @@ -12,14 +12,7 @@ use thiserror::Error; use crate::contract_class::compiled_class_hash::{HashVersion, HashableCompiledClass}; use crate::contract_class::{ClassInfo, ContractClass}; -use crate::core::{ - AddressDerivationHash, - ChainId, - ClassHash, - CompiledClassHash, - ContractAddress, - Nonce, -}; +use crate::core::{ChainId, ClassHash, CompiledClassHash, ContractAddress, Nonce}; use crate::data_availability::DataAvailabilityMode; use crate::transaction::fields::{ AccountDeploymentData, @@ -331,8 +324,8 @@ impl DeployAccountTransaction { deploy_account_tx: crate::transaction::DeployAccountTransaction, chain_id: &ChainId, ) -> Result { - let contract_address = - deploy_account_tx.calculate_contract_address(AddressDerivationHash::Pedersen)?; + let contract_address = deploy_account_tx + .calculate_contract_address(deploy_account_tx.address_derivation_hash())?; let tx_hash = deploy_account_tx.calculate_transaction_hash(chain_id, &deploy_account_tx.version())?; Ok(Self { tx: deploy_account_tx, tx_hash, contract_address }) diff --git a/crates/starknet_api/src/rpc_transaction.rs b/crates/starknet_api/src/rpc_transaction.rs index 74962060b49..f004b3ec7ca 100644 --- a/crates/starknet_api/src/rpc_transaction.rs +++ b/crates/starknet_api/src/rpc_transaction.rs @@ -39,6 +39,7 @@ use crate::transaction::{ DeclareTransactionV3, DeployAccountTransaction, DeployAccountTransactionV3, + DeployAccountTransactionV4, DeployTransactionTrait, InvokeTransaction, InvokeTransactionV3, @@ -98,6 +99,9 @@ impl TransactionHasher for InternalRpcDeployAccountTransaction { RpcDeployAccountTransaction::V3(tx) => { tx.calculate_transaction_hash(chain_id, transaction_version) } + RpcDeployAccountTransaction::V4(tx) => { + tx.calculate_transaction_hash(chain_id, transaction_version) + } } } } @@ -163,6 +167,9 @@ macro_rules! implement_ref_getters { RpcTransaction::DeployAccount( RpcDeployAccountTransaction::V3(tx) ) => &tx.$member_name, + RpcTransaction::DeployAccount( + RpcDeployAccountTransaction::V4(tx) + ) => &tx.$member_name, RpcTransaction::Invoke( RpcInvokeTransaction::V3(tx) ) => &tx.$member_name @@ -187,6 +194,9 @@ impl RpcTransaction { RpcTransaction::DeployAccount(RpcDeployAccountTransaction::V3(tx)) => { tx.calculate_contract_address(AddressDerivationHash::Pedersen) } + RpcTransaction::DeployAccount(RpcDeployAccountTransaction::V4(tx)) => { + tx.calculate_contract_address(AddressDerivationHash::Blake2) + } RpcTransaction::Invoke(RpcInvokeTransaction::V3(tx)) => Ok(tx.sender_address), } } @@ -210,9 +220,9 @@ macro_rules! implement_internal_getters_for_internal_rpc { pub fn $field_name(&self) -> $field_ty { match &self.tx { InternalRpcTransactionWithoutTxHash::Declare(tx) => tx.$field_name.clone(), - InternalRpcTransactionWithoutTxHash::DeployAccount(tx) => { - let RpcDeployAccountTransaction::V3(tx) = &tx.tx; - tx.$field_name.clone() + InternalRpcTransactionWithoutTxHash::DeployAccount(tx) => match &tx.tx { + RpcDeployAccountTransaction::V3(tx) => tx.$field_name.clone(), + RpcDeployAccountTransaction::V4(tx) => tx.$field_name.clone(), }, InternalRpcTransactionWithoutTxHash::Invoke(tx) => tx.$field_name.clone(), } @@ -280,12 +290,15 @@ impl From for DeclareTransaction { pub enum RpcDeployAccountTransaction { #[serde(rename = "0x3")] V3(RpcDeployAccountTransactionV3), + #[serde(rename = "0x4")] + V4(RpcDeployAccountTransactionV4), } impl RpcDeployAccountTransaction { fn version(&self) -> TransactionVersion { match self { RpcDeployAccountTransaction::V3(_) => TransactionVersion::THREE, + RpcDeployAccountTransaction::V4(_) => TransactionVersion::FOUR, } } } @@ -294,6 +307,7 @@ impl From for DeployAccountTransaction { fn from(rpc_deploy_account_transaction: RpcDeployAccountTransaction) -> Self { match rpc_deploy_account_transaction { RpcDeployAccountTransaction::V3(tx) => DeployAccountTransaction::V3(tx.into()), + RpcDeployAccountTransaction::V4(tx) => DeployAccountTransaction::V4(tx.into()), } } } @@ -559,6 +573,113 @@ impl TransactionHasher for RpcDeployAccountTransactionV3 { } } +/// A v4 deploy account transaction that can be added to Starknet through the RPC: the fields of +/// v3, with the contract address derived using Blake2 instead of Pedersen. +#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, SizeOf)] +pub struct RpcDeployAccountTransactionV4 { + pub signature: TransactionSignature, + pub nonce: Nonce, + pub class_hash: ClassHash, + pub contract_address_salt: ContractAddressSalt, + pub constructor_calldata: Calldata, + pub resource_bounds: AllResourceBounds, + pub tip: Tip, + pub paymaster_data: PaymasterData, + pub nonce_data_availability_mode: DataAvailabilityMode, + pub fee_data_availability_mode: DataAvailabilityMode, +} + +impl_deploy_transaction_trait!(RpcDeployAccountTransactionV4); + +impl From for DeployAccountTransactionV4 { + fn from(tx: RpcDeployAccountTransactionV4) -> Self { + Self { + resource_bounds: ValidResourceBounds::AllResources(tx.resource_bounds), + tip: tx.tip, + signature: tx.signature, + nonce: tx.nonce, + class_hash: tx.class_hash, + contract_address_salt: tx.contract_address_salt, + constructor_calldata: tx.constructor_calldata, + nonce_data_availability_mode: tx.nonce_data_availability_mode, + fee_data_availability_mode: tx.fee_data_availability_mode, + paymaster_data: tx.paymaster_data, + } + } +} + +impl TryFrom for RpcDeployAccountTransactionV4 { + type Error = StarknetApiError; + + fn try_from(value: DeployAccountTransactionV4) -> Result { + Ok(Self { + resource_bounds: match value.resource_bounds { + ValidResourceBounds::AllResources(bounds) => bounds, + _ => { + return Err(StarknetApiError::OutOfRange { + string: "resource_bounds".to_string(), + }); + } + }, + signature: value.signature, + nonce: value.nonce, + class_hash: value.class_hash, + contract_address_salt: value.contract_address_salt, + constructor_calldata: value.constructor_calldata, + tip: value.tip, + paymaster_data: value.paymaster_data, + nonce_data_availability_mode: value.nonce_data_availability_mode, + fee_data_availability_mode: value.fee_data_availability_mode, + }) + } +} + +impl DeployAccountTransactionV3Trait for RpcDeployAccountTransactionV4 { + fn resource_bounds(&self) -> ValidResourceBounds { + ValidResourceBounds::AllResources(self.resource_bounds) + } + fn tip(&self) -> &Tip { + &self.tip + } + fn paymaster_data(&self) -> &PaymasterData { + &self.paymaster_data + } + fn nonce_data_availability_mode(&self) -> &DataAvailabilityMode { + &self.nonce_data_availability_mode + } + fn fee_data_availability_mode(&self) -> &DataAvailabilityMode { + &self.fee_data_availability_mode + } + fn constructor_calldata(&self) -> &Calldata { + &self.constructor_calldata + } + fn nonce(&self) -> &Nonce { + &self.nonce + } + fn class_hash(&self) -> &ClassHash { + &self.class_hash + } + fn contract_address_salt(&self) -> &ContractAddressSalt { + &self.contract_address_salt + } +} + +impl TransactionHasher for RpcDeployAccountTransactionV4 { + fn calculate_transaction_hash( + &self, + chain_id: &ChainId, + transaction_version: &TransactionVersion, + ) -> Result { + let contract_address = self.calculate_contract_address(AddressDerivationHash::Blake2)?; + get_deploy_account_transaction_v3_hash( + self, + chain_id, + transaction_version, + contract_address, + ) + } +} + /// An invoke account transaction that can be added to Starknet through the RPC. #[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, SizeOf)] pub struct RpcInvokeTransactionV3 { diff --git a/crates/starknet_api/src/rpc_transaction_test.rs b/crates/starknet_api/src/rpc_transaction_test.rs index 61f4b5fbb6c..ee31a9926ed 100644 --- a/crates/starknet_api/src/rpc_transaction_test.rs +++ b/crates/starknet_api/src/rpc_transaction_test.rs @@ -101,7 +101,9 @@ fn test_rpc_transactions(#[case] tx: RpcTransaction) { fn test_deploy_account_tx_size_of() { let tx = create_deploy_account_tx(); if let RpcTransaction::DeployAccount(deploy_tx) = tx { - let RpcDeployAccountTransaction::V3(tx_v3) = deploy_tx; + let RpcDeployAccountTransaction::V3(tx_v3) = deploy_tx else { + panic!("Expected a V3 deploy account transaction.") + }; // hardcoded number was generated using: // diff --git a/crates/starknet_api/src/serde_utils.rs b/crates/starknet_api/src/serde_utils.rs index 771936f3b86..f33e7c6a256 100644 --- a/crates/starknet_api/src/serde_utils.rs +++ b/crates/starknet_api/src/serde_utils.rs @@ -220,6 +220,9 @@ pub fn deserialize_transaction_json_to_starknet_api_tx( raw_transaction, )?))) } + ("DEPLOY_ACCOUNT", "0x4") => Ok(Transaction::DeployAccount(DeployAccountTransaction::V4( + serde_json::from_value(raw_transaction)?, + ))), ("DECLARE", "0x0") => Ok(Transaction::Declare(DeclareTransaction::V0( serde_json::from_value(raw_transaction)?, ))), diff --git a/crates/starknet_api/src/test_utils/deploy_account.rs b/crates/starknet_api/src/test_utils/deploy_account.rs index 64d6a43cabc..a3b2531076c 100644 --- a/crates/starknet_api/src/test_utils/deploy_account.rs +++ b/crates/starknet_api/src/test_utils/deploy_account.rs @@ -29,6 +29,7 @@ use crate::transaction::{ DeployAccountTransaction, DeployAccountTransactionV1, DeployAccountTransactionV3, + DeployAccountTransactionV4, TransactionHash, TransactionVersion, }; @@ -114,6 +115,19 @@ pub fn deploy_account_tx( contract_address_salt: deploy_tx_args.contract_address_salt, constructor_calldata: deploy_tx_args.constructor_calldata, }) + } else if deploy_tx_args.version == TransactionVersion::FOUR { + DeployAccountTransaction::V4(DeployAccountTransactionV4 { + signature: deploy_tx_args.signature, + resource_bounds: deploy_tx_args.resource_bounds, + tip: deploy_tx_args.tip, + nonce_data_availability_mode: deploy_tx_args.nonce_data_availability_mode, + fee_data_availability_mode: deploy_tx_args.fee_data_availability_mode, + paymaster_data: deploy_tx_args.paymaster_data, + nonce, + class_hash: deploy_tx_args.class_hash, + contract_address_salt: deploy_tx_args.contract_address_salt, + constructor_calldata: deploy_tx_args.constructor_calldata, + }) } else { panic!("Unsupported transaction version: {:?}.", deploy_tx_args.version) } @@ -125,7 +139,7 @@ pub fn executable_deploy_account_tx(deploy_tx_args: DeployAccountTxArgs) -> Acco let tx_hash = deploy_tx_args.tx_hash; let tx_nonce = deploy_tx_args.nonce; let tx = deploy_account_tx(deploy_tx_args, tx_nonce); - let contract_address = tx.calculate_contract_address(AddressDerivationHash::Pedersen).unwrap(); + let contract_address = tx.calculate_contract_address(tx.address_derivation_hash()).unwrap(); let deploy_account_tx = ExecutableDeployAccountTransaction { tx, tx_hash, contract_address }; AccountTransaction::DeployAccount(deploy_account_tx) diff --git a/crates/starknet_api/src/transaction.rs b/crates/starknet_api/src/transaction.rs index cc3a8ca1593..565f15ccc61 100644 --- a/crates/starknet_api/src/transaction.rs +++ b/crates/starknet_api/src/transaction.rs @@ -160,7 +160,7 @@ impl TryFrom<(Transaction, &ChainId)> for executable_transaction::Transaction { match tx { Transaction::DeployAccount(tx) => { let contract_address = - tx.calculate_contract_address(AddressDerivationHash::Pedersen)?; + tx.calculate_contract_address(tx.address_derivation_hash())?; Ok(executable_transaction::Transaction::Account( executable_transaction::AccountTransaction::DeployAccount( executable_transaction::DeployAccountTransaction { @@ -544,12 +544,47 @@ impl TransactionHasher for DeployAccountTransactionV3 { impl_deploy_transaction_trait!(DeployAccountTransactionV3); +/// A deploy account V4 transaction: the fields of V3, with the contract address derived using +/// Blake2 instead of Pedersen. +#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)] +pub struct DeployAccountTransactionV4 { + pub resource_bounds: ValidResourceBounds, + pub tip: Tip, + pub signature: TransactionSignature, + pub nonce: Nonce, + pub class_hash: ClassHash, + pub contract_address_salt: ContractAddressSalt, + pub constructor_calldata: Calldata, + pub nonce_data_availability_mode: DataAvailabilityMode, + pub fee_data_availability_mode: DataAvailabilityMode, + pub paymaster_data: PaymasterData, +} + +impl TransactionHasher for DeployAccountTransactionV4 { + fn calculate_transaction_hash( + &self, + chain_id: &ChainId, + transaction_version: &TransactionVersion, + ) -> Result { + let contract_address = self.calculate_contract_address(AddressDerivationHash::Blake2)?; + get_deploy_account_transaction_v3_hash( + self, + chain_id, + transaction_version, + contract_address, + ) + } +} + +impl_deploy_transaction_trait!(DeployAccountTransactionV4); + #[derive( Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord, derive_more::From, )] pub enum DeployAccountTransaction { V1(DeployAccountTransactionV1), V3(DeployAccountTransactionV3), + V4(DeployAccountTransactionV4), } impl CalculateContractAddress for DeployAccountTransaction { @@ -564,6 +599,9 @@ impl CalculateContractAddress for DeployAccountTransaction { DeployAccountTransaction::V3(tx) => { tx.calculate_contract_address(address_derivation_hash) } + DeployAccountTransaction::V4(tx) => { + tx.calculate_contract_address(address_derivation_hash) + } } } } @@ -575,12 +613,30 @@ macro_rules! implement_deploy_account_tx_getters { match self { Self::V1(tx) => tx.$field.clone(), Self::V3(tx) => tx.$field.clone(), + Self::V4(tx) => tx.$field.clone(), } } )* }; } +macro_rules! implement_deploy_account_v3_v4_tx_getters { + ($(($field:ident, $field_type:ty)),*) => { + $(pub fn $field(&self) -> $field_type { + match self { + Self::V3(tx) => tx.$field.clone(), + Self::V4(tx) => tx.$field.clone(), + _ => panic!( + "{:?} does not support the field {}; it is only available for V3/V4 \ + transactions.", + self.version(), + stringify!($field) + ), + } + })* + }; +} + impl DeployAccountTransaction { // TODO(Arni): Consider using a direct reference to the getters from [DeployTrait]. implement_deploy_account_tx_getters!( @@ -591,7 +647,7 @@ impl DeployAccountTransaction { (signature, TransactionSignature) ); - implement_v3_tx_getters!( + implement_deploy_account_v3_v4_tx_getters!( (resource_bounds, ValidResourceBounds), (tip, Tip), (nonce_data_availability_mode, DataAvailabilityMode), @@ -603,6 +659,18 @@ impl DeployAccountTransaction { match self { DeployAccountTransaction::V1(_) => TransactionVersion::ONE, DeployAccountTransaction::V3(_) => TransactionVersion::THREE, + DeployAccountTransaction::V4(_) => TransactionVersion::FOUR, + } + } + + /// The address-derivation hash implied by the transaction version: Pedersen for V1/V3, Blake2 + /// for V4. The derived address is covered by the signature, through the transaction hash. + pub fn address_derivation_hash(&self) -> AddressDerivationHash { + match self { + DeployAccountTransaction::V1(_) | DeployAccountTransaction::V3(_) => { + AddressDerivationHash::Pedersen + } + DeployAccountTransaction::V4(_) => AddressDerivationHash::Blake2, } } } @@ -620,6 +688,9 @@ impl TransactionHasher for DeployAccountTransaction { DeployAccountTransaction::V3(tx) => { tx.calculate_transaction_hash(chain_id, transaction_version) } + DeployAccountTransaction::V4(tx) => { + tx.calculate_transaction_hash(chain_id, transaction_version) + } } } } @@ -983,6 +1054,9 @@ impl TransactionVersion { /// [TransactionVersion] constant that's equal to 3. pub const THREE: Self = { Self(Felt::THREE) }; + + /// [TransactionVersion] constant that's equal to 4. + pub const FOUR: Self = { Self(Felt::from_hex_unchecked("0x4")) }; } // TODO(Dori): TransactionVersion and SignedTransactionVersion should probably be separate types. diff --git a/crates/starknet_api/src/transaction_hash.rs b/crates/starknet_api/src/transaction_hash.rs index 3caea7c94cb..f0eb54fd822 100644 --- a/crates/starknet_api/src/transaction_hash.rs +++ b/crates/starknet_api/src/transaction_hash.rs @@ -34,6 +34,7 @@ use crate::transaction::{ DeployAccountTransaction, DeployAccountTransactionV1, DeployAccountTransactionV3, + DeployAccountTransactionV4, DeployTransaction, InvokeTransaction, InvokeTransactionV0, @@ -119,6 +120,14 @@ pub fn get_transaction_hash( .calculate_contract_address(AddressDerivationHash::Pedersen)?, ) } + DeployAccountTransaction::V4(deploy_account_v4) => { + get_deploy_account_transaction_v3_hash( + deploy_account_v4, + chain_id, + transaction_version, + deploy_account_v4.calculate_contract_address(AddressDerivationHash::Blake2)?, + ) + } }, Transaction::Invoke(invoke) => match invoke { InvokeTransaction::V0(invoke_v0) => { @@ -738,6 +747,8 @@ pub(crate) trait DeployAccountTransactionV3Trait { fn contract_address_salt(&self) -> &ContractAddressSalt; } +/// Also computes the v4 hash: v4 shares the v3 preimage layout, and differs only through the +/// chained version felt and the Blake2-derived `contract_address`. pub(crate) fn get_deploy_account_transaction_v3_hash( transaction: &T, chain_id: &ChainId, @@ -773,6 +784,36 @@ pub(crate) fn get_deploy_account_transaction_v3_hash ValidResourceBounds { + self.resource_bounds + } + fn tip(&self) -> &Tip { + &self.tip + } + fn paymaster_data(&self) -> &PaymasterData { + &self.paymaster_data + } + fn nonce_data_availability_mode(&self) -> &DataAvailabilityMode { + &self.nonce_data_availability_mode + } + fn fee_data_availability_mode(&self) -> &DataAvailabilityMode { + &self.fee_data_availability_mode + } + fn constructor_calldata(&self) -> &Calldata { + &self.constructor_calldata + } + fn nonce(&self) -> &Nonce { + &self.nonce + } + fn class_hash(&self) -> &ClassHash { + &self.class_hash + } + fn contract_address_salt(&self) -> &ContractAddressSalt { + &self.contract_address_salt + } +} + impl DeployAccountTransactionV3Trait for DeployAccountTransactionV3 { fn resource_bounds(&self) -> ValidResourceBounds { self.resource_bounds diff --git a/crates/starknet_os/src/hints/hint_implementation/execution/implementation.rs b/crates/starknet_os/src/hints/hint_implementation/execution/implementation.rs index 601de424875..f3ab43da5b6 100644 --- a/crates/starknet_os/src/hints/hint_implementation/execution/implementation.rs +++ b/crates/starknet_os/src/hints/hint_implementation/execution/implementation.rs @@ -129,6 +129,13 @@ pub(crate) fn prepare_constructor_execution( let constructor_calldata = match &deploy_account_tx.tx { DeployAccountTransaction::V1(v1_tx) => &v1_tx.constructor_calldata, DeployAccountTransaction::V3(v3_tx) => &v3_tx.constructor_calldata, + // TODO(Ron): support v4 in the OS (Blake2 address derivation) before enabling v4 + // ingestion at the gateway. + DeployAccountTransaction::V4(_) => { + return Err(OsHintError::AssertionFailed { + message: "Deploy account v4 is not yet supported by the OS.".to_string(), + }); + } }; ctx.insert_value(Ids::ConstructorCalldataSize, constructor_calldata.0.len())?; let constructor_calldata_base = ctx.vm.add_memory_segment();