Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
112 changes: 37 additions & 75 deletions crates/bundle/src/call/driver.rs
Original file line number Diff line number Diff line change
@@ -1,64 +1,35 @@
use crate::{SignetCallBundle, SignetCallBundleResponse};
use crate::{SignetBundleError, SignetCallBundle, SignetCallBundleResponse};
use alloy::{consensus::TxEnvelope, primitives::U256};
use signet_evm::OrderDetector;
use signet_types::{MarketContext, MarketError};
use std::fmt::Debug;
use tracing::{debug_span, instrument, Level};
use trevm::{
revm::{primitives::EVMError, Database, DatabaseCommit},
revm::{Database, DatabaseCommit},
trevm_bail, trevm_ensure, unwrap_or_trevm_err, BundleDriver, BundleError,
};

/// Errors that can occur when running a bundle on the Signet EVM.
#[derive(thiserror::Error)]
pub enum SignetBundleError<Db: Database> {
/// A primitive [`BundleError`] error ocurred.
#[error(transparent)]
BundleError(#[from] BundleError<Db>),
/// A [`MarketError`] ocurred.
#[error(transparent)]
MarketError(#[from] MarketError),
}

impl<Db: Database> Debug for SignetBundleError<Db> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SignetBundleError::BundleError(e) => write!(f, "BundleError({:?})", e),
SignetBundleError::MarketError(e) => write!(f, "MarketError({:?})", e),
}
}
}

impl<Db: Database> From<EVMError<Db::Error>> for SignetBundleError<Db> {
fn from(e: EVMError<Db::Error>) -> Self {
SignetBundleError::BundleError(BundleError::EVMError { inner: e })
}
}

impl<Db: Database> SignetBundleError<Db> {
/// Instantiate a new [`SignetBundleError`] from a [`Database::Error`].
pub const fn evm_db(e: Db::Error) -> Self {
SignetBundleError::BundleError(BundleError::EVMError { inner: EVMError::Database(e) })
}
}

/// A bundle driver for the Signet EVM.
///
/// This type allows for the simulation of a [`SignetCallBundle`] and ensuring
/// that it conforms to market rules as a unit.
#[derive(Debug)]
pub struct SignetBundleDriver<'a> {
/// The bundle to drive.
bundle: &'a SignetCallBundle,
/// The accumulated results of the bundle, if applicable.
response: SignetCallBundleResponse,
/// The market context.
context: MarketContext,
/// The host chain id.
host_chain_id: u64,
}

impl<'a> From<&'a SignetCallBundle> for SignetBundleDriver<'a> {
fn from(bundle: &'a SignetCallBundle) -> Self {
Self::new(bundle)
}
}

impl<'a> SignetBundleDriver<'a> {
/// Create a new bundle driver with the given bundle and response.
pub fn new(bundle: &'a SignetCallBundle, host_chain_id: u64) -> Self {
let context = bundle.make_context(host_chain_id);
Self { bundle, response: Default::default(), context, host_chain_id }
pub fn new(bundle: &'a SignetCallBundle) -> Self {
Self { bundle, response: Default::default() }
}
}

Expand All @@ -73,50 +44,31 @@ impl SignetBundleDriver<'_> {
&self.response
}

/// Get a reference to the market context.
pub const fn context(&self) -> &MarketContext {
&self.context
}

/// Take the response from the bundle driver. This consumes
pub fn into_response(self) -> SignetCallBundleResponse {
self.response
}

