diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index 1190058e8d..4eb7565337 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -8,7 +8,7 @@ use std::ops::Rem; use std::str::FromStr; use anyhow::Context; -use fake::Dummy; +use fake::{Dummy, Fake, Faker}; use pathfinder_crypto::hash::HashChain; use pathfinder_crypto::Felt; use primitive_types::H160; @@ -342,6 +342,23 @@ impl Dummy for EthereumAddress { } } +#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)] +pub enum SettlementLayerAddress { + Ethereum(EthereumAddress), + Starknet(ContractAddress), +} + +impl Dummy for SettlementLayerAddress { + fn dummy_with_rng(_: &T, rng: &mut R) -> Self { + if rng.gen_bool(0.5) { + Self::Ethereum(EthereumAddress(H160::random_using(rng))) + } else { + Self::Starknet(Faker.fake_with_rng(rng)) + } + } +} + + #[derive(Debug, thiserror::Error)] #[error("expected slice length of 16 or less, got {0}")] pub struct FromSliceError(usize); diff --git a/crates/consensus-fetcher/src/lib.rs b/crates/consensus-fetcher/src/lib.rs index 95a2099ff1..b5afdf975d 100644 --- a/crates/consensus-fetcher/src/lib.rs +++ b/crates/consensus-fetcher/src/lib.rs @@ -123,6 +123,7 @@ fn get_proposer_contract_address( pub fn get_validators_at_height( storage: &Storage, chain_id: ChainId, + is_l3: bool, height: u64, ) -> Result, ConsensusFetcherError> { let mut db_conn = storage @@ -146,6 +147,7 @@ pub fn get_validators_at_height( // Create execution state for call let execution_state = ExecutionState::simulation( chain_id, + is_l3, header, None, // No pending state for this call L1BlobDataAvailability::Disabled, @@ -182,6 +184,7 @@ pub fn get_validators_at_height( pub fn get_proposers_at_height( storage: &Storage, chain_id: ChainId, + is_l3: bool, height: u64, ) -> Result, ConsensusFetcherError> { let mut db_conn = storage @@ -205,6 +208,7 @@ pub fn get_proposers_at_height( // Create execution state for call let execution_state = ExecutionState::simulation( chain_id, + is_l3, header, None, // No pending state for this call L1BlobDataAvailability::Disabled, diff --git a/crates/executor/src/block.rs b/crates/executor/src/block.rs index 4177a8bc47..839a1939b4 100644 --- a/crates/executor/src/block.rs +++ b/crates/executor/src/block.rs @@ -32,6 +32,7 @@ type ReceiptAndEvents = (Receipt, Vec); impl BlockExecutor { pub fn new( chain_id: ChainId, + is_l3: bool, block_info: BlockInfo, eth_fee_address: ContractAddress, strk_fee_address: ContractAddress, @@ -39,6 +40,7 @@ impl BlockExecutor { ) -> anyhow::Result { let execution_state = ExecutionState::validation( chain_id, + is_l3, block_info, None, Default::default(), @@ -67,6 +69,7 @@ impl BlockExecutor { /// the final state of a previous executor pub fn new_with_initial_state( chain_id: ChainId, + is_l3: bool, block_info: BlockInfo, eth_fee_address: ContractAddress, strk_fee_address: ContractAddress, @@ -75,6 +78,7 @@ impl BlockExecutor { ) -> anyhow::Result { let execution_state = ExecutionState::validation( chain_id, + is_l3, block_info, None, Default::default(), @@ -505,6 +509,7 @@ mod tests { // Execute them all in a single executor let mut single_executor = BlockExecutor::new( chain_id, + false, block_info, ETH_FEE_TOKEN_ADDRESS, STRK_FEE_TOKEN_ADDRESS, @@ -527,6 +532,7 @@ mod tests { // Execute batch 1 let mut executor1 = BlockExecutor::new( chain_id, + false, block_info, ETH_FEE_TOKEN_ADDRESS, STRK_FEE_TOKEN_ADDRESS, @@ -542,6 +548,7 @@ mod tests { // Execute batch 2 with state from batch 1 let mut executor2 = BlockExecutor::new_with_initial_state( chain_id, + false, block_info, ETH_FEE_TOKEN_ADDRESS, STRK_FEE_TOKEN_ADDRESS, @@ -558,6 +565,7 @@ mod tests { // Execute batch 3 with state from batch 2 let mut executor3 = BlockExecutor::new_with_initial_state( chain_id, + false, block_info, ETH_FEE_TOKEN_ADDRESS, STRK_FEE_TOKEN_ADDRESS, @@ -616,6 +624,7 @@ mod tests { // Single executor execution let mut single_executor = BlockExecutor::new( chain_id, + false, block_info, ETH_FEE_TOKEN_ADDRESS, STRK_FEE_TOKEN_ADDRESS, @@ -643,6 +652,7 @@ mod tests { let mut current_executor = BlockExecutor::new( chain_id, + false, block_info, ETH_FEE_TOKEN_ADDRESS, STRK_FEE_TOKEN_ADDRESS, @@ -655,6 +665,7 @@ mod tests { for batch in batches.into_iter() { current_executor = BlockExecutor::new( chain_id, + false, block_info, ETH_FEE_TOKEN_ADDRESS, STRK_FEE_TOKEN_ADDRESS, diff --git a/crates/executor/src/execution_state.rs b/crates/executor/src/execution_state.rs index bcfbd3a544..a009570020 100644 --- a/crates/executor/src/execution_state.rs +++ b/crates/executor/src/execution_state.rs @@ -166,6 +166,7 @@ pub struct ExecutionState { eth_fee_address: ContractAddress, strk_fee_address: ContractAddress, native_class_cache: Option, + is_l3: bool, } pub fn create_executor( @@ -320,7 +321,7 @@ impl ExecutionState { strk_fee_token_address, eth_fee_token_address, }, - is_l3: false, + is_l3: self.is_l3, }) } @@ -401,6 +402,7 @@ impl ExecutionState { #[allow(clippy::too_many_arguments)] pub fn trace( chain_id: ChainId, + is_l3: bool, header: BlockHeader, pending_state: Option>, versioned_constants_map: VersionedConstantsMap, @@ -418,12 +420,14 @@ impl ExecutionState { eth_fee_address, strk_fee_address, native_class_cache, + is_l3, } } #[allow(clippy::too_many_arguments)] pub fn simulation( chain_id: ChainId, + is_l3: bool, header: BlockHeader, pending_state: Option>, l1_blob_data_availability: L1BlobDataAvailability, @@ -442,12 +446,14 @@ impl ExecutionState { eth_fee_address, strk_fee_address, native_class_cache, + is_l3, } } #[allow(clippy::too_many_arguments)] pub fn validation( chain_id: ChainId, + is_l3: bool, block_info: BlockInfo, pending_state: Option>, versioned_constants_map: VersionedConstantsMap, @@ -465,6 +471,7 @@ impl ExecutionState { eth_fee_address, strk_fee_address, native_class_cache, + is_l3, } } } diff --git a/crates/gateway-types/src/reply.rs b/crates/gateway-types/src/reply.rs index 9ca648a7c2..76c5368589 100644 --- a/crates/gateway-types/src/reply.rs +++ b/crates/gateway-types/src/reply.rs @@ -1,7 +1,7 @@ //! Structures used for deserializing replies from Starkware's sequencer REST //! API. -use pathfinder_common::prelude::*; -use pathfinder_serde::{EthereumAddressAsHexStr, GasPriceAsHexStr}; +use pathfinder_common::{SettlementLayerAddress, prelude::*}; +use pathfinder_serde::{GasPriceAsHexStr, SettlementLayerAddressAsHexStr}; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, DisplayFromStr}; pub use transaction::DataAvailabilityMode; @@ -269,12 +269,12 @@ pub mod transaction_status { /// Types used when deserializing L2 transaction related data. pub mod transaction { use fake::{Dummy, Fake, Faker}; - use pathfinder_common::prelude::*; + use pathfinder_common::{SettlementLayerAddress, prelude::*}; use pathfinder_crypto::Felt; use pathfinder_serde::{ CallParamAsDecimalStr, ConstructorParamAsDecimalStr, - EthereumAddressAsHexStr, + SettlementLayerAddressAsHexStr, L1ToL2MessagePayloadElemAsDecimalStr, L2ToL1MessagePayloadElemAsDecimalStr, ResourceAmountAsHexStr, @@ -535,8 +535,8 @@ pub mod transaction { #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct L1ToL2Message { - #[serde_as(as = "EthereumAddressAsHexStr")] - pub from_address: EthereumAddress, + #[serde_as(as = "SettlementLayerAddressAsHexStr")] + pub from_address: SettlementLayerAddress, #[serde_as(as = "Vec")] pub payload: Vec, pub selector: EntryPoint, @@ -2220,8 +2220,8 @@ pub mod state_update { #[derive(Clone, Debug, Deserialize)] pub struct EthContractAddresses { #[serde(rename = "Starknet")] - #[serde_as(as = "EthereumAddressAsHexStr")] - pub starknet: EthereumAddress, + #[serde_as(as = "SettlementLayerAddressAsHexStr")] + pub starknet: SettlementLayerAddress, pub strk_l2_token_address: Option, diff --git a/crates/pathfinder/examples/re_execute.rs b/crates/pathfinder/examples/re_execute.rs index ed2da15d99..91f1eca6ae 100644 --- a/crates/pathfinder/examples/re_execute.rs +++ b/crates/pathfinder/examples/re_execute.rs @@ -148,6 +148,7 @@ fn execute( let execution_state = ExecutionState::trace( chain_id, + false, work.header.clone(), None, Default::default(), diff --git a/crates/pathfinder/src/bin/pathfinder/main.rs b/crates/pathfinder/src/bin/pathfinder/main.rs index 313a871b9e..9f5f3d6757 100644 --- a/crates/pathfinder/src/bin/pathfinder/main.rs +++ b/crates/pathfinder/src/bin/pathfinder/main.rs @@ -247,6 +247,7 @@ Hint: This is usually caused by exceeding the file descriptor limit of your syst execution_storage, sync_state.clone(), pathfinder_context.network_id, + pathfinder_context.is_l3, pathfinder_context.contract_addresses, pathfinder_context.gateway.clone(), rx_pending.clone(), @@ -736,6 +737,7 @@ If you are trying to connect to a custom Starknet on another Ethereum network, p struct PathfinderContext { network: Chain, network_id: ChainId, + is_l3: bool, gateway: starknet_gateway_client::Client, database: PathBuf, contract_addresses: EthContractAddresses, @@ -747,7 +749,7 @@ mod pathfinder_context { use std::time::Duration; use anyhow::Context; - use pathfinder_common::{Chain, ChainId}; + use pathfinder_common::{Chain, ChainId, SettlementLayerAddress}; use pathfinder_ethereum::core_addr; use pathfinder_rpc::context::EthContractAddresses; use reqwest::Url; @@ -767,6 +769,7 @@ mod pathfinder_context { NetworkConfig::Mainnet => Self { network: Chain::Mainnet, network_id: ChainId::MAINNET, + is_l3: false, gateway: GatewayClient::mainnet(gateway_timeout).with_api_key(api_key), database: data_directory.join("mainnet.sqlite"), contract_addresses: EthContractAddresses::new_known(core_addr::MAINNET), @@ -774,6 +777,7 @@ mod pathfinder_context { NetworkConfig::SepoliaTestnet => Self { network: Chain::SepoliaTestnet, network_id: ChainId::SEPOLIA_TESTNET, + is_l3: false, gateway: GatewayClient::sepolia_testnet(gateway_timeout).with_api_key(api_key), database: data_directory.join("testnet-sepolia.sqlite"), contract_addresses: EthContractAddresses::new_known(core_addr::SEPOLIA_TESTNET), @@ -781,6 +785,7 @@ mod pathfinder_context { NetworkConfig::SepoliaIntegration => Self { network: Chain::SepoliaIntegration, network_id: ChainId::SEPOLIA_INTEGRATION, + is_l3: false, gateway: GatewayClient::sepolia_integration(gateway_timeout) .with_api_key(api_key), database: data_directory.join("integration-sepolia.sqlite"), @@ -792,10 +797,12 @@ mod pathfinder_context { gateway, feeder_gateway, chain_id, + is_l3, } => Self::configure_custom( gateway, feeder_gateway, chain_id, + is_l3, data_directory, api_key, gateway_timeout, @@ -815,6 +822,7 @@ mod pathfinder_context { gateway: Url, feeder: Url, chain_id: String, + is_l3: bool, data_directory: &Path, api_key: Option, gateway_timeout: Duration, @@ -833,7 +841,30 @@ mod pathfinder_context { .eth_contract_addresses() .await .context("Downloading starknet L1 address from gateway for proxy check")?; - let l1_core_address = reply_contract_addresses.starknet.0; + + let l1_core_address = if is_l3 { + // For L3 networks, assert we got Starknet variant (ContractAddress) + match reply_contract_addresses.starknet { + SettlementLayerAddress::Starknet(_contract_address) => { + // TODO(mehul 14/11/2025): This is a placeholder return. L1 syncing for L3 networks would + // require significant changes that are not prioritized for pathfinder. + // Returning 0 address as a dummy return + primitive_types::H160::zero() + } + SettlementLayerAddress::Ethereum(_) => { + anyhow::bail!("L3 networks should have ContractAddress (Starknet variant) in starknet field, but got EthereumAddress (Ethereum variant)"); + } + } + } else { + // For L2 networks, assert we got Ethereum variant (EthereumAddress) + match reply_contract_addresses.starknet { + SettlementLayerAddress::Starknet(_) => { + anyhow::bail!("L2 networks should have EthereumAddress (Ethereum variant) in starknet field, but got ContractAddress (Starknet variant)"); + } + SettlementLayerAddress::Ethereum(address) => address.0, + } + }; + let contract_addresses = EthContractAddresses::new_custom( l1_core_address, reply_contract_addresses.eth_l2_token_address, @@ -856,6 +887,7 @@ mod pathfinder_context { let context = Self { network, network_id, + is_l3, gateway, database: data_directory.join("custom.sqlite"), contract_addresses, @@ -902,9 +934,7 @@ async fn verify_database( if let Some(database_genesis) = db_genesis { use pathfinder_common::consts::{ - MAINNET_GENESIS_HASH, - SEPOLIA_INTEGRATION_GENESIS_HASH, - SEPOLIA_TESTNET_GENESIS_HASH, + MAINNET_GENESIS_HASH, SEPOLIA_INTEGRATION_GENESIS_HASH, SEPOLIA_TESTNET_GENESIS_HASH, }; let db_network = match database_genesis { diff --git a/crates/pathfinder/src/config.rs b/crates/pathfinder/src/config.rs index 8c5192c9f9..06fd1155df 100644 --- a/crates/pathfinder/src/config.rs +++ b/crates/pathfinder/src/config.rs @@ -562,6 +562,16 @@ Note that 'custom' requires also setting the --gateway-url and --feeder-gateway- required_if_eq("network", Network::Custom) )] chain_id: Option, + + #[arg( + long = "is-l3", + long_help = "Set if the network is an L3 network", + env = "PATHFINDER_IS_L3", + required_if_eq("network", Network::Custom), + default_value = "false" + )] + is_l3: Option, + #[arg( long = "feeder-gateway-url", value_name = "URL", @@ -881,6 +891,7 @@ pub enum NetworkConfig { gateway: Url, feeder_gateway: Url, chain_id: String, + is_l3: bool, }, } @@ -923,22 +934,24 @@ impl NetworkConfig { args.gateway, args.feeder_gateway, args.chain_id, + args.is_l3, ) { - (None, None, None, None) => return None, - (Some(Custom), Some(gateway), Some(feeder_gateway), Some(chain_id)) => { + (None, None, None, None, None) => return None, + (Some(Custom), Some(gateway), Some(feeder_gateway), Some(chain_id), Some(is_l3)) => { NetworkConfig::Custom { gateway, feeder_gateway, chain_id, + is_l3, } } - (Some(Custom), _, _, _) => { + (Some(Custom), _, _, _, _) => { unreachable!("`--network custom` requirements are handled by clap derive") } // Handle non-custom variants in an inner match so that the compiler will force // us to handle a new network variants explicitly. Otherwise we end up with a // catch-all arm that would swallow new variants silently. - (Some(non_custom), None, None, None) => match non_custom { + (Some(non_custom), None, None, None, None) => match non_custom { Mainnet => NetworkConfig::Mainnet, SepoliaTestnet => NetworkConfig::SepoliaTestnet, SepoliaIntegration => NetworkConfig::SepoliaIntegration, diff --git a/crates/pathfinder/src/validator.rs b/crates/pathfinder/src/validator.rs index 08fcabe55c..b867feae35 100644 --- a/crates/pathfinder/src/validator.rs +++ b/crates/pathfinder/src/validator.rs @@ -50,7 +50,8 @@ pub fn new( chain_id: ChainId, proposal_init: ProposalInit, ) -> anyhow::Result { - ValidatorBlockInfoStage::new(chain_id, proposal_init) + let is_l3 = false; + ValidatorBlockInfoStage::new(chain_id, is_l3, proposal_init) } /// Validates the basic block metadata and proposal information before any @@ -58,17 +59,20 @@ pub fn new( #[derive(Debug)] pub struct ValidatorBlockInfoStage { chain_id: ChainId, + is_l3: bool, proposal_height: BlockNumber, } impl ValidatorBlockInfoStage { pub fn new( chain_id: ChainId, + is_l3: bool, proposal_init: ProposalInit, ) -> anyhow::Result { // TODO(validator) how can we validate the proposal init? Ok(ValidatorBlockInfoStage { chain_id, + is_l3, proposal_height: BlockNumber::new(proposal_init.block_number) .context("ProposalInit height exceeds i64::MAX")?, }) @@ -89,6 +93,7 @@ impl ValidatorBlockInfoStage { let Self { chain_id, + is_l3, proposal_height, } = self; @@ -135,9 +140,10 @@ impl ValidatorBlockInfoStage { Ok(ValidatorTransactionBatchStage { chain_id, + is_l3, block_info, expected_block_header: None, - block_executor: LazyBlockExecutor::new(chain_id, block_info, storage.clone()), + block_executor: LazyBlockExecutor::new(chain_id, is_l3, block_info, storage.clone()), transactions: Vec::new(), receipts: Vec::new(), events: Vec::new(), @@ -152,6 +158,7 @@ impl ValidatorBlockInfoStage { /// Executes transactions and manages the block execution state. pub struct ValidatorTransactionBatchStage { chain_id: ChainId, + is_l3: bool, block_info: pathfinder_executor::types::BlockInfo, expected_block_header: Option, block_executor: LazyBlockExecutor, @@ -173,6 +180,7 @@ enum LazyBlockExecutor { /// on first use. Uninitialized { chain_id: ChainId, + is_l3: bool, block_info: Box, storage: Storage, }, @@ -189,11 +197,13 @@ enum LazyBlockExecutor { impl LazyBlockExecutor { fn new( chain_id: ChainId, + is_l3: bool, block_info: pathfinder_executor::types::BlockInfo, storage: Storage, ) -> Self { LazyBlockExecutor::Uninitialized { chain_id, + is_l3, block_info: Box::new(block_info), storage, } @@ -206,6 +216,7 @@ impl LazyBlockExecutor { let this = std::mem::replace(self, Self::Initializing); let LazyBlockExecutor::Uninitialized { chain_id, + is_l3, block_info, storage, } = this @@ -216,6 +227,7 @@ impl LazyBlockExecutor { let db_conn = storage.connection().context("Create database connection")?; let be = BlockExecutor::new( chain_id, + is_l3, *block_info, ETH_FEE_TOKEN_ADDRESS, STRK_FEE_TOKEN_ADDRESS, @@ -245,16 +257,24 @@ impl LazyBlockExecutor { impl ValidatorTransactionBatchStage { /// Create a new ValidatorTransactionBatchStage + /// + /// Note: This method is primarily used in tests. For production code, use + /// `ValidatorBlockInfoStage::validate_consensus_block_info` which properly + /// passes `is_l3` from the network config. pub fn new( chain_id: ChainId, block_info: pathfinder_executor::types::BlockInfo, storage: Storage, ) -> anyhow::Result { + // For tests and when called directly, determine is_l3 from chain_id + // (returns false for known networks, false for custom by default) + let is_l3 = false; Ok(ValidatorTransactionBatchStage { chain_id, + is_l3, block_info, expected_block_header: None, - block_executor: LazyBlockExecutor::new(chain_id, block_info, storage.clone()), + block_executor: LazyBlockExecutor::new(chain_id, is_l3, block_info, storage.clone()), transactions: Vec::new(), receipts: Vec::new(), events: Vec::new(), @@ -321,6 +341,7 @@ impl ValidatorTransactionBatchStage { // First batch - start from initial state BlockExecutor::new( self.chain_id, + self.is_l3, self.block_info, ETH_FEE_TOKEN_ADDRESS, STRK_FEE_TOKEN_ADDRESS, @@ -337,6 +358,7 @@ impl ValidatorTransactionBatchStage { let previous_state = last_executor.get_final_state()?; BlockExecutor::new_with_initial_state( self.chain_id, + self.is_l3, self.block_info, ETH_FEE_TOKEN_ADDRESS, STRK_FEE_TOKEN_ADDRESS, diff --git a/crates/rpc/src/context.rs b/crates/rpc/src/context.rs index 71223aa8cb..fa5acd4945 100644 --- a/crates/rpc/src/context.rs +++ b/crates/rpc/src/context.rs @@ -1,7 +1,7 @@ use std::num::{NonZeroU64, NonZeroUsize}; use std::sync::Arc; -use pathfinder_common::{contract_address, ChainId, ConsensusInfo, ContractAddress}; +use pathfinder_common::{ChainId, ConsensusInfo, ContractAddress, EthereumAddress, SettlementLayerAddress, contract_address}; use pathfinder_ethereum::EthereumClient; use pathfinder_executor::{NativeClassCache, TraceCache, VersionedConstantsMap}; use pathfinder_storage::Storage; @@ -86,6 +86,7 @@ pub struct RpcContext { pub sync_status: Arc, pub submission_tracker: SubmittedTransactionTracker, pub chain_id: ChainId, + pub is_l3: bool, pub contract_addresses: EthContractAddresses, pub sequencer: SequencerClient, pub websocket: Option, @@ -103,6 +104,7 @@ impl RpcContext { execution_storage: Storage, sync_status: Arc, chain_id: ChainId, + is_l3: bool, contract_addresses: EthContractAddresses, sequencer: SequencerClient, pending_data: tokio_watch::Receiver, @@ -127,6 +129,7 @@ impl RpcContext { sync_status, submission_tracker, chain_id, + is_l3, contract_addresses, pending_data: pending_watcher, sequencer, @@ -250,6 +253,7 @@ impl RpcContext { storage, sync_state, chain_id, + false, EthContractAddresses::new_known(core_contract_address), sequencer.disable_retry_for_tests(), rx, diff --git a/crates/rpc/src/executor.rs b/crates/rpc/src/executor.rs index 88f9e76b97..a58b395739 100644 --- a/crates/rpc/src/executor.rs +++ b/crates/rpc/src/executor.rs @@ -1,16 +1,15 @@ use anyhow::Context; use pathfinder_common::transaction::TransactionVariant; -use pathfinder_common::{BlockNumber, ChainId, StarknetVersion}; +use pathfinder_common::{BlockNumber, ChainId, StarknetVersion, TransactionVersion}; +use pathfinder_crypto::Felt; use pathfinder_executor::types::to_starknet_api_transaction; use pathfinder_executor::{ClassInfo, IntoStarkFelt}; use starknet_api::contract_class::SierraVersion; use starknet_api::core::PatriciaKey; -use starknet_api::transaction::fields::Fee; +use starknet_api::transaction::fields::{Fee, ValidResourceBounds}; use crate::types::request::{ - BroadcastedDeployAccountTransaction, - BroadcastedInvokeTransaction, - BroadcastedTransaction, + BroadcastedDeployAccountTransaction, BroadcastedInvokeTransaction, BroadcastedTransaction, }; pub enum ExecutionStateError { @@ -359,6 +358,24 @@ pub fn compose_executor_transaction( tracing::trace!(%tx_hash, "Converting transaction"); let transaction = to_starknet_api_transaction(transaction.variant.clone())?; + let mut charge_fee = true; + if transaction.version().0 == starknet_types_core::felt::Felt::ZERO { + // Only used during bootstrapper v1 bootstrapping + charge_fee = false; + } else if let Some(resource_bounds) = transaction.resource_bounds() { + match resource_bounds { + ValidResourceBounds::AllResources(all_resources) => { + if all_resources.l2_gas.max_amount.0 == 0 { + charge_fee = false; + } + } + ValidResourceBounds::L1Gas(l1_gas) => { + if l1_gas.max_amount.0 == 0 { + charge_fee = false; + } + } + } + } let tx = pathfinder_executor::Transaction::from_api( transaction, @@ -366,7 +383,12 @@ pub fn compose_executor_transaction( class_info, paid_fee_on_l1, deployed_address, - pathfinder_executor::AccountTransactionExecutionFlags::default(), + pathfinder_executor::AccountTransactionExecutionFlags { + only_query: false, + charge_fee, + validate: true, + strict_nonce_check: true, + }, )?; Ok(tx) diff --git a/crates/rpc/src/method/call.rs b/crates/rpc/src/method/call.rs index 17a26314a3..c4f908bb2a 100644 --- a/crates/rpc/src/method/call.rs +++ b/crates/rpc/src/method/call.rs @@ -162,6 +162,7 @@ pub async fn call( let state = ExecutionState::simulation( context.chain_id, + context.is_l3, header, pending, L1BlobDataAvailability::Disabled, diff --git a/crates/rpc/src/method/estimate_fee.rs b/crates/rpc/src/method/estimate_fee.rs index 0649dcd3f9..8f1278993f 100644 --- a/crates/rpc/src/method/estimate_fee.rs +++ b/crates/rpc/src/method/estimate_fee.rs @@ -107,6 +107,7 @@ pub async fn estimate_fee( let state = ExecutionState::simulation( context.chain_id, + context.is_l3, header, pending, L1BlobDataAvailability::Enabled, diff --git a/crates/rpc/src/method/estimate_message_fee.rs b/crates/rpc/src/method/estimate_message_fee.rs index ccff853381..2dc8741e50 100644 --- a/crates/rpc/src/method/estimate_message_fee.rs +++ b/crates/rpc/src/method/estimate_message_fee.rs @@ -111,6 +111,7 @@ pub async fn estimate_message_fee( let state = ExecutionState::simulation( context.chain_id, + context.is_l3, header, pending, L1BlobDataAvailability::Enabled, diff --git a/crates/rpc/src/method/fetch_proposers.rs b/crates/rpc/src/method/fetch_proposers.rs index eb9a3b1e1f..c910463cdd 100644 --- a/crates/rpc/src/method/fetch_proposers.rs +++ b/crates/rpc/src/method/fetch_proposers.rs @@ -106,7 +106,7 @@ pub async fn fetch_proposers( let span = tracing::Span::current(); let proposers = util::task::spawn_blocking(move |_| { let _g = span.enter(); - consensus_fetcher::get_proposers_at_height(&context.storage, context.chain_id, input.height) + consensus_fetcher::get_proposers_at_height(&context.storage, context.chain_id, context.is_l3, input.height) }) .await .context("Database read panic or shutting down")? diff --git a/crates/rpc/src/method/fetch_validators.rs b/crates/rpc/src/method/fetch_validators.rs index ed18b2d2ef..77851fafcf 100644 --- a/crates/rpc/src/method/fetch_validators.rs +++ b/crates/rpc/src/method/fetch_validators.rs @@ -109,6 +109,7 @@ pub async fn fetch_validators( consensus_fetcher::get_validators_at_height( &context.storage, context.chain_id, + context.is_l3, input.height, ) }) diff --git a/crates/rpc/src/method/simulate_transactions.rs b/crates/rpc/src/method/simulate_transactions.rs index 2557a38328..213ca197ec 100644 --- a/crates/rpc/src/method/simulate_transactions.rs +++ b/crates/rpc/src/method/simulate_transactions.rs @@ -109,6 +109,7 @@ pub async fn simulate_transactions( let state = pathfinder_executor::ExecutionState::simulation( context.chain_id, + context.is_l3, header, pending, pathfinder_executor::L1BlobDataAvailability::Enabled, diff --git a/crates/rpc/src/method/trace_block_transactions.rs b/crates/rpc/src/method/trace_block_transactions.rs index d51cb9fe86..a4c38c97b1 100644 --- a/crates/rpc/src/method/trace_block_transactions.rs +++ b/crates/rpc/src/method/trace_block_transactions.rs @@ -139,6 +139,7 @@ pub async fn trace_block_transactions( let hash = header.hash; let state = pathfinder_executor::ExecutionState::trace( context.chain_id, + context.is_l3, header, None, context.config.versioned_constants_map, diff --git a/crates/rpc/src/method/trace_transaction.rs b/crates/rpc/src/method/trace_transaction.rs index cd57713ba5..fec56b3a8c 100644 --- a/crates/rpc/src/method/trace_transaction.rs +++ b/crates/rpc/src/method/trace_transaction.rs @@ -170,6 +170,7 @@ pub async fn trace_transaction( let hash = header.hash; let state = pathfinder_executor::ExecutionState::trace( context.chain_id, + context.is_l3, header, None, context.config.versioned_constants_map, diff --git a/crates/serde/src/lib.rs b/crates/serde/src/lib.rs index fd063d98a3..8b72054558 100644 --- a/crates/serde/src/lib.rs +++ b/crates/serde/src/lib.rs @@ -5,7 +5,7 @@ use std::borrow::Cow; use std::str::FromStr; use num_bigint::BigUint; -use pathfinder_common::prelude::*; +use pathfinder_common::{SettlementLayerAddress, prelude::*}; use pathfinder_crypto::{Felt, HexParseError, OverflowError}; use primitive_types::{H160, H256, U256}; use serde::de::Visitor; @@ -88,6 +88,77 @@ impl<'de> DeserializeAs<'de, EthereumAddress> for EthereumAddressAsHexStr { } } +pub struct SettlementLayerAddressAsHexStr; + +impl SerializeAs for SettlementLayerAddressAsHexStr { + fn serialize_as(source: &SettlementLayerAddress, serializer: S) -> Result + where + S: serde::Serializer { + match source { + SettlementLayerAddress::Ethereum(address) => { + EthereumAddressAsHexStr::serialize_as(address, serializer) + }, + SettlementLayerAddress::Starknet(address) => { + // ContractAddress is a Felt, serialize as 64-char hex string + let bytes = address.0.to_be_bytes(); + // ContractAddress is "0x" + 64 digits + let mut buf = [0u8; 2 + 64]; + let s = bytes_as_hex_str(&bytes, &mut buf); + serializer.serialize_str(s) + } + } + } +} + +impl<'de> DeserializeAs<'de, SettlementLayerAddress> for SettlementLayerAddressAsHexStr { + fn deserialize_as(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct SettlementLayerAddressVisitor; + + impl Visitor<'_> for SettlementLayerAddressVisitor { + type Value = SettlementLayerAddress; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("a hex string of 40 digits (Ethereum) or 41-64 digits (Starknet) with an optional '0x' prefix") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + + let hex_str = v.strip_prefix("0x").unwrap_or(v); + let hex_len = hex_str.len(); + + if hex_len <= 40 { + // Ethereum address: 20 bytes = 40 hex digits + let bytes = bytes_from_hex_str::<{ H160::len_bytes() }>(hex_str) + .map_err(serde::de::Error::custom)?; + Ok(SettlementLayerAddress::Ethereum(EthereumAddress(H160::from(bytes)))) + } else if hex_len <= 64 { + // Starknet ContractAddress: 32 bytes = 64 hex digits + let bytes = bytes_from_hex_str::<32>(hex_str) + .map_err(serde::de::Error::custom)?; + let felt = Felt::from_be_bytes(bytes) + .map_err(|e| serde::de::Error::custom(format!("Felt overflow: {}", e)))?; + Ok(SettlementLayerAddress::Starknet(ContractAddress(felt))) + } else { + Err(serde::de::Error::custom(format!( + "hex string too long: expected at most 64 digits (with optional '0x' prefix), got {}", + hex_len + ))) + } + } + } + + deserializer.deserialize_str(SettlementLayerAddressVisitor) + } +} + + + pub struct H256AsNoLeadingZerosHexStr; impl SerializeAs for H256AsNoLeadingZerosHexStr {