use crate::error::{CctpError, Result};
use alloy_network::Ethereum;
use alloy_primitives::U256;
use alloy_provider::Provider;
use alloy_rpc_types::TransactionRequest;
use std::time::Duration;
pub const DEFAULT_GAS_BUFFER_PERCENT: u64 = 20;
pub const DEFAULT_TIMEOUT_SECS: u64 = 30;
pub const DEFAULT_RETRY_ATTEMPTS: u32 = 3;
pub async fn estimate_gas_with_buffer<P: Provider<Ethereum>>(
provider: &P,
tx: &TransactionRequest,
buffer_percent: Option<u64>,
) -> Result<u64> {
let buffer = buffer_percent.unwrap_or(DEFAULT_GAS_BUFFER_PERCENT);
let estimate = provider
.estimate_gas(tx.clone())
.await
.map_err(|e| CctpError::Provider(format!("Gas estimation failed: {e}")))?;
let with_buffer = estimate.saturating_mul(100 + buffer) / 100;
Ok(with_buffer)
}
#[derive(Debug, Clone)]
pub struct ProviderConfig {
pub retry_attempts: u32,
pub timeout: Duration,
pub rate_limit_rps: Option<u32>,
}
impl Default for ProviderConfig {
fn default() -> Self {
Self {
retry_attempts: DEFAULT_RETRY_ATTEMPTS,
timeout: Duration::from_secs(DEFAULT_TIMEOUT_SECS),
rate_limit_rps: None,
}
}
}
impl ProviderConfig {
pub fn builder() -> ProviderConfigBuilder {
ProviderConfigBuilder::default()
}
pub fn fast_transfer() -> Self {
Self {
retry_attempts: 5,
timeout: Duration::from_secs(15),
rate_limit_rps: None,
}
}
pub fn high_reliability() -> Self {
Self {
retry_attempts: 10,
timeout: Duration::from_secs(60),
rate_limit_rps: None,
}
}
pub fn rate_limited(rps: u32) -> Self {
Self {
retry_attempts: DEFAULT_RETRY_ATTEMPTS,
timeout: Duration::from_secs(DEFAULT_TIMEOUT_SECS),
rate_limit_rps: Some(rps),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ProviderConfigBuilder {
retry_attempts: Option<u32>,
timeout: Option<Duration>,
rate_limit_rps: Option<u32>,
}
impl ProviderConfigBuilder {
pub fn retry_attempts(mut self, attempts: u32) -> Self {
self.retry_attempts = Some(attempts);
self
}
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
pub fn rate_limit_rps(mut self, rps: u32) -> Self {
self.rate_limit_rps = Some(rps);
self
}
pub fn build(self) -> ProviderConfig {
ProviderConfig {
retry_attempts: self.retry_attempts.unwrap_or(DEFAULT_RETRY_ATTEMPTS),
timeout: self
.timeout
.unwrap_or(Duration::from_secs(DEFAULT_TIMEOUT_SECS)),
rate_limit_rps: self.rate_limit_rps,
}
}
}
pub fn calculate_gas_price_with_buffer(
base_fee: U256,
max_priority_fee: U256,
buffer_percent: u64,
) -> (U256, U256) {
let buffered_priority = max_priority_fee * U256::from(100 + buffer_percent) / U256::from(100);
let max_fee = base_fee * U256::from(2) + buffered_priority;
(max_fee, buffered_priority)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_provider_config_default() {
let config = ProviderConfig::default();
assert_eq!(config.retry_attempts, 3);
assert_eq!(config.timeout, Duration::from_secs(30));
assert!(config.rate_limit_rps.is_none());
}
#[test]
fn test_provider_config_builder() {
let config = ProviderConfig::builder()
.retry_attempts(5)
.timeout(Duration::from_secs(60))
.rate_limit_rps(10)
.build();
assert_eq!(config.retry_attempts, 5);
assert_eq!(config.timeout, Duration::from_secs(60));
assert_eq!(config.rate_limit_rps, Some(10));
}
#[test]
fn test_provider_config_fast_transfer() {
let config = ProviderConfig::fast_transfer();
assert_eq!(config.retry_attempts, 5);
assert_eq!(config.timeout, Duration::from_secs(15));
}
#[test]
fn test_provider_config_high_reliability() {
let config = ProviderConfig::high_reliability();
assert_eq!(config.retry_attempts, 10);
assert_eq!(config.timeout, Duration::from_secs(60));
}
#[test]
fn test_provider_config_rate_limited() {
let config = ProviderConfig::rate_limited(5);
assert_eq!(config.rate_limit_rps, Some(5));
}
#[test]
fn test_gas_price_with_buffer() {
let base_fee = U256::from(30_000_000_000u64); let priority_fee = U256::from(2_000_000_000u64);
let (max_fee, max_priority) = calculate_gas_price_with_buffer(base_fee, priority_fee, 20);
assert_eq!(max_priority, U256::from(2_400_000_000u64));
assert_eq!(max_fee, U256::from(62_400_000_000u64));
}
#[test]
fn test_gas_price_with_zero_buffer() {
let base_fee = U256::from(30_000_000_000u64);
let priority_fee = U256::from(2_000_000_000u64);
let (max_fee, max_priority) = calculate_gas_price_with_buffer(base_fee, priority_fee, 0);
assert_eq!(max_priority, priority_fee);
assert_eq!(max_fee, base_fee * U256::from(2) + priority_fee);
}
}