Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
158 changes: 128 additions & 30 deletions script/bin/operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ use std::ops::{Div, Mul};
use std::str::FromStr;
use std::time::Duration;
use std::{cmp::min, collections::HashMap};
use tokio::time::sleep;

use anyhow::{Context, Result};
use services::input::{fetch_eth_to_usd_rate, HeaderRangeRequestData, RpcDataFetcher};
use services::input::{fetch_usd_rate, HeaderRangeRequestData, RpcDataFetcher};
use services::Timeout;
use sp1_sdk::network::FulfillmentStrategy;
use sp1_sdk::EnvProver;
Expand Down Expand Up @@ -778,6 +779,62 @@ where
}
}

async fn submit_proof(
&self,
chain_id: u64,
tx: N::TransactionRequest,
) -> Result<N::ReceiptResponse> {
let contract = self
.contracts
.get(&chain_id)
.expect("No contract for chain id");

let receipt = contract
.provider()
.send_transaction(tx)
.await?
.with_required_confirmations(NUM_CONFIRMATIONS)
.with_timeout(Some(Duration::from_secs(RELAY_TIMEOUT_SECONDS)))
.get_receipt()
.await?;

if !receipt.status() {
return Err(anyhow::anyhow!("Transaction reverted!"));
}
Ok(receipt)
}

async fn estimate_effective_usd_gas_fee(
&self,
chain_id: u64,
tx: &N::TransactionRequest,
) -> f64 {
let contract = self
.contracts
.get(&chain_id)
.expect("No contract for chain id");

let wei = Unit::ETHER.wei_const().to::<u64>() as f64;
let gas_estimate = contract.provider().estimate_gas(tx.clone()).await.unwrap() as f64;

let max_fee_per_gas = contract
.provider()
.estimate_eip1559_fees()
.await
.unwrap()
.max_fee_per_gas as f64;
let effective_gas_estimate = gas_estimate.mul(max_fee_per_gas).div(wei);
let usd_estimate = convert_to_usd_gas_fee(effective_gas_estimate).await;
info!(
message = "Gas estimate",
gas_estimate = gas_estimate,
effective_gas_estimate = effective_gas_estimate,
max_fee_per_gas = max_fee_per_gas,
usd_estimate = round_to_decimals(usd_estimate, 2)
);
round_to_decimals(usd_estimate, 6)
}

/// Relay a transaction to the given chain id.
///
/// NOTE: Assumes the provider has a wallet.
Expand All @@ -796,39 +853,50 @@ where
NUM_RELAY_RETRIES,
)
.await
} else {
let contract = self
.contracts
.get(&chain_id)
.expect("No contract for chain id");

let receipt = contract
.provider()
.send_transaction(tx)
.await?
.with_required_confirmations(NUM_CONFIRMATIONS)
.with_timeout(Some(Duration::from_secs(RELAY_TIMEOUT_SECONDS)))
.get_receipt()
.await?;

if !receipt.status() {
return Err(anyhow::anyhow!("Transaction reverted!"));
}
} else if matches!(chain_id, 1) {
let (max_estimate_retries, retry_sleep_interval, max_usd_fee_threshold) =
get_retry_envs()?;

let wei = Unit::ETHER.wei_const().to::<u128>() as f64;
let effective_gas_price: f64 = receipt.effective_gas_price() as f64;
let effective_gas_used = effective_gas_price.mul(receipt.gas_used() as f64).div(wei);
let mut attempt: u8 = 0;

let eth_to_usd_rate = fetch_eth_to_usd_rate().await;
let usd_fee = effective_gas_used.mul(eth_to_usd_rate.from_asset.to_asset);
let tx_hash: B256 = loop {
let effective_gas_estimate =
self.estimate_effective_usd_gas_fee(chain_id, &tx).await;

info!(
message = "Transaction gas fee used",
gas_fee = effective_gas_used,
usd_fee = usd_fee,
tx_hash = %receipt.transaction_hash()
);
let should_send_now = effective_gas_estimate <= max_usd_fee_threshold
|| attempt == max_estimate_retries;

if should_send_now {
let receipt = self.submit_proof(chain_id, tx).await?;
let wei = Unit::ETHER.wei_const().to::<u128>() as f64;
let effective_gas_price: f64 = receipt.effective_gas_price() as f64;
let effective_gas_used =
effective_gas_price.mul(receipt.gas_used() as f64).div(wei);

let eth_to_usd_rate = fetch_usd_rate().await;
let usd_fee = effective_gas_used.mul(eth_to_usd_rate.from_asset.to_asset);

info!(
message = "Transaction gas fee used",
gas_fee = effective_gas_used,
usd_fee = usd_fee,
tx_hash = %receipt.transaction_hash()
);

break receipt.transaction_hash();
}

info!(
message = "USD Gas fee too high!!",
usd_estimate = round_to_decimals(effective_gas_estimate, 2)
);
sleep(Duration::from_secs(retry_sleep_interval)).await;
attempt += 1;
};

return Ok(tx_hash);
} else {
let receipt = self.submit_proof(chain_id, tx).await?;
Ok(receipt.transaction_hash())
}
}
Expand Down Expand Up @@ -930,6 +998,36 @@ fn get_block_update_interval() -> u32 {
block_update_interval
}

fn get_retry_envs() -> Result<(u8, u64, f64)> {
let max_estimate_retries: u8 = env::var("MAX_ESTIMATE_RETRIES")
.unwrap_or("5".to_string())
.parse()?;

let retry_sleep_interval: u64 = env::var("RETRY_SLEEP_INTERVAL")
.unwrap_or("60".to_string())
.parse()?;

let max_usd_fee_threshold: f64 = env::var("MAX_USD_FEE_THRESHOLD")
.unwrap_or("2.00".to_string())
.parse()?;

return Ok((
max_estimate_retries,
retry_sleep_interval,
max_usd_fee_threshold,
));
}

fn round_to_decimals(value: f64, decimals: u32) -> f64 {
let factor = 10f64.powi(decimals as i32);
(value * factor).round() / factor
}

async fn convert_to_usd_gas_fee(gas_fee: f64) -> f64 {
let eth_to_usd_rate = fetch_usd_rate().await;
gas_fee * eth_to_usd_rate.from_asset.to_asset
}

#[tokio::main]
async fn main() -> Result<()> {
dotenv::dotenv().ok();
Expand Down
5 changes: 3 additions & 2 deletions services/src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -550,13 +550,14 @@ impl RpcDataFetcher {
}
}

pub async fn fetch_eth_to_usd_rate() -> CoingekoApiResponse {
pub async fn fetch_usd_rate() -> CoingekoApiResponse {
let from_token: String = env::var("ASSET_TO_USD_CONVERSION").unwrap_or("ethereum".to_string());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still fetches only the eth usd rate.
Can it be ignored for other chains?

let coingeko_url =
env::var("COINGEKO_URL").unwrap_or("https://api.coingecko.com/api".to_string());
let coingeko_api_key = env::var("COINGEKO_API_KEY").expect("Missing COINGEKO_API_KEY env");
let price_endpoint = format!(
"{}/v3/simple/price?ids={}&vs_currencies={}&x_cg_api_key={}",
coingeko_url, "ethereum", "usd", coingeko_api_key
coingeko_url, from_token, "usd", coingeko_api_key
);
let response = reqwest::get(price_endpoint).await.unwrap();

Expand Down
Loading