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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 7 additions & 16 deletions config/networks/polygon.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@
"https://api.polygonscan.com/api",
"https://polygonscan.com"
],
"features": [
"eip1559"
],
"features": ["eip1559"],
"is_testnet": false,
"network": "polygon",
"required_confirmations": 1,
Expand Down Expand Up @@ -58,25 +56,18 @@
"https://polygon-zkevm-public.nodies.app"
],
"symbol": "ETH",
"type": "evm"
"type": "evm",
"tags": ["polygon-zkevm-based"]
},
{
"from": "polygon-zkevm",
"type": "evm",
"network": "polygon-zkevm-testnet",
"chain_id": 1442,
"explorer_urls": [
"https://api-testnet-zkevm.polygonscan.com/api",
"https://testnet-zkevm.polygonscan.com"
],
"chain_id": 2442,
"explorer_urls": ["https://cardona-zkevm.polygonscan.com/"],
"is_testnet": true,
"rpc_urls": [
"https://rpc.cardona.zkevm-rpc.com",
"https://polygon-zkevm-cardona.drpc.org"
],
"tags": [
"deprecated"
]
"rpc_urls": ["https://rpc.cardona.zkevm-rpc.com"],
"tags": ["polygon-zkevm-based"]
}
]
}
1 change: 1 addition & 0 deletions src/constants/network_tags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pub const NO_MEMPOOL_TAG: &str = "no-mempool";
pub const ARBITRUM_BASED_TAG: &str = "arbitrum-based";
pub const OPTIMISM_BASED_TAG: &str = "optimism-based";
pub const POLYGON_ZKEVM_TAG: &str = "polygon-zkevm-based";
/// @deprecated Use OPTIMISM_BASED_TAG instead. Will be removed in a future version.
pub const OPTIMISM_TAG: &str = "optimism";
pub const ROLLUP_TAG: &str = "rollup";
Expand Down
181 changes: 62 additions & 119 deletions src/domain/transaction/evm/price_calculator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,6 @@ pub trait PriceCalculatorTrait: Send + Sync {
) -> Result<PriceParams, TransactionError>;
}

type GasPriceCapResult = (Option<u128>, Option<u128>, Option<u128>);

const PRECISION: u128 = 1_000_000_000; // 10^9 (similar to Gwei)
const MINUTE_AND_HALF_MS: u128 = 90000;
const BASE_FEE_INCREASE_RATE: f64 = 1.125; // 12.5% increase per block (1 + 0.125)
Expand Down Expand Up @@ -162,24 +160,6 @@ where
}
}

/// Helper method to build an EvmTransactionRequest from transaction data and price parameters
fn build_request_from(
tx_data: &EvmTransactionData,
params: &PriceParams,
) -> crate::models::EvmTransactionRequest {
crate::models::EvmTransactionRequest {
to: tx_data.to.clone(),
value: tx_data.value,
data: tx_data.data.clone(),
gas_limit: tx_data.gas_limit,
gas_price: params.gas_price,
speed: tx_data.speed.clone(),
max_fee_per_gas: params.max_fee_per_gas,
max_priority_fee_per_gas: params.max_priority_fee_per_gas,
valid_until: None,
}
}

/// Calculates transaction price parameters based on the transaction type and network conditions.
///
/// This function determines the appropriate gas pricing strategy based on the transaction type:
Expand All @@ -200,59 +180,16 @@ where
tx_data: &EvmTransactionData,
relayer: &RelayerRepoModel,
) -> Result<PriceParams, TransactionError> {
let price_params = self
let mut price_final_params = self
.fetch_price_params_based_on_tx_type(tx_data, relayer)
.await?;
let (gas_price_capped, max_fee_per_gas_capped, max_priority_fee_per_gas_capped) = self
.apply_gas_price_cap(
price_params.gas_price.unwrap_or_default(),
price_params.max_fee_per_gas,
price_params.max_priority_fee_per_gas,
relayer,
)?;

let mut final_params = PriceParams {
gas_price: gas_price_capped,
max_fee_per_gas: max_fee_per_gas_capped,
max_priority_fee_per_gas: max_priority_fee_per_gas_capped,
is_min_bumped: None,
extra_fee: None,
total_cost: U256::ZERO,
};

// Use price params overrider if available for custom network pricing
let is_eip1559 = tx_data.is_eip1559();

let mut recompute_total_cost = true;
if let Some(handler) = &self.price_params_handler {
let req = Self::build_request_from(tx_data, &final_params);
final_params = handler.handle_price_params(&req, final_params).await?;

// Re-apply cap after overrider in case it changed fee fields
let (gas_price_capped, max_fee_per_gas_capped, max_priority_fee_per_gas_capped) = self
.apply_gas_price_cap(
final_params.gas_price.unwrap_or_default(),
final_params.max_fee_per_gas,
final_params.max_priority_fee_per_gas,
relayer,
)?;
final_params.gas_price = gas_price_capped;
final_params.max_fee_per_gas = max_fee_per_gas_capped;
final_params.max_priority_fee_per_gas = max_priority_fee_per_gas_capped;

recompute_total_cost = final_params.total_cost == U256::ZERO;
}

// Only recompute total cost if it was not set by the overrider
if recompute_total_cost {
final_params.total_cost = final_params.calculate_total_cost(
is_eip1559,
tx_data.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT),
U256::from(tx_data.value),
);
}
// Apply gas price caps and constraints
self.apply_gas_price_cap_and_constraints(&mut price_final_params, relayer)?;

