diff --git a/crates/madara/client/gateway/server/src/handler.rs b/crates/madara/client/gateway/server/src/handler.rs index c239c64c4..d9e2e1442 100644 --- a/crates/madara/client/gateway/server/src/handler.rs +++ b/crates/madara/client/gateway/server/src/handler.rs @@ -11,9 +11,10 @@ use mc_rpc::{ }; use mp_block::{BlockId, BlockTag, MadaraBlock, MadaraMaybePendingBlockInfo, MadaraPendingBlock}; use mp_class::{ClassInfo, ContractClass}; -use mp_gateway::error::StarknetError; +use mp_gateway::error::{StarknetError, StarknetErrorCode}; use mp_gateway::user_transaction::{ - UserDeclareTransaction, UserDeployAccountTransaction, UserInvokeFunctionTransaction, UserTransaction, + AddTransactionResult, UserDeclareTransaction, UserDeployAccountTransaction, UserInvokeFunctionTransaction, + UserTransaction, }; use mp_gateway::{ block::{BlockStatus, ProviderBlock, ProviderBlockPending, ProviderBlockSignature}, @@ -23,7 +24,7 @@ use mp_utils::service::ServiceContext; use serde::Serialize; use serde_json::json; use starknet_types_core::felt::Felt; -use starknet_types_rpc::TraceBlockTransactionsResult; +use starknet_types_rpc::{BroadcastedDeclareTxn, TraceBlockTransactionsResult}; use super::{ error::{GatewayError, OptionExt, ResultExt}, @@ -349,8 +350,16 @@ async fn declare_transaction( tx: UserDeclareTransaction, add_transaction_provider: Arc, ) -> Response { - match add_transaction_provider.add_declare_transaction(tx.into()).await { - Ok(result) => create_json_response(hyper::StatusCode::OK, &result), + let tx: BroadcastedDeclareTxn = match tx.try_into() { + Ok(tx) => tx, + Err(e) => { + let error = StarknetError::new(StarknetErrorCode::InvalidContractDefinition, e.to_string()); + return create_json_response(hyper::StatusCode::OK, &error); + } + }; + + match add_transaction_provider.add_declare_transaction(tx).await { + Ok(result) => create_json_response(hyper::StatusCode::OK, &AddTransactionResult::from(result)), Err(e) => create_json_response(hyper::StatusCode::OK, &e), } } @@ -360,7 +369,7 @@ async fn deploy_account_transaction( add_transaction_provider: Arc, ) -> Response { match add_transaction_provider.add_deploy_account_transaction(tx.into()).await { - Ok(result) => create_json_response(hyper::StatusCode::OK, &result), + Ok(result) => create_json_response(hyper::StatusCode::OK, &AddTransactionResult::from(result)), Err(e) => create_json_response(hyper::StatusCode::OK, &e), } } @@ -370,7 +379,7 @@ async fn invoke_transaction( add_transaction_provider: Arc, ) -> Response { match add_transaction_provider.add_invoke_transaction(tx.into()).await { - Ok(result) => create_json_response(hyper::StatusCode::OK, &result), + Ok(result) => create_json_response(hyper::StatusCode::OK, &AddTransactionResult::from(result)), Err(e) => create_json_response(hyper::StatusCode::OK, &e), } } diff --git a/crates/madara/client/rpc/src/errors.rs b/crates/madara/client/rpc/src/errors.rs index 9319309fd..77f19bc81 100644 --- a/crates/madara/client/rpc/src/errors.rs +++ b/crates/madara/client/rpc/src/errors.rs @@ -1,6 +1,7 @@ use crate::utils::display_internal_server_error; use mc_db::MadaraStorageError; use mp_gateway::error::{StarknetError, StarknetErrorCode}; +use mp_gateway::user_transaction::UserTransactionConversionError; use serde::Serialize; use serde_json::json; use starknet_api::StarknetApiError; @@ -228,6 +229,15 @@ impl From for StarknetRpcApiError { } } +impl From for StarknetRpcApiError { + fn from(err: UserTransactionConversionError) -> Self { + match err { + UserTransactionConversionError::ContractClassDecodeError(_) => StarknetRpcApiError::InvalidContractClass, + UserTransactionConversionError::UnsupportedQueryTransaction => StarknetRpcApiError::UnsupportedTxnVersion, + } + } +} + #[cfg_attr(test, derive(PartialEq, Eq))] #[derive(Debug)] pub enum StarknetWsApiError { diff --git a/crates/madara/client/rpc/src/providers/forward_to_provider.rs b/crates/madara/client/rpc/src/providers/forward_to_provider.rs index 7b399072a..0af2d9494 100644 --- a/crates/madara/client/rpc/src/providers/forward_to_provider.rs +++ b/crates/madara/client/rpc/src/providers/forward_to_provider.rs @@ -35,9 +35,7 @@ impl AddTransactionProvider for ForwardToProvider { ) -> RpcResult> { let sequencer_response = match self .provider - .add_declare_transaction( - declare_transaction.try_into().map_err(|_| StarknetRpcApiError::InvalidContractClass)?, - ) + .add_declare_transaction(declare_transaction.try_into().map_err(StarknetRpcApiError::from)?) .await { Ok(response) => response, @@ -53,14 +51,17 @@ impl AddTransactionProvider for ForwardToProvider { &self, deploy_account_transaction: BroadcastedDeployAccountTxn, ) -> RpcResult> { - let sequencer_response = - match self.provider.add_deploy_account_transaction(deploy_account_transaction.into()).await { - Ok(response) => response, - Err(SequencerError::StarknetError(e)) => { - return Err(StarknetRpcApiError::from(e).into()); - } - Err(e) => bail_internal_server_error!("Failed to add deploy account transaction to sequencer: {e}"), - }; + let sequencer_response = match self + .provider + .add_deploy_account_transaction(deploy_account_transaction.try_into().map_err(StarknetRpcApiError::from)?) + .await + { + Ok(response) => response, + Err(SequencerError::StarknetError(e)) => { + return Err(StarknetRpcApiError::from(e).into()); + } + Err(e) => bail_internal_server_error!("Failed to add deploy account transaction to sequencer: {e}"), + }; Ok(sequencer_response) } @@ -69,7 +70,11 @@ impl AddTransactionProvider for ForwardToProvider { &self, invoke_transaction: BroadcastedInvokeTxn, ) -> RpcResult> { - let sequencer_response = match self.provider.add_invoke_transaction(invoke_transaction.into()).await { + let sequencer_response = match self + .provider + .add_invoke_transaction(invoke_transaction.try_into().map_err(StarknetRpcApiError::from)?) + .await + { Ok(response) => response, Err(SequencerError::StarknetError(e)) => { return Err(StarknetRpcApiError::from(e).into()); diff --git a/crates/madara/primitives/class/src/into_starknet_types.rs b/crates/madara/primitives/class/src/into_starknet_types.rs index 86b897be2..a331185dc 100644 --- a/crates/madara/primitives/class/src/into_starknet_types.rs +++ b/crates/madara/primitives/class/src/into_starknet_types.rs @@ -10,7 +10,7 @@ use crate::{ }; impl TryFrom> for ContractClass { - type Error = base64::DecodeError; + type Error = std::io::Error; fn try_from(contract_class: starknet_types_rpc::MaybeDeprecatedContractClass) -> Result { match contract_class { @@ -123,15 +123,16 @@ impl From for starknet_types_rpc::SierraEntryPoint { } impl TryFrom> for CompressedLegacyContractClass { - type Error = base64::DecodeError; + type Error = std::io::Error; fn try_from( compressed_legacy_contract_class: starknet_types_rpc::DeprecatedContractClass, ) -> Result { use base64::Engine; - let decoded_program = - base64::engine::general_purpose::STANDARD.decode(&compressed_legacy_contract_class.program)?; + let decoded_program = base64::engine::general_purpose::STANDARD + .decode(&compressed_legacy_contract_class.program) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?; Ok(CompressedLegacyContractClass { program: decoded_program, diff --git a/crates/madara/primitives/class/src/lib.rs b/crates/madara/primitives/class/src/lib.rs index 4ed54ec31..8eb3f41a4 100644 --- a/crates/madara/primitives/class/src/lib.rs +++ b/crates/madara/primitives/class/src/lib.rs @@ -168,6 +168,59 @@ impl FlattenedSierraClass { } } +#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct CompressedSierraClass { + /// The gzipped compressed program as a base64 string. + pub sierra_program: String, + pub contract_class_version: String, + pub entry_points_by_type: EntryPointsByType, + pub abi: String, +} + +impl TryFrom for CompressedSierraClass { + type Error = std::io::Error; + + fn try_from(flattened_sierra_class: FlattenedSierraClass) -> Result { + let mut base64_encoder = + base64::write::EncoderWriter::new(Vec::new(), &base64::engine::general_purpose::STANDARD); + let mut gzip_encoder = flate2::write::GzEncoder::new(&mut base64_encoder, flate2::Compression::default()); + serde_json::to_writer(&mut gzip_encoder, &flattened_sierra_class.sierra_program)?; + gzip_encoder.try_finish()?; + drop(gzip_encoder); + let encoded_data = base64_encoder + .finish() + .map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, "base64 encoding error"))?; + let sierra_program = String::from_utf8(encoded_data) + .map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, "base64 encoding error: invalid utf8"))?; + + Ok(Self { + sierra_program, + contract_class_version: flattened_sierra_class.contract_class_version, + entry_points_by_type: flattened_sierra_class.entry_points_by_type, + abi: flattened_sierra_class.abi, + }) + } +} + +impl TryFrom for FlattenedSierraClass { + type Error = std::io::Error; + + fn try_from(compressed_sierra_class: CompressedSierraClass) -> Result { + let string_reader = std::io::Cursor::new(compressed_sierra_class.sierra_program); + let base64_decoder = + base64::read::DecoderReader::new(string_reader, &base64::engine::general_purpose::STANDARD); + let gzip_decoder = flate2::read::GzDecoder::new(base64_decoder); + let sierra_program = serde_json::from_reader(gzip_decoder)?; + + Ok(Self { + sierra_program, + contract_class_version: compressed_sierra_class.contract_class_version, + entry_points_by_type: compressed_sierra_class.entry_points_by_type, + abi: compressed_sierra_class.abi, + }) + } +} + #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "UPPERCASE")] pub struct EntryPointsByType { diff --git a/crates/madara/primitives/gateway/src/user_transaction.rs b/crates/madara/primitives/gateway/src/user_transaction.rs index 6e0acab2b..8b816c38e 100644 --- a/crates/madara/primitives/gateway/src/user_transaction.rs +++ b/crates/madara/primitives/gateway/src/user_transaction.rs @@ -1,13 +1,99 @@ -use mp_class::{CompressedLegacyContractClass, FlattenedSierraClass}; +//! User transactions type for the gateway. +//! +//! This module handles user transactions that are sent to or received by the gateway. +//! It defines the structure and conversion logic for different types of user transactions. +//! +//! # Important Note +//! +//! Query-only transactions are intentionally not supported in this module. This is because +//! UserTransactions are specifically designed for transactions that are meant to be added +//! to the sequencer's mempool via the gateway. Query transactions, which are executed +//! without affecting the chain state, are handled through different pathways. +//! +//! # Transaction Types +//! +//! The module supports three main types of transactions: +//! - [`UserDeclareTransaction`] - For declaring new contracts +//! - [`UserInvokeFunctionTransaction`] - For invoking functions on existing contracts +//! - [`UserDeployAccountTransaction`] - For deploying new account contracts +//! +//! # Features +//! +//! - Conversion between user and broadcasted transaction formats +//! - Serialization/deserialization using serde +//! - Error handling for invalid transaction types +//! +//! # Error Handling +//! +//! The module defines [`UserTransactionConversionError`] for handling conversion failures: +//! +//! - [`UnsupportedQueryTransaction`]: When attempting to convert a query-only transaction +//! - [`ContractClassDecodeError`]: When contract class decoding fails +//! +//! [`UnsupportedQueryTransaction`]: UserTransactionConversionError::UnsupportedQueryTransaction +//! [`ContractClassDecodeError`]: UserTransactionConversionError::ContractClassDecodeError + +use mp_class::{CompressedLegacyContractClass, CompressedSierraClass, FlattenedSierraClass}; +use mp_convert::hex_serde::U64AsHex; use mp_transactions::{DataAvailabilityMode, ResourceBoundsMapping}; use serde::{Deserialize, Serialize}; +use serde_with::serde_as; use starknet_types_core::felt::Felt; use starknet_types_rpc::{ - BroadcastedDeclareTxn, BroadcastedDeclareTxnV1, BroadcastedDeclareTxnV2, BroadcastedDeclareTxnV3, - BroadcastedDeployAccountTxn, BroadcastedInvokeTxn, BroadcastedTxn, DeployAccountTxnV1, DeployAccountTxnV3, - InvokeTxnV0, InvokeTxnV1, InvokeTxnV3, + AddInvokeTransactionResult, BroadcastedDeclareTxn, BroadcastedDeclareTxnV1, BroadcastedDeclareTxnV2, + BroadcastedDeclareTxnV3, BroadcastedDeployAccountTxn, BroadcastedInvokeTxn, BroadcastedTxn, + ClassAndTxnHash as AddDeclareTransactionResult, ContractAndTxnHash as AddDeployAccountTransactionResult, + DeployAccountTxnV1, DeployAccountTxnV3, InvokeTxnV0, InvokeTxnV1, InvokeTxnV3, }; +/// Gateway response when a transaction is successfully added to the mempool. +/// Generic type T represents the specific transaction result type +/// (Invoke, Declare, or DeployAccount). +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct AddTransactionResult { + /// Status code indicating the transaction was received successfully + code: AddTransactionCode, + /// The transaction result + #[serde(flatten)] + result: T, +} + +/// Marker trait to restrict which types can be used as transaction results. +/// Only valid Starknet transaction responses implement this trait. +pub trait ValidTransactionResult {} + +// Implement the marker trait for the three valid transaction result types +impl ValidTransactionResult for AddInvokeTransactionResult {} +impl ValidTransactionResult for AddDeclareTransactionResult {} +impl ValidTransactionResult for AddDeployAccountTransactionResult {} + +// Conversion from a valid transaction result into a gateway response. +/// This ensures the response can only be created from permitted transaction types. +impl From for AddTransactionResult +where + R: ValidTransactionResult, +{ + fn from(result: R) -> Self { + Self { code: AddTransactionCode::TransactionReceived, result } + } +} + +/// Status code used in the gateway response. +/// Currently only indicates successful addition to mempool. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum AddTransactionCode { + TransactionReceived, +} + +#[derive(Debug, thiserror::Error)] +pub enum UserTransactionConversionError { + #[error("User transaction can't be a query only transaction")] + UnsupportedQueryTransaction, + #[error("Error while decoding the contract class: {0}")] + ContractClassDecodeError(#[from] std::io::Error), +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "SCREAMING_SNAKE_CASE")] #[serde(deny_unknown_fields)] @@ -17,24 +103,26 @@ pub enum UserTransaction { DeployAccount(UserDeployAccountTransaction), } -impl From for BroadcastedTxn { - fn from(transaction: UserTransaction) -> Self { +impl TryFrom for BroadcastedTxn { + type Error = UserTransactionConversionError; + + fn try_from(transaction: UserTransaction) -> Result { match transaction { - UserTransaction::Declare(tx) => BroadcastedTxn::Declare(tx.into()), - UserTransaction::InvokeFunction(tx) => BroadcastedTxn::Invoke(tx.into()), - UserTransaction::DeployAccount(tx) => BroadcastedTxn::DeployAccount(tx.into()), + UserTransaction::Declare(tx) => Ok(BroadcastedTxn::Declare(tx.try_into()?)), + UserTransaction::InvokeFunction(tx) => Ok(BroadcastedTxn::Invoke(tx.into())), + UserTransaction::DeployAccount(tx) => Ok(BroadcastedTxn::DeployAccount(tx.into())), } } } impl TryFrom> for UserTransaction { - type Error = base64::DecodeError; + type Error = UserTransactionConversionError; fn try_from(transaction: BroadcastedTxn) -> Result { match transaction { BroadcastedTxn::Declare(tx) => Ok(UserTransaction::Declare(tx.try_into()?)), - BroadcastedTxn::Invoke(tx) => Ok(UserTransaction::InvokeFunction(tx.into())), - BroadcastedTxn::DeployAccount(tx) => Ok(UserTransaction::DeployAccount(tx.into())), + BroadcastedTxn::Invoke(tx) => Ok(UserTransaction::InvokeFunction(tx.try_into()?)), + BroadcastedTxn::DeployAccount(tx) => Ok(UserTransaction::DeployAccount(tx.try_into()?)), } } } @@ -50,42 +138,29 @@ pub enum UserDeclareTransaction { V3(UserDeclareV3Transaction), } -impl From for BroadcastedDeclareTxn { - fn from(transaction: UserDeclareTransaction) -> Self { +impl TryFrom for BroadcastedDeclareTxn { + type Error = UserTransactionConversionError; + + fn try_from(transaction: UserDeclareTransaction) -> Result { match transaction { - UserDeclareTransaction::V1(tx) if tx.is_query => BroadcastedDeclareTxn::QueryV1(tx.into()), - UserDeclareTransaction::V1(tx) => BroadcastedDeclareTxn::V1(tx.into()), - UserDeclareTransaction::V2(tx) if tx.is_query => BroadcastedDeclareTxn::QueryV2(tx.into()), - UserDeclareTransaction::V2(tx) => BroadcastedDeclareTxn::V2(tx.into()), - UserDeclareTransaction::V3(tx) if tx.is_query => BroadcastedDeclareTxn::QueryV3(tx.into()), - UserDeclareTransaction::V3(tx) => BroadcastedDeclareTxn::V3(tx.into()), + UserDeclareTransaction::V1(tx) => Ok(BroadcastedDeclareTxn::V1(tx.into())), + UserDeclareTransaction::V2(tx) => Ok(BroadcastedDeclareTxn::V2(tx.try_into()?)), + UserDeclareTransaction::V3(tx) => Ok(BroadcastedDeclareTxn::V3(tx.try_into()?)), } } } impl TryFrom> for UserDeclareTransaction { - type Error = base64::DecodeError; + type Error = UserTransactionConversionError; fn try_from(transaction: BroadcastedDeclareTxn) -> Result { match transaction { - BroadcastedDeclareTxn::V1(tx) => { - Ok(UserDeclareTransaction::V1(UserDeclareV1Transaction::try_from_broadcasted(tx, false)?)) - } - BroadcastedDeclareTxn::QueryV1(tx) => { - Ok(UserDeclareTransaction::V1(UserDeclareV1Transaction::try_from_broadcasted(tx, true)?)) - } - BroadcastedDeclareTxn::V2(tx) => { - Ok(UserDeclareTransaction::V2(UserDeclareV2Transaction::from_broadcasted(tx, false))) - } - BroadcastedDeclareTxn::QueryV2(tx) => { - Ok(UserDeclareTransaction::V2(UserDeclareV2Transaction::from_broadcasted(tx, true))) - } - BroadcastedDeclareTxn::V3(tx) => { - Ok(UserDeclareTransaction::V3(UserDeclareV3Transaction::from_broadcasted(tx, false))) - } - BroadcastedDeclareTxn::QueryV3(tx) => { - Ok(UserDeclareTransaction::V3(UserDeclareV3Transaction::from_broadcasted(tx, true))) - } + BroadcastedDeclareTxn::V1(tx) => Ok(UserDeclareTransaction::V1(tx.try_into()?)), + BroadcastedDeclareTxn::V2(tx) => Ok(UserDeclareTransaction::V2(tx.try_into()?)), + BroadcastedDeclareTxn::V3(tx) => Ok(UserDeclareTransaction::V3(tx.try_into()?)), + BroadcastedDeclareTxn::QueryV1(_) + | BroadcastedDeclareTxn::QueryV2(_) + | BroadcastedDeclareTxn::QueryV3(_) => Err(UserTransactionConversionError::UnsupportedQueryTransaction), } } } @@ -97,7 +172,6 @@ pub struct UserDeclareV1Transaction { pub max_fee: Felt, pub signature: Vec, pub nonce: Felt, - pub is_query: bool, } impl From for BroadcastedDeclareTxnV1 { @@ -112,63 +186,68 @@ impl From for BroadcastedDeclareTxnV1 { } } -impl UserDeclareV1Transaction { - fn try_from_broadcasted( - transaction: BroadcastedDeclareTxnV1, - is_query: bool, - ) -> Result { +impl TryFrom> for UserDeclareV1Transaction { + type Error = std::io::Error; + + fn try_from(transaction: BroadcastedDeclareTxnV1) -> Result { Ok(Self { sender_address: transaction.sender_address, max_fee: transaction.max_fee, signature: transaction.signature, nonce: transaction.nonce, contract_class: transaction.contract_class.try_into()?, - is_query, }) } } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct UserDeclareV2Transaction { - pub contract_class: FlattenedSierraClass, + pub contract_class: CompressedSierraClass, pub compiled_class_hash: Felt, pub sender_address: Felt, pub max_fee: Felt, pub signature: Vec, pub nonce: Felt, - pub is_query: bool, } -impl From for BroadcastedDeclareTxnV2 { - fn from(transaction: UserDeclareV2Transaction) -> Self { - Self { +impl TryFrom for BroadcastedDeclareTxnV2 { + type Error = std::io::Error; + + fn try_from(transaction: UserDeclareV2Transaction) -> Result { + let flattened_sierra_class: FlattenedSierraClass = transaction.contract_class.try_into()?; + + Ok(Self { sender_address: transaction.sender_address, compiled_class_hash: transaction.compiled_class_hash, max_fee: transaction.max_fee, signature: transaction.signature, nonce: transaction.nonce, - contract_class: transaction.contract_class.into(), - } + contract_class: flattened_sierra_class.into(), + }) } } -impl UserDeclareV2Transaction { - fn from_broadcasted(transaction: BroadcastedDeclareTxnV2, is_query: bool) -> Self { - Self { +impl TryFrom> for UserDeclareV2Transaction { + type Error = std::io::Error; + + fn try_from(transaction: BroadcastedDeclareTxnV2) -> Result { + let flattened_sierra_class: FlattenedSierraClass = transaction.contract_class.into(); + + Ok(Self { sender_address: transaction.sender_address, compiled_class_hash: transaction.compiled_class_hash, + max_fee: transaction.max_fee, signature: transaction.signature, nonce: transaction.nonce, - contract_class: transaction.contract_class.into(), - max_fee: transaction.max_fee, - is_query, - } + contract_class: flattened_sierra_class.try_into()?, + }) } } +#[serde_as] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct UserDeclareV3Transaction { - pub contract_class: FlattenedSierraClass, + pub contract_class: CompressedSierraClass, pub compiled_class_hash: Felt, pub sender_address: Felt, pub signature: Vec, @@ -176,15 +255,19 @@ pub struct UserDeclareV3Transaction { pub nonce_data_availability_mode: DataAvailabilityMode, pub fee_data_availability_mode: DataAvailabilityMode, pub resource_bounds: ResourceBoundsMapping, + #[serde_as(as = "U64AsHex")] pub tip: u64, pub paymaster_data: Vec, pub account_deployment_data: Vec, - pub is_query: bool, } -impl From for BroadcastedDeclareTxnV3 { - fn from(transaction: UserDeclareV3Transaction) -> Self { - Self { +impl TryFrom for BroadcastedDeclareTxnV3 { + type Error = std::io::Error; + + fn try_from(transaction: UserDeclareV3Transaction) -> Result { + let flattened_sierra_class: FlattenedSierraClass = transaction.contract_class.try_into()?; + + Ok(Self { sender_address: transaction.sender_address, compiled_class_hash: transaction.compiled_class_hash, signature: transaction.signature, @@ -193,16 +276,20 @@ impl From for BroadcastedDeclareTxnV3 { fee_data_availability_mode: transaction.fee_data_availability_mode.into(), resource_bounds: transaction.resource_bounds.into(), tip: transaction.tip, - contract_class: transaction.contract_class.into(), + contract_class: flattened_sierra_class.into(), paymaster_data: transaction.paymaster_data, account_deployment_data: transaction.account_deployment_data, - } + }) } } -impl UserDeclareV3Transaction { - fn from_broadcasted(transaction: BroadcastedDeclareTxnV3, is_query: bool) -> Self { - Self { +impl TryFrom> for UserDeclareV3Transaction { + type Error = std::io::Error; + + fn try_from(transaction: BroadcastedDeclareTxnV3) -> Result { + let flattened_sierra_class: FlattenedSierraClass = transaction.contract_class.into(); + + Ok(Self { sender_address: transaction.sender_address, compiled_class_hash: transaction.compiled_class_hash, signature: transaction.signature, @@ -211,11 +298,10 @@ impl UserDeclareV3Transaction { fee_data_availability_mode: transaction.fee_data_availability_mode.into(), resource_bounds: transaction.resource_bounds.into(), tip: transaction.tip, - contract_class: transaction.contract_class.into(), + contract_class: flattened_sierra_class.try_into()?, paymaster_data: transaction.paymaster_data, account_deployment_data: transaction.account_deployment_data, - is_query, - } + }) } } @@ -233,36 +319,23 @@ pub enum UserInvokeFunctionTransaction { impl From for BroadcastedInvokeTxn { fn from(transaction: UserInvokeFunctionTransaction) -> Self { match transaction { - UserInvokeFunctionTransaction::V0(tx) if tx.is_query => BroadcastedInvokeTxn::QueryV0(tx.into()), UserInvokeFunctionTransaction::V0(tx) => BroadcastedInvokeTxn::V0(tx.into()), - UserInvokeFunctionTransaction::V1(tx) if tx.is_query => BroadcastedInvokeTxn::QueryV1(tx.into()), UserInvokeFunctionTransaction::V1(tx) => BroadcastedInvokeTxn::V1(tx.into()), - UserInvokeFunctionTransaction::V3(tx) if tx.is_query => BroadcastedInvokeTxn::QueryV3(tx.into()), UserInvokeFunctionTransaction::V3(tx) => BroadcastedInvokeTxn::V3(tx.into()), } } } -impl From> for UserInvokeFunctionTransaction { - fn from(transaction: BroadcastedInvokeTxn) -> Self { +impl TryFrom> for UserInvokeFunctionTransaction { + type Error = UserTransactionConversionError; + + fn try_from(transaction: BroadcastedInvokeTxn) -> Result { match transaction { - BroadcastedInvokeTxn::V0(tx) => { - UserInvokeFunctionTransaction::V0(UserInvokeFunctionV0Transaction::from_broadcasted(tx, false)) - } - BroadcastedInvokeTxn::QueryV0(tx) => { - UserInvokeFunctionTransaction::V0(UserInvokeFunctionV0Transaction::from_broadcasted(tx, true)) - } - BroadcastedInvokeTxn::V1(tx) => { - UserInvokeFunctionTransaction::V1(UserInvokeFunctionV1Transaction::from_broadcasted(tx, false)) - } - BroadcastedInvokeTxn::QueryV1(tx) => { - UserInvokeFunctionTransaction::V1(UserInvokeFunctionV1Transaction::from_broadcasted(tx, true)) - } - BroadcastedInvokeTxn::V3(tx) => { - UserInvokeFunctionTransaction::V3(UserInvokeFunctionV3Transaction::from_broadcasted(tx, false)) - } - BroadcastedInvokeTxn::QueryV3(tx) => { - UserInvokeFunctionTransaction::V3(UserInvokeFunctionV3Transaction::from_broadcasted(tx, true)) + BroadcastedInvokeTxn::V0(tx) => Ok(UserInvokeFunctionTransaction::V0(tx.into())), + BroadcastedInvokeTxn::V1(tx) => Ok(UserInvokeFunctionTransaction::V1(tx.into())), + BroadcastedInvokeTxn::V3(tx) => Ok(UserInvokeFunctionTransaction::V3(tx.into())), + BroadcastedInvokeTxn::QueryV0(_) | BroadcastedInvokeTxn::QueryV1(_) | BroadcastedInvokeTxn::QueryV3(_) => { + Err(UserTransactionConversionError::UnsupportedQueryTransaction) } } } @@ -275,7 +348,6 @@ pub struct UserInvokeFunctionV0Transaction { pub calldata: Vec, pub signature: Vec, pub max_fee: Felt, - pub is_query: bool, } impl From for InvokeTxnV0 { @@ -290,15 +362,14 @@ impl From for InvokeTxnV0 { } } -impl UserInvokeFunctionV0Transaction { - fn from_broadcasted(transaction: InvokeTxnV0, is_query: bool) -> Self { +impl From> for UserInvokeFunctionV0Transaction { + fn from(transaction: InvokeTxnV0) -> Self { Self { sender_address: transaction.contract_address, entry_point_selector: transaction.entry_point_selector, calldata: transaction.calldata, signature: transaction.signature, max_fee: transaction.max_fee, - is_query, } } } @@ -310,7 +381,6 @@ pub struct UserInvokeFunctionV1Transaction { pub signature: Vec, pub max_fee: Felt, pub nonce: Felt, - pub is_query: bool, } impl From for InvokeTxnV1 { @@ -325,19 +395,19 @@ impl From for InvokeTxnV1 { } } -impl UserInvokeFunctionV1Transaction { - fn from_broadcasted(transaction: InvokeTxnV1, is_query: bool) -> Self { +impl From> for UserInvokeFunctionV1Transaction { + fn from(transaction: InvokeTxnV1) -> Self { Self { sender_address: transaction.sender_address, calldata: transaction.calldata, signature: transaction.signature, max_fee: transaction.max_fee, nonce: transaction.nonce, - is_query, } } } +#[serde_as] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct UserInvokeFunctionV3Transaction { pub sender_address: Felt, @@ -347,10 +417,10 @@ pub struct UserInvokeFunctionV3Transaction { pub nonce_data_availability_mode: DataAvailabilityMode, pub fee_data_availability_mode: DataAvailabilityMode, pub resource_bounds: ResourceBoundsMapping, + #[serde_as(as = "U64AsHex")] pub tip: u64, pub paymaster_data: Vec, pub account_deployment_data: Vec, - pub is_query: bool, } impl From for InvokeTxnV3 { @@ -370,8 +440,8 @@ impl From for InvokeTxnV3 { } } -impl UserInvokeFunctionV3Transaction { - fn from_broadcasted(transaction: InvokeTxnV3, is_query: bool) -> Self { +impl From> for UserInvokeFunctionV3Transaction { + fn from(transaction: InvokeTxnV3) -> Self { Self { sender_address: transaction.sender_address, calldata: transaction.calldata, @@ -383,7 +453,6 @@ impl UserInvokeFunctionV3Transaction { tip: transaction.tip, paymaster_data: transaction.paymaster_data, account_deployment_data: transaction.account_deployment_data, - is_query, } } } @@ -400,27 +469,21 @@ pub enum UserDeployAccountTransaction { impl From for BroadcastedDeployAccountTxn { fn from(transaction: UserDeployAccountTransaction) -> Self { match transaction { - UserDeployAccountTransaction::V1(tx) if tx.is_query => BroadcastedDeployAccountTxn::QueryV1(tx.into()), UserDeployAccountTransaction::V1(tx) => BroadcastedDeployAccountTxn::V1(tx.into()), - UserDeployAccountTransaction::V3(tx) => BroadcastedDeployAccountTxn::QueryV3(tx.into()), + UserDeployAccountTransaction::V3(tx) => BroadcastedDeployAccountTxn::V3(tx.into()), } } } -impl From> for UserDeployAccountTransaction { - fn from(transaction: BroadcastedDeployAccountTxn) -> Self { +impl TryFrom> for UserDeployAccountTransaction { + type Error = UserTransactionConversionError; + + fn try_from(transaction: BroadcastedDeployAccountTxn) -> Result { match transaction { - BroadcastedDeployAccountTxn::V1(tx) => { - UserDeployAccountTransaction::V1(UserDeployAccountV1Transaction::from_broadcasted(tx, false)) - } - BroadcastedDeployAccountTxn::V3(tx) => { - UserDeployAccountTransaction::V3(UserDeployAccountV3Transaction::from_broadcasted(tx, false)) - } - BroadcastedDeployAccountTxn::QueryV1(tx) => { - UserDeployAccountTransaction::V1(UserDeployAccountV1Transaction::from_broadcasted(tx, true)) - } - BroadcastedDeployAccountTxn::QueryV3(tx) => { - UserDeployAccountTransaction::V3(UserDeployAccountV3Transaction::from_broadcasted(tx, true)) + BroadcastedDeployAccountTxn::V1(tx) => Ok(UserDeployAccountTransaction::V1(tx.into())), + BroadcastedDeployAccountTxn::V3(tx) => Ok(UserDeployAccountTransaction::V3(tx.into())), + BroadcastedDeployAccountTxn::QueryV1(_) | BroadcastedDeployAccountTxn::QueryV3(_) => { + Err(UserTransactionConversionError::UnsupportedQueryTransaction) } } } @@ -434,7 +497,6 @@ pub struct UserDeployAccountV1Transaction { pub max_fee: Felt, pub signature: Vec, pub nonce: Felt, - pub is_query: bool, } impl From for DeployAccountTxnV1 { @@ -450,8 +512,8 @@ impl From for DeployAccountTxnV1 { } } -impl UserDeployAccountV1Transaction { - fn from_broadcasted(transaction: DeployAccountTxnV1, is_query: bool) -> Self { +impl From> for UserDeployAccountV1Transaction { + fn from(transaction: DeployAccountTxnV1) -> Self { Self { class_hash: transaction.class_hash, contract_address_salt: transaction.contract_address_salt, @@ -459,11 +521,11 @@ impl UserDeployAccountV1Transaction { max_fee: transaction.max_fee, signature: transaction.signature, nonce: transaction.nonce, - is_query, } } } +#[serde_as] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct UserDeployAccountV3Transaction { pub class_hash: Felt, @@ -474,9 +536,9 @@ pub struct UserDeployAccountV3Transaction { pub nonce_data_availability_mode: DataAvailabilityMode, pub fee_data_availability_mode: DataAvailabilityMode, pub resource_bounds: ResourceBoundsMapping, + #[serde_as(as = "U64AsHex")] pub tip: u64, pub paymaster_data: Vec, - pub is_query: bool, } impl From for DeployAccountTxnV3 { @@ -496,8 +558,8 @@ impl From for DeployAccountTxnV3 { } } -impl UserDeployAccountV3Transaction { - fn from_broadcasted(transaction: DeployAccountTxnV3, is_query: bool) -> Self { +impl From> for UserDeployAccountV3Transaction { + fn from(transaction: DeployAccountTxnV3) -> Self { Self { class_hash: transaction.class_hash, contract_address_salt: transaction.contract_address_salt, @@ -509,7 +571,6 @@ impl UserDeployAccountV3Transaction { resource_bounds: transaction.resource_bounds.into(), tip: transaction.tip, paymaster_data: transaction.paymaster_data, - is_query, } } } diff --git a/crates/madara/primitives/transactions/src/to_blockifier.rs b/crates/madara/primitives/transactions/src/to_blockifier.rs index 64d9b989b..3f52e5c39 100644 --- a/crates/madara/primitives/transactions/src/to_blockifier.rs +++ b/crates/madara/primitives/transactions/src/to_blockifier.rs @@ -177,7 +177,7 @@ pub enum ToBlockifierError { #[error("Compiled class hash mismatch: expected {expected}, actual {compilation}")] CompiledClassHashMismatch { expected: Felt, compilation: Felt }, #[error("Failed to convert base64 program to cairo program: {0}")] - Base64ToCairoError(#[from] base64::DecodeError), + Base64ToCairoError(#[from] std::io::Error), #[error("Missing class")] MissingClass, }