/// Clear the driver, resetting the response and the market context. This
/// reset the driver, allowing for resimulation of the same bundle.
/// resets the driver, allowing for re-simulation of the same bundle.
///
/// The returned context contains the amount of overfill, i.e. the amount
/// that was filled, but not required by the orders in the bundle.
pub fn clear(&mut self) -> (SignetCallBundleResponse, MarketContext) {
let r = std::mem::take(&mut self.response);
let context = self.bundle.make_context(self.host_chain_id);
let c = std::mem::replace(&mut self.context, context);
(r, c)
pub fn clear(&mut self) -> SignetCallBundleResponse {
std::mem::take(&mut self.response)
}

/// Check the market context, accept the result, accumulate the transaction
/// details into the response.
fn check_market_and_accept<'a, Db: Database + DatabaseCommit, I>(
fn accept_and_accumulate<'a, Db: Database + DatabaseCommit, I>(
&mut self,
mut trevm: signet_evm::EvmTransacted<'a, Db, I>,
trevm: signet_evm::EvmTransacted<'a, Db, I>,
tx: &TxEnvelope,
pre_sim_coinbase_balance: &mut U256,
basefee: U256,
) -> signet_evm::DriveBundleResult<'a, Db, Self, I> {
let coinbase = trevm.inner().block().coinbase;

// Taking these clears the context for reuse.
let (aggregate, market_context) =
trevm.inner_mut_unchecked().context.external.take_aggregate();

// We check the market context here, and if it fails, we discard the
// transaction outcome and push a failure receipt.
if let Err(err) = self.context.checked_remove_ru_tx_events(&aggregate, &market_context) {
tracing::debug!(%err, "Discarding transaction outcome due to market error");
return Err(trevm.errored(SignetBundleError::MarketError(err)));
}

let (execution_result, mut trevm) = trevm.accept();

// Get the post simulation coinbase balance
Expand Down Expand Up @@ -147,6 +99,7 @@ impl SignetBundleDriver<'_> {
impl<I> BundleDriver<OrderDetector<I>> for SignetBundleDriver<'_> {
type Error<Db: Database + DatabaseCommit> = SignetBundleError<Db>;

#[instrument(skip_all, level = Level::DEBUG)]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

should we maybe add a custom target here as well?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

it gets the fn name as the target, which seems fine to me

fn run_bundle<'a, Db: Database + DatabaseCommit>(
&mut self,
trevm: signet_evm::EvmNeedsTx<'a, Db, I>,
Expand All @@ -159,10 +112,12 @@ impl<I> BundleDriver<OrderDetector<I>> for SignetBundleDriver<'_> {

// Check if the block we're in is valid for this bundle. Both must match
trevm_ensure!(
trevm.inner().block().number.to::<u64>() == bundle.block_number,
trevm.block_number().to::<u64>() == bundle.block_number,
trevm,
BundleError::BlockNumberMismatch.into()
);
// Set the state block number this simulation was based on
self.response.state_block_number = trevm.block_number().to::<u64>();

