Skip to content

Commit d0d37c4

Browse files
authored
fix: SignetEthBundleDriver now enforces market rules internally (#109)
* fix: SignetEthBundleDriver now enforces market rules internally * lint: clippy
1 parent 67d4748 commit d0d37c4

File tree

3 files changed

+111
-34
lines changed

3 files changed

+111
-34
lines changed

crates/bundle/src/send/driver.rs

Lines changed: 97 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
use crate::send::SignetEthBundle;
22
use alloy::primitives::U256;
3-
use signet_evm::{DriveBundleResult, EvmNeedsTx, SignetLayered};
4-
use signet_types::SignedPermitError;
3+
use signet_evm::{DriveBundleResult, EvmNeedsTx, EvmTransacted, SignetInspector, SignetLayered};
4+
use signet_types::{AggregateFills, MarketError, SignedPermitError};
5+
use tracing::debug;
56
use trevm::{
67
helpers::Ctx,
78
inspectors::{Layered, TimeLimit},
89
revm::{
9-
context::result::{EVMError, ExecutionResult, HaltReason},
10-
inspector::InspectorEvmTr,
11-
Database, DatabaseCommit, Inspector,
10+
context::result::EVMError, inspector::InspectorEvmTr, Database, DatabaseCommit, Inspector,
1211
},
1312
trevm_bail, trevm_ensure, trevm_try, BundleDriver, BundleError,
1413
};
@@ -58,18 +57,42 @@ impl<Db: Database> From<EVMError<Db::Error>> for SignetEthBundleError<Db> {
5857
/// Driver for applying a Signet Ethereum bundle to an EVM.
5958
#[derive(Debug, Clone)]
6059
pub struct SignetEthBundleDriver<'a> {
60+
/// The bundle to apply.
6161
bundle: &'a SignetEthBundle,
62+
63+
/// Execution deadline for this bundle. This limits the total WALLCLOCK
64+
/// time spent simulating the bundle.
6265
deadline: std::time::Instant,
6366

67+
/// Aggregate fills derived from the bundle's host fills.
68+
agg_fills: AggregateFills,
69+
70+
/// Total gas used by this bundle during execution, an output of the driver.
6471
total_gas_used: u64,
72+
/// Beneficiary balance increase during execution, an output of the driver.
6573
beneficiary_balance_increase: U256,
6674
}
6775

6876
impl<'a> SignetEthBundleDriver<'a> {
6977
/// Creates a new [`SignetEthBundleDriver`] with the given bundle and
7078
/// response.
71-
pub const fn new(bundle: &'a SignetEthBundle, deadline: std::time::Instant) -> Self {
72-
Self { bundle, deadline, total_gas_used: 0, beneficiary_balance_increase: U256::ZERO }
79+
pub fn new(
80+
bundle: &'a SignetEthBundle,
81+
host_chain_id: u64,
82+
deadline: std::time::Instant,
83+
) -> Self {
84+
let mut agg_fills = AggregateFills::default();
85+
if let Some(host_fills) = &bundle.host_fills {
86+
agg_fills.add_signed_fill(host_chain_id, host_fills);
87+
}
88+
89+
Self {
90+
bundle,
91+
deadline,
92+
agg_fills,
93+
total_gas_used: 0,
94+
beneficiary_balance_increase: U256::ZERO,
95+
}
7396
}
7497

7598
/// Get a reference to the bundle.
@@ -91,6 +114,39 @@ impl<'a> SignetEthBundleDriver<'a> {
91114
pub const fn beneficiary_balance_increase(&self) -> U256 {
92115
self.beneficiary_balance_increase
93116
}
117+
118+
/// Get the aggregate fills for this driver.
119+
///
120+
/// This may be used to check that the bundle does not overfill, by
121+
/// inspecting the agg fills after execution.
122+
pub const fn agg_fills(&self) -> &AggregateFills {
123+
&self.agg_fills
124+
}
125+
126+
/// Check the [`AggregateFills`], discard if invalid, otherwise accumulate
127+
/// payable gas and call [`Self::accept_tx`].
128+
///
129+
/// This path is used by
130+
/// - [`TransactionSigned`] objects
131+
/// - [`Transactor::Transact`] events
132+
pub(crate) fn check_fills<Db, Insp>(
133+
&mut self,
134+
trevm: &mut EvmTransacted<Db, Insp>,
135+
) -> Result<(), MarketError>
136+
where
137+
Db: Database + DatabaseCommit,
138+
Insp: Inspector<Ctx<Db>>,
139+
{
140+
// Taking these clears the context for reuse.
141+
let (agg_orders, agg_fills) =
142+
trevm.inner_mut_unchecked().inspector.as_mut_detector().take_aggregates();
143+
144+
// We check the AggregateFills here, and if it fails, we discard the
145+
// transaction outcome and push a failure receipt.
146+
self.agg_fills.checked_remove_ru_tx_events(&agg_orders, &agg_fills).inspect_err(|err| {
147+
debug!(%err, "Discarding transaction outcome due to market error");
148+
})
149+
}
94150
}
95151

96152
impl<Db, Insp> BundleDriver<Db, SignetLayered<Layered<TimeLimit, Insp>>>
@@ -150,33 +206,41 @@ where
150206

151207
let tx_hash = tx.hash();
152208

153-
trevm = match trevm.run_tx(&tx) {
154-
Ok(trevm) => {
155-
// Check if the transaction was reverted or halted
156-
let result = trevm.result();
157-
158-
match result {
159-
ExecutionResult::Success { gas_used, .. } => {
160-
self.total_gas_used = self.total_gas_used.saturating_add(*gas_used);
161-
}
162-
// `CallTooDeep` represents the timelimit being reached
163-
ExecutionResult::Halt { reason, .. }
164-
if *reason == HaltReason::CallTooDeep =>
165-
{
166-
return Err(trevm.errored(BundleError::BundleReverted.into()));
167-
}
168-
ExecutionResult::Halt { gas_used, .. }
169-
| ExecutionResult::Revert { gas_used, .. } => {
170-
if !self.bundle.reverting_tx_hashes().contains(tx_hash) {
171-
return Err(trevm.errored(BundleError::BundleReverted.into()));
172-
}
173-
self.total_gas_used = self.total_gas_used.saturating_add(*gas_used);
174-
}
175-
}
176-
trevm.accept_state()
209+
// Temporary rebinding of trevm within each loop iteration.
210+
// The type of t is `EvmTransacted`, while the type of trevm is
211+
// `EvmNeedsTx`.
212+
let mut t = trevm.run_tx(&tx).map_err(|err| err.err_into())?;
213+
214+
// Check the result of the transaction.
215+
let result = t.result();
216+
217+
let gas_used = result.gas_used();
218+
219+
// EVM Execution succeeded.
220+
// We now check if the orders are valid with the bundle's fills. If
221+
// not, and the tx is not marked as revertible by the bundle, we
222+
// error our simulation.
223+
if result.is_success() {
224+
if self.check_fills(&mut t).is_err()
225+
&& !self.bundle.reverting_tx_hashes().contains(tx_hash)
226+
{
227+
return Err(t.errored(BundleError::BundleReverted.into()));
177228
}
178-
Err(err) => return Err(err.err_into()),
179-
};
229+
230+
self.total_gas_used = self.total_gas_used.saturating_add(gas_used);
231+
} else {
232+
// If not success, we are in a revert or halt. If the tx is
233+
// not marked as revertible by the bundle, we error our
234+
// simulation.
235+
if !self.bundle.reverting_tx_hashes().contains(tx_hash) {
236+
return Err(t.errored(BundleError::BundleReverted.into()));
237+
}
238+
self.total_gas_used = self.total_gas_used.saturating_add(gas_used);
239+
}
240+
241+
// If we did not shortcut return, we accept the state changes
242+
// from this transaction.
243+
trevm = t.accept_state()
180244
}
181245

182246
let beneficiary_balance =

crates/sim/src/env.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,8 @@ where
330330
where
331331
Insp: Inspector<Ctx<SimDb<Db>>> + Default + Sync,
332332
{
333-
let mut driver = SignetEthBundleDriver::new(bundle, self.finish_by);
333+
let mut driver =
334+
SignetEthBundleDriver::new(bundle, self.constants.host_chain_id(), self.finish_by);
334335
let trevm = self.create_with_block(&self.cfg, &self.block).unwrap();
335336

336337
// Run the bundle

crates/types/src/agg/fill.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::AggregateOrders;
22
use crate::MarketError;
3+
use crate::SignedFill;
34
use alloy::primitives::{Address, U256};
45
use serde::{Deserialize, Serialize};
56
use signet_zenith::RollupOrders;
@@ -108,6 +109,17 @@ impl AggregateFills {
108109
fill.outputs.iter().for_each(|o| self.add_fill_output(chain_id, o));
109110
}
110111

112+
/// Ingest a [`SignedFill`] into the aggregate. The chain_id is the ID
113+
/// of the chain which emitted the event.
114+
///
115+
/// # Note:
116+
///
117+
/// This uses saturating arithmetic to avoid panics. If filling more than
118+
/// [`U256::MAX`], re-examine life choices and don't do that.
119+
pub fn add_signed_fill(&mut self, chain_id: u64, fill: &SignedFill) {
120+
fill.outputs.iter().for_each(|o| self.add_fill_output(chain_id, o));
121+
}
122+
111123
/// Absorb the fills from another context.
112124
fn absorb(&mut self, other: &Self) {
113125
for (output_asset, recipients) in other.fills.iter() {

0 commit comments

Comments
 (0)