diff --git a/crates/apollo_gateway_types/src/gateway_types.rs b/crates/apollo_gateway_types/src/gateway_types.rs index 1151aaf3f71..f341fa9befe 100644 --- a/crates/apollo_gateway_types/src/gateway_types.rs +++ b/crates/apollo_gateway_types/src/gateway_types.rs @@ -7,7 +7,9 @@ use starknet_api::transaction::TransactionHash; use crate::errors::GatewayError; const TRANSACTION_RECEIVED: &str = "TRANSACTION_RECEIVED"; -pub const SUPPORTED_TRANSACTION_VERSIONS: [u64; 1] = [3]; +// Version 4 is deploy_account only; the per-type gate is the serde of the transaction enums, so +// this list only shapes the deprecated endpoint's fallback version error. +pub const SUPPORTED_TRANSACTION_VERSIONS: [u64; 2] = [3, 4]; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct GatewayInput { diff --git a/crates/apollo_http_server/resources/deprecated_gateway/deploy_account_v4_tx.json b/crates/apollo_http_server/resources/deprecated_gateway/deploy_account_v4_tx.json new file mode 100644 index 00000000000..be48c375676 --- /dev/null +++ b/crates/apollo_http_server/resources/deprecated_gateway/deploy_account_v4_tx.json @@ -0,0 +1,32 @@ +{ + "version": "0x4", + "signature": [ + "0x6" + ], + "nonce": "0x0", + "nonce_data_availability_mode": 0, + "fee_data_availability_mode": 0, + "resource_bounds": { + "L1_GAS": { + "max_amount": "0x10000000000", + "max_price_per_unit": "0x10000000000" + }, + "L1_DATA_GAS": { + "max_amount": "0x10000000000", + "max_price_per_unit": "0x10000000000" + }, + "L2_GAS": { + "max_amount": "0x10000000000", + "max_price_per_unit": "0x10000000000" + } + }, + "tip": "0x0", + "paymaster_data": [], + "class_hash": "0xaa17", + "contract_address_salt": "0x14d", + "constructor_calldata": [ + "0x7", + "0x8" + ], + "type": "DEPLOY_ACCOUNT" +} diff --git a/crates/apollo_http_server/src/deprecated_gateway_transaction.rs b/crates/apollo_http_server/src/deprecated_gateway_transaction.rs index 9ae5c34fcf9..35480e47161 100644 --- a/crates/apollo_http_server/src/deprecated_gateway_transaction.rs +++ b/crates/apollo_http_server/src/deprecated_gateway_transaction.rs @@ -10,6 +10,7 @@ use starknet_api::rpc_transaction::{ RpcDeclareTransactionV3, RpcDeployAccountTransaction, RpcDeployAccountTransactionV3, + RpcDeployAccountTransactionV4, RpcInvokeTransaction, RpcInvokeTransactionV3, RpcTransaction, @@ -63,6 +64,11 @@ impl DeprecatedGatewayTransactionV3 { ) => RpcTransaction::DeployAccount(RpcDeployAccountTransaction::V3( deprecated_deploy_account_tx.into(), )), + DeprecatedGatewayTransactionV3::DeployAccount( + DeprecatedGatewayDeployAccountTransaction::V4(deprecated_deploy_account_tx), + ) => RpcTransaction::DeployAccount(RpcDeployAccountTransaction::V4( + deprecated_deploy_account_tx.into(), + )), DeprecatedGatewayTransactionV3::Invoke(DeprecatedGatewayInvokeTransaction::V3( deprecated_invoke_tx, )) => RpcTransaction::Invoke(RpcInvokeTransaction::V3(deprecated_invoke_tx.into())), @@ -84,10 +90,10 @@ 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::DeployAccount(RpcDeployAccountTransaction::V4(deploy_account_tx)) => { + DeprecatedGatewayTransactionV3::DeployAccount( + DeprecatedGatewayDeployAccountTransaction::V4(deploy_account_tx.into()), + ) } RpcTransaction::Invoke(RpcInvokeTransaction::V3(invoke_tx)) => { DeprecatedGatewayTransactionV3::Invoke(DeprecatedGatewayInvokeTransaction::V3( @@ -167,6 +173,8 @@ impl From for DeprecatedGatewayInvokeTransactionV3 { pub enum DeprecatedGatewayDeployAccountTransaction { #[serde(rename = "0x3")] V3(DeprecatedGatewayDeployAccountTransactionV3), + #[serde(rename = "0x4")] + V4(DeprecatedGatewayDeployAccountTransactionV4), } #[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] @@ -218,6 +226,57 @@ impl From for DeprecatedGatewayDeployAccountTrans } } +/// A v4 deploy account transaction in the legacy gateway dialect: the fields of v3, with the +/// contract address derived using Blake2 instead of Pedersen. +#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] +pub struct DeprecatedGatewayDeployAccountTransactionV4 { + pub signature: TransactionSignature, + pub nonce: Nonce, + pub class_hash: ClassHash, + pub contract_address_salt: ContractAddressSalt, + pub constructor_calldata: Calldata, + pub resource_bounds: DeprecatedGatewayAllResourceBounds, + pub tip: Tip, + pub paymaster_data: PaymasterData, + pub nonce_data_availability_mode: DataAvailabilityMode, + pub fee_data_availability_mode: DataAvailabilityMode, +} + +impl From for RpcDeployAccountTransactionV4 { + fn from(deprecated_deploy_account_tx: DeprecatedGatewayDeployAccountTransactionV4) -> Self { + RpcDeployAccountTransactionV4 { + signature: deprecated_deploy_account_tx.signature, + nonce: deprecated_deploy_account_tx.nonce, + class_hash: deprecated_deploy_account_tx.class_hash, + contract_address_salt: deprecated_deploy_account_tx.contract_address_salt, + constructor_calldata: deprecated_deploy_account_tx.constructor_calldata, + resource_bounds: deprecated_deploy_account_tx.resource_bounds.into(), + tip: deprecated_deploy_account_tx.tip, + paymaster_data: deprecated_deploy_account_tx.paymaster_data, + nonce_data_availability_mode: deprecated_deploy_account_tx.nonce_data_availability_mode, + fee_data_availability_mode: deprecated_deploy_account_tx.fee_data_availability_mode, + } + } +} + +#[cfg(any(feature = "testing", test))] +impl From for DeprecatedGatewayDeployAccountTransactionV4 { + fn from(value: RpcDeployAccountTransactionV4) -> Self { + Self { + signature: value.signature, + nonce: value.nonce, + class_hash: value.class_hash, + contract_address_salt: value.contract_address_salt, + constructor_calldata: value.constructor_calldata, + resource_bounds: value.resource_bounds.into(), + 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, + } + } +} + #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Hash)] #[serde(tag = "version")] pub enum DeprecatedGatewayDeclareTransaction { diff --git a/crates/apollo_http_server/src/deprecated_gateway_transaction_test.rs b/crates/apollo_http_server/src/deprecated_gateway_transaction_test.rs index a8c0c1d2e40..aa001dfb7ad 100644 --- a/crates/apollo_http_server/src/deprecated_gateway_transaction_test.rs +++ b/crates/apollo_http_server/src/deprecated_gateway_transaction_test.rs @@ -20,6 +20,8 @@ const DEPRECATED_GATEWAY_INVOKE_TX_CLIENT_SIDE_PROVING_JSON_PATH: &str = "deprecated_gateway/invoke_tx_client_side_proving.json"; const DEPRECATED_GATEWAY_DEPLOY_ACCOUNT_TX_JSON_PATH: &str = "deprecated_gateway/deploy_account_tx.json"; +const DEPRECATED_GATEWAY_DEPLOY_ACCOUNT_V4_TX_JSON_PATH: &str = + "deprecated_gateway/deploy_account_v4_tx.json"; const DEPRECATED_GATEWAY_DECLARE_TX_JSON_PATH: &str = "deprecated_gateway/declare_tx.json"; fn deprecated_gateway_declare_tx() -> DeprecatedGatewayDeclareTransaction { @@ -37,8 +39,16 @@ fn deprecated_gateway_invoke_tx_deserialization(#[case] json_path: &str) { #[test] fn deprecated_gateway_deploy_account_tx_deserialization() { - let _: DeprecatedGatewayDeployAccountTransaction = + let deploy_account_tx: DeprecatedGatewayDeployAccountTransaction = read_json_file(DEPRECATED_GATEWAY_DEPLOY_ACCOUNT_TX_JSON_PATH); + assert_matches!(deploy_account_tx, DeprecatedGatewayDeployAccountTransaction::V3(_)); +} + +#[test] +fn deprecated_gateway_deploy_account_v4_tx_deserialization() { + let deploy_account_tx: DeprecatedGatewayDeployAccountTransaction = + read_json_file(DEPRECATED_GATEWAY_DEPLOY_ACCOUNT_V4_TX_JSON_PATH); + assert_matches!(deploy_account_tx, DeprecatedGatewayDeployAccountTransaction::V4(_)); } #[test] diff --git a/crates/apollo_http_server/src/http_server_test.rs b/crates/apollo_http_server/src/http_server_test.rs index 3d5db7a1065..76742faac2c 100644 --- a/crates/apollo_http_server/src/http_server_test.rs +++ b/crates/apollo_http_server/src/http_server_test.rs @@ -308,17 +308,17 @@ async fn test_response(#[case] index: u16, #[case] tx: impl GatewayTransaction) code: StarknetErrorCode::KnownErrorCode( KnownStarknetErrorCode::InvalidTransactionVersion, ), - message: "Transaction version 1 is not supported. Supported versions: [3].".to_string(), + message: "Transaction version 1 is not supported. Supported versions: [3, 4].".to_string(), }, )] #[case::newer_version( unique_u16!(), - Some("0x4"), + Some("0x5"), StarknetError { code: StarknetErrorCode::KnownErrorCode( KnownStarknetErrorCode::InvalidTransactionVersion, ), - message: "Transaction version 4 is not supported. Supported versions: [3].".to_string(), + message: "Transaction version 5 is not supported. Supported versions: [3, 4].".to_string(), } )] #[tokio::test] diff --git a/crates/apollo_transaction_converter/src/transaction_converter_test.rs b/crates/apollo_transaction_converter/src/transaction_converter_test.rs index e8f0026c553..a451cdf40f9 100644 --- a/crates/apollo_transaction_converter/src/transaction_converter_test.rs +++ b/crates/apollo_transaction_converter/src/transaction_converter_test.rs @@ -12,12 +12,20 @@ use mempool_test_utils::starknet_api_test_utils::{ }; use mockall::predicate::eq; use rstest::{fixture, rstest}; -use starknet_api::compiled_class_hash; use starknet_api::consensus_transaction::ConsensusTransaction; +use starknet_api::core::{is_pedersen_reachable_address, ContractAddress}; use starknet_api::executable_transaction::ValidateCompiledClassHashError; -use starknet_api::rpc_transaction::{RpcDeclareTransaction, RpcTransaction}; +use starknet_api::rpc_transaction::{ + InternalRpcTransactionWithoutTxHash, + RpcDeclareTransaction, + RpcTransaction, +}; +use starknet_api::test_utils::deploy_account::rpc_deploy_account_tx; use starknet_api::test_utils::{path_in_resources, read_json_file, CHAIN_ID_FOR_TESTS}; -use starknet_api::transaction::fields::{Proof, ProofFacts}; +use starknet_api::transaction::fields::{Calldata, ContractAddressSalt, Proof, ProofFacts}; +use starknet_api::transaction::TransactionVersion; +use starknet_api::{class_hash, compiled_class_hash, deploy_account_tx_args}; +use starknet_types_core::felt::Felt; use crate::transaction_converter::{ TransactionConverter, @@ -270,3 +278,37 @@ async fn test_internal_rpc_to_rpc_in_echonet_mode_skips_proof_manager_lookup( assert_eq!(round_tripped_tx.proof_facts, proof_facts); assert!(round_tripped_tx.proof.is_empty(), "proof must remain empty in an echonet round-trip"); } + +#[rstest] +#[tokio::test] +async fn test_convert_rpc_deploy_account_v4_to_internal() { + let transaction_converter = create_transaction_converter(MockProofManagerClient::new()); + + // Frozen vector: deployer = 0, salt = 771, class_hash = 0x4242, + // constructor_calldata = [42, 2^63, 1337], escaping after one increment. + let rpc_tx = rpc_deploy_account_tx(deploy_account_tx_args! { + version: TransactionVersion::FOUR, + class_hash: class_hash!("0x4242"), + contract_address_salt: ContractAddressSalt(Felt::from(771_u16)), + constructor_calldata: Calldata(Arc::new(vec![ + Felt::from(42_u8), + Felt::from(1_u64 << 63), + Felt::from(1337_u16), + ])), + }); + + let (internal_tx, _verification_handle) = + transaction_converter.convert_rpc_tx_to_internal_rpc_tx(rpc_tx).await.unwrap(); + let InternalRpcTransactionWithoutTxHash::DeployAccount(internal_deploy_account) = + &internal_tx.tx + else { + panic!("Expected a deploy account transaction."); + }; + + let expected_address = ContractAddress::try_from(Felt::from_hex_unchecked( + "0x566c3e328f3fd5a311267250cadc3c1c4de799db54180fcf862fe90b622571d", + )) + .unwrap(); + assert_eq!(internal_deploy_account.contract_address, expected_address); + assert!(!is_pedersen_reachable_address(internal_deploy_account.contract_address.0.key())); +} diff --git a/crates/starknet_api/src/test_utils/deploy_account.rs b/crates/starknet_api/src/test_utils/deploy_account.rs index a3b2531076c..a7d61fd67c8 100644 --- a/crates/starknet_api/src/test_utils/deploy_account.rs +++ b/crates/starknet_api/src/test_utils/deploy_account.rs @@ -13,6 +13,7 @@ use crate::rpc_transaction::{ InternalRpcTransactionWithoutTxHash, RpcDeployAccountTransaction, RpcDeployAccountTransactionV3, + RpcDeployAccountTransactionV4, RpcTransaction, }; use crate::transaction::fields::{ @@ -161,26 +162,43 @@ pub fn create_executable_deploy_account_tx_and_update_nonce( } pub fn rpc_deploy_account_tx(deploy_tx_args: DeployAccountTxArgs) -> RpcTransaction { - if deploy_tx_args.version != TransactionVersion::THREE { - panic!("Unsupported transaction version: {:?}.", deploy_tx_args.version); - } - let ValidResourceBounds::AllResources(resource_bounds) = deploy_tx_args.resource_bounds else { panic!("Unsupported resource bounds type: {:?}.", deploy_tx_args.resource_bounds) }; - RpcTransaction::DeployAccount(RpcDeployAccountTransaction::V3(RpcDeployAccountTransactionV3 { - resource_bounds, - tip: deploy_tx_args.tip, - contract_address_salt: deploy_tx_args.contract_address_salt, - class_hash: deploy_tx_args.class_hash, - constructor_calldata: deploy_tx_args.constructor_calldata, - nonce: deploy_tx_args.nonce, - signature: deploy_tx_args.signature, - 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, - })) + if deploy_tx_args.version == TransactionVersion::THREE { + RpcTransaction::DeployAccount(RpcDeployAccountTransaction::V3( + RpcDeployAccountTransactionV3 { + resource_bounds, + tip: deploy_tx_args.tip, + contract_address_salt: deploy_tx_args.contract_address_salt, + class_hash: deploy_tx_args.class_hash, + constructor_calldata: deploy_tx_args.constructor_calldata, + nonce: deploy_tx_args.nonce, + signature: deploy_tx_args.signature, + 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, + }, + )) + } else if deploy_tx_args.version == TransactionVersion::FOUR { + RpcTransaction::DeployAccount(RpcDeployAccountTransaction::V4( + RpcDeployAccountTransactionV4 { + resource_bounds, + tip: deploy_tx_args.tip, + contract_address_salt: deploy_tx_args.contract_address_salt, + class_hash: deploy_tx_args.class_hash, + constructor_calldata: deploy_tx_args.constructor_calldata, + nonce: deploy_tx_args.nonce, + signature: deploy_tx_args.signature, + 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, + }, + )) + } else { + panic!("Unsupported transaction version: {:?}.", deploy_tx_args.version); + } } pub fn internal_deploy_account_tx(deploy_tx_args: DeployAccountTxArgs) -> InternalRpcTransaction {