diff --git a/crates/bundle/src/call/driver.rs b/crates/bundle/src/call/driver.rs index ef485ce7..da992d45 100644 --- a/crates/bundle/src/call/driver.rs +++ b/crates/bundle/src/call/driver.rs @@ -184,7 +184,7 @@ where self.response.bundle_hash = self.bundle.bundle_hash(); // Taking these clears the order detector - let (orders, fills) = + let (fills, orders) = trevm.inner_mut_unchecked().inspector.as_mut_detector().take_aggregates(); self.response.orders = orders; self.response.fills = fills; diff --git a/crates/bundle/src/send/driver.rs b/crates/bundle/src/send/driver.rs index c4d1008b..f220c30f 100644 --- a/crates/bundle/src/send/driver.rs +++ b/crates/bundle/src/send/driver.rs @@ -1,9 +1,8 @@ use crate::send::SignetEthBundle; -use alloy::primitives::U256; -use signet_evm::{ - DriveBundleResult, EvmErrored, EvmNeedsTx, EvmTransacted, SignetInspector, SignetLayered, -}; -use signet_types::{AggregateFills, MarketError, SignedPermitError}; +use alloy::{hex, primitives::U256}; +use signet_evm::{DriveBundleResult, EvmErrored, EvmNeedsTx, SignetInspector, SignetLayered}; +use signet_types::{AggregateFills, AggregateOrders, MarketError, SignedPermitError}; +use std::borrow::Cow; use tracing::{debug, error}; use trevm::{ helpers::Ctx, @@ -18,85 +17,170 @@ use trevm::{ /// [`SignetEthBundleDriver`]. pub type SignetEthBundleInsp = Layered; +/// The output of the [`SignetEthBundleDriver`]. +#[derive(Debug)] +pub struct DriverOutput +where + Db: Database, + Insp: Inspector>, +{ + /// The host evm used to run the bundle. + pub host_evm: Option>, + + /// Total gas used by this bundle during execution. + pub total_gas_used: u64, + + /// Total host gas used by this bundle during execution. + pub total_host_gas_used: u64, + + /// Beneficiary balance increase during execution. + pub beneficiary_balance_increase: U256, + + /// Running aggregate of fills during execution. + pub bundle_fills: AggregateFills, + + /// Running aggregate of orders during execution. + pub bundle_orders: AggregateOrders, +} + +impl DriverOutput +where + Db: Database, + Insp: Inspector>, +{ + /// Increase the total gas used by the given amount. + pub const fn use_gas(&mut self, gas: u64) { + self.total_gas_used = self.total_gas_used.saturating_add(gas); + } + + /// Increase the total host gas used by the given amount. + pub const fn use_host_gas(&mut self, gas: u64) { + self.total_host_gas_used = self.total_host_gas_used.saturating_add(gas); + } + + /// Absorb fills and orders into the running totals. + pub fn absorb(&mut self, fills: &AggregateFills, orders: &AggregateOrders) { + self.bundle_fills.absorb(fills); + self.bundle_orders.absorb(orders); + } + + /// Record an increase in the beneficiary balance. + pub const fn record_beneficiary_increase(&mut self, increase: U256) { + self.beneficiary_balance_increase = + self.beneficiary_balance_increase.saturating_add(increase); + } +} + /// Errors while running a [`SignetEthBundle`] on the EVM. #[derive(thiserror::Error)] pub enum SignetEthBundleError { /// Bundle error. #[error(transparent)] - BundleError(#[from] BundleError), + Bundle(#[from] BundleError), /// SignedPermitError. #[error(transparent)] - SignedPermitError(#[from] SignedPermitError), + SignetPermit(#[from] SignedPermitError), /// Contract error. #[error(transparent)] - ContractError(#[from] alloy::contract::Error), + Contract(#[from] alloy::contract::Error), + + /// Market error. + #[error(transparent)] + Market(#[from] MarketError), + + /// Host simulation error. + #[error("{0}")] + HostSimulation(&'static str), } impl core::fmt::Debug for SignetEthBundleError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - SignetEthBundleError::BundleError(bundle_error) => { + SignetEthBundleError::Bundle(bundle_error) => { f.debug_tuple("BundleError").field(bundle_error).finish() } - SignetEthBundleError::SignedPermitError(signed_order_error) => { + SignetEthBundleError::SignetPermit(signed_order_error) => { f.debug_tuple("SignedPermitError").field(signed_order_error).finish() } - SignetEthBundleError::ContractError(contract_error) => { + SignetEthBundleError::Contract(contract_error) => { f.debug_tuple("ContractError").field(contract_error).finish() } + SignetEthBundleError::Market(market_error) => { + f.debug_tuple("MarketError").field(market_error).finish() + } + SignetEthBundleError::HostSimulation(msg) => { + f.debug_tuple("HostSimulationError").field(msg).finish() + } } } } impl From> for SignetEthBundleError { fn from(err: EVMError) -> Self { - Self::BundleError(BundleError::from(err)) + Self::Bundle(BundleError::from(err)) } } /// Driver for applying a Signet Ethereum bundle to an EVM. -#[derive(Debug, Clone)] -pub struct SignetEthBundleDriver<'a> { +#[derive(Debug)] +pub struct SignetEthBundleDriver<'a, 'b, Db, Insp> +where + Db: Database, + Insp: Inspector>, +{ /// The bundle to apply. bundle: &'a SignetEthBundle, + /// Reference to the fill state to check against. + pub fill_state: Cow<'b, AggregateFills>, + /// Execution deadline for this bundle. This limits the total WALLCLOCK /// time spent simulating the bundle. deadline: std::time::Instant, - /// Aggregate fills derived from the bundle's host fills. - agg_fills: AggregateFills, - - /// Total gas used by this bundle during execution, an output of the driver. - total_gas_used: u64, - /// Beneficiary balance increase during execution, an output of the driver. - beneficiary_balance_increase: U256, + // -- Accumulated outputs below here-- + output: DriverOutput, } -impl<'a> SignetEthBundleDriver<'a> { +impl<'a, 'b, Db, Insp> SignetEthBundleDriver<'a, 'b, Db, Insp> +where + Db: Database, + Insp: Inspector>, +{ /// Creates a new [`SignetEthBundleDriver`] with the given bundle and /// response. - pub fn new(bundle: &'a SignetEthBundle, deadline: std::time::Instant) -> Self { - Self::new_with_agg_fills(bundle, deadline, Default::default()) + pub fn new( + bundle: &'a SignetEthBundle, + host_evm: signet_evm::EvmNeedsTx, + deadline: std::time::Instant, + ) -> Self { + Self::new_with_fill_state(bundle, host_evm, deadline, Default::default()) } /// Creates a new [`SignetEthBundleDriver`] with the given bundle, /// response, and aggregate fills. /// /// This is useful for testing, and for combined host-rollup simulation. - pub const fn new_with_agg_fills( + pub fn new_with_fill_state( bundle: &'a SignetEthBundle, + host_evm: signet_evm::EvmNeedsTx, deadline: std::time::Instant, - agg_fills: AggregateFills, + fill_state: Cow<'b, AggregateFills>, ) -> Self { Self { bundle, + fill_state, deadline, - agg_fills, - total_gas_used: 0, - beneficiary_balance_increase: U256::ZERO, + output: DriverOutput { + host_evm: Some(host_evm), + total_gas_used: 0, + total_host_gas_used: 0, + beneficiary_balance_increase: U256::ZERO, + bundle_fills: AggregateFills::default(), + bundle_orders: AggregateOrders::default(), + }, } } @@ -112,77 +196,36 @@ impl<'a> SignetEthBundleDriver<'a> { /// Get the total gas used by this driver during tx execution. pub const fn total_gas_used(&self) -> u64 { - self.total_gas_used + self.output.total_gas_used } /// Get the beneficiary balance increase for this driver during tx execution. pub const fn beneficiary_balance_increase(&self) -> U256 { - self.beneficiary_balance_increase + self.output.beneficiary_balance_increase } - /// Get the aggregate fills for this driver. - /// - /// This may be used to check that the bundle does not overfill, by - /// inspecting the agg fills after execution. - pub const fn agg_fills(&self) -> &AggregateFills { - &self.agg_fills - } - - /// Get a mutable reference to the aggregate fills for this driver. - /// - /// Accessing this is not recommended outside of testing or advanced - /// usage. - pub const fn agg_fills_mut(&mut self) -> &mut AggregateFills { - &mut self.agg_fills - } - - /// Check the [`AggregateFills`], discard if invalid, otherwise accumulate - /// payable gas and call [`Self::accept_tx`]. - /// - /// This path is used by - /// - [`TransactionSigned`] objects - /// - [`Transactor::Transact`] events - pub(crate) fn check_fills( - &mut self, - trevm: &mut EvmTransacted, - ) -> Result<(), MarketError> - where - Db: Database + DatabaseCommit, - Insp: Inspector>, - { - // Taking these clears the context for reuse. - let (agg_orders, agg_fills) = - trevm.inner_mut_unchecked().inspector.as_mut_detector().take_aggregates(); - - // We check the AggregateFills here, and if it fails, we discard the - // transaction outcome and push a failure receipt. - self.agg_fills.checked_remove_ru_tx_events(&agg_orders, &agg_fills) + /// Take the aggregate orders and fills from this driver. + pub fn into_outputs(self) -> DriverOutput { + self.output } } -impl BundleDriver>> - for SignetEthBundleDriver<'_> +impl BundleDriver>> + for SignetEthBundleDriver<'_, '_, HostDb, HostInsp> where - Db: Database + DatabaseCommit, - Insp: Inspector>, + RuDb: Database + DatabaseCommit, + RuInsp: Inspector>, + HostDb: Database + DatabaseCommit, + HostInsp: Inspector>, { - type Error = SignetEthBundleError; + type Error = SignetEthBundleError; fn run_bundle( &mut self, - mut trevm: EvmNeedsTx>, - ) -> DriveBundleResult> { + mut trevm: EvmNeedsTx>, + ) -> DriveBundleResult> { let bundle = &self.bundle.bundle; - - // Reset the total gas used and beneficiary balance increase - // to 0 before running the bundle. - self.total_gas_used = 0; - self.beneficiary_balance_increase = U256::ZERO; - - // Get the beneficiary address and its initial balance - let beneficiary = trevm.beneficiary(); - let inital_beneficiary_balance = - trevm_try!(trevm.try_read_balance(beneficiary).map_err(EVMError::Database), trevm); + // -- STATELESS CHECKS -- // Ensure that the bundle has transactions trevm_ensure!(!bundle.txs.is_empty(), trevm, BundleError::BundleEmpty.into()); @@ -203,8 +246,65 @@ where ); // Decode and validate the transactions in the bundle + let host_txs = trevm_try!(self.bundle.decode_and_validate_host_txs(), trevm); let txs = trevm_try!(self.bundle.decode_and_validate_txs(), trevm); + // -- STATEFUL ACTIONS -- + + // Get the beneficiary address and its initial balance + let beneficiary = trevm.beneficiary(); + let inital_beneficiary_balance = + trevm_try!(trevm.try_read_balance(beneficiary).map_err(EVMError::Database), trevm); + + // -- HOST PORTION -- + + // We simply run all host transactions first, accumulating their state + // changes into the host_evm's state. If any reverts, we error out the + // simulation. + for tx in host_txs.into_iter() { + self.output.host_evm = Some(trevm_try!( + self.output + .host_evm + .take() + .expect("host_evm missing") + .run_tx(&tx) + .and_then(|mut htrevm| { + let result = htrevm.result(); + if let Some(output) = result.output() { + if !result.is_success() { + debug!(output = hex::encode(output), "host transaction reverted"); + } + } + + trevm_ensure!( + result.is_success(), + htrevm, + EVMError::Custom("host transaction reverted".to_string()) + ); + + // Accumulate gas used + self.output.use_host_gas(result.gas_used()); + + // The host fills go in the bundle fills. + let host_fills = htrevm + .inner_mut_unchecked() + .inspector + .as_mut_detector() + .take_aggregates() + .0; + self.output.bundle_fills.absorb(&host_fills); + + Ok(htrevm.accept_state()) + }) + .map_err(|err| { + error!(err = %err.error(), err_dbg = ?err.error(), "error while running host transaction"); + SignetEthBundleError::HostSimulation("host simulation error") + }), + trevm + )); + } + + // -- ROLLUP PORTION -- for tx in txs.into_iter() { let _span = tracing::debug_span!("bundle_tx_loop", tx_hash = %tx.hash()).entered(); @@ -217,33 +317,50 @@ where // Temporary rebinding of trevm within each loop iteration. // The type of t is `EvmTransacted`, while the type of trevm is // `EvmNeedsTx`. - let mut t = trevm - .run_tx(&tx) - .map_err(EvmErrored::err_into) - .inspect_err(|err| error!(err = %err.error(), "error while running transaction"))?; + let mut t = trevm.run_tx(&tx).map_err(EvmErrored::err_into).inspect_err( + |err| error!(err = %err.error(), "error while running rollup transaction"), + )?; // Check the result of the transaction. let result = t.result(); - let gas_used = result.gas_used(); // EVM Execution succeeded. - // We now check if the orders are valid with the bundle's fills. If - // not, and the tx is not marked as revertible by the bundle, we - // error our simulation. + // We now check if the orders are valid with the bundle's fills + // state. If not, and the tx is not marked as revertible by the + // bundle, we error our simulation. if result.is_success() { - if self.check_fills(&mut t).is_err() { - debug!("transaction dropped due to insufficient fills"); + let (tx_fills, tx_orders) = + t.inner_mut_unchecked().inspector.as_mut_detector().take_aggregates(); + + // These clones are inefficient. We can optimize later if + // needed. + let mut candidate_fills = self.output.bundle_fills.clone(); + let mut candidate_orders = self.output.bundle_orders.clone(); + + // The candidate is the updated + candidate_fills.absorb(&tx_fills); + candidate_orders.absorb(&tx_orders); + + // Then we check that the fills are sufficient against the + // provided fill state. This does nothing on error. + if self.fill_state.check_ru_tx_events(&candidate_fills, &candidate_orders).is_err() + { if self.bundle.reverting_tx_hashes().contains(tx_hash) { + debug!("transaction marked as revertible, reverting"); trevm = t.reject(); continue; } else { + debug!("transaction dropped due to insufficient fills, not marked as revertible"); return Err(t.errored(BundleError::BundleReverted.into())); } } - self.total_gas_used = self.total_gas_used.saturating_add(gas_used); + // Now we accept the fills and order candidates + self.output.bundle_fills = candidate_fills; + self.output.bundle_orders = candidate_orders; } else { + // EVM Execution did not succeed. // If not success, we are in a revert or halt. If the tx is // not marked as revertible by the bundle, we error our // simulation. @@ -251,26 +368,29 @@ where debug!("transaction reverted, not marked as revertible"); return Err(t.errored(BundleError::BundleReverted.into())); } - self.total_gas_used = self.total_gas_used.saturating_add(gas_used); } // If we did not shortcut return/continue, we accept the state // changes from this transaction. + self.output.use_gas(gas_used); trevm = t.accept_state() } + // -- CLEANUP -- + let beneficiary_balance = trevm_try!(trevm.try_read_balance(beneficiary).map_err(EVMError::Database), trevm); - self.beneficiary_balance_increase = - beneficiary_balance.saturating_sub(inital_beneficiary_balance); + self.output.record_beneficiary_increase( + beneficiary_balance.saturating_sub(inital_beneficiary_balance), + ); Ok(trevm) } fn post_bundle( &mut self, - _trevm: &EvmNeedsTx>, + _trevm: &EvmNeedsTx>, ) -> Result<(), Self::Error> { Ok(()) } diff --git a/crates/evm/src/driver.rs b/crates/evm/src/driver.rs index c5c9ae8f..2f453121 100644 --- a/crates/evm/src/driver.rs +++ b/crates/evm/src/driver.rs @@ -389,12 +389,12 @@ impl<'a, 'b, C: Extractable> SignetDriver<'a, 'b, C> { Insp: Inspector>, { // Taking these clears the context for reuse. - let (agg_orders, agg_fills) = + let (agg_fills, agg_orders) = trevm.inner_mut_unchecked().inspector.as_mut_detector().take_aggregates(); // We check the AggregateFills here, and if it fails, we discard the // transaction outcome and push a failure receipt. - if let Err(err) = self.working_context.checked_remove_ru_tx_events(&agg_orders, &agg_fills) + if let Err(err) = self.working_context.checked_remove_ru_tx_events(&agg_fills, &agg_orders) { debug!(%err, "Discarding transaction outcome due to market error"); return Ok(trevm.reject()); diff --git a/crates/evm/src/lib.rs b/crates/evm/src/lib.rs index e2859028..8418760a 100644 --- a/crates/evm/src/lib.rs +++ b/crates/evm/src/lib.rs @@ -51,7 +51,7 @@ pub fn signet_evm( ) -> EvmNeedsCfg { TrevmBuilder::new() .with_db(db) - .with_insp(Layered::new(NoOpInspector, OrderDetector::new(constants))) + .with_insp(Layered::new(NoOpInspector, OrderDetector::for_rollup(constants))) .with_precompiles(signet_precompiles()) .build_trevm() .expect("db set") @@ -67,7 +67,7 @@ where I: Inspector>, Db: Database + DatabaseCommit, { - let inspector = SignetLayered::new(inner, OrderDetector::new(constants)); + let inspector = SignetLayered::new(inner, OrderDetector::for_rollup(constants)); TrevmBuilder::new() .with_db(db) diff --git a/crates/evm/src/orders/inspector.rs b/crates/evm/src/orders/inspector.rs index 20e9a70c..7dc5c684 100644 --- a/crates/evm/src/orders/inspector.rs +++ b/crates/evm/src/orders/inspector.rs @@ -1,6 +1,6 @@ use crate::{FramedFilleds, FramedOrders}; use alloy::{ - primitives::{Address, Log, U256}, + primitives::{map::HashSet, Address, Log, U256}, sol_types::SolEvent, }; use signet_types::{constants::SignetSystemConstants, AggregateFills, AggregateOrders}; @@ -36,28 +36,62 @@ use trevm::{ #[derive(Debug, Clone, PartialEq, Eq)] pub struct OrderDetector { /// The signet system constants. - constants: SignetSystemConstants, + contracts: HashSet
, + + /// True if only detecting fills + fills_only: bool, + + /// The chain ID. + chain_id: u64, + /// Orders detected so far, account for EVM reverts orders: FramedOrders, + /// Fills detected so far, accounting for EVM reverts filleds: FramedFilleds, } impl OrderDetector { - /// Create a new [`OrderDetector`] with the given `orders` contract address - /// and `outputs` mapping. - pub fn new(constants: SignetSystemConstants) -> OrderDetector { - OrderDetector { constants, orders: Default::default(), filleds: Default::default() } + /// Create a new [`OrderDetector`] with the given `contracts` addresses, + /// `chain_id`, and `fills_only` flag. + pub fn new(contracts: HashSet
, chain_id: u64, fills_only: bool) -> Self { + Self { + contracts, + chain_id, + fills_only, + orders: Default::default(), + filleds: Default::default(), + } + } + + /// Create a new [`OrderDetector`] for the rollup environment. This detector + /// will detect both orders and fills. + pub fn for_rollup(constants: SignetSystemConstants) -> OrderDetector { + Self::new( + std::iter::once(constants.rollup().orders()).collect(), + constants.ru_chain_id(), + false, + ) + } + + /// Create a new [`OrderDetector`] for the host environment. This detector + /// will only detect fills. + pub fn for_host(constants: SignetSystemConstants) -> OrderDetector { + Self::new( + std::iter::once(constants.host().orders()).collect(), + constants.host_chain_id(), + true, + ) } /// Get the address of the orders contract. - pub const fn contract(&self) -> Address { - self.constants.rollup().orders() + pub fn is_contract(&self, address: Address) -> bool { + self.contracts.contains(&address) } /// Get the chain ID. pub const fn chain_id(&self) -> u64 { - self.constants.ru_chain_id() + self.chain_id } /// Take the orders from the inspector, clearing it. @@ -67,9 +101,9 @@ impl OrderDetector { /// Take the orders from the inspector, clearing it, and convert them to /// aggregate orders. - pub fn take_aggregates(&mut self) -> (AggregateOrders, AggregateFills) { + pub fn take_aggregates(&mut self) -> (AggregateFills, AggregateOrders) { let (orders, filleds) = self.take(); - (orders.aggregate(), filleds.aggregate(self.chain_id())) + (filleds.aggregate(self.chain_id()), orders.aggregate()) } /// Take the inner inspector and the framed events. @@ -94,15 +128,28 @@ where Int: InterpreterTypes, { fn log(&mut self, _interp: &mut Interpreter, _context: &mut Ctx, log: Log) { - // skip if the log is not from the orders contract - if log.address != self.contract() { + // skip if the log is not from a configured orders contract + if !self.is_contract(log.address) { return; } + // Try to decode as a filled first + if let Ok(Log { data, .. }) = RollupOrders::Filled::decode_log(&log) { + self.filleds.add(data); + return; + } + + // Skip any other logs if we're only tracking fills + if self.fills_only { + return; + } + + // Try to decode as an order next if let Ok(Log { data, .. }) = RollupOrders::Order::decode_log(&log) { + if self.fills_only { + return; + } self.orders.add(data); - } else if let Ok(Log { data, .. }) = RollupOrders::Filled::decode_log(&log) { - self.filleds.add(data); } } diff --git a/crates/evm/src/sys/transact.rs b/crates/evm/src/sys/transact.rs index ab466d3c..356fc777 100644 --- a/crates/evm/src/sys/transact.rs +++ b/crates/evm/src/sys/transact.rs @@ -30,7 +30,7 @@ impl<'a, R> From<&ExtractedEvent<'a, R, Transactor::Transact>> for TransactSysTx } impl TransactSysTx { - /// Instantiate a new [`TransactFiller`]. + /// Instantiate a new [`TransactSysTx`]. pub fn new(transact: &ExtractedEvent<'_, R, Transactor::Transact>) -> Self { let magic_sig = transact.magic_sig(); let tx = transact.make_transaction(0); @@ -53,8 +53,9 @@ impl TransactSysTx { // NB: manual impl because of incorrect auto-derive bound on `R: Debug` impl fmt::Debug for TransactSysTx { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("TransactFiller") + f.debug_struct("TransactSysTx") .field("transact", &self.tx) + .field("nonce", &self.nonce) .field("magic_sig", &self.magic_sig) .finish() } diff --git a/crates/extract/src/extracted.rs b/crates/extract/src/extracted.rs index a1aa9a2f..13b9f63a 100644 --- a/crates/extract/src/extracted.rs +++ b/crates/extract/src/extracted.rs @@ -226,7 +226,7 @@ impl ExtractedEvent<'_, R, Transactor::Transact> { /// information. pub fn magic_sig(&self) -> MagicSig { MagicSig { - ty: MagicSigInfo::Transact { sender: self.event.sender() }, + ty: MagicSigInfo::Transact { sender: self.event.host_sender() }, txid: self.tx_hash(), event_idx: self.log_index, } diff --git a/crates/sim/src/built.rs b/crates/sim/src/built.rs index 5d04dadc..c26972d7 100644 --- a/crates/sim/src/built.rs +++ b/crates/sim/src/built.rs @@ -7,7 +7,6 @@ use alloy::{ }; use core::fmt; use signet_bundle::SignetEthBundle; -use signet_types::SignedFill; use signet_zenith::{encode_txns, Alloy2718Coder}; use std::sync::OnceLock; use tracing::{error, trace}; @@ -15,9 +14,6 @@ use tracing::{error, trace}; /// A block that has been built by the simulator. #[derive(Clone, Default)] pub struct BuiltBlock { - /// The host fill actions. - pub(crate) host_fills: Vec, - /// The host transactions to be included in a resulting bundle. pub(crate) host_txns: Vec, @@ -30,6 +26,9 @@ pub struct BuiltBlock { /// The amount of gas used by the block so far pub(crate) gas_used: u64, + /// The amount of host gas used by the block so far + pub(crate) host_gas_used: u64, + // -- Memoization fields -- /// Memoized raw encoding of the block. pub(crate) raw_encoding: OnceLock, @@ -40,9 +39,10 @@ pub struct BuiltBlock { impl fmt::Debug for BuiltBlock { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("BuiltBlock") - .field("host_fills", &self.host_fills.len()) .field("transactions", &self.transactions.len()) + .field("host_txns", &self.host_txns.len()) .field("gas_used", &self.gas_used) + .field("host_gas_used", &self.host_gas_used) .field("block_number", &self.block_number) .finish_non_exhaustive() } @@ -52,11 +52,11 @@ impl BuiltBlock { /// Create a new `BuiltBlock` pub const fn new(block_number: u64) -> Self { Self { - host_fills: Vec::new(), host_txns: Vec::new(), transactions: Vec::new(), block_number, gas_used: 0, + host_gas_used: 0, raw_encoding: OnceLock::new(), hash: OnceLock::new(), } @@ -72,6 +72,11 @@ impl BuiltBlock { self.gas_used } + /// Get the amount of host gas used by the block. + pub const fn host_gas_used(&self) -> u64 { + self.host_gas_used + } + /// Get the number of transactions in the block. pub fn tx_count(&self) -> usize { self.transactions.len() @@ -88,15 +93,9 @@ impl BuiltBlock { &self.transactions } - /// Get the current list of host fills included in this block. - #[allow(clippy::missing_const_for_fn)] // false positive, const deref - pub fn host_fills(&self) -> &[SignedFill] { - &self.host_fills - } - /// Get the current list of host transactions included in this block. #[allow(clippy::missing_const_for_fn)] // false positive, const deref - pub fn host_txns(&self) -> &[Bytes] { + pub fn host_transactions(&self) -> &[Bytes] { &self.host_txns } @@ -146,6 +145,7 @@ impl BuiltBlock { /// Ingest a simulated item, extending the block. pub fn ingest(&mut self, item: SimulatedItem) { self.gas_used += item.gas_used; + self.host_gas_used += item.host_gas_used; match item.item { SimItem::Bundle(bundle) => self.ingest_bundle(bundle), diff --git a/crates/sim/src/env/host.rs b/crates/sim/src/env/host.rs index 110939aa..06abda08 100644 --- a/crates/sim/src/env/host.rs +++ b/crates/sim/src/env/host.rs @@ -1,48 +1,94 @@ use crate::{InnerDb, SimDb, TimeLimited}; +use signet_evm::{signet_precompiles, EvmNeedsTx, OrderDetector, SignetLayered}; +use signet_types::constants::SignetSystemConstants; use std::{marker::PhantomData, sync::Arc, time::Instant}; use trevm::{ + db::TryCachingDb, helpers::Ctx, inspectors::{Layered, TimeLimit}, revm::{ - database::CacheDB, + context::{BlockEnv, CfgEnv}, + database::{Cache, CacheDB}, inspector::{Inspector, NoOpInspector}, DatabaseRef, }, - TrevmBuilder, + Block, Cfg, TrevmBuilder, }; /// A host simulation environment. #[derive(Debug)] pub struct HostEnv { db: InnerDb, + + constants: SignetSystemConstants, + + cfg: CfgEnv, + block: BlockEnv, + _pd: PhantomData Insp>, } impl Clone for HostEnv { fn clone(&self) -> Self { - Self { db: self.db.clone(), _pd: PhantomData } - } -} - -impl From for HostEnv -where - Db: DatabaseRef + Send + Sync, -{ - fn from(db: Db) -> Self { - Self::new(db) + Self { + db: self.db.clone(), + constants: self.constants.clone(), + cfg: self.cfg.clone(), + block: self.block.clone(), + _pd: PhantomData, + } } } impl HostEnv { /// Create a new host environment. - pub fn new(db: Db) -> Self { - Self { db: Arc::new(CacheDB::new(db)), _pd: PhantomData } + pub fn new(db: Db, constants: SignetSystemConstants, cfg_ref: &C, block_ref: &B) -> Self + where + C: Cfg, + B: Block, + { + let mut cfg = CfgEnv::default(); + cfg_ref.fill_cfg_env(&mut cfg); + let mut block = BlockEnv::default(); + block_ref.fill_block_env(&mut block); + + Self { db: Arc::new(CacheDB::new(db)), constants, cfg, block, _pd: PhantomData } + } + + /// Get a reference to the inner database. + pub const fn db(&self) -> &InnerDb { + &self.db } /// Get a mutable reference to the inner database. pub const fn db_mut(&mut self) -> &mut InnerDb { &mut self.db } + + /// Get a reference to the signet system constants. + pub const fn constants(&self) -> &SignetSystemConstants { + &self.constants + } + + /// Get a reference to the [`CfgEnv`]. + pub const fn cfg(&self) -> &CfgEnv { + &self.cfg + } + + /// Get a mutable reference to the [`CfgEnv`]. + pub const fn cfg_mut(&mut self) -> &mut CfgEnv { + &mut self.cfg + } + + /// Get a reference to the [`BlockEnv`]. + pub const fn block(&self) -> &BlockEnv { + &self.block + } + + /// Get a mutable reference to the [`BlockEnv`]. + pub const fn block_mut(&mut self) -> &mut BlockEnv { + &mut self.block + } } impl HostEnv @@ -60,10 +106,46 @@ where pub fn create_evm( &self, finish_by: Instant, - ) -> trevm::EvmNeedsCfg, TimeLimited> { + ) -> EvmNeedsTx, TimeLimited> { let db = self.sim_db(); let inspector = Layered::new(TimeLimit::new(finish_by - Instant::now()), Insp::default()); - TrevmBuilder::new().with_insp(inspector).with_db(db).build_trevm().unwrap() + // We layer on a order detector specific to the host environment. + let inspector = + SignetLayered::new(inspector, OrderDetector::for_host(self.constants.clone())); + + // This is the same code as `signet_evm::signet_evm_with_inspector`, but + // we need to build the EVM manually to insert our layered inspector, + // as the shortcut will insert a rollup order detector. + TrevmBuilder::new() + .with_db(db) + .with_insp(inspector) + .with_precompiles(signet_precompiles()) + .build_trevm() + .expect("db set") + .fill_cfg(&self.cfg) + .fill_block(&self.block) + } +} + +impl HostEnv +where + Db: DatabaseRef, + Insp: Inspector>> + Default + Sync, +{ + /// Accepts a cache from the simulation and extends the database with it. + pub fn accept_cache( + &mut self, + cache: Cache, + ) -> Result<(), as TryCachingDb>::Error> { + self.db_mut().try_extend(cache) + } + + /// Accepts a cache from the simulation and extends the database with it. + pub fn accept_cache_ref( + &mut self, + cache: &Cache, + ) -> Result<(), as TryCachingDb>::Error> { + self.db_mut().try_extend_ref(cache) } } diff --git a/crates/sim/src/env/rollup.rs b/crates/sim/src/env/rollup.rs index 02f69cb2..1a5c5099 100644 --- a/crates/sim/src/env/rollup.rs +++ b/crates/sim/src/env/rollup.rs @@ -1,16 +1,20 @@ use crate::{InnerDb, SimDb, TimeLimited}; -use signet_evm::EvmNeedsCfg; -use signet_types::constants::SignetSystemConstants; +use signet_evm::EvmNeedsTx; +use signet_types::{ + constants::SignetSystemConstants, AggregateFills, AggregateOrders, MarketError, +}; use std::{marker::PhantomData, sync::Arc, time::Instant}; use trevm::{ db::{cow::CacheOnWrite, TryCachingDb}, helpers::Ctx, inspectors::{Layered, TimeLimit}, revm::{ + context::{BlockEnv, CfgEnv}, database::{Cache, CacheDB}, inspector::NoOpInspector, DatabaseRef, Inspector, }, + Block, Cfg, }; /// A rollup simulation environment. @@ -18,19 +22,52 @@ use trevm::{ pub struct RollupEnv { db: InnerDb, constants: SignetSystemConstants, + fill_state: AggregateFills, + + cfg: CfgEnv, + block: BlockEnv, + _pd: PhantomData Insp>, } impl Clone for RollupEnv { fn clone(&self) -> Self { - Self { db: self.db.clone(), constants: self.constants.clone(), _pd: PhantomData } + Self { + db: self.db.clone(), + constants: self.constants.clone(), + fill_state: self.fill_state.clone(), + cfg: self.cfg.clone(), + block: self.block.clone(), + _pd: PhantomData, + } } } impl RollupEnv { /// Create a new rollup environment. - pub fn new(db: Db, constants: SignetSystemConstants) -> Self { - Self { db: Arc::new(CacheDB::new(db)), constants, _pd: PhantomData } + pub fn new(db: Db, constants: SignetSystemConstants, cfg_ref: &C, block_ref: &B) -> Self + where + C: Cfg, + B: Block, + { + let mut cfg = CfgEnv::default(); + cfg_ref.fill_cfg_env(&mut cfg); + let mut block = BlockEnv::default(); + block_ref.fill_block_env(&mut block); + + Self { + db: Arc::new(CacheDB::new(db)), + constants, + fill_state: AggregateFills::default(), + cfg, + block, + _pd: PhantomData, + } + } + + /// Get a reference to the inner database. + pub const fn db(&self) -> &InnerDb { + &self.db } /// Get a mutable reference to the inner database. @@ -42,6 +79,45 @@ impl RollupEnv { pub const fn constants(&self) -> &SignetSystemConstants { &self.constants } + + /// Get a reference to the fill state. + pub const fn fill_state(&self) -> &AggregateFills { + &self.fill_state + } + + /// Get a mutable reference to the fill state. + pub const fn fill_state_mut(&mut self) -> &mut AggregateFills { + &mut self.fill_state + } + + /// Accepts aggregate fills and orders, updating the fill state. + pub fn accept_aggregates( + &mut self, + fills: &AggregateFills, + orders: &AggregateOrders, + ) -> Result<(), MarketError> { + self.fill_state.checked_remove_ru_tx_events(fills, orders) + } + + /// Get a reference to the [`CfgEnv`]. + pub const fn cfg(&self) -> &CfgEnv { + &self.cfg + } + + /// Get a mutable reference to the [`CfgEnv`]. + pub const fn cfg_mut(&mut self) -> &mut CfgEnv { + &mut self.cfg + } + + /// Get a reference to the [`BlockEnv`]. + pub const fn block(&self) -> &BlockEnv { + &self.block + } + + /// Get a mutable reference to the [`BlockEnv`]. + pub const fn block_mut(&mut self) -> &mut BlockEnv { + &mut self.block + } } impl RollupEnv @@ -56,12 +132,14 @@ where /// Create a new EVM for the rollup environment that will finish by the /// given instant. - pub fn create_evm(&self, finish_by: Instant) -> EvmNeedsCfg, TimeLimited> { + pub fn create_evm(&self, finish_by: Instant) -> EvmNeedsTx, TimeLimited> { let db = self.sim_db(); let inspector = Layered::new(TimeLimit::new(finish_by - Instant::now()), Insp::default()); signet_evm::signet_evm_with_inspector(db, inspector, self.constants.clone()) + .fill_cfg(&self.cfg) + .fill_block(&self.block) } } diff --git a/crates/sim/src/env/shared.rs b/crates/sim/src/env/shared.rs index 8ead9b68..6445784e 100644 --- a/crates/sim/src/env/shared.rs +++ b/crates/sim/src/env/shared.rs @@ -1,4 +1,4 @@ -use crate::{env::RollupEnv, outcome::SimulatedItem, SimCache, SimDb, SimEnv}; +use crate::{env::RollupEnv, outcome::SimulatedItem, HostEnv, SimCache, SimDb, SimEnv}; use core::fmt; use std::{ops::Deref, sync::Arc}; use tokio::{select, sync::watch}; @@ -6,17 +6,16 @@ use tracing::{instrument, trace}; use trevm::{ helpers::Ctx, revm::{inspector::NoOpInspector, DatabaseRef, Inspector}, - Block, Cfg, }; /// A simulation environment. /// /// Contains enough information to run a simulation. -pub struct SharedSimEnv { - inner: Arc>, +pub struct SharedSimEnv { + inner: Arc>, } -impl fmt::Debug for SharedSimEnv { +impl fmt::Debug for SharedSimEnv { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("SharedSimEnv") .field("finish_by", &self.inner.finish_by()) @@ -25,54 +24,80 @@ impl fmt::Debug for SharedSimEnv { } } -impl Deref for SharedSimEnv { - type Target = SimEnv; +impl Deref for SharedSimEnv { + type Target = SimEnv; fn deref(&self) -> &Self::Target { &self.inner } } -impl From> for SharedSimEnv +impl From> + for SharedSimEnv where - Db: DatabaseRef + Send + Sync + 'static, - Insp: Inspector>> + Default + Sync + 'static, + RuDb: DatabaseRef + Send + Sync + 'static, + RuInsp: Inspector>> + Default + Sync + 'static, + HostDb: DatabaseRef + Send + Sync + 'static, + HostInsp: Inspector>> + Default + Sync + 'static, { - fn from(inner: SimEnv) -> Self { + fn from(inner: SimEnv) -> Self { Self { inner: Arc::new(inner) } } } -impl SharedSimEnv +impl SharedSimEnv where - Db: DatabaseRef + Send + Sync + 'static, - Insp: Inspector>> + Default + Sync + 'static, + RuDb: DatabaseRef + Send + Sync + 'static, + RuInsp: Inspector>> + Default + Sync + 'static, + HostDb: DatabaseRef + Send + Sync + 'static, + HostInsp: Inspector>> + Default + Sync + 'static, { /// Creates a new `SimEnv` instance. - pub fn new( - rollup: RollupEnv, - cfg: C, - block: B, + pub fn new( + rollup: RollupEnv, + host: HostEnv, finish_by: std::time::Instant, concurrency_limit: usize, sim_items: SimCache, - ) -> Self - where - C: Cfg, - B: Block, - { - SimEnv::new(rollup, cfg, block, finish_by, concurrency_limit, sim_items).into() + ) -> Self { + SimEnv::new(rollup, host, finish_by, concurrency_limit, sim_items).into() + } + + /// Get a reference the simulation cache used by this builder. + pub fn sim_items(&self) -> &SimCache { + self.inner.sim_items() + } + + /// Get a reference to the rollup environment. + pub fn rollup_env(&self) -> &RollupEnv { + self.inner.rollup_env() + } + + /// Get a mutable reference to the rollup environment. + pub fn rollup_env_mut(&mut self) -> &mut RollupEnv { + Arc::get_mut(&mut self.inner).expect("sims dropped already").rollup_mut() + } + + /// Get a reference to the host environment. + pub fn host_env(&self) -> &HostEnv { + self.inner.host_env() + } + + /// Get a mutable reference to the host environment. + pub fn host_env_mut(&mut self) -> &mut HostEnv { + Arc::get_mut(&mut self.inner).expect("sims dropped already").host_mut() } /// Run a simulation round, returning the best item. #[instrument(skip(self))] - pub async fn sim_round(&mut self, max_gas: u64) -> Option { + pub async fn sim_round(&mut self, max_gas: u64, max_host_gas: u64) -> Option { let (best_tx, mut best_watcher) = watch::channel(None); let this = self.inner.clone(); // Spawn a blocking task to run the simulations. - let sim_task = tokio::task::spawn_blocking(move || this.sim_round(max_gas, best_tx)); + let sim_task = + tokio::task::spawn_blocking(move || this.sim_round(max_gas, max_host_gas, best_tx)); // Either simulation is done, or we time out select! { @@ -91,13 +116,24 @@ where // Remove the item from the cache. let item = self.sim_items().remove(outcome.cache_rank)?; + + let inner = Arc::get_mut(&mut self.inner).expect("sims dropped already"); + // Accept the cache from the simulation. - Arc::get_mut(&mut self.inner) - .expect("sims dropped already") + inner.rollup_mut().accept_cache_ref(&outcome.rollup_cache).ok()?; + // Accept the host cache from the simulation. + inner.host_mut().accept_cache_ref(&outcome.host_cache).ok()?; + // Accept the aggregate fills and orders. + inner .rollup_mut() - .accept_cache_ref(&outcome.cache) - .ok()?; + .accept_aggregates(&outcome.bundle_fills, &outcome.bundle_orders) + .expect("checked during simulation"); - Some(SimulatedItem { gas_used: outcome.gas_used, score: outcome.score, item }) + Some(SimulatedItem { + gas_used: outcome.gas_used, + host_gas_used: outcome.host_gas_used, + score: outcome.score, + item, + }) } } diff --git a/crates/sim/src/env/sim_env.rs b/crates/sim/src/env/sim_env.rs index f98df1b0..8c3269a0 100644 --- a/crates/sim/src/env/sim_env.rs +++ b/crates/sim/src/env/sim_env.rs @@ -1,37 +1,33 @@ -use crate::{env::RollupEnv, SimCache, SimDb, SimItem, SimOutcomeWithCache, TimeLimited}; +use crate::{env::RollupEnv, HostEnv, SimCache, SimDb, SimItem, SimOutcomeWithCache}; use alloy::{consensus::TxEnvelope, hex}; use core::fmt; use signet_bundle::{SignetEthBundle, SignetEthBundleDriver, SignetEthBundleError}; +use signet_evm::SignetInspector; use signet_types::constants::SignetSystemConstants; -use std::sync::Arc; +use std::{borrow::Cow, sync::Arc}; use tokio::sync::{mpsc, watch}; use tracing::{instrument, trace, trace_span}; use trevm::{ helpers::Ctx, revm::{ - context::{ - result::{EVMError, ExecutionResult}, - BlockEnv, CfgEnv, - }, + context::result::{EVMError, ExecutionResult}, inspector::NoOpInspector, DatabaseRef, Inspector, }, - Block, BundleDriver, Cfg, + BundleDriver, }; /// A simulation environment. -pub struct SimEnv { +pub struct SimEnv { + /// The rollup environment. rollup: RollupEnv, + /// The host environment. + host: HostEnv, + /// The cache of items to simulate. sim_items: SimCache, - /// Chain cfg to use for the simulation. - cfg: CfgEnv, - - /// Block to use for the simulation. - block: BlockEnv, - /// The instant by which the simulation should finish. finish_by: std::time::Instant, @@ -39,7 +35,7 @@ pub struct SimEnv { concurrency_limit: usize, } -impl fmt::Debug for SimEnv { +impl fmt::Debug for SimEnv { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("SimEnv") .field("finish_by", &self.finish_by) @@ -48,33 +44,38 @@ impl fmt::Debug for SimEnv { } } -impl SimEnv { - /// Creates a new `SimFactory` instance. - pub fn new( +impl SimEnv { + /// Create a new `SimEnv` instance. + pub const fn new( rollup: RollupEnv, - cfg_ref: C, - block_ref: B, + host: HostEnv, finish_by: std::time::Instant, concurrency_limit: usize, sim_items: SimCache, - ) -> Self - where - C: Cfg, - B: Block, - { - let mut cfg = CfgEnv::default(); - cfg_ref.fill_cfg_env(&mut cfg); - let mut block = BlockEnv::default(); - block_ref.fill_block_env(&mut block); + ) -> Self { + Self { rollup, host, finish_by, concurrency_limit, sim_items } + } - Self { rollup, cfg, block, finish_by, concurrency_limit, sim_items } + /// Get a reference to the rollup environment. + pub const fn rollup_env(&self) -> &RollupEnv { + &self.rollup } - /// Get a reference to the database. + /// Get a mutable reference to the rollup environment. pub const fn rollup_mut(&mut self) -> &mut RollupEnv { &mut self.rollup } + /// Get a reference to the host environment. + pub const fn host_env(&self) -> &HostEnv { + &self.host + } + + /// Get a mutable reference to the host environment. + pub const fn host_mut(&mut self) -> &mut HostEnv { + &mut self.host + } + /// Get a reference to the system constants. pub const fn constants(&self) -> &SignetSystemConstants { self.rollup.constants() @@ -85,16 +86,6 @@ impl SimEnv { &self.sim_items } - /// Get a reference to the chain cfg. - pub const fn cfg(&self) -> &CfgEnv { - &self.cfg - } - - /// Get a reference to the block. - pub const fn block(&self) -> &BlockEnv { - &self.block - } - /// Get the exectuion timeout. pub const fn finish_by(&self) -> std::time::Instant { self.finish_by @@ -111,15 +102,13 @@ impl SimEnv { } } -impl SimEnv +impl SimEnv where RuDb: DatabaseRef + Send + Sync, RuInsp: Inspector>> + Default + Sync, + HostDb: DatabaseRef + Send + Sync, + HostInsp: Inspector>> + Default + Sync, { - fn rollup_evm(&self) -> signet_evm::EvmNeedsTx, TimeLimited> { - self.rollup.create_evm(self.finish_by).fill_cfg(&self.cfg).fill_block(&self.block) - } - /// Simulates a transaction in the context of a block. /// /// This function runs the simulation in a separate thread and waits for @@ -130,7 +119,7 @@ where cache_rank: u128, transaction: &TxEnvelope, ) -> Result>> { - let trevm = self.rollup_evm(); + let trevm = self.rollup.create_evm(self.finish_by); // Get the initial beneficiary balance let beneficiary = trevm.beneficiary(); @@ -139,19 +128,30 @@ where // If succesful, take the cache. If failed, return the error. match trevm.run_tx(transaction) { - Ok(trevm) => { + Ok(mut trevm) => { // Get the simulation results let gas_used = trevm.result().gas_used(); let success = trevm.result().is_success(); let reason = trevm.result().output().cloned().map(hex::encode); let halted = trevm.result().is_halt(); let halt_reason = if let ExecutionResult::Halt { reason, .. } = trevm.result() { - Some(reason) + Some(reason.clone()) } else { None - } - .cloned(); + }; + + // We collect the orders and fills from the inspector, and check + // them against the provided fill state. If the fills are + // insufficient, we error out. Otherwise we'll return them as + // part of the `SimOutcomeWithCache`, to allow _later_ stages to + // process them (e.g., to update the fill state). + let (bundle_fills, bundle_orders) = + trevm.inner_mut_unchecked().inspector.as_mut_detector().take_aggregates(); + + self.rollup.fill_state().check_ru_tx_events(&bundle_fills, &bundle_orders)?; + // We will later commit these to the trevm DB when the + // SimOutcome is accepted. let cache = trevm.accept_state().into_db().into_cache(); let beneficiary_balance = cache @@ -162,9 +162,7 @@ where let score = beneficiary_balance.saturating_sub(initial_beneficiary_balance); trace!( - ?cache_rank, - tx_hash = %transaction.hash(), - gas_used = gas_used, + gas_used, score = %score, reverted = !success, halted, @@ -174,7 +172,16 @@ where ); // Create the outcome - Ok(SimOutcomeWithCache { cache_rank, score, cache, gas_used }) + Ok(SimOutcomeWithCache { + cache_rank, + score, + rollup_cache: cache, + host_cache: Default::default(), + host_gas_used: 0, + gas_used, + bundle_fills, + bundle_orders, + }) } Err(e) => Err(SignetEthBundleError::from(e.into_error())), } @@ -190,8 +197,14 @@ where where RuInsp: Inspector>> + Default + Sync, { - let mut driver = SignetEthBundleDriver::new(bundle, self.finish_by); - let trevm = self.rollup_evm(); + let trevm = self.rollup.create_evm(self.finish_by); + + let mut driver = SignetEthBundleDriver::new_with_fill_state( + bundle, + self.host.create_evm(self.finish_by), + self.finish_by, + Cow::Borrowed(self.rollup.fill_state()), + ); // Run the bundle let trevm = match driver.run_bundle(trevm) { @@ -201,18 +214,32 @@ where // Build the SimOutcome let score = driver.beneficiary_balance_increase(); - let gas_used = driver.total_gas_used(); - let cache = trevm.into_db().into_cache(); + let outputs = driver.into_outputs(); + + // This is redundant with the driver, however, we double check here. + // If perf is hit too much we can remove. + self.rollup + .fill_state() + .check_ru_tx_events(&outputs.bundle_fills, &outputs.bundle_orders)?; + let host_cache = outputs.host_evm.map(|evm| evm.into_db().into_cache()).unwrap_or_default(); trace!( - ?cache_rank, - uuid = %bundle.replacement_uuid().expect("Bundle must have a replacement UUID"), - gas_used = gas_used, - score = %score, + gas_used = outputs.total_gas_used, + host_gas_used = outputs.total_host_gas_used, + %score, "Bundle simulation successful" ); - Ok(SimOutcomeWithCache { cache_rank, score, cache, gas_used }) + Ok(SimOutcomeWithCache { + cache_rank, + score: score.to(), + rollup_cache: trevm.into_db().into_cache(), + host_cache, + gas_used: outputs.total_gas_used, + host_gas_used: outputs.total_host_gas_used, + bundle_fills: outputs.bundle_fills, + bundle_orders: outputs.bundle_orders, + }) } /// Simulates a transaction or bundle in the context of a block. @@ -231,6 +258,7 @@ where pub(crate) fn sim_round( self: Arc, max_gas: u64, + max_host_gas: u64, best_tx: watch::Sender>, ) { // Pull the `n` best items from the cache. @@ -244,27 +272,38 @@ where let _og = outer.enter(); // to be used in the scope - let this_ref = &self; + let this_ref = self.clone(); - std::thread::scope(move |scope| { + std::thread::scope(|scope| { // Spawn a thread per bundle to simulate. for (cache_rank, item) in active_sim.into_iter() { let c = candidates.clone(); - + let this_ref = this_ref.clone(); scope.spawn(move || { let identifier = item.identifier(); let _ig = trace_span!(parent: outer_ref, "sim_task", %identifier).entered(); // If simulation is succesful, send the outcome via the // channel. + match this_ref.simulate(cache_rank, &item) { + Ok(candidate) if candidate.score.is_zero() => { + trace!("zero score candidate, skipping"); + } + Ok(candidate) if candidate.host_gas_used > max_host_gas => { + trace!( + host_gas_used = candidate.host_gas_used, + max_host_gas, + "Host gas limit exceeded" + ); + } + Ok(candidate) if candidate.gas_used > max_gas => { + trace!(gas_used = candidate.gas_used, max_gas, "Gas limit exceeded"); + } Ok(candidate) => { - if candidate.gas_used <= max_gas { - // shortcut return on success - let _ = c.blocking_send(candidate); - return; - } - trace!(gas_used = candidate.gas_used, max_gas, %identifier, "Gas limit exceeded"); + // shortcut return on success + let _ = c.blocking_send(candidate); + return; } Err(e) => { trace!(?identifier, %e, "Simulation failed"); @@ -278,7 +317,6 @@ where // Drop the TX so that the channel is closed when all threads // are done. drop(candidates); - // Wait for each thread to finish. Find the best outcome. while let Some(candidate) = candidates_rx.blocking_recv() { // Update the best score and send it to the channel. diff --git a/crates/sim/src/outcome.rs b/crates/sim/src/outcome.rs index 05942993..5ac66b34 100644 --- a/crates/sim/src/outcome.rs +++ b/crates/sim/src/outcome.rs @@ -1,8 +1,8 @@ +use crate::SimItem; use alloy::primitives::U256; +use signet_types::{AggregateFills, AggregateOrders}; use trevm::revm::database::Cache; -use crate::SimItem; - /// A simulation outcome that includes the score, gas used, and a cache of /// state changes. #[derive(Debug, Clone)] @@ -14,12 +14,25 @@ pub struct SimOutcomeWithCache { /// increase in the beneficiary's balance. pub score: U256, + /// The total amount of gas used by the simulation. + pub gas_used: u64, + + /// The total amount of host gas used by the simulation. + pub host_gas_used: u64, + /// The result of the simulation, a [`Cache`] containing state changes that /// can be applied. - pub cache: Cache, + pub rollup_cache: Cache, - /// The total amount of gas used by the simulation. - pub gas_used: u64, + /// The result of the bundle host simulation a [`Cache`] containing state + /// changes that can be applied. + pub host_cache: Cache, + + /// The aggregate fills after simulation. + pub bundle_fills: AggregateFills, + + /// The aggregate orders after simulation. + pub bundle_orders: AggregateOrders, } /// An item after simulation, containing the score and gas used. @@ -32,6 +45,9 @@ pub struct SimulatedItem { /// The total amount of gas used by the simulation. pub gas_used: u64, + /// The total amount of host gas used by the simulation. + pub host_gas_used: u64, + /// The transaction or bundle that was simulated. pub item: SimItem, } diff --git a/crates/sim/src/task.rs b/crates/sim/src/task.rs index 3a2c1c02..b5380dc9 100644 --- a/crates/sim/src/task.rs +++ b/crates/sim/src/task.rs @@ -1,12 +1,10 @@ -use crate::{env::SimEnv, BuiltBlock, RollupEnv, SharedSimEnv, SimCache, SimDb}; -use signet_types::constants::SignetSystemConstants; +use crate::{env::SimEnv, BuiltBlock, HostEnv, RollupEnv, SharedSimEnv, SimCache, SimDb}; use std::time::Duration; use tokio::{select, time::Instant}; use tracing::{debug, info_span, trace, Instrument}; use trevm::{ helpers::Ctx, revm::{inspector::NoOpInspector, DatabaseRef, Inspector}, - Block, Cfg, }; /// The amount of time to sleep between simulation rounds when there are no items to simulate. @@ -14,9 +12,9 @@ pub(crate) const SIM_SLEEP_MS: u64 = 50; /// Builds a single block by repeatedly invoking [`SimEnv`]. #[derive(Debug)] -pub struct BlockBuild { +pub struct BlockBuild { /// The simulation environment. - env: SharedSimEnv, + env: SharedSimEnv, /// The block being built. block: BuiltBlock, @@ -26,54 +24,122 @@ pub struct BlockBuild { /// The maximum amount of gas to use in the built block max_gas: u64, + + /// The maximum amount of host gas to use in the user portion of the built + /// block, not including overhead for the signet RU block submission. + max_host_gas: u64, } -impl BlockBuild +impl BlockBuild where - Db: DatabaseRef + Send + Sync + 'static, - Insp: Inspector>> + Default + Sync + 'static, + RuDb: DatabaseRef + Send + Sync + 'static, + RuInsp: Inspector>> + Default + Sync + 'static, + HostDb: DatabaseRef + Send + Sync + 'static, + HostInsp: Inspector>> + Default + Sync + 'static, { /// Create a new block building process. - #[allow(clippy::too_many_arguments)] // sadge but. - pub fn new( - db: Db, - constants: SignetSystemConstants, - cfg: C, - block: B, + pub fn new( + rollup: RollupEnv, + host: HostEnv, finish_by: std::time::Instant, concurrency_limit: usize, sim_items: SimCache, max_gas: u64, - ) -> Self - where - C: Cfg, - B: Block, - { - let env = SimEnv::::new( - RollupEnv::new(db, constants), - cfg, - block, + max_host_gas: u64, + ) -> Self { + let number = rollup.block().number; + + let env = SimEnv::::new( + rollup, + host, finish_by, concurrency_limit, sim_items, ); let finish_by = env.finish_by(); - let number = env.block().number; - Self { env: env.into(), block: BuiltBlock::new(number.to()), finish_by, max_gas } + Self { + env: env.into(), + block: BuiltBlock::new(number.to()), + finish_by, + max_gas, + max_host_gas, + } + } + + /// Get the maximum gas limit for the block being built. + pub const fn max_gas(&self) -> u64 { + self.max_gas + } + + /// Set the maximum gas limit for the block being built. + pub const fn set_max_gas(&mut self, max_gas: u64) { + self.max_gas = max_gas; + } + + /// Get the maximum host gas limit for the block being built. + pub const fn max_host_gas(&self) -> u64 { + self.max_host_gas + } + + /// Set the maximum host gas limit for the block being built. + pub const fn set_max_host_gas(&mut self, max_host_gas: u64) { + self.max_host_gas = max_host_gas; + } + + /// Get a reference the simulation cache used by this builder. + pub fn sim_items(&self) -> &SimCache { + self.env.sim_items() + } + + /// Get a reference to the rollup environment. + pub fn rollup_env(&self) -> &RollupEnv { + self.env.rollup_env() + } + + /// Get a reference to the host environment. + pub fn host_env(&self) -> &HostEnv { + self.env.host_env() + } + + /// Consume the builder and return the built block. + /// + /// This should generally not be called directly; use [`BlockBuild::build`] + /// instead. + pub fn into_block(self) -> BuiltBlock { + self.block } /// Run a simulation round, and accumulate the results into the block. async fn round(&mut self) { let gas_allowed = self.max_gas - self.block.gas_used(); - - if let Some(simulated) = self.env.sim_round(gas_allowed).await { - tracing::debug!(score = %simulated.score, gas_used = simulated.gas_used, identifier = %simulated.item.identifier(), "Adding item to block"); + let host_gas_allowed = self.max_host_gas - self.block.host_gas_used(); + + if let Some(simulated) = self.env.sim_round(gas_allowed, host_gas_allowed).await { + tracing::debug!( + score = %simulated.score, + gas_used = simulated.gas_used, + host_gas_used = simulated.host_gas_used, + identifier = %simulated.item.identifier(), + "Adding item to block" + ); self.block.ingest(simulated); } } - /// Run several rounds, building - pub async fn build(mut self) -> BuiltBlock { + /// Run several rounds, building a block by iteratively adding simulated + /// items. + /// + /// This version returns self to allow inspection of the building process. + /// It does nothing if the block already has transactions (i.e. this + /// function should be idempotent). + pub async fn run_build(mut self) -> Self { + if !self.block.transactions.is_empty() { + debug!( + transactions = self.block.transactions.len(), + "Starting block build with pre-existing transactions", + ); + return self; + } let mut i = 1; // Run until the deadline is reached. loop { @@ -115,8 +181,13 @@ where } debug!(rounds = i, transactions = self.block.transactions.len(), "Building completed",); + self + } - self.block + /// Run several rounds, building a block by iteratively adding simulated + /// items. + pub async fn build(self) -> BuiltBlock { + self.run_build().await.block } } @@ -128,10 +199,13 @@ mod test { /// Compile-time check to ensure that the block building process is /// `Send`. - fn _build_fut_is_send(b: BlockBuild) - where - Db: DatabaseRef + Send + Sync + 'static, - Insp: Inspector>> + Default + Sync + 'static, + fn _build_fut_is_send( + b: BlockBuild, + ) where + RuDb: DatabaseRef + Send + Sync + 'static, + RuInsp: Inspector>> + Default + Sync + 'static, + HostDb: DatabaseRef + Send + Sync + 'static, + HostInsp: Inspector>> + Default + Sync + 'static, { let _: Box + Send> = Box::new(b.build()); } diff --git a/crates/test-utils/Cargo.toml b/crates/test-utils/Cargo.toml index 0f280c51..ffbfe15a 100644 --- a/crates/test-utils/Cargo.toml +++ b/crates/test-utils/Cargo.toml @@ -24,6 +24,7 @@ alloy.workspace = true tracing-subscriber.workspace = true tokio.workspace = true +uuid = { workspace = true, features = ["v4"] } [dev-dependencies] -tracing-subscriber = { workspace = true, features = ["env-filter"] } \ No newline at end of file +tracing-subscriber = { workspace = true, features = ["env-filter"] } diff --git a/crates/test-utils/src/contracts/system.rs b/crates/test-utils/src/contracts/system.rs index 642dbe98..82cba36c 100644 --- a/crates/test-utils/src/contracts/system.rs +++ b/crates/test-utils/src/contracts/system.rs @@ -20,3 +20,9 @@ pub const RU_PASSAGE_BYTECODE: Bytes = bytes!("0x60806040526004361061004d575f356 /// Rollup orders, from pecorino genesis. pub const RU_ORDERS_BYTECODE: Bytes = bytes!("0x608060405260043610610054575f3560e01c80630828f139146100585780631bbf03a81461008d57806362c06767146100ae578063897bc0d6146100cd5780639181004e146100e0578063f9c50441146100ff575b5f80fd5b348015610063575f80fd5b50610077610072366004610fe9565b610112565b6040516100849190611050565b60405180910390f35b348015610098575f80fd5b506100ac6100a736600461108a565b61024e565b005b3480156100b9575f80fd5b506100ac6100c83660046110ed565b6102c8565b6100ac6100db366004610fe9565b610364565b3480156100eb575f80fd5b506100ac6100fa366004611127565b6103b7565b6100ac61010d366004611199565b610453565b604080518082019091525f81526060602082015281515f816001600160401b0381111561014157610141610e5c565b60405190808252806020026020018201604052801561016a578160200160208202803683370190505b5090505f5b828110156101fe577f988262d9186cf8a1cd1dd5e2cc7bfa353f55a542d86db1fcd06e076a6544250d8582815181106101aa576101aa61127d565b60200260200101516040516020016101c39291906112c8565b604051602081830303815290604052805190602001208282815181106101eb576101eb61127d565b602090810291909101015260010161016f565b508060405160200161021091906112dc565b60408051601f1981840301815291815281516020928301208552805160c081019091526084808252909161168f908301396020840152509092915050565b6102566104b8565b61028561026283610112565b61027f846102708580611311565b61027a908061132f565b610527565b83610709565b7f14b3027353aba71f468d178fdede9ac211a25ae484028823bce1e6700e58e624826040516102b491906113c0565b60405180910390a16102c46107bb565b5050565b6102d06104b8565b6001600160a01b0382166102f6576102f16001600160a01b038416826107e5565b61030a565b61030a6001600160a01b0383168483610884565b816001600160a01b0316836001600160a01b03167fed679328aebf74ede77ae09efcf36e90244f83643dadac1c2d9f0b21a46f6ab78360405161034f91815260200190565b60405180910390a361035f6107bb565b505050565b61036c6104b8565b610375816108e3565b7f14b3027353aba71f468d178fdede9ac211a25ae484028823bce1e6700e58e624816040516103a491906113c0565b60405180910390a16103b46107bb565b50565b6103bf6104b8565b6103e86103cb83610112565b61027f856103d98580611311565b6103e3908061132f565b610a28565b7f80c9b8738a5ff299b770efb55e4372a5fc655294aca7145b3c529c2d89732d626104138280611311565b604001356104336104248480611311565b61042e908061132f565b610af1565b84604051610443939291906113d2565b60405180910390a161035f6107bb565b61045b6104b8565b8242111561047c576040516362b439dd60e11b815260040160405180910390fd5b61048582610be8565b7f80c9b8738a5ff299b770efb55e4372a5fc655294aca7145b3c529c2d89732d62838383604051610443939291906113d2565b7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005c156104f857604051633ee5aeb560e01b815260040160405180910390fd5b61052560017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005b90610c78565b565b8251606090821461054e576040516001621398b960e31b0319815260040160405180910390fd5b816001600160401b0381111561056657610566610e5c565b6040519080825280602002602001820160405280156105aa57816020015b604080518082019091525f80825260208201528152602001906001900390816105845790505b5090505f5b82811015610701578481815181106105c9576105c961127d565b60200260200101515f01516001600160a01b03168484838181106105ef576105ef61127d565b6106059260206040909202019081019150611450565b6001600160a01b03161461062c57604051635f670cf360e11b815260040160405180910390fd5b84818151811061063e5761063e61127d565b60200260200101516020015184848381811061065c5761065c61127d565b905060400201602001351461068457604051635f670cf360e11b815260040160405180910390fd5b60405180604001604052808683815181106106a1576106a161127d565b6020026020010151604001516001600160a01b031681526020018683815181106106cd576106cd61127d565b6020026020010151602001518152508282815181106106ee576106ee61127d565b60209081029190910101526001016105af565b509392505050565b6001600160a01b037f000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba31663fe8ec1a76107428380611311565b846107536040860160208701611450565b875160208901516107676040890189611469565b6040518863ffffffff1660e01b81526004016107899796959493929190611521565b5f604051808303815f87803b1580156107a0575f80fd5b505af11580156107b2573d5f803e3d5ffd5b50505050505050565b6105255f7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0061051f565b804710156108145760405163cf47918160e01b8152476004820152602481018290526044015b60405180910390fd5b5f826001600160a01b0316826040515f6040518083038185875af1925050503d805f811461085d576040519150601f19603f3d011682016040523d82523d5f602084013e610862565b606091505b505090508061035f5760405163d6bda27560e01b815260040160405180910390fd5b6040516001600160a01b0383811660248301526044820183905261035f91859182169063a9059cbb906064015b604051602081830303815290604052915060e01b6020820180516001600160e01b038381831617835250505050610c7f565b345f5b825181101561035f575f6001600160a01b031683828151811061090b5761090b61127d565b60200260200101515f01516001600160a01b0316036109aa578281815181106109365761093661127d565b6020026020010151602001518261094d919061163a565b91506109a58382815181106109645761096461127d565b6020026020010151602001518483815181106109825761098261127d565b6020026020010151604001516001600160a01b03166107e590919063ffffffff16565b610a20565b610a20338483815181106109c0576109c061127d565b6020026020010151604001518584815181106109de576109de61127d565b6020026020010151602001518685815181106109fc576109fc61127d565b60200260200101515f01516001600160a01b0316610ce0909392919063ffffffff16565b6001016108e6565b6060816001600160401b03811115610a4257610a42610e5c565b604051908082528060200260200182016040528015610a8657816020015b604080518082019091525f8082526020820152815260200190600190039081610a605790505b5090505f5b82811015610701576040518060400160405280866001600160a01b03168152602001858584818110610abf57610abf61127d565b90506040020160200135815250828281518110610ade57610ade61127d565b6020908102919091010152600101610a8b565b6060816001600160401b03811115610b0b57610b0b610e5c565b604051908082528060200260200182016040528015610b4f57816020015b604080518082019091525f8082526020820152815260200190600190039081610b295790505b5090505f5b82811015610be1576040518060400160405280858584818110610b7957610b7961127d565b610b8f9260206040909202019081019150611450565b6001600160a01b03168152602001858584818110610baf57610baf61127d565b90506040020160200135815250828281518110610bce57610bce61127d565b6020908102919091010152600101610b54565b5092915050565b345f5b825181101561035f575f6001600160a01b0316838281518110610c1057610c1061127d565b60200260200101515f01516001600160a01b031603610c5957828181518110610c3b57610c3b61127d565b60200260200101516020015182610c52919061163a565b9150610c70565b610c7033308584815181106109de576109de61127d565b600101610beb565b80825d5050565b5f610c936001600160a01b03841683610d1f565b905080515f14158015610cb7575080806020019051810190610cb59190611659565b155b1561035f57604051635274afe760e01b81526001600160a01b038416600482015260240161080b565b6040516001600160a01b038481166024830152838116604483015260648201839052610d199186918216906323b872dd906084016108b1565b50505050565b6060610d2c83835f610d35565b90505b92915050565b606081471015610d615760405163cf47918160e01b81524760048201526024810183905260440161080b565b5f80856001600160a01b03168486604051610d7c9190611678565b5f6040518083038185875af1925050503d805f8114610db6576040519150601f19603f3d011682016040523d82523d5f602084013e610dbb565b606091505b5091509150610dcb868383610dd7565b925050505b9392505050565b606082610dec57610de782610e33565b610dd0565b8151158015610e0357506001600160a01b0384163b155b15610e2c57604051639996b31560e01b81526001600160a01b038516600482015260240161080b565b5080610dd0565b805115610e435780518082602001fd5b60405163d6bda27560e01b815260040160405180910390fd5b634e487b7160e01b5f52604160045260245ffd5b604051608081016001600160401b0381118282101715610e9257610e92610e5c565b60405290565b604080519081016001600160401b0381118282101715610e9257610e92610e5c565b604051601f8201601f191681016001600160401b0381118282101715610ee257610ee2610e5c565b604052919050565b5f6001600160401b03821115610f0257610f02610e5c565b5060051b60200190565b80356001600160a01b0381168114610f22575f80fd5b919050565b5f82601f830112610f36575f80fd5b8135610f49610f4482610eea565b610eba565b8082825260208201915060208360071b860101925085831115610f6a575f80fd5b602085015b83811015610fdf5760808188031215610f86575f80fd5b610f8e610e70565b610f9782610f0c565b815260208281013590820152610faf60408301610f0c565b6040820152606082013563ffffffff81168114610fca575f80fd5b60608201528352602090920191608001610f6f565b5095945050505050565b5f60208284031215610ff9575f80fd5b81356001600160401b0381111561100e575f80fd5b61101a84828501610f27565b949350505050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b60208152815160208201525f602083015160408084015261101a6060840182611022565b5f60608284031215611084575f80fd5b50919050565b5f806040838503121561109b575f80fd5b82356001600160401b038111156110b0575f80fd5b6110bc85828601610f27565b92505060208301356001600160401b038111156110d7575f80fd5b6110e385828601611074565b9150509250929050565b5f805f606084860312156110ff575f80fd5b61110884610f0c565b925061111660208501610f0c565b929592945050506040919091013590565b5f805f60608486031215611139575f80fd5b61114284610f0c565b925060208401356001600160401b0381111561115c575f80fd5b61116886828701610f27565b92505060408401356001600160401b03811115611183575f80fd5b61118f86828701611074565b9150509250925092565b5f805f606084860312156111ab575f80fd5b8335925060208401356001600160401b038111156111c7575f80fd5b8401601f810186136111d7575f80fd5b80356111e5610f4482610eea565b8082825260208201915060208360061b850101925088831115611206575f80fd5b6020840193505b82841015611254576040848a031215611224575f80fd5b61122c610e98565b61123585610f0c565b815260208581013581830152908352604090940193919091019061120d565b945050505060408401356001600160401b03811115611271575f80fd5b61118f86828701610f27565b634e487b7160e01b5f52603260045260245ffd5b80516001600160a01b039081168352602080830151908401526040808301519091169083015260609081015163ffffffff16910152565b82815260a08101610dd06020830184611291565b81515f90829060208501835b828110156113065781518452602093840193909101906001016112e8565b509195945050505050565b5f8235605e19833603018112611325575f80fd5b9190910192915050565b5f808335601e19843603018112611344575f80fd5b8301803591506001600160401b0382111561135d575f80fd5b6020019150600681901b3603821315611374575f80fd5b9250929050565b5f8151808452602084019350602083015f5b828110156113b6576113a0868351611291565b608095909501946020919091019060010161138d565b5093949350505050565b602081525f610d2c602083018461137b565b5f60608201858352606060208401528085518083526080850191506020870192505f5b818110156114315761141b83855180516001600160a01b03168252602090810151910152565b60209390930192604092909201916001016113f5565b50508381036040850152611445818661137b565b979650505050505050565b5f60208284031215611460575f80fd5b610d2c82610f0c565b5f808335601e1984360301811261147e575f80fd5b8301803591506001600160401b03821115611497575f80fd5b602001915036819003821315611374575f80fd5b5f8151808452602084019350602083015f5b828110156113b6576114e386835180516001600160a01b03168252602090810151910152565b60409590950194602091909101906001016114bd565b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b60c081525f61012082018935601e198b360301811261153e575f80fd5b8a016020810190356001600160401b03811115611559575f80fd5b8060061b360382131561156a575f80fd5b606060c086015291829052905f9061014085015b818310156115b7576001600160a01b0361159785610f0c565b16815260208481013590820152604093840193600193909301920161157e565b60208d81013560e088015260408e013561010088018190528783039188019190915293506115e5818d6114ab565b93505050506115ff60408401896001600160a01b03169052565b86606084015282810360808401526116178187611022565b905082810360a084015261162c8185876114f9565b9a9950505050505050505050565b81810381811115610d2f57634e487b7160e01b5f52601160045260245ffd5b5f60208284031215611669575f80fd5b81518015158114610dd0575f80fd5b5f82518060208501845e5f92019182525091905056fe4f75747075745b5d206f757470757473294f7574707574286164647265737320746f6b656e2c75696e7432353620616d6f756e742c6164647265737320726563697069656e742c75696e74333220636861696e496429546f6b656e5065726d697373696f6e73286164647265737320746f6b656e2c75696e7432353620616d6f756e7429a2646970667358221220322fe8b32453c17a0efd4f702e415163224990591aefcb85e698039547a4393764736f6c634300081a0033"); + +/// The HostOrders contract bytecode. +pub const HOST_ORDERS_BYTECODE: Bytes = bytes!("0x608060405260043610610033575f3560e01c80630828f139146100375780631bbf03a814610073578063897bc0d61461009b575b5f80fd5b348015610042575f80fd5b5061005d60048036038101906100589190610d0e565b6100b7565b60405161006a9190610e07565b60405180910390f35b34801561007e575f80fd5b5061009960048036038101906100949190610e49565b6101ff565b005b6100b560048036038101906100b09190610d0e565b610282565b005b6100bf610a24565b5f825190505f8167ffffffffffffffff8111156100df576100de610a93565b5b60405190808252806020026020018201604052801561010d5781602001602082028036833780820191505090505b5090505f5b828110156101a7577f988262d9186cf8a1cd1dd5e2cc7bfa353f55a542d86db1fcd06e076a6544250d85828151811061014e5761014d610ebf565b5b6020026020010151604051602001610167929190610f7b565b604051602081830303815290604052805190602001208282815181106101905761018f610ebf565b5b602002602001018181525050806001019050610112565b50806040516020016101b99190611053565b60405160208183030381529060405280519060200120835f0181815250506040518060c00160405280608481526020016117286084913983602001819052505050919050565b6102076102d5565b61023f610213836100b7565b6102398484805f01906102269190611075565b805f0190610234919061109c565b610302565b8361055f565b7f14b3027353aba71f468d178fdede9ac211a25ae484028823bce1e6700e58e6248260405161026e91906111f9565b60405180910390a161027e61062b565b5050565b61028a6102d5565b6102938161064f565b7f14b3027353aba71f468d178fdede9ac211a25ae484028823bce1e6700e58e624816040516102c291906111f9565b60405180910390a16102d261062b565b50565b6102dd6107dd565b61030060016102f26102ed61081e565b610847565b61085090919063ffffffff16565b565b606083518383905014610341576040517fff633a3800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8282905067ffffffffffffffff81111561035e5761035d610a93565b5b60405190808252806020026020018201604052801561039757816020015b610384610a40565b81526020019060019003908161037c5790505b5090505f5b83839050811015610557578481815181106103ba576103b9610ebf565b5b60200260200101515f015173ffffffffffffffffffffffffffffffffffffffff168484838181106103ee576103ed610ebf565b5b9050604002015f0160208101906104059190611219565b73ffffffffffffffffffffffffffffffffffffffff1614610452576040517fbece19e600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b84818151811061046557610464610ebf565b5b60200260200101516020015184848381811061048457610483610ebf565b5b90506040020160200135146104c5576040517fbece19e600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405180604001604052808683815181106104e3576104e2610ebf565b5b60200260200101516040015173ffffffffffffffffffffffffffffffffffffffff16815260200186838151811061051d5761051c610ebf565b5b60200260200101516020015181525082828151811061053f5761053e610ebf565b5b6020026020010181905250808060010191505061039c565b509392505050565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663fe8ec1a782805f01906105aa9190611075565b848460200160208101906105be9190611219565b875f015188602001518780604001906105d79190611244565b6040518863ffffffff1660e01b81526004016105f997969594939291906115fb565b5f604051808303815f87803b158015610610575f80fd5b505af1158015610622573d5f803e3d5ffd5b50505050505050565b61064d5f61063f61063a61081e565b610847565b61085090919063ffffffff16565b565b5f3490505f5b82518110156107d8575f73ffffffffffffffffffffffffffffffffffffffff1683828151811061068857610687610ebf565b5b60200260200101515f015173ffffffffffffffffffffffffffffffffffffffff1603610744578281815181106106c1576106c0610ebf565b5b602002602001015160200151826106d891906116a5565b915061073f8382815181106106f0576106ef610ebf565b5b60200260200101516020015184838151811061070f5761070e610ebf565b5b60200260200101516040015173ffffffffffffffffffffffffffffffffffffffff1661085790919063ffffffff16565b6107cb565b6107ca3384838151811061075b5761075a610ebf565b5b60200260200101516040015185848151811061077a57610779610ebf565b5b60200260200101516020015186858151811061079957610798610ebf565b5b60200260200101515f015173ffffffffffffffffffffffffffffffffffffffff1661090e909392919063ffffffff16565b5b8080600101915050610655565b505050565b6107e5610963565b1561081c576040517f3ee5aeb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b5f7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005f1b905090565b5f819050919050565b80825d5050565b8047101561089e5747816040517fcf4791810000000000000000000000000000000000000000000000000000000081526004016108959291906116e7565b60405180910390fd5b6108b7828260405180602001604052805f815250610981565b61090a575f6108c4610997565b11156108d7576108d261099e565b610909565b6040517fd6bda27500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5b5050565b61091c8484848460016109a9565b61095d57836040517f5274afe7000000000000000000000000000000000000000000000000000000008152600401610954919061170e565b60405180910390fd5b50505050565b5f61097c61097761097261081e565b610847565b610a1a565b905090565b5f805f83516020850186885af190509392505050565b5f3d905090565b6040513d5f823e3d81fd5b5f806323b872dd60e01b9050604051815f525f1960601c87166004525f1960601c86166024528460445260205f60645f808c5af1925060015f51148316610a075783831516156109fb573d5f823e3d81fd5b5f883b113d1516831692505b806040525f606052505095945050505050565b5f815c9050919050565b60405180604001604052805f8019168152602001606081525090565b60405180604001604052805f73ffffffffffffffffffffffffffffffffffffffff1681526020015f81525090565b5f604051905090565b5f80fd5b5f80fd5b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b610ac982610a83565b810181811067ffffffffffffffff82111715610ae857610ae7610a93565b5b80604052505050565b5f610afa610a6e565b9050610b068282610ac0565b919050565b5f67ffffffffffffffff821115610b2557610b24610a93565b5b602082029050602081019050919050565b5f80fd5b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610b6782610b3e565b9050919050565b610b7781610b5d565b8114610b81575f80fd5b50565b5f81359050610b9281610b6e565b92915050565b5f819050919050565b610baa81610b98565b8114610bb4575f80fd5b50565b5f81359050610bc581610ba1565b92915050565b5f63ffffffff82169050919050565b610be381610bcb565b8114610bed575f80fd5b50565b5f81359050610bfe81610bda565b92915050565b5f60808284031215610c1957610c18610b3a565b5b610c236080610af1565b90505f610c3284828501610b84565b5f830152506020610c4584828501610bb7565b6020830152506040610c5984828501610b84565b6040830152506060610c6d84828501610bf0565b60608301525092915050565b5f610c8b610c8684610b0b565b610af1565b90508083825260208201905060808402830185811115610cae57610cad610b36565b5b835b81811015610cd75780610cc38882610c04565b845260208401935050608081019050610cb0565b5050509392505050565b5f82601f830112610cf557610cf4610a7f565b5b8135610d05848260208601610c79565b91505092915050565b5f60208284031215610d2357610d22610a77565b5b5f82013567ffffffffffffffff811115610d4057610d3f610a7b565b5b610d4c84828501610ce1565b91505092915050565b5f819050919050565b610d6781610d55565b82525050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f610d9f82610d6d565b610da98185610d77565b9350610db9818560208601610d87565b610dc281610a83565b840191505092915050565b5f604083015f830151610de25f860182610d5e565b5060208301518482036020860152610dfa8282610d95565b9150508091505092915050565b5f6020820190508181035f830152610e1f8184610dcd565b905092915050565b5f80fd5b5f60608284031215610e4057610e3f610e27565b5b81905092915050565b5f8060408385031215610e5f57610e5e610a77565b5b5f83013567ffffffffffffffff811115610e7c57610e7b610a7b565b5b610e8885828601610ce1565b925050602083013567ffffffffffffffff811115610ea957610ea8610a7b565b5b610eb585828601610e2b565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b610ef581610d55565b82525050565b610f0481610b5d565b82525050565b610f1381610b98565b82525050565b610f2281610bcb565b82525050565b608082015f820151610f3c5f850182610efb565b506020820151610f4f6020850182610f0a565b506040820151610f626040850182610efb565b506060820151610f756060850182610f19565b50505050565b5f60a082019050610f8e5f830185610eec565b610f9b6020830184610f28565b9392505050565b5f81519050919050565b5f81905092915050565b5f819050602082019050919050565b610fce81610d55565b82525050565b5f610fdf8383610fc5565b60208301905092915050565b5f602082019050919050565b5f61100182610fa2565b61100b8185610fac565b935061101683610fb6565b805f5b8381101561104657815161102d8882610fd4565b975061103883610feb565b925050600181019050611019565b5085935050505092915050565b5f61105e8284610ff7565b915081905092915050565b5f80fd5b5f80fd5b5f80fd5b5f823560016060038336030381126110905761108f611069565b5b80830191505092915050565b5f80833560016020038436030381126110b8576110b7611069565b5b80840192508235915067ffffffffffffffff8211156110da576110d961106d565b5b6020830192506040820236038313156110f6576110f5611071565b5b509250929050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b608082015f82015161113b5f850182610efb565b50602082015161114e6020850182610f0a565b5060408201516111616040850182610efb565b5060608201516111746060850182610f19565b50505050565b5f6111858383611127565b60808301905092915050565b5f602082019050919050565b5f6111a7826110fe565b6111b18185611108565b93506111bc83611118565b805f5b838110156111ec5781516111d3888261117a565b97506111de83611191565b9250506001810190506111bf565b5085935050505092915050565b5f6020820190508181035f830152611211818461119d565b905092915050565b5f6020828403121561122e5761122d610a77565b5b5f61123b84828501610b84565b91505092915050565b5f80833560016020038436030381126112605761125f611069565b5b80840192508235915067ffffffffffffffff8211156112825761128161106d565b5b60208301925060018202360383131561129e5761129d611071565b5b509250929050565b5f80fd5b5f80fd5b5f80fd5b5f80833560016020038436030381126112ce576112cd6112ae565b5b83810192508235915060208301925067ffffffffffffffff8211156112f6576112f56112a6565b5b60408202360383131561130c5761130b6112aa565b5b509250929050565b5f82825260208201905092915050565b5f819050919050565b5f61133b6020840184610b84565b905092915050565b5f6113516020840184610bb7565b905092915050565b604082016113695f83018361132d565b6113755f850182610efb565b506113836020830183611343565b6113906020850182610f0a565b50505050565b5f6113a18383611359565b60408301905092915050565b5f82905092915050565b5f604082019050919050565b5f6113ce8385611314565b93506113d982611324565b805f5b85811015611411576113ee82846113ad565b6113f88882611396565b9750611403836113b7565b9250506001810190506113dc565b5085925050509392505050565b5f6060830161142f5f8401846112b2565b8583035f8701526114418382846113c3565b925050506114526020840184611343565b61145f6020860182610f0a565b5061146d6040840184611343565b61147a6040860182610f0a565b508091505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b604082015f8201516114c25f850182610efb565b5060208201516114d56020850182610f0a565b50505050565b5f6114e683836114ae565b60408301905092915050565b5f602082019050919050565b5f61150882611485565b611512818561148f565b935061151d8361149f565b805f5b8381101561154d57815161153488826114db565b975061153f836114f2565b925050600181019050611520565b5085935050505092915050565b61156381610b5d565b82525050565b5f82825260208201905092915050565b5f61158382610d6d565b61158d8185611569565b935061159d818560208601610d87565b6115a681610a83565b840191505092915050565b5f82825260208201905092915050565b828183375f83830152505050565b5f6115da83856115b1565b93506115e78385846115c1565b6115f083610a83565b840190509392505050565b5f60c0820190508181035f830152611613818a61141e565b9050818103602083015261162781896114fe565b9050611636604083018861155a565b6116436060830187610eec565b81810360808301526116558186611579565b905081810360a083015261166a8184866115cf565b905098975050505050505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6116af82610b98565b91506116ba83610b98565b92508282039050818111156116d2576116d1611678565b5b92915050565b6116e181610b98565b82525050565b5f6040820190506116fa5f8301856116d8565b61170760208301846116d8565b9392505050565b5f6020820190506117215f83018461155a565b9291505056fe4f75747075745b5d206f757470757473294f7574707574286164647265737320746f6b656e2c75696e7432353620616d6f756e742c6164647265737320726563697069656e742c75696e74333220636861696e496429546f6b656e5065726d697373696f6e73286164647265737320746f6b656e2c75696e7432353620616d6f756e7429a26469706673582212204184dbeabea50eb3ba11a8c8ef767e5223cdcc29a0ad181391654235ea7fda5164736f6c634300081a0033"); + +/// The Host Passage contract bytecode. +pub const HOST_PASSAGE_BYTECODE: Bytes = bytes!("0x6080604052600436106100aa575f3560e01c8063b32ed95e11610063578063b32ed95e14610221578063b7e1917c14610249578063d014c01f14610273578063d9caed121461028f578063ea3b9ba1146102b7578063eba1f981146102d3576100db565b806322ac3885146101075780633930e3911461012f578063416ef5a81461016b5780634f8b4a3c14610193578063942c39db146101cf57806395577b72146101f7576100db565b366100db576100d97f00000000000000000000000000000000000000000000000000000000000000003361030f565b005b6101057f00000000000000000000000000000000000000000000000000000000000000003361030f565b005b348015610112575f80fd5b5061012d60048036038101906101289190610f72565b61036b565b005b34801561013a575f80fd5b5061015560048036038101906101509190610fd6565b6103ba565b60405161016291906110d6565b60405180910390f35b348015610176575f80fd5b50610191600480360381019061018c91906110f6565b610464565b005b34801561019e575f80fd5b506101b960048036038101906101b49190611146565b610495565b6040516101c6919061118b565b60405180910390f35b3480156101da575f80fd5b506101f560048036038101906101f091906111c6565b6104b1565b005b348015610202575f80fd5b5061020b610502565b6040516102189190611241565b60405180910390f35b34801561022c575f80fd5b5061024760048036038101906102429190611284565b610526565b005b348015610254575f80fd5b5061025d61060d565b60405161026a91906112d1565b60405180910390f35b61028d60048036038101906102889190611146565b610631565b005b34801561029a575f80fd5b506102b560048036038101906102b091906110f6565b61065e565b005b6102d160048036038101906102cc9190610fd6565b61030f565b005b3480156102de575f80fd5b506102f960048036038101906102f49190611146565b6107ea565b60405161030691906110d6565b60405180910390f35b5f340315610367578073ffffffffffffffffffffffffffffffffffffffff16827f5f67e0a44fbb8ec0f3794c6687c657244a50a7da2411d14707aa219d86b854923460405161035e9190611241565b60405180910390a35b5050565b61037361088d565b6103a03330838573ffffffffffffffffffffffffffffffffffffffff166108ba909392919063ffffffff16565b6103ac8484848461090f565b6103b4610a0e565b50505050565b6103c2610e93565b7fc5d03356b54a64d088070694ab907941e70a5ea4ae772ff6df7f162d054037b660405180604001604052808581526020018473ffffffffffffffffffffffffffffffffffffffff1681525060405160200161041f929190611344565b60405160208183030381529060405280519060200120815f0181815250506040518060a00160405280607e815260200161163e607e9139816020018190525092915050565b6104907f000000000000000000000000000000000000000000000000000000000000000084848461036b565b505050565b5f602052805f5260405f205f915054906101000a900460ff1681565b6104b961088d565b6104cc6104c684846103ba565b82610a32565b6104f58383835f015f015f0160208101906104e79190611146565b845f015f016020013561090f565b6104fd610a0e565b505050565b7f000000000000000000000000000000000000000000000000000000000000000081565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146105ab576040517f21dba17e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8015155f808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff16151514610609576106088282610b01565b5b5050565b7f000000000000000000000000000000000000000000000000000000000000000081565b61065b7f00000000000000000000000000000000000000000000000000000000000000008261030f565b50565b61066661088d565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146106eb576040517f21dba17e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361074c57610747818373ffffffffffffffffffffffffffffffffffffffff16610b9e90919063ffffffff16565b610778565b61077782828573ffffffffffffffffffffffffffffffffffffffff16610c559092919063ffffffff16565b5b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f2717ead6b9200dd235aad468c9809ea400fe33ac69b5bfaa6d3e90fc922b6398836040516107d59190611241565b60405180910390a36107e5610a0e565b505050565b6107f2610e93565b7fa91a2bac0243280e19cfd0a3ae9d7639a15fa41d49eab3e0bf320c8485cc66a960405180602001604052808473ffffffffffffffffffffffffffffffffffffffff16815250604051602001610849929190611385565b60405160208183030381529060405280519060200120815f0181815250506040518060a00160405280606481526020016116bc606491398160200181905250919050565b610895610ca8565b6108b860016108aa6108a5610ce9565b610d12565b610d1b90919063ffffffff16565b565b6108c8848484846001610d22565b61090957836040517f5274afe700000000000000000000000000000000000000000000000000000000815260040161090091906112d1565b60405180910390fd5b50505050565b5f810315610a08575f808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff166109a157816040517f087fe76b00000000000000000000000000000000000000000000000000000000815260040161099891906112d1565b60405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16857f918d96675770fc73e96feacdd04b31ed125e0e5e0223938cd0043a42228a49e4846040516109ff9190611241565b60405180910390a45b50505050565b610a305f610a22610a1d610ce9565b610d12565b610d1b90919063ffffffff16565b565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663137c29fe825f01610a82845f015f0160200135610d93565b846080016020810190610a959190611146565b865f01518760200151878060a00190610aae91906113b8565b6040518863ffffffff1660e01b8152600401610ad097969594939291906115a4565b5f604051808303815f87803b158015610ae7575f80fd5b505af1158015610af9573d5f803e3d5ffd5b505050505050565b805f808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff0219169083151502179055508015158273ffffffffffffffffffffffffffffffffffffffff167f2f8601534249821eb3b2cf9f1d88960ce5b7feb89b9e4a117d2ac7725c56539360405160405180910390a35050565b80471015610be55747816040517fcf479181000000000000000000000000000000000000000000000000000000008152600401610bdc929190611616565b60405180910390fd5b610bfe828260405180602001604052805f815250610de1565b610c51575f610c0b610df7565b1115610c1e57610c19610dfe565b610c50565b6040517fd6bda27500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5b5050565b610c628383836001610e09565b610ca357826040517f5274afe7000000000000000000000000000000000000000000000000000000008152600401610c9a91906112d1565b60405180910390fd5b505050565b610cb0610e6b565b15610ce7576040517f3ee5aeb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b5f7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005f1b905090565b5f819050919050565b80825d5050565b5f806323b872dd60e01b9050604051815f525f1960601c87166004525f1960601c86166024528460445260205f60645f808c5af1925060015f51148316610d80578383151615610d74573d5f823e3d81fd5b5f883b113d1516831692505b806040525f606052505095945050505050565b610d9b610eaf565b30815f019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff168152505081816020018181525050919050565b5f805f83516020850186885af190509392505050565b5f3d905090565b6040513d5f823e3d81fd5b5f8063a9059cbb60e01b9050604051815f525f1960601c86166004528460245260205f60445f808b5af1925060015f51148316610e5d578383151615610e51573d5f823e3d81fd5b5f873b113d1516831692505b806040525050949350505050565b5f610e84610e7f610e7a610ce9565b610d12565b610e89565b905090565b5f815c9050919050565b60405180604001604052805f8019168152602001606081525090565b60405180604001604052805f73ffffffffffffffffffffffffffffffffffffffff1681526020015f81525090565b5f80fd5b5f80fd5b5f819050919050565b610ef781610ee5565b8114610f01575f80fd5b50565b5f81359050610f1281610eee565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610f4182610f18565b9050919050565b610f5181610f37565b8114610f5b575f80fd5b50565b5f81359050610f6c81610f48565b92915050565b5f805f8060808587031215610f8a57610f89610edd565b5b5f610f9787828801610f04565b9450506020610fa887828801610f5e565b9350506040610fb987828801610f5e565b9250506060610fca87828801610f04565b91505092959194509250565b5f8060408385031215610fec57610feb610edd565b5b5f610ff985828601610f04565b925050602061100a85828601610f5e565b9150509250929050565b5f819050919050565b61102681611014565b82525050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f61106e8261102c565b6110788185611036565b9350611088818560208601611046565b61109181611054565b840191505092915050565b5f604083015f8301516110b15f86018261101d565b50602083015184820360208601526110c98282611064565b9150508091505092915050565b5f6020820190508181035f8301526110ee818461109c565b905092915050565b5f805f6060848603121561110d5761110c610edd565b5b5f61111a86828701610f5e565b935050602061112b86828701610f5e565b925050604061113c86828701610f04565b9150509250925092565b5f6020828403121561115b5761115a610edd565b5b5f61116884828501610f5e565b91505092915050565b5f8115159050919050565b61118581611171565b82525050565b5f60208201905061119e5f83018461117c565b92915050565b5f80fd5b5f60c082840312156111bd576111bc6111a4565b5b81905092915050565b5f805f606084860312156111dd576111dc610edd565b5b5f6111ea86828701610f04565b93505060206111fb86828701610f5e565b925050604084013567ffffffffffffffff81111561121c5761121b610ee1565b5b611228868287016111a8565b9150509250925092565b61123b81610ee5565b82525050565b5f6020820190506112545f830184611232565b92915050565b61126381611171565b811461126d575f80fd5b50565b5f8135905061127e8161125a565b92915050565b5f806040838503121561129a57611299610edd565b5b5f6112a785828601610f5e565b92505060206112b885828601611270565b9150509250929050565b6112cb81610f37565b82525050565b5f6020820190506112e45f8301846112c2565b92915050565b6112f381611014565b82525050565b61130281610ee5565b82525050565b61131181610f37565b82525050565b604082015f82015161132b5f8501826112f9565b50602082015161133e6020850182611308565b50505050565b5f6060820190506113575f8301856112ea565b6113646020830184611317565b9392505050565b602082015f82015161137f5f850182611308565b50505050565b5f6040820190506113985f8301856112ea565b6113a5602083018461136b565b9392505050565b5f80fd5b5f80fd5b5f80fd5b5f80833560016020038436030381126113d4576113d36113ac565b5b80840192508235915067ffffffffffffffff8211156113f6576113f56113b0565b5b602083019250600182023603831315611412576114116113b4565b5b509250929050565b5f82905092915050565b5f6114326020840184610f5e565b905092915050565b5f6114486020840184610f04565b905092915050565b604082016114605f830183611424565b61146c5f850182611308565b5061147a602083018361143a565b61148760208501826112f9565b50505050565b6080820161149d5f83018361141a565b6114a95f850182611450565b506114b7604083018361143a565b6114c460408501826112f9565b506114d2606083018361143a565b6114df60608501826112f9565b50505050565b604082015f8201516114f95f850182611308565b50602082015161150c60208501826112f9565b50505050565b5f82825260208201905092915050565b5f61152c8261102c565b6115368185611512565b9350611546818560208601611046565b61154f81611054565b840191505092915050565b5f82825260208201905092915050565b828183375f83830152505050565b5f611583838561155a565b935061159083858461156a565b61159983611054565b840190509392505050565b5f610140820190506115b85f83018a61148d565b6115c560808301896114e5565b6115d260c08301886112c2565b6115df60e08301876112ea565b8181036101008301526115f28186611522565b9050818103610120830152611608818486611578565b905098975050505050505050565b5f6040820190506116295f830185611232565b6116366020830184611232565b939250505056fe456e7465725769746e657373207769746e65737329456e7465725769746e6573732875696e7432353620726f6c6c7570436861696e49642c6164647265737320726f6c6c7570526563697069656e7429546f6b656e5065726d697373696f6e73286164647265737320746f6b656e2c75696e7432353620616d6f756e7429457869745769746e657373207769746e65737329457869745769746e657373286164647265737320686f7374526563697069656e7429546f6b656e5065726d697373696f6e73286164647265737320746f6b656e2c75696e7432353620616d6f756e7429a2646970667358221220500ef88c22eec3ae76b6ccee7042f4fe83ba0d0b849aeab36565f5186d511c8164736f6c634300081a0033"); diff --git a/crates/test-utils/src/contracts/token.rs b/crates/test-utils/src/contracts/token.rs index 1a370c8c..0dd17098 100644 --- a/crates/test-utils/src/contracts/token.rs +++ b/crates/test-utils/src/contracts/token.rs @@ -1,8 +1,18 @@ use alloy::{ - primitives::{bytes, Bytes, U256}, + primitives::{bytes, Address, Bytes, Keccak256, U256}, uint, }; +use trevm::revm::{ + state::{Account, AccountInfo, Bytecode, EvmState, EvmStorageSlot}, + Database, DatabaseCommit, +}; +/// Slot at which the balances mapping is stored. +pub const BALANCES_SLOT: U256 = uint!(0_U256); +/// Slot at which the allowances mapping is stored. +pub const ALLOWANCES_SLOT: U256 = uint!(1_U256); +/// Slot at which the total supply is stored. +pub const TOTAL_SUPPLY_SLOT: U256 = uint!(2_U256); /// Slot at which the token name is stored. pub const NAME_SLOT: U256 = uint!(3_U256); /// Slot at which the token symbol is stored. @@ -28,3 +38,65 @@ pub const WETH_SYMBOL: U256 = /// Token Contract bytecode, from pecorino genesis. pub const TOKEN_BYTECODE: Bytes = bytes!("0x608060405234801561000f575f80fd5b50600436106100fb575f3560e01c806370a082311161009357806395d89b411161006357806395d89b4114610246578063a9059cbb1461024e578063dd62ed3e14610261578063f2fde38b14610299575f80fd5b806370a08231146101e8578063715018a61461021057806379cc6790146102185780638da5cb5b1461022b575f80fd5b8063313ce567116100ce578063313ce5671461016557806332424aa31461019957806340c10f19146101c057806342966c68146101d3575f80fd5b806306fdde03146100ff578063095ea7b31461011d57806318160ddd1461014057806323b872dd14610152575b5f80fd5b6101076102ac565b60405161011491906107f0565b60405180910390f35b61013061012b366004610840565b61033c565b6040519015158152602001610114565b6002545b604051908152602001610114565b610130610160366004610868565b610355565b7f00000000000000000000000000000000000000000000000000000000000000085b60405160ff9091168152602001610114565b6101877f000000000000000000000000000000000000000000000000000000000000000881565b6101306101ce366004610840565b610378565b6101e66101e13660046108a2565b610394565b005b6101446101f63660046108b9565b6001600160a01b03165f9081526020819052604090205490565b6101e66103a1565b6101e6610226366004610840565b6103b4565b6005546040516001600160a01b039091168152602001610114565b6101076103cd565b61013061025c366004610840565b6103dc565b61014461026f3660046108d9565b6001600160a01b039182165f90815260016020908152604080832093909416825291909152205490565b6101e66102a73660046108b9565b6103e9565b6060600380546102bb9061090a565b80601f01602080910402602001604051908101604052809291908181526020018280546102e79061090a565b80156103325780601f1061030957610100808354040283529160200191610332565b820191905f5260205f20905b81548152906001019060200180831161031557829003601f168201915b5050505050905090565b5f33610349818585610428565b60019150505b92915050565b5f3361036285828561043a565b61036d8585856104b5565b506001949350505050565b5f610381610512565b61038b838361053f565b50600192915050565b61039e3382610573565b50565b6103a9610512565b6103b25f6105a7565b565b6103bf82338361043a565b6103c98282610573565b5050565b6060600480546102bb9061090a565b5f336103498185856104b5565b6103f1610512565b6001600160a01b03811661041f57604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b61039e816105a7565b61043583838360016105f8565b505050565b6001600160a01b038381165f908152600160209081526040808320938616835292905220545f1981146104af57818110156104a157604051637dc7a0d960e11b81526001600160a01b03841660048201526024810182905260448101839052606401610416565b6104af84848484035f6105f8565b50505050565b6001600160a01b0383166104de57604051634b637e8f60e11b81525f6004820152602401610416565b6001600160a01b0382166105075760405163ec442f0560e01b81525f6004820152602401610416565b6104358383836106ca565b6005546001600160a01b031633146103b25760405163118cdaa760e01b8152336004820152602401610416565b6001600160a01b0382166105685760405163ec442f0560e01b81525f6004820152602401610416565b6103c95f83836106ca565b6001600160a01b03821661059c57604051634b637e8f60e11b81525f6004820152602401610416565b6103c9825f836106ca565b600580546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b6001600160a01b0384166106215760405163e602df0560e01b81525f6004820152602401610416565b6001600160a01b03831661064a57604051634a1406b160e11b81525f6004820152602401610416565b6001600160a01b038085165f90815260016020908152604080832093871683529290522082905580156104af57826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040516106bc91815260200190565b60405180910390a350505050565b6001600160a01b0383166106f4578060025f8282546106e99190610942565b909155506107649050565b6001600160a01b0383165f90815260208190526040902054818110156107465760405163391434e360e21b81526001600160a01b03851660048201526024810182905260448101839052606401610416565b6001600160a01b0384165f9081526020819052604090209082900390555b6001600160a01b0382166107805760028054829003905561079e565b6001600160a01b0382165f9081526020819052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516107e391815260200190565b60405180910390a3505050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b80356001600160a01b038116811461083b575f80fd5b919050565b5f8060408385031215610851575f80fd5b61085a83610825565b946020939093013593505050565b5f805f6060848603121561087a575f80fd5b61088384610825565b925061089160208501610825565b929592945050506040919091013590565b5f602082840312156108b2575f80fd5b5035919050565b5f602082840312156108c9575f80fd5b6108d282610825565b9392505050565b5f80604083850312156108ea575f80fd5b6108f383610825565b915061090160208401610825565b90509250929050565b600181811c9082168061091e57607f821691505b60208210810361093c57634e487b7160e01b5f52602260045260245ffd5b50919050565b8082018082111561034f57634e487b7160e01b5f52601160045260245ffdfea2646970667358221220a373554eb2e797644d86447291ab79350d58226b5edc56a67e2d820f602062bb64736f6c634300081a0033"); + +/// Deploys a token contract at the specified address in the given database. +pub fn deploy_token_at( + db: &mut Db, + addr: Address, + name: U256, + symbol: U256, +) -> Result<(), Db::Error> { + let info: AccountInfo = + db.basic(addr)?.unwrap_or_default().with_code(Bytecode::new_legacy(TOKEN_BYTECODE)); + + let acct = Account { info, transaction_id: 0, ..Default::default() } + .with_storage( + [ + (NAME_SLOT, EvmStorageSlot::new(name, 0)), + (SYMBOL_SLOT, EvmStorageSlot::new(symbol, 0)), + (MINTER_SLOT, EvmStorageSlot::new(MINTER, 0)), + ] + .into_iter(), + ) + .with_touched_mark(); + + let changes: EvmState = [(addr, acct)].into_iter().collect(); + db.commit(changes); + + Ok(()) +} + +/// Deploys a WETH token contract at the specified address in the given database. +pub fn deploy_weth_at( + db: &mut Db, + addr: Address, +) -> Result<(), Db::Error> { + deploy_token_at(db, addr, WETH_NAME, WETH_SYMBOL) +} + +/// Deploys a WBTC token contract at the specified address in the given database. +pub fn deploy_wbtc_at( + db: &mut Db, + addr: Address, +) -> Result<(), Db::Error> { + deploy_token_at(db, addr, WBTC_NAME, WBTC_SYMBOL) +} + +fn mapping_slot(base: U256, key: Address) -> U256 { + let mut hasher = Keccak256::new(); + hasher.update([0u8; 12]); + hasher.update(key); + hasher.update(base.to_be_bytes::<32>()); + U256::from_be_bytes::<32>(hasher.finalize().into()) +} + +/// Computes the storage slot for the balance of the given owner. +pub fn balance_slot_for(owner: Address) -> U256 { + mapping_slot(BALANCES_SLOT, owner) +} + +/// Computes the storage slot for the allowance mapping for the given owner and spender. +pub fn allowances_slot_for(owner: Address, spender: Address) -> U256 { + let inner_map_loc = mapping_slot(ALLOWANCES_SLOT, owner); + mapping_slot(inner_map_loc, spender) +} diff --git a/crates/test-utils/src/evm.rs b/crates/test-utils/src/evm.rs index adc7eab9..c43e3cf9 100644 --- a/crates/test-utils/src/evm.rs +++ b/crates/test-utils/src/evm.rs @@ -1,23 +1,33 @@ +use std::sync::Arc; + use crate::{ contracts::{ counter::{COUNTER_BYTECODE, COUNTER_TEST_ADDRESS}, reverts::{REVERT_BYTECODE, REVERT_TEST_ADDRESS}, - system::{RU_ORDERS_BYTECODE, RU_PASSAGE_BYTECODE}, - token::{ - MINTER, MINTER_SLOT, NAME_SLOT, SYMBOL_SLOT, TOKEN_BYTECODE, WBTC_NAME, WBTC_SYMBOL, - WETH_NAME, WETH_SYMBOL, + system::{ + HOST_ORDERS_BYTECODE, HOST_PASSAGE_BYTECODE, RU_ORDERS_BYTECODE, RU_PASSAGE_BYTECODE, }, + token::{allowances_slot_for, balance_slot_for, deploy_wbtc_at, deploy_weth_at}, }, users::TEST_USERS, }; -use alloy::{consensus::constants::ETH_TO_WEI, primitives::U256}; +use alloy::{ + consensus::constants::ETH_TO_WEI, + primitives::{Address, Bytes, KECCAK256_EMPTY, U256}, +}; use signet_constants::test_utils::*; +use signet_sim::{BlockBuild, HostEnv, RollupEnv}; use trevm::{ helpers::Ctx, revm::{ - context::CfgEnv, database::in_memory_db::InMemoryDB, inspector::NoOpInspector, - primitives::hardfork::SpecId, state::Bytecode, Inspector, + context::CfgEnv, + database::in_memory_db::InMemoryDB, + inspector::NoOpInspector, + primitives::hardfork::SpecId, + state::{Account, AccountInfo, Bytecode, EvmState, EvmStorageSlot}, + Database, DatabaseCommit, Inspector, }, + NoopBlock, }; /// Create a new Signet EVM with an in-memory database for testing. @@ -50,37 +60,10 @@ pub fn test_signet_evm_with_inspector(inspector: I) -> signet_evm::EvmNeedsBl where I: Inspector>, { - let mut evm = signet_evm::signet_evm_with_inspector(InMemoryDB::default(), inspector, TEST_SYS) - .fill_cfg(&TestCfg); - - // Set the bytecode for system contracts - evm.set_bytecode_unchecked(TEST_SYS.ru_orders(), Bytecode::new_legacy(RU_ORDERS_BYTECODE)); - evm.set_bytecode_unchecked(TEST_SYS.ru_passage(), Bytecode::new_legacy(RU_PASSAGE_BYTECODE)); - - // Set WBTC bytecode and storage - evm.set_bytecode_unchecked(RU_WBTC, Bytecode::new_legacy(TOKEN_BYTECODE)); - evm.set_storage_unchecked(RU_WBTC, NAME_SLOT, WBTC_NAME); - evm.set_storage_unchecked(RU_WBTC, SYMBOL_SLOT, WBTC_SYMBOL); - evm.set_storage_unchecked(RU_WBTC, MINTER_SLOT, MINTER); - - // Set WETH bytecode and storage - evm.set_bytecode_unchecked(RU_WETH, Bytecode::new_legacy(TOKEN_BYTECODE)); - evm.set_storage_unchecked(RU_WETH, NAME_SLOT, WETH_NAME); - evm.set_storage_unchecked(RU_WETH, SYMBOL_SLOT, WETH_SYMBOL); - evm.set_storage_unchecked(RU_WETH, MINTER_SLOT, MINTER); - - // Set the bytecode for the Counter contract - evm.set_bytecode_unchecked(COUNTER_TEST_ADDRESS, Bytecode::new_legacy(COUNTER_BYTECODE)); - - // Set the bytecode for the Revert contract - evm.set_bytecode_unchecked(REVERT_TEST_ADDRESS, Bytecode::new_legacy(REVERT_BYTECODE)); + let mut db = InMemoryDB::default(); + setup_rollup_db(&mut db).unwrap(); - // increment the balance for each test signer - TEST_USERS.iter().copied().for_each(|user| { - evm.set_balance_unchecked(user, U256::from(1000 * ETH_TO_WEI)); - }); - - evm + signet_evm::signet_evm_with_inspector(db, inspector, TEST_SYS).fill_cfg(&TestCfg) } /// Test configuration for the Signet EVM. @@ -95,3 +78,160 @@ impl trevm::Cfg for TestCfg { *spec = SpecId::default(); } } + +/// Test configuration for the Host EVM. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct HostTestCfg; + +impl trevm::Cfg for HostTestCfg { + fn fill_cfg_env(&self, cfg_env: &mut CfgEnv) { + let CfgEnv { chain_id, spec, .. } = cfg_env; + + *chain_id = HOST_CHAIN_ID; + *spec = SpecId::default(); + } +} + +/// Create a rollup EVM environment for testing the simulator +pub fn rollup_sim_env() -> RollupEnv, NoOpInspector> { + let mut ru_db = InMemoryDB::default(); + + setup_rollup_db(&mut ru_db).unwrap(); + + let ru_db = Arc::new(ru_db); + + RollupEnv::new(ru_db, TEST_SYS, &TestCfg, &NoopBlock) +} + +/// Create a host EVM environment for testing. +pub fn host_sim_env() -> HostEnv, NoOpInspector> { + let mut host_db = InMemoryDB::default(); + setup_host_db(&mut host_db).unwrap(); + let host_db = Arc::new(host_db); + + HostEnv::new(host_db, TEST_SYS, &HostTestCfg, &NoopBlock) +} + +/// Create a [`BlockBuild`] simulator environment for testing. +pub fn test_sim_env(deadline: std::time::Instant) -> BlockBuild, Arc> { + let (ru_evm, host_evm) = (rollup_sim_env(), host_sim_env()); + BlockBuild::new(ru_evm, host_evm, deadline, 10, Default::default(), 50_000_000, 50_000_000) +} + +fn modify_account(db: &mut Db, addr: Address, f: F) -> Result +where + F: FnOnce(&mut AccountInfo), + Db: Database + DatabaseCommit, +{ + let mut acct: AccountInfo = db.basic(addr)?.unwrap_or_default(); + let old = acct.clone(); + f(&mut acct); + + let mut acct: Account = acct.into(); + acct.mark_touch(); + + let changes: EvmState = [(addr, acct)].into_iter().collect(); + db.commit(changes); + Ok(old) +} + +/// Set the bytecode at the given address in the database. +fn set_bytecode_at( + db: &mut Db, + addr: Address, + code: Bytes, +) -> Result<(), Db::Error> { + modify_account(db, addr, |acct| { + acct.set_code(Bytecode::new_legacy(code)); + }) + .map(|_| ()) + .inspect(|_| { + assert_ne!(db.basic(addr).unwrap().unwrap().code_hash, KECCAK256_EMPTY); + }) +} + +fn set_balance_of( + db: &mut Db, + addr: Address, + balance: U256, +) -> Result<(), Db::Error> { + modify_account(db, addr, |acct| { + acct.balance = balance; + }) + .map(|_| ()) + .inspect(|_| { + assert_eq!(db.basic(addr).unwrap().unwrap().balance, balance); + }) +} + +fn set_storage_at( + db: &mut Db, + addr: Address, + slot: U256, + value: U256, +) -> Result<(), Db::Error> { + let mut account: Account = db.basic(addr)?.unwrap_or_default().into(); + let mut changes = EvmState::default(); + account.storage.insert(slot, EvmStorageSlot::new(value, 1)); + account.mark_touch(); + changes.insert(addr, account); + db.commit(changes); + assert_eq!(db.storage(addr, slot).unwrap(), value); + Ok(()) +} + +fn setup_db(db: &mut Db, rollup: bool) -> Result<(), Db::Error> { + let (weth, wbtc, orders, orders_bytecode, passage, passage_bytecode); + if rollup { + weth = RU_WETH; + wbtc = RU_WBTC; + orders = TEST_SYS.ru_orders(); + orders_bytecode = RU_ORDERS_BYTECODE; + passage = TEST_SYS.ru_passage(); + passage_bytecode = RU_PASSAGE_BYTECODE; + } else { + weth = HOST_WETH; + wbtc = HOST_WBTC; + orders = TEST_SYS.host_orders(); + orders_bytecode = HOST_ORDERS_BYTECODE; + passage = TEST_SYS.host_passage(); + passage_bytecode = HOST_PASSAGE_BYTECODE; + } + + // Deploy WETH and WBTC + deploy_weth_at(db, weth)?; + deploy_wbtc_at(db, wbtc)?; + + // Set the bytecode for system contracts + set_bytecode_at(db, orders, orders_bytecode)?; + set_bytecode_at(db, passage, passage_bytecode)?; + + set_bytecode_at(db, COUNTER_TEST_ADDRESS, COUNTER_BYTECODE)?; + + // Set the bytecode for the Revert contract + set_bytecode_at(db, REVERT_TEST_ADDRESS, REVERT_BYTECODE)?; + + let max_approve = U256::MAX; + let token_balance = U256::from(1000 * ETH_TO_WEI); + + // increment the balance for each test signer + TEST_USERS.iter().copied().for_each(|user| { + set_balance_of(db, user, U256::from(1000 * ETH_TO_WEI)).unwrap(); + + set_storage_at(db, weth, balance_slot_for(user), token_balance).unwrap(); + set_storage_at(db, weth, allowances_slot_for(user, orders), max_approve).unwrap(); + + set_storage_at(db, wbtc, balance_slot_for(user), token_balance).unwrap(); + set_storage_at(db, wbtc, allowances_slot_for(user, orders), max_approve).unwrap(); + }); + + Ok(()) +} + +fn setup_rollup_db(db: &mut Db) -> Result<(), Db::Error> { + setup_db(db, true) +} + +fn setup_host_db(db: &mut Db) -> Result<(), Db::Error> { + setup_db(db, false) +} diff --git a/crates/test-utils/src/lib.rs b/crates/test-utils/src/lib.rs index 264145b9..3b2799cc 100644 --- a/crates/test-utils/src/lib.rs +++ b/crates/test-utils/src/lib.rs @@ -5,3 +5,11 @@ pub mod specs; pub mod users; pub use signet_constants::test_utils as test_constants; + +/// Initialize tracing for tests. This is just for local debugging purposes. +pub fn init_tracing() { + tracing_subscriber::fmt() + .with_env_filter(tracing_subscriber::filter::EnvFilter::from_default_env()) + .try_init() + .unwrap(); +} diff --git a/crates/test-utils/src/specs/mod.rs b/crates/test-utils/src/specs/mod.rs index 5afda772..461c0466 100644 --- a/crates/test-utils/src/specs/mod.rs +++ b/crates/test-utils/src/specs/mod.rs @@ -45,13 +45,25 @@ pub fn simple_send(to: Address, amount: U256, nonce: u64, ru_chain_id: u64) -> T .into() } +/// Create and sign a simple send transaction. +pub fn signed_simple_send( + wallet: &PrivateKeySigner, + to: Address, + amount: U256, + nonce: u64, + ru_chain_id: u64, +) -> TxEnvelope { + let tx = simple_send(to, amount, nonce, ru_chain_id); + sign_tx_with_key_pair(wallet, tx) +} + /// Make a simple contract call transaction. pub fn simple_call( to: Address, input: &T, value: U256, nonce: u64, - ru_chain_id: u64, + chain_id: u64, ) -> TypedTransaction where T: SolCall, @@ -61,7 +73,7 @@ where gas_limit: 2_100_000, to: TxKind::Call(to), value, - chain_id: ru_chain_id, + chain_id, max_fee_per_gas: GWEI_TO_WEI as u128 * 100, max_priority_fee_per_gas: GWEI_TO_WEI as u128, input: input.abi_encode().into(), @@ -70,6 +82,19 @@ where .into() } +/// Create and sign a simple contract call transaction. +pub fn signed_simple_call( + wallet: &PrivateKeySigner, + to: Address, + input: &impl SolCall, + value: U256, + nonce: u64, + chain_id: u64, +) -> TxEnvelope { + let tx = simple_call(to, input, value, nonce, chain_id); + sign_tx_with_key_pair(wallet, tx) +} + /// Create a simple bundle from a list of transactions. pub fn simple_bundle( txs: Vec, @@ -86,7 +111,7 @@ pub fn simple_bundle( min_timestamp: None, max_timestamp: None, reverting_tx_hashes: vec![], - replacement_uuid: None, + replacement_uuid: Some(uuid::Uuid::new_v4().to_string()), dropping_tx_hashes: vec![], refund_percent: None, refund_recipient: None, diff --git a/crates/test-utils/tests/basic_sim.rs b/crates/test-utils/tests/basic_sim.rs index ed218a68..fb2b83de 100644 --- a/crates/test-utils/tests/basic_sim.rs +++ b/crates/test-utils/tests/basic_sim.rs @@ -1,51 +1,24 @@ use alloy::{ - consensus::{ - constants::{ETH_TO_WEI, GWEI_TO_WEI}, - Signed, TxEip1559, TxEnvelope, - }, + consensus::{constants::GWEI_TO_WEI, Signed, TxEip1559, TxEnvelope}, network::TxSigner, primitives::{Address, TxKind, U256}, signers::Signature, }; -use signet_sim::{BlockBuild, SimCache}; use signet_test_utils::{ - evm::TestCfg, + evm::test_sim_env, test_constants::*, users::{TEST_SIGNERS, TEST_USERS}, }; -use std::sync::Arc; -use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer}; -use trevm::{ - revm::{ - database::InMemoryDB, - inspector::NoOpInspector, - state::{Account, AccountInfo, EvmState}, - Database, DatabaseCommit, - }, - NoopBlock, -}; +use std::time::Instant; #[tokio::test] -pub async fn test_simulator() { - let filter = EnvFilter::from_default_env(); - let fmt = tracing_subscriber::fmt::layer().with_filter(filter); - let registry = tracing_subscriber::registry().with(fmt); - registry.try_init().unwrap(); - - let mut db = InMemoryDB::default(); - - // increment the balance for each test signer - TEST_USERS.iter().copied().for_each(|user| { - modify_account(&mut db, user, |acct| acct.balance = U256::from(1000 * ETH_TO_WEI)).unwrap(); - }); - - let db = Arc::new(db); +pub async fn complex_simulation() { + let builder = test_sim_env(Instant::now() + std::time::Duration::from_millis(200)); // Set up 10 simple sends with escalating priority fee - let sim_cache = SimCache::new(); for (i, sender) in TEST_SIGNERS.iter().enumerate() { - sim_cache.add_tx( - signed_simple_send( + builder.sim_items().add_tx( + signed_send_with_mfpg( sender, TEST_USERS[i], U256::from(1000), @@ -57,18 +30,7 @@ pub async fn test_simulator() { } // Set up the simulator - let built = BlockBuild::<_, NoOpInspector>::new( - db, - TEST_SYS, - TestCfg, - NoopBlock, - std::time::Instant::now() + std::time::Duration::from_millis(200), - 10, - sim_cache, - 100_000_000, - ) - .build() - .await; + let built = builder.build().await; assert!(!built.transactions().is_empty()); @@ -88,24 +50,7 @@ pub async fn test_simulator() { /// Modify an account with a closure and commit the modified account. /// /// This code is reproduced and modified from trevm -fn modify_account(db: &mut Db, addr: Address, f: F) -> Result -where - F: FnOnce(&mut AccountInfo), - Db: Database + DatabaseCommit, -{ - let mut acct: AccountInfo = db.basic(addr)?.unwrap_or_default(); - let old = acct.clone(); - f(&mut acct); - - let mut acct: Account = acct.into(); - acct.mark_touch(); - - let changes: EvmState = [(addr, acct)].into_iter().collect(); - db.commit(changes); - Ok(old) -} - -fn simple_send(to: Address, value: U256, mpfpg: u128) -> TxEip1559 { +fn send_with_mfpg(to: Address, value: U256, mpfpg: u128) -> TxEip1559 { TxEip1559 { nonce: 0, gas_limit: 21_000, @@ -118,13 +63,13 @@ fn simple_send(to: Address, value: U256, mpfpg: u128) -> TxEip1559 { } } -async fn signed_simple_send>( +async fn signed_send_with_mfpg>( from: S, to: Address, value: U256, mpfpg: u128, ) -> TxEnvelope { - let mut tx = simple_send(to, value, mpfpg); + let mut tx = send_with_mfpg(to, value, mpfpg); let res = from.sign_transaction(&mut tx).await.unwrap(); Signed::new_unhashed(tx, res).into() diff --git a/crates/test-utils/tests/bundle.rs b/crates/test-utils/tests/bundle.rs index c959dab6..e78cea56 100644 --- a/crates/test-utils/tests/bundle.rs +++ b/crates/test-utils/tests/bundle.rs @@ -27,6 +27,7 @@ use signet_test_utils::{ use signet_types::AggregateFills; use signet_zenith::HostOrders::{initiateCall, Filled, Input, Output}; use std::{ + borrow::Cow, sync::LazyLock, time::{Duration, Instant}, }; @@ -48,6 +49,10 @@ const INPUT_AMOUNT: U256 = uint!(100_000_000_000_000_000_000_U256); const OUTPUT_WBTC: U256 = uint!(100_U256); const OUTPUT_WETH: U256 = uint!(200_U256); +fn host_evm() -> EvmNeedsTx { + test_signet_evm_with_inspector(NoOpInspector).fill_block(&NoopBlock) +} + fn bundle_evm() -> EvmNeedsTx { let inspector: BundleInspector<_> = Layered::new(TimeLimit::new(Duration::from_secs(5)), NoOpInspector); @@ -136,7 +141,8 @@ fn test_bundle_ok() { let bundle = counter_bundle(false); - let mut driver = SignetEthBundleDriver::new(&bundle, Instant::now() + Duration::from_secs(5)); + let mut driver = + SignetEthBundleDriver::new(&bundle, host_evm(), Instant::now() + Duration::from_secs(5)); // We expect this to work. let trevm = driver.run_bundle(trevm).unwrap(); @@ -155,10 +161,11 @@ fn test_bundle_revert() { let bundle = counter_bundle(true); - let mut driver = SignetEthBundleDriver::new(&bundle, Instant::now() + Duration::from_secs(5)); + let mut driver = + SignetEthBundleDriver::new(&bundle, host_evm(), Instant::now() + Duration::from_secs(5)); let (err, trevm) = driver.run_bundle(trevm).unwrap_err().take_err(); - assert!(matches!(err, SignetEthBundleError::BundleError(BundleError::BundleReverted))); + assert!(matches!(err, SignetEthBundleError::Bundle(BundleError::BundleReverted))); // Erroring leaves the evm in a dirty state. The first txn was executed, // the second reverted, and the third was not executed. @@ -177,7 +184,8 @@ fn test_bundle_droppable() { let hash = keccak256(&bundle.txs()[1]); bundle.bundle.reverting_tx_hashes.push(hash); - let mut driver = SignetEthBundleDriver::new(&bundle, Instant::now() + Duration::from_secs(5)); + let mut driver = + SignetEthBundleDriver::new(&bundle, host_evm(), Instant::now() + Duration::from_secs(5)); // We expect this to work and drop the second transaction. let trevm = driver.run_bundle(trevm).unwrap(); @@ -200,10 +208,11 @@ fn test_order_bundle() { let bundle = order_bundle(vec![]); - let mut driver = SignetEthBundleDriver::new_with_agg_fills( + let mut driver = SignetEthBundleDriver::new_with_fill_state( &bundle, + host_evm(), Instant::now() + Duration::from_secs(5), - agg_fills, + Cow::Owned(agg_fills), ); // We expect this to work and drop the second transaction. @@ -230,10 +239,11 @@ fn test_order_bundle_revert() { // This should cause the order to be invalid, as no fill is provided. let bundle = order_bundle(vec![]); - let mut driver = SignetEthBundleDriver::new(&bundle, Instant::now() + Duration::from_secs(5)); + let mut driver = + SignetEthBundleDriver::new(&bundle, host_evm(), Instant::now() + Duration::from_secs(5)); let (err, trevm) = driver.run_bundle(trevm).unwrap_err().take_err(); - assert!(matches!(err, SignetEthBundleError::BundleError(BundleError::BundleReverted))); + assert!(matches!(err, SignetEthBundleError::Bundle(BundleError::BundleReverted))); // Erroring leaves the evm in a dirty state. The first txn was executed, // the second was dropped, and the third was not executed. @@ -244,6 +254,8 @@ fn test_order_bundle_revert() { #[test] fn test_order_bundle_droppable() { + tracing_subscriber::fmt::init(); + let trevm = bundle_evm(); let inital_balance = trevm.read_balance_ref(*ORDERER); @@ -253,11 +265,16 @@ fn test_order_bundle_droppable() { // Mark the second transaction as droppable. let hash = keccak256(&bundle.txs()[1]); bundle.bundle.reverting_tx_hashes.push(hash); + dbg!(hash); - let mut driver = SignetEthBundleDriver::new(&bundle, Instant::now() + Duration::from_secs(5)); + let mut driver = + SignetEthBundleDriver::new(&bundle, host_evm(), Instant::now() + Duration::from_secs(5)); // We expect this to work and drop the second transaction. - let trevm = driver.run_bundle(trevm).unwrap(); + let trevm = match driver.run_bundle(trevm) { + Ok(t) => t, + Err(err) => panic!("unexpected error running droppable order bundle: {:?}", err.error()), + }; // The order tx was dropped, but both sends were executed. assert_eq!(trevm.read_balance_ref(TX_0_RECIPIENT), U256::ONE); diff --git a/crates/test-utils/tests/host_sim.rs b/crates/test-utils/tests/host_sim.rs new file mode 100644 index 00000000..d51499af --- /dev/null +++ b/crates/test-utils/tests/host_sim.rs @@ -0,0 +1,502 @@ +use alloy::primitives::U256; +use signet_test_utils::{ + contracts::counter::{Counter::incrementCall, COUNTER_SLOT, COUNTER_TEST_ADDRESS}, + evm::test_sim_env, + specs::{signed_simple_call, simple_bundle}, + test_constants::*, + users::{TEST_SIGNERS, TEST_USERS}, +}; +use signet_types::UnsignedOrder; +use signet_zenith::{HostOrders::fillCall, RollupOrders::initiateCall}; +use std::time::{Duration, Instant}; +use trevm::revm::DatabaseRef; + +#[tokio::test] +async fn host_sim() { + let builder = test_sim_env(Instant::now() + Duration::from_millis(200)); + + // Set up an order, fill pair + let order = UnsignedOrder::default() + .with_input(TEST_SYS.rollup().tokens().weth(), U256::from(1000)) + .with_output( + TEST_SYS.host().tokens().weth(), + U256::from(1000), + TEST_USERS[5], + TEST_SYS.host_chain_id() as u32, + ) + .to_order(); + + let fill_outputs = order + .outputs + .clone() + .into_iter() + .map(|mut output| { + output.chainId = TEST_SYS.host_chain_id() as u32; + output + }) + .collect::>(); + + let order_call = + initiateCall { deadline: U256::MAX, inputs: order.inputs, outputs: order.outputs }; + + let fill_call = fillCall { outputs: fill_outputs }; + + // Make RU and HOST transactions + let ru_tx = signed_simple_call( + &TEST_SIGNERS[0], + TEST_SYS.ru_orders(), + &order_call, + U256::ZERO, + 0, + TEST_SYS.ru_chain_id(), + ); + + let host_tx = signed_simple_call( + &TEST_SIGNERS[1], + TEST_SYS.host_orders(), + &fill_call, + U256::ZERO, + 0, + TEST_SYS.host_chain_id(), + ); + + // Make a bundle containing the two transactions + let bundle = simple_bundle(vec![ru_tx], vec![host_tx], 0); + + builder.sim_items().add_bundle(bundle, 0).unwrap(); + + let block = builder.build().await; + + assert_eq!(block.transactions().len(), 1); + assert_eq!(block.host_transactions().len(), 1); +} + +#[tokio::test] +async fn host_sim_insufficient_fill() { + let builder = test_sim_env(Instant::now() + Duration::from_millis(200)); + + // Set up an order, fill pair + let order = UnsignedOrder::default() + .with_input(TEST_SYS.rollup().tokens().weth(), U256::from(1000)) + .with_output( + TEST_SYS.host().tokens().weth(), + U256::from(1000), + TEST_USERS[5], + TEST_SYS.host_chain_id() as u32, + ) + .to_order(); + + let fill_outputs = order + .outputs + .clone() + .into_iter() + .map(|mut output| { + output.chainId = TEST_SYS.host_chain_id() as u32; + output.amount -= U256::ONE; // Make the fill insufficient + output + }) + .collect::>(); + + let order_call = + initiateCall { deadline: U256::MAX, inputs: order.inputs, outputs: order.outputs }; + + let fill_call = fillCall { outputs: fill_outputs }; + + // Make RU and HOST transactions + let ru_tx = signed_simple_call( + &TEST_SIGNERS[0], + TEST_SYS.ru_orders(), + &order_call, + U256::ZERO, + 0, + TEST_SYS.ru_chain_id(), + ); + + let host_tx = signed_simple_call( + &TEST_SIGNERS[1], + TEST_SYS.host_orders(), + &fill_call, + U256::ZERO, + 0, + TEST_SYS.host_chain_id(), + ); + + // Make a bundle containing the two transactions + let bundle = simple_bundle(vec![ru_tx], vec![host_tx], 0); + + builder.sim_items().add_bundle(bundle, 0).unwrap(); + + let block = builder.build().await; + + assert!(block.transactions().is_empty()); + assert!(block.host_transactions().is_empty()); +} + +// Currently 0-score bundles are dropped entirely. This may change in future. +#[tokio::test] +async fn host_sim_insufficient_fill_reverting_ok() { + let builder = test_sim_env(Instant::now() + Duration::from_millis(200)); + + // Set up an order, fill pair + let order = UnsignedOrder::default() + .with_input(TEST_SYS.rollup().tokens().weth(), U256::from(1000)) + .with_output( + TEST_SYS.host().tokens().weth(), + U256::from(1000), + TEST_USERS[5], + TEST_SYS.host_chain_id() as u32, + ) + .to_order(); + + let fill_outputs = order + .outputs + .clone() + .into_iter() + .map(|mut output| { + output.chainId = TEST_SYS.host_chain_id() as u32; + output.amount -= U256::ONE; // Make the fill insufficient + output + }) + .collect::>(); + + let order_call = + initiateCall { deadline: U256::MAX, inputs: order.inputs, outputs: order.outputs }; + + let fill_call = fillCall { outputs: fill_outputs }; + + // Make RU and HOST transactions + let ru_tx = signed_simple_call( + &TEST_SIGNERS[0], + TEST_SYS.ru_orders(), + &order_call, + U256::ZERO, + 0, + TEST_SYS.ru_chain_id(), + ); + + let host_tx = signed_simple_call( + &TEST_SIGNERS[1], + TEST_SYS.host_orders(), + &fill_call, + U256::ZERO, + 0, + TEST_SYS.host_chain_id(), + ); + + // Make a bundle containing the two transactions + let ru_tx_hash = *ru_tx.hash(); + let mut bundle = simple_bundle(vec![ru_tx], vec![host_tx], 0); + bundle.bundle.reverting_tx_hashes.push(ru_tx_hash); + + builder.sim_items().add_bundle(bundle, 0).unwrap(); + + let block = builder.build().await; + + // Score 0 bundles are dropped + assert!(block.transactions().is_empty()); + assert!(block.host_transactions().is_empty()); +} + +#[tokio::test] +async fn too_much_host_gas() { + let mut builder = test_sim_env(Instant::now() + Duration::from_millis(200)); + builder.set_max_host_gas(1); // Set max host gas very low + + // Set up an order, fill pair + let order = UnsignedOrder::default() + .with_input(TEST_SYS.rollup().tokens().weth(), U256::from(1000)) + .with_output( + TEST_SYS.host().tokens().weth(), + U256::from(1000), + TEST_USERS[5], + TEST_SYS.host_chain_id() as u32, + ) + .to_order(); + + let fill_outputs = order + .outputs + .clone() + .into_iter() + .map(|mut output| { + output.chainId = TEST_SYS.host_chain_id() as u32; + output + }) + .collect::>(); + + let order_call = + initiateCall { deadline: U256::MAX, inputs: order.inputs, outputs: order.outputs }; + + let fill_call = fillCall { outputs: fill_outputs }; + + // Make RU and HOST transactions + let ru_tx = signed_simple_call( + &TEST_SIGNERS[0], + TEST_SYS.ru_orders(), + &order_call, + U256::ZERO, + 0, + TEST_SYS.ru_chain_id(), + ); + + let host_tx = signed_simple_call( + &TEST_SIGNERS[1], + TEST_SYS.host_orders(), + &fill_call, + U256::ZERO, + 0, + TEST_SYS.host_chain_id(), + ); + + // Make a bundle containing the two transactions + let ru_tx_hash = *ru_tx.hash(); + let mut bundle = simple_bundle(vec![ru_tx], vec![host_tx], 0); + bundle.bundle.reverting_tx_hashes.push(ru_tx_hash); + + builder.sim_items().add_bundle(bundle, 0).unwrap(); + + let block = builder.build().await; + + assert!(block.transactions().is_empty()); + assert!(block.host_transactions().is_empty()); +} + +#[tokio::test] +async fn larger_bundle() { + let builder = test_sim_env(Instant::now() + Duration::from_millis(200)); + + // Set up an order, fill pair + let order = UnsignedOrder::default() + .with_input(TEST_SYS.rollup().tokens().weth(), U256::from(1000)) + .with_output( + TEST_SYS.host().tokens().weth(), + U256::from(1000), + TEST_USERS[5], + TEST_SYS.host_chain_id() as u32, + ) + .to_order(); + + let fill_outputs = order + .outputs + .clone() + .into_iter() + .map(|mut output| { + output.chainId = TEST_SYS.host_chain_id() as u32; + output + }) + .collect::>(); + + let order_call = + initiateCall { deadline: U256::MAX, inputs: order.inputs, outputs: order.outputs }; + + let fill_call = fillCall { outputs: fill_outputs }; + + // Make RU and HOST transactions + let ru_tx = signed_simple_call( + &TEST_SIGNERS[0], + TEST_SYS.ru_orders(), + &order_call, + U256::ZERO, + 0, + TEST_SYS.ru_chain_id(), + ); + let ru_tx_1 = signed_simple_call( + &TEST_SIGNERS[0], + TEST_SYS.ru_orders(), + &order_call, + U256::ZERO, + 1, + TEST_SYS.ru_chain_id(), + ); + let ru_tx_2 = signed_simple_call( + &TEST_SIGNERS[0], + TEST_SYS.ru_orders(), + &order_call, + U256::ZERO, + 2, + TEST_SYS.ru_chain_id(), + ); + + let host_tx = signed_simple_call( + &TEST_SIGNERS[1], + TEST_SYS.host_orders(), + &fill_call, + U256::ZERO, + 0, + TEST_SYS.host_chain_id(), + ); + let host_tx_1 = signed_simple_call( + &TEST_SIGNERS[1], + TEST_SYS.host_orders(), + &fill_call, + U256::ZERO, + 1, + TEST_SYS.host_chain_id(), + ); + let host_tx_2 = signed_simple_call( + &TEST_SIGNERS[1], + TEST_SYS.host_orders(), + &fill_call, + U256::ZERO, + 2, + TEST_SYS.host_chain_id(), + ); + + let bundle = + simple_bundle(vec![ru_tx, ru_tx_1, ru_tx_2], vec![host_tx, host_tx_1, host_tx_2], 0); + + builder.sim_items().add_bundle(bundle, 0).unwrap(); + + let block = builder.build().await; + + assert_eq!(block.transactions().len(), 3); + assert_eq!(block.host_transactions().len(), 3); +} + +#[tokio::test] +async fn larger_bundle_revert_ok() { + let builder = test_sim_env(Instant::now() + Duration::from_millis(200)); + + // Set up an order, fill pair + let order = UnsignedOrder::default() + .with_input(TEST_SYS.rollup().tokens().weth(), U256::from(1000)) + .with_output( + TEST_SYS.host().tokens().weth(), + U256::from(1000), + TEST_USERS[5], + TEST_SYS.host_chain_id() as u32, + ) + .to_order(); + + let fill_outputs = order + .outputs + .clone() + .into_iter() + .map(|mut output| { + output.chainId = TEST_SYS.host_chain_id() as u32; + output + }) + .collect::>(); + + let order_call = + initiateCall { deadline: U256::MAX, inputs: order.inputs, outputs: order.outputs }; + + let fill_call = fillCall { outputs: fill_outputs }; + + // Make RU and HOST transactions + let ru_tx = signed_simple_call( + &TEST_SIGNERS[0], + TEST_SYS.ru_orders(), + &order_call, + U256::ZERO, + 0, + TEST_SYS.ru_chain_id(), + ); + let ru_tx_1 = signed_simple_call( + &TEST_SIGNERS[0], + TEST_SYS.ru_orders(), + &order_call, + U256::ZERO, + 1, + TEST_SYS.ru_chain_id(), + ); + let ru_tx_2 = signed_simple_call( + &TEST_SIGNERS[0], + TEST_SYS.ru_orders(), + &order_call, + U256::ZERO, + 2, + TEST_SYS.ru_chain_id(), + ); + + let host_tx = signed_simple_call( + &TEST_SIGNERS[1], + TEST_SYS.host_orders(), + &fill_call, + U256::ZERO, + 0, + TEST_SYS.host_chain_id(), + ); + let host_tx_1 = signed_simple_call( + &TEST_SIGNERS[1], + TEST_SYS.host_orders(), + &fill_call, + U256::ZERO, + 1, + TEST_SYS.host_chain_id(), + ); + + let ru_tx_2_hash = *ru_tx_2.hash(); + + let mut bundle = simple_bundle(vec![ru_tx, ru_tx_1], vec![host_tx, host_tx_1], 0); + + bundle.bundle.reverting_tx_hashes.push(ru_tx_2_hash); + + builder.sim_items().add_bundle(bundle, 0).unwrap(); + + let block = builder.build().await; + + assert_eq!(block.transactions().len(), 2); + assert_eq!(block.host_transactions().len(), 2); +} + +// Test checks that host cache simulation works when consuming all host balance +#[tokio::test] +async fn host_cache_between_bundles() { + let builder = test_sim_env(Instant::now() + Duration::from_millis(200)); + + let increment_call = incrementCall {}; + + // Make RU and HOST transactions + // In this test the ru txns are dummies. We're testing that the host cache + // updates correctly. + let ru_tx = signed_simple_call( + &TEST_SIGNERS[0], + TEST_USERS[1], + &increment_call, + U256::ZERO, + 0, + TEST_SYS.ru_chain_id(), + ); + let ru_tx_1 = signed_simple_call( + &TEST_SIGNERS[1], + TEST_USERS[1], + &increment_call, + U256::ZERO, + 0, + TEST_SYS.ru_chain_id(), + ); + + let host_tx = signed_simple_call( + &TEST_SIGNERS[2], + COUNTER_TEST_ADDRESS, + &increment_call, + U256::ZERO, + 0, + TEST_SYS.host_chain_id(), + ); + let host_tx_1 = signed_simple_call( + &TEST_SIGNERS[3], + COUNTER_TEST_ADDRESS, + &increment_call, + U256::ZERO, + 0, + TEST_SYS.host_chain_id(), + ); + + let bundle = simple_bundle(vec![ru_tx], vec![host_tx], 0); + let bundle_1 = simple_bundle(vec![ru_tx_1], vec![host_tx_1], 0); + + builder.sim_items().add_bundle(bundle, 0).unwrap(); + builder.sim_items().add_bundle(bundle_1, 0).unwrap(); + + let builder = builder.run_build().await; + + assert_eq!( + builder.host_env().db().storage_ref(COUNTER_TEST_ADDRESS, COUNTER_SLOT).unwrap(), + U256::from(2) + ); + + let block = builder.into_block(); + assert_eq!(block.transactions().len(), 2); + assert_eq!(block.host_transactions().len(), 2); +} diff --git a/crates/types/src/agg/fill.rs b/crates/types/src/agg/fill.rs index a604656f..c038dbe7 100644 --- a/crates/types/src/agg/fill.rs +++ b/crates/types/src/agg/fill.rs @@ -121,7 +121,7 @@ impl AggregateFills { } /// Absorb the fills from another context. - fn absorb(&mut self, other: &Self) { + pub fn absorb(&mut self, other: &Self) { for (output_asset, recipients) in other.fills.iter() { let context_recipients = self.fills.entry(*output_asset).or_default(); for (recipient, value) in recipients { @@ -131,6 +131,26 @@ impl AggregateFills { } } + /// Unabsorb the fills from another context. + pub fn unchecked_unabsorb(&mut self, other: &Self) -> Result<(), MarketError> { + for (output_asset, recipients) in other.fills.iter() { + if let Some(context_recipients) = self.fills.get_mut(output_asset) { + for (recipient, value) in recipients { + if let Some(filled) = context_recipients.get_mut(recipient) { + *filled = + filled.checked_sub(*value).ok_or(MarketError::InsufficientBalance { + chain_id: output_asset.0, + asset: output_asset.1, + recipient: *recipient, + amount: *value, + })?; + } + } + } + } + Ok(()) + } + /// Check that the context can remove the aggregate. pub fn check_aggregate(&self, aggregate: &AggregateOrders) -> Result<(), MarketError> { for (output_asset, recipients) in aggregate.outputs.iter() { @@ -234,12 +254,12 @@ impl AggregateFills { pub fn check_ru_tx_events( &self, fills: &AggregateFills, - aggregate: &AggregateOrders, + orders: &AggregateOrders, ) -> Result<(), MarketError> { // Check the aggregate against the combined contexts. let combined = CombinedContext { context: self, extra: fills }; - combined.check_aggregate(aggregate)?; + combined.check_aggregate(orders)?; Ok(()) } @@ -251,12 +271,24 @@ impl AggregateFills { /// This will process all fills first, and all orders second. pub fn checked_remove_ru_tx_events( &mut self, - aggregate: &AggregateOrders, fills: &AggregateFills, + orders: &AggregateOrders, ) -> Result<(), MarketError> { - self.check_ru_tx_events(fills, aggregate)?; + self.check_ru_tx_events(fills, orders)?; self.absorb(fills); - self.unchecked_remove_aggregate(aggregate) + self.unchecked_remove_aggregate(orders) + } + + /// Check and remove the events emitted by a rollup transaction. This + /// function allows atomic ingestion of multiple Fills and Orders. **If + /// the check fails, the aggregate may be mutated.** + pub fn unchecked_remove_ru_tx_events( + &mut self, + fills: &AggregateFills, + orders: &AggregateOrders, + ) -> Result<(), MarketError> { + self.absorb(fills); + self.unchecked_remove_aggregate(orders) } } @@ -361,4 +393,28 @@ mod test { &U256::from(0) ); } + + // Empty removal should work + #[test] + fn empty_everything() { + AggregateFills::default() + .checked_remove_ru_tx_events(&Default::default(), &Default::default()) + .unwrap(); + } + + #[test] + fn absorb_unabsorb() { + let mut context_a = AggregateFills::default(); + let mut context_b = AggregateFills::default(); + let user = Address::with_last_byte(1); + let asset = Address::with_last_byte(2); + context_a.add_raw_fill(1, asset, user, U256::from(100)); + context_b.add_raw_fill(1, asset, user, U256::from(200)); + + let pre_absorb = context_a.clone(); + context_a.absorb(&context_b); + assert_eq!(context_a.filled(&(1, asset), user), U256::from(300)); + context_a.unchecked_unabsorb(&context_b).unwrap(); + assert_eq!(context_a, pre_absorb); + } } diff --git a/crates/types/src/agg/order.rs b/crates/types/src/agg/order.rs index cee8cf4d..a2d592bc 100644 --- a/crates/types/src/agg/order.rs +++ b/crates/types/src/agg/order.rs @@ -124,6 +124,19 @@ impl AggregateOrders { } o } + + /// Absorb the orders from another context. + pub fn absorb(&mut self, other: &Self) { + for (address, amount) in other.inputs.iter() { + self.ingest_raw_input(*address, *amount); + } + + for ((chain_id, output_asset), recipients) in other.outputs.iter() { + for (recipient, value) in recipients { + self.ingest_raw_output(*chain_id, *output_asset, *recipient, *value); + } + } + } } impl<'a> FromIterator<&'a RollupOrders::Order> for AggregateOrders { diff --git a/crates/types/src/signing/order.rs b/crates/types/src/signing/order.rs index 22a4e097..2be7b16f 100644 --- a/crates/types/src/signing/order.rs +++ b/crates/types/src/signing/order.rs @@ -139,6 +139,11 @@ impl<'a> UnsignedOrder<'a> { } } + /// Get the inputs of the UnsignedOrder. + pub fn inputs(&self) -> &[Input] { + self.order.inputs() + } + /// Add an input to the UnsignedOrder. pub fn with_raw_input(self, input: Input) -> UnsignedOrder<'static> { let order = self.order.into_owned().with_input(input); @@ -151,6 +156,11 @@ impl<'a> UnsignedOrder<'a> { self.with_raw_input(Input { token, amount }) } + /// Get the outputs of the UnsignedOrder. + pub fn outputs(&self) -> &[Output] { + self.order.outputs() + } + /// Add an output to the UnsignedOrder. pub fn with_raw_output(self, output: Output) -> UnsignedOrder<'static> { let order = self.order.into_owned().with_output(output); @@ -190,6 +200,17 @@ impl<'a> UnsignedOrder<'a> { } } + /// Convert the UnsignedOrder into an Order, cloning the inner data if + /// necessary. + pub fn to_order(&self) -> Order { + self.order.clone().into_owned() + } + + /// Convert the UnsignedOrder into an Order + pub fn into_order(self) -> Cow<'a, Order> { + self.order + } + /// Sign the UnsignedOrder, generating a SignedOrder. pub async fn sign(&self, signer: &S) -> Result { // if nonce is None, populate it with the current time diff --git a/crates/zenith/src/bindings.rs b/crates/zenith/src/bindings.rs index a73503e0..95da925a 100644 --- a/crates/zenith/src/bindings.rs +++ b/crates/zenith/src/bindings.rs @@ -374,30 +374,37 @@ mod transactor { impl Copy for Transactor::GasConfigured {} impl Transactor::Transact { + /// Get the chain ID of the event (discarding high bytes). pub const fn rollup_chain_id(&self) -> u64 { self.rollupChainId.as_limbs()[0] } - pub const fn sender(&self) -> Address { + /// Get the host sender that triggered the event. + pub const fn host_sender(&self) -> Address { self.sender } + /// Get the recipient of the transact. pub const fn to(&self) -> Address { self.to } + /// Get the data of the transact. pub const fn data(&self) -> &Bytes { &self.data } + /// Get the value of the transact. pub const fn value(&self) -> U256 { self.value } + /// Get the max fee per gas of the transact. pub fn max_fee_per_gas(&self) -> u128 { self.maxFeePerGas.to::() } + /// Get the gas limit of the transact. pub fn gas(&self) -> u128 { self.gas.to::() }