Ok(final_params)
// Use price params handler if available for custom network pricing and finalize
self.finalize_price_params(relayer, tx_data, price_final_params)
.await
}

/// Computes bumped gas price for transaction resubmission, factoring in network conditions.
Expand Down Expand Up @@ -323,42 +260,9 @@ where
}
};

// Add extra fee if needed
let mut final_params = bumped_price_params;
let value = tx_data.value;
let gas_limit = tx_data.gas_limit;
let is_eip1559 = tx_data.is_eip1559();

// Use price params overrider if available for custom network pricing
let mut recompute_total_cost = true;
if let Some(handler) = &self.price_params_handler {
let req = Self::build_request_from(tx_data, &final_params);
final_params = handler.handle_price_params(&req, final_params).await?;

let (gas_price_capped, max_fee_per_gas_capped, max_priority_fee_per_gas_capped) = self
.apply_gas_price_cap(
final_params.gas_price.unwrap_or_default(),
final_params.max_fee_per_gas,
final_params.max_priority_fee_per_gas,
relayer,
)?;
final_params.gas_price = gas_price_capped;
final_params.max_fee_per_gas = max_fee_per_gas_capped;
final_params.max_priority_fee_per_gas = max_priority_fee_per_gas_capped;

recompute_total_cost = final_params.total_cost == U256::ZERO;
}

// Only recompute total cost if it was not set by the overrider
if recompute_total_cost {
final_params.total_cost = final_params.calculate_total_cost(
is_eip1559,
gas_limit.unwrap_or(DEFAULT_GAS_LIMIT),
U256::from(value),
);
}

Ok(final_params)
// Use price params handler if available for custom network pricing and finalize
self.finalize_price_params(relayer, tx_data, bumped_price_params)
.await
}

/// Computes the bumped gas parameters for an EIP-1559 transaction resubmission.
Expand Down Expand Up @@ -632,44 +536,83 @@ where
})
}

