Skip to content

Commit 0c49557

Browse files
Merge pull request #105 from availproject/feat/simulate-fees
feat: Adds retry + usd estimate logic + prevents submission above threshold
2 parents b890681 + bf32efe commit 0c49557

File tree

2 files changed

+135
-35
lines changed

2 files changed

+135
-35
lines changed

script/bin/operator.rs

Lines changed: 131 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ use std::ops::{Div, Mul};
1313
use std::str::FromStr;
1414
use std::time::Duration;
1515
use std::{cmp::min, collections::HashMap};
16+
use tokio::time::sleep;
1617

1718
use anyhow::{Context, Result};
18-
use services::input::{fetch_eth_to_usd_rate, HeaderRangeRequestData, RpcDataFetcher};
19+
use services::input::{fetch_usd_rate, HeaderRangeRequestData, RpcDataFetcher};
1920
use services::Timeout;
2021
use sp1_sdk::network::FulfillmentStrategy;
2122
use sp1_sdk::EnvProver;
@@ -778,6 +779,61 @@ where
778779
}
779780
}
780781

782+
async fn submit_proof(
783+
&self,
784+
chain_id: u64,
785+
tx: N::TransactionRequest,
786+
) -> Result<N::ReceiptResponse> {
787+
let contract = self
788+
.contracts
789+
.get(&chain_id)
790+
.expect("No contract for chain id");
791+
792+
let receipt = contract
793+
.provider()
794+
.send_transaction(tx)
795+
.await?
796+
.with_required_confirmations(NUM_CONFIRMATIONS)
797+
.with_timeout(Some(Duration::from_secs(RELAY_TIMEOUT_SECONDS)))
798+
.get_receipt()
799+
.await?;
800+
801+
if !receipt.status() {
802+
return Err(anyhow::anyhow!("Transaction reverted!"));
803+
}
804+
Ok(receipt)
805+
}
806+
807+
async fn estimate_effective_usd_gas_fee(
808+
&self,
809+
chain_id: u64,
810+
tx: &N::TransactionRequest,
811+
) -> Result<f64> {
812+
let contract = self
813+
.contracts
814+
.get(&chain_id)
815+
.expect("No contract for chain id");
816+
817+
let wei: f64 = Unit::ETHER.wei_const().to::<u64>() as f64;
818+
let gas_estimate: f64 = contract.provider().estimate_gas(tx.clone()).await? as f64;
819+
820+
let max_fee_per_gas = contract
821+
.provider()
822+
.estimate_eip1559_fees()
823+
.await?
824+
.max_fee_per_gas as f64;
825+
let effective_gas_estimate = gas_estimate.mul(max_fee_per_gas).div(wei);
826+
let usd_estimate = convert_to_usd_gas_fee(effective_gas_estimate).await?;
827+
info!(
828+
message = "Gas estimate",
829+
gas_estimate = gas_estimate,
830+
effective_gas_estimate = effective_gas_estimate,
831+
max_fee_per_gas = max_fee_per_gas,
832+
usd_estimate = round_to_decimals(usd_estimate, 2)
833+
);
834+
Ok(round_to_decimals(usd_estimate, 6))
835+
}
836+
781837
/// Relay a transaction to the given chain id.
782838
///
783839
/// NOTE: Assumes the provider has a wallet.
@@ -796,39 +852,52 @@ where
796852
NUM_RELAY_RETRIES,
797853
)
798854
.await
799-
} else {
800-
let contract = self
801-
.contracts
802-
.get(&chain_id)
803-
.expect("No contract for chain id");
804-
805-
let receipt = contract
806-
.provider()
807-
.send_transaction(tx)
808-
.await?
809-
.with_required_confirmations(NUM_CONFIRMATIONS)
810-
.with_timeout(Some(Duration::from_secs(RELAY_TIMEOUT_SECONDS)))
811-
.get_receipt()
812-
.await?;
813-
814-
if !receipt.status() {
815-
return Err(anyhow::anyhow!("Transaction reverted!"));
816-
}
817-
818-
let wei = Unit::ETHER.wei_const().to::<u128>() as f64;
819-
let effective_gas_price: f64 = receipt.effective_gas_price() as f64;
820-
let effective_gas_used = effective_gas_price.mul(receipt.gas_used() as f64).div(wei);
821-
822-
let eth_to_usd_rate = fetch_eth_to_usd_rate().await;
823-
let usd_fee = effective_gas_used.mul(eth_to_usd_rate.from_asset.to_asset);
855+
} else if matches!(chain_id, 1) {
856+
let (max_estimate_retries, retry_sleep_interval, max_usd_fee_threshold) =
857+
get_retry_envs()?;
858+
859+
let mut attempt: u8 = 0;
860+
861+
let tx_hash: B256 = loop {
862+
let effective_gas_estimate = self
863+
.estimate_effective_usd_gas_fee(chain_id, &tx)
864+
.await
865+
.expect("Fail to estimate USD gas fees");
866+
867+
let should_send_now = effective_gas_estimate <= max_usd_fee_threshold
868+
|| attempt == max_estimate_retries;
869+
870+
if should_send_now {
871+
let receipt = self.submit_proof(chain_id, tx).await?;
872+
let wei = Unit::ETHER.wei_const().to::<u128>() as f64;
873+
let effective_gas_price: f64 = receipt.effective_gas_price() as f64;
874+
let effective_gas_used =
875+
effective_gas_price.mul(receipt.gas_used() as f64).div(wei);
876+
877+
let eth_to_usd_rate = fetch_usd_rate().await?;
878+
let usd_fee = effective_gas_used.mul(eth_to_usd_rate.from_asset.to_asset);
879+
880+
info!(
881+
message = "Transaction gas fee used",
882+
gas_fee = effective_gas_used,
883+
usd_fee = usd_fee,
884+
tx_hash = %receipt.transaction_hash()
885+
);
886+
887+
break receipt.transaction_hash();
888+
}
824889

825-
info!(
826-
message = "Transaction gas fee used",
827-
gas_fee = effective_gas_used,
828-
usd_fee = usd_fee,
829-
tx_hash = %receipt.transaction_hash()
830-
);
890+
info!(
891+
message = "USD Gas fee too high!!",
892+
usd_estimate = round_to_decimals(effective_gas_estimate, 2)
893+
);
894+
sleep(Duration::from_secs(retry_sleep_interval)).await;
895+
attempt += 1;
896+
};
831897

898+
return Ok(tx_hash);
899+
} else {
900+
let receipt = self.submit_proof(chain_id, tx).await?;
832901
Ok(receipt.transaction_hash())
833902
}
834903
}
@@ -930,6 +999,36 @@ fn get_block_update_interval() -> u32 {
930999
block_update_interval
9311000
}
9321001

