oxidized-builder 0.1.0-delta

Oxidized Builder - Ethereum block and transactions framework
Documentation
use crate::common::error::AppError;
use crate::network::provider::HttpProvider;
use alloy::providers::Provider;
use alloy::rpc::types::eth::simulate::{SimBlock, SimulatePayload};
use alloy::rpc::types::eth::state::StateOverride;
use alloy::rpc::types::eth::Transaction;
use alloy::rpc::types::eth::TransactionRequest;

#[derive(Debug, Clone)]
pub struct SimulationOutcome {
    pub success: bool,
    pub gas_used: u64,
    pub return_data: Vec<u8>,
}

#[derive(Clone)]
pub struct Simulator {
    provider: HttpProvider,
}

impl Simulator {
    pub fn new(provider: HttpProvider) -> Self {
        Self { provider }
    }

    pub async fn simulate_transaction(
        &self,
        tx: &Transaction,
    ) -> Result<SimulationOutcome, AppError> {
        let req = tx.clone().into_request();
        self.simulate_request(req, None).await
    }

    pub async fn simulate_request(
        &self,
        req: TransactionRequest,
        state_override: Option<StateOverride>,
    ) -> Result<SimulationOutcome, AppError> {
        if state_override.is_some() {
            let block = SimBlock {
                block_overrides: None,
                state_overrides: state_override.clone(),
                calls: vec![req.clone()],
            };
            let payload = SimulatePayload {
                block_state_calls: vec![block],
                trace_transfers: false,
                validation: false,
                return_full_transactions: false,
            };

            if let Ok(simulated) = self.provider.simulate(&payload).await {
                if let Some(call) = simulated.first().and_then(|b| b.calls.first()) {
                    return Ok(SimulationOutcome {
                        success: call.error.is_none() && call.status,
                        gas_used: call.gas_used,
                        return_data: call.return_data.to_vec(),
                    });
                }
            }
        }

        let gas_used = match self.provider.estimate_gas(req.clone()).await {
            Ok(g) => g,
            Err(e) => {
                return Ok(SimulationOutcome {
                    success: false,
                    gas_used: 0,
                    return_data: format!("estimate_gas failed: {e}").into_bytes(),
                })
            }
        };

        let call_res = self.provider.call(req).await;

        let (success, return_data) = match call_res {
            Ok(bytes) => (true, bytes.to_vec()),
            Err(_) => (false, Vec::new()),
        };

        Ok(SimulationOutcome {
            success,
            gas_used,
            return_data,
        })
    }

    pub async fn simulate_bundle(
        &self,
        txs: &[Transaction],
        state_override: Option<StateOverride>,
    ) -> Result<Vec<SimulationOutcome>, AppError> {
        let reqs: Vec<TransactionRequest> = txs.iter().cloned().map(|t| t.into_request()).collect();
        self.simulate_bundle_requests(&reqs, state_override).await
    }

    pub async fn simulate_bundle_requests(
        &self,
        txs: &[TransactionRequest],
        state_override: Option<StateOverride>,
    ) -> Result<Vec<SimulationOutcome>, AppError> {
        if txs.is_empty() {
            return Ok(Vec::new());
        }

        let calls = txs.to_vec();
        let block = SimBlock {
            block_overrides: None,
            state_overrides: state_override.clone(),
            calls,
        };
        let payload = SimulatePayload {
            block_state_calls: vec![block],
            trace_transfers: false,
            validation: false,
            return_full_transactions: false,
        };

        match self.provider.simulate(&payload).await {
            Ok(blocks) => {
                let mut out = Vec::new();
                for blk in blocks {
                    for tx in blk.calls {
                        let success = tx.error.is_none();
                        let gas_used = tx.gas_used;
                        let return_data = tx.return_data.to_vec();
                        out.push(SimulationOutcome {
                            success,
                            gas_used,
                            return_data,
                        });
                    }
                }
                return Ok(out);
            }
            Err(_) => {
                let mut out = Vec::new();
                for tx in txs {
                    out.push(
                        self.simulate_request(tx.clone(), state_override.clone())
                            .await?,
                    );
                }
                Ok(out)
            }
        }
    }
}