/// Applies gas price caps to the calculated prices.
/// Applies gas price caps and constraints to PriceParams in place.
///
/// Ensures that gas prices don't exceed the configured maximum limits and
/// maintains proper relationships between different price parameters.
fn apply_gas_price_cap(
/// This method modifies the provided PriceParams struct directly.
fn apply_gas_price_cap_and_constraints(
&self,
gas_price: u128,
max_fee_per_gas: Option<u128>,
max_priority_fee_per_gas: Option<u128>,
price_params: &mut PriceParams,
relayer: &RelayerRepoModel,
) -> Result<GasPriceCapResult, TransactionError> {
) -> Result<(), TransactionError> {
let gas_price_cap = relayer
.policies
.get_evm_policy()
.gas_price_cap
.unwrap_or(u128::MAX);

if let (Some(max_fee), Some(max_priority)) = (max_fee_per_gas, max_priority_fee_per_gas) {
if let (Some(max_fee), Some(max_priority)) = (
price_params.max_fee_per_gas,
price_params.max_priority_fee_per_gas,
) {
// Cap the maxFeePerGas
let capped_max_fee = Self::cap_gas_price(max_fee, gas_price_cap);
price_params.max_fee_per_gas = Some(capped_max_fee);

// Ensure maxPriorityFeePerGas < maxFeePerGas to avoid client errors
let capped_max_priority = Self::cap_gas_price(max_priority, capped_max_fee);
Ok((None, Some(capped_max_fee), Some(capped_max_priority)))
price_params.max_priority_fee_per_gas =
Some(Self::cap_gas_price(max_priority, capped_max_fee));

// For EIP1559 transactions, gas_price should be None
price_params.gas_price = None;
} else {
// Handle legacy transaction
Ok((
Some(Self::cap_gas_price(gas_price, gas_price_cap)),
None,
None,
))
price_params.gas_price = Some(Self::cap_gas_price(
price_params.gas_price.unwrap_or_default(),
gas_price_cap,
));

// For legacy transactions, EIP1559 fields should be None
price_params.max_fee_per_gas = None;
price_params.max_priority_fee_per_gas = None;
}

Ok(())
}

fn cap_gas_price(price: u128, cap: u128) -> u128 {
std::cmp::min(price, cap)
}

/// Applies price params handler and finalizes price parameters.
async fn finalize_price_params(
&self,
relayer: &RelayerRepoModel,
tx_data: &EvmTransactionData,
mut price_params: PriceParams,
) -> Result<PriceParams, TransactionError> {
let is_eip1559 = tx_data.is_eip1559();

// Apply price params handler if available
if let Some(handler) = &self.price_params_handler {
price_params = handler.handle_price_params(tx_data, price_params).await?;

// Re-apply cap after handler in case it changed fee fields
self.apply_gas_price_cap_and_constraints(&mut price_params, relayer)?;
}

if price_params.total_cost == U256::ZERO {
price_params.total_cost = price_params.calculate_total_cost(
is_eip1559,
tx_data.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT),
U256::from(tx_data.value),
);
}

Ok(price_params)
}

/// Returns the market price for the given speed. If `is_eip1559` is true, use `max_priority_fee_per_gas`,
/// otherwise use `legacy_prices`.
fn get_market_price_for_speed(prices: &GasPrices, is_eip1559: bool, speed: &Speed) -> u128 {
Expand Down
17 changes: 16 additions & 1 deletion src/models/network/evm/network.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::config::GasPriceCacheConfig;
use crate::constants::{
ARBITRUM_BASED_TAG, LACKS_MEMPOOL_TAGS, OPTIMISM_BASED_TAG, OPTIMISM_TAG, ROLLUP_TAG,
ARBITRUM_BASED_TAG, LACKS_MEMPOOL_TAGS, OPTIMISM_BASED_TAG, OPTIMISM_TAG, POLYGON_ZKEVM_TAG,
ROLLUP_TAG,
};
use crate::models::{NetworkConfigData, NetworkRepoModel, RepositoryError};
use std::time::Duration;
Expand Down Expand Up @@ -127,6 +128,10 @@ impl EvmNetwork {
self.tags.iter().any(|t| t == ARBITRUM_BASED_TAG)
}

pub fn is_polygon_zkevm(&self) -> bool {
self.tags.iter().any(|t| t == POLYGON_ZKEVM_TAG)
}

pub fn is_testnet(&self) -> bool {
self.is_testnet
}
Expand Down Expand Up @@ -249,6 +254,16 @@ mod tests {
assert!(network.lacks_mempool());
}

#[test]
fn test_polygon_zkevm_network() {
let network = create_test_evm_network_with_tags(vec![ROLLUP_TAG, POLYGON_ZKEVM_TAG]);
assert!(network.is_rollup());
assert!(network.is_polygon_zkevm());
assert!(!network.lacks_mempool());
assert!(!network.is_optimism());
assert!(!network.is_arbitrum());
}

#[test]
fn test_ethereum_mainnet_like_network() {
let network = create_test_evm_network_with_tags(vec!["mainnet", "ethereum"]);
Expand Down
36 changes: 34 additions & 2 deletions src/services/gas/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
config::GasPriceCacheConfig,
constants::{GAS_PRICE_CACHE_REFRESH_TIMEOUT_SECS, HISTORICAL_BLOCKS},
models::{EvmNetwork, TransactionError},
services::EvmProviderTrait,
services::{gas::fetchers::GasPriceFetcherFactory, EvmProviderTrait},
};
use alloy::rpc::types::{BlockNumberOrTag, FeeHistory};
use dashmap::DashMap;
Expand Down Expand Up @@ -259,7 +259,12 @@ impl GasPriceCache {
let refresh = async {
// Get network provider and fetch fresh data
let provider = crate::services::get_network_provider(&network, None).ok()?;
let fresh_gas_price = provider.get_gas_price().await.ok()?;

// Use the generic fetcher factory to get the best gas price for this network
let fresh_gas_price = GasPriceFetcherFactory::fetch_gas_price(&provider, &network)
.await
.ok()?;

let block = provider.get_block_by_number().await.ok()?;
let fresh_base_fee: u128 = block.header.base_fee_per_gas.unwrap_or(0).into();
let fee_hist = provider
Expand Down Expand Up @@ -468,4 +473,31 @@ mod tests {
// Removing again should return false (nothing to remove)
assert!(!cache.remove_network(chain_id));
}

#[tokio::test]
async fn test_polygon_zkevm_network_detection() {
use crate::constants::POLYGON_ZKEVM_TAG;

// Create a mock zkEVM network
let mut zkevm_network = EvmNetwork {
network: "polygon-zkevm".to_string(),
rpc_urls: vec!["https://zkevm-rpc.com".to_string()],
explorer_urls: None,
average_blocktime_ms: 2000,
is_testnet: false,
tags: vec![POLYGON_ZKEVM_TAG.to_string()],
chain_id: 1101,
required_confirmations: 1,
features: vec!["eip1559".to_string()],
symbol: "ETH".to_string(),
gas_price_cache: None,
};

// Test zkEVM detection
assert!(zkevm_network.is_polygon_zkevm());

// Test non-zkEVM network
zkevm_network.tags = vec!["rollup".to_string()];
assert!(!zkevm_network.is_polygon_zkevm());
}
}
Loading
Loading