1002+
fn get_retry_envs() -> Result<(u8, u64, f64)> {
1003+
let max_estimate_retries: u8 = env::var("MAX_ESTIMATE_RETRIES")
1004+
.unwrap_or("5".to_string())
1005+
.parse()?;
1006+
1007+
let retry_sleep_interval: u64 = env::var("RETRY_SLEEP_INTERVAL")
1008+
.unwrap_or("60".to_string())
1009+
.parse()?;
1010+
1011+
let max_usd_fee_threshold: f64 = env::var("MAX_USD_FEE_THRESHOLD")
1012+
.unwrap_or("2.00".to_string())
1013+
.parse()?;
1014+
1015+
return Ok((
1016+
max_estimate_retries,
1017+
retry_sleep_interval,
1018+
max_usd_fee_threshold,
1019+
));
1020+
}
1021+
1022+
fn round_to_decimals(value: f64, decimals: u32) -> f64 {
1023+
let factor = 10f64.powi(decimals as i32);
1024+
(value * factor).round() / factor
1025+
}
1026+
1027+
async fn convert_to_usd_gas_fee(gas_fee: f64) -> Result<f64> {
1028+
let eth_to_usd_rate = fetch_usd_rate().await?;
1029+
Ok(gas_fee * eth_to_usd_rate.from_asset.to_asset)
1030+
}
1031+
9331032
#[tokio::main]
9341033
async fn main() -> Result<()> {
9351034
dotenv::dotenv().ok();

services/src/input.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -550,17 +550,18 @@ impl RpcDataFetcher {
550550
}
551551
}
552552

553-
pub async fn fetch_eth_to_usd_rate() -> CoingekoApiResponse {
553+
pub async fn fetch_usd_rate() -> Result<CoingekoApiResponse> {
554+
let from_token: String = env::var("ASSET_TO_USD_CONVERSION").unwrap_or("ethereum".to_string());
554555
let coingeko_url =
555556
env::var("COINGEKO_URL").unwrap_or("https://api.coingecko.com/api".to_string());
556557
let coingeko_api_key = env::var("COINGEKO_API_KEY").expect("Missing COINGEKO_API_KEY env");
557558
let price_endpoint = format!(
558559
"{}/v3/simple/price?ids={}&vs_currencies={}&x_cg_api_key={}",
559-
coingeko_url, "ethereum", "usd", coingeko_api_key
560+
coingeko_url, from_token, "usd", coingeko_api_key
560561
);
561562
let response = reqwest::get(price_endpoint).await.unwrap();
562563

563-
response.json::<CoingekoApiResponse>().await.unwrap()
564+
Ok(response.json::<CoingekoApiResponse>().await?)
564565
}
565566

566567
/// Converts GrandpaJustification and validator set to CircuitJustification.

0 commit comments

Comments
 (0)