// Check if the state block number is valid (not 0, and not a tag)
trevm_ensure!(
Expand All @@ -177,11 +132,10 @@ impl<I> BundleDriver<OrderDetector<I>> for SignetBundleDriver<'_> {

trevm.try_with_block(self.bundle, |mut trevm| {
// Get the coinbase and basefee from the block
let coinbase = trevm.inner().block().coinbase;
let basefee = trevm.inner().block().basefee;

// Set the state block number this simulation was based on
self.response.state_block_number = trevm.inner().block().number.to::<u64>();
// NB: Do not move these outside the `try_with_block` closure, as
// they may be rewritten by the bundle
let coinbase = trevm.block().coinbase;
let basefee = trevm.block().basefee;

// Cache the pre simulation coinbase balance, so we can use it to calculate the coinbase diff after every tx simulated.
let initial_coinbase_balance = unwrap_or_trevm_err!(
Expand All @@ -192,19 +146,22 @@ impl<I> BundleDriver<OrderDetector<I>> for SignetBundleDriver<'_> {
// Stack vars to keep track of the coinbase balance across txs.
let mut pre_sim_coinbase_balance = initial_coinbase_balance;

for tx in txs.iter() {
let span = debug_span!("bundle loop", count = txs.len()).entered();
for (idx, tx) in txs.iter().enumerate() {
let _span = debug_span!("tx loop", tx = %tx.tx_hash(), idx).entered();
let run_result = trevm.run_tx(tx);

let transacted_trevm = run_result.map_err(|e| e.map_err(Into::into))?;

// Set the trevm instance to the committed one
trevm = self.check_market_and_accept(
trevm = self.accept_and_accumulate(
transacted_trevm,
tx,
&mut pre_sim_coinbase_balance,
basefee,
)?;
}
drop(span);

// Accumulate the total results
self.response.coinbase_diff =
Expand All @@ -218,6 +175,11 @@ impl<I> BundleDriver<OrderDetector<I>> for SignetBundleDriver<'_> {
.unwrap_or_default();
self.response.bundle_hash = self.bundle.bundle_hash();

// Taking these clears the context for reuse.
let (orders, fills) = trevm.inner_mut_unchecked().context.external.take_aggregate();
self.response.orders = orders;
self.response.fills = fills;

// return the final state
Ok(trevm)
})
Expand Down
39 changes: 39 additions & 0 deletions crates/bundle/src/call/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use signet_types::MarketError;
use std::fmt::Debug;
use trevm::{
revm::{primitives::EVMError, Database},
BundleError,
};

/// Errors that can occur when running a bundle on the Signet EVM.
#[derive(thiserror::Error)]
pub enum SignetBundleError<Db: Database> {
/// A primitive [`BundleError`] error ocurred.
#[error(transparent)]
BundleError(#[from] BundleError<Db>),
/// A [`MarketError`] ocurred.
#[error(transparent)]
MarketError(#[from] MarketError),
}

impl<Db: Database> Debug for SignetBundleError<Db> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SignetBundleError::BundleError(e) => write!(f, "BundleError({:?})", e),
SignetBundleError::MarketError(e) => write!(f, "MarketError({:?})", e),
}
}
}

impl<Db: Database> From<EVMError<Db::Error>> for SignetBundleError<Db> {
fn from(e: EVMError<Db::Error>) -> Self {
SignetBundleError::BundleError(BundleError::EVMError { inner: e })
}
}

impl<Db: Database> SignetBundleError<Db> {
/// Instantiate a new [`SignetBundleError`] from a [`Database::Error`].
pub const fn evm_db(e: Db::Error) -> Self {
SignetBundleError::BundleError(BundleError::EVMError { inner: EVMError::Database(e) })
}
}
5 changes: 4 additions & 1 deletion crates/bundle/src/call/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
mod driver;
pub use driver::{SignetBundleDriver, SignetBundleError};
pub use driver::SignetBundleDriver;

mod error;
pub use error::SignetBundleError;

mod trevm;

Expand Down
24 changes: 17 additions & 7 deletions crates/bundle/src/call/trevm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,22 @@ use trevm::{revm::primitives::BlockEnv, Block};

impl Block for SignetCallBundle {
fn fill_block_env(&self, block_env: &mut BlockEnv) {
block_env.number =
self.bundle.state_block_number.as_number().map(U256::from).unwrap_or(block_env.number);
block_env.timestamp = self.bundle.timestamp.map(U256::from).unwrap_or(block_env.timestamp);
block_env.gas_limit = self.bundle.gas_limit.map(U256::from).unwrap_or(block_env.gas_limit);
block_env.difficulty =
self.bundle.difficulty.map(U256::from).unwrap_or(block_env.difficulty);
block_env.basefee = self.bundle.base_fee.map(U256::from).unwrap_or(block_env.basefee);
let BlockEnv {
number,
coinbase,
timestamp,
gas_limit,
basefee,
difficulty,
prevrandao: _,
blob_excess_gas_and_price: _,
} = block_env;

*number = self.bundle.state_block_number.as_number().map(U256::from).unwrap_or(*number);
*coinbase = self.bundle.coinbase.unwrap_or(*coinbase);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

bugfix is here, coinbase was previously not set

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

great catch

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

ya it's why i recommend destructuring in the implementation guide. easy to forget things

*timestamp = self.bundle.timestamp.map(U256::from).unwrap_or(*timestamp);
*gas_limit = self.bundle.gas_limit.map(U256::from).unwrap_or(*gas_limit);
*difficulty = self.bundle.difficulty.map(U256::from).unwrap_or(*difficulty);
*basefee = self.bundle.base_fee.map(U256::from).unwrap_or(*basefee);
}
}
Loading