Skip to content

Commit 0547177

Browse files
authored
test: add tests for bundle guarantees (#112)
* test: add tests for bundle guarantees * fix: bundle driver rejection works more correctly * refactor: distinguish between sender and orderer * lint: clippy
1 parent ad0f058 commit 0547177

File tree

13 files changed

+508
-55
lines changed

13 files changed

+508
-55
lines changed

crates/bundle/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,6 @@ pub use call::{SignetBundleDriver, SignetCallBundle, SignetCallBundleResponse};
5151

5252
mod send;
5353
pub use send::{
54-
SignetEthBundle, SignetEthBundleDriver, SignetEthBundleError, SignetEthBundleInsp,
55-
SignetEthBundleResponse,
54+
BundleInspector, SignetEthBundle, SignetEthBundleDriver, SignetEthBundleError,
55+
SignetEthBundleInsp, SignetEthBundleResponse,
5656
};

crates/bundle/src/send/bundle.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,14 @@ use alloy::{
1212
use serde::{Deserialize, Serialize};
1313
use signet_types::{SignedFill, SignedPermitError};
1414
use signet_zenith::HostOrders::HostOrdersInstance;
15-
use trevm::{revm::Database, BundleError};
15+
use trevm::{
16+
inspectors::{Layered, TimeLimit},
17+
revm::{inspector::NoOpInspector, Database},
18+
BundleError,
19+
};
20+
21+
/// The inspector type required by the Signet bundle driver.
22+
pub type BundleInspector<I = NoOpInspector> = Layered<TimeLimit, I>;
1623

1724
/// Bundle of transactions for `signet_sendBundle`.
1825
///

crates/bundle/src/send/driver.rs

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use crate::send::SignetEthBundle;
22
use alloy::primitives::U256;
3-
use signet_evm::{DriveBundleResult, EvmNeedsTx, EvmTransacted, SignetInspector, SignetLayered};
3+
use signet_evm::{
4+
DriveBundleResult, EvmErrored, EvmNeedsTx, EvmTransacted, SignetInspector, SignetLayered,
5+
};
46
use signet_types::{AggregateFills, MarketError, SignedPermitError};
5-
use tracing::debug;
7+
use tracing::{debug, error};
68
use trevm::{
79
helpers::Ctx,
810
inspectors::{Layered, TimeLimit},
@@ -143,9 +145,7 @@ impl<'a> SignetEthBundleDriver<'a> {
143145

144146
// We check the AggregateFills here, and if it fails, we discard the
145147
// 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-
})
148+
self.agg_fills.checked_remove_ru_tx_events(&agg_orders, &agg_fills)
149149
}
150150
}
151151

@@ -200,6 +200,8 @@ where
200200
let txs = trevm_try!(self.bundle.decode_and_validate_txs(), trevm);
201201

202202
for tx in txs.into_iter() {
203+
let _span = tracing::debug_span!("bundle_tx_loop", tx_hash = %tx.hash()).entered();
204+
203205
// Update the inner deadline.
204206
let limit = trevm.inner_mut_unchecked().ctx_inspector().1.outer_mut().outer_mut();
205207
*limit = TimeLimit::new(self.deadline - std::time::Instant::now());
@@ -209,7 +211,10 @@ where
209211
// Temporary rebinding of trevm within each loop iteration.
210212
// The type of t is `EvmTransacted`, while the type of trevm is
211213
// `EvmNeedsTx`.
212-
let mut t = trevm.run_tx(&tx).map_err(|err| err.err_into())?;
214+
let mut t = trevm
215+
.run_tx(&tx)
216+
.map_err(EvmErrored::err_into)
217+
.inspect_err(|err| error!(err = %err.error(), "error while running transaction"))?;
213218

214219
// Check the result of the transaction.
215220
let result = t.result();
@@ -221,10 +226,14 @@ where
221226
// not, and the tx is not marked as revertible by the bundle, we
222227
// error our simulation.
223228
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()));
229+
if self.check_fills(&mut t).is_err() {
230+
debug!("transaction dropped due to insufficient fills");
231+
if self.bundle.reverting_tx_hashes().contains(tx_hash) {
232+
trevm = t.reject();
233+
continue;
234+
} else {
235+
return Err(t.errored(BundleError::BundleReverted.into()));
236+
}
228237
}
229238

230239
self.total_gas_used = self.total_gas_used.saturating_add(gas_used);
@@ -233,13 +242,14 @@ where
233242
// not marked as revertible by the bundle, we error our
234243
// simulation.
235244
if !self.bundle.reverting_tx_hashes().contains(tx_hash) {
245+
debug!("transaction reverted, not marked as revertible");
236246
return Err(t.errored(BundleError::BundleReverted.into()));
237247
}
238248
self.total_gas_used = self.total_gas_used.saturating_add(gas_used);
239249
}
240250

241-
// If we did not shortcut return, we accept the state changes
242-
// from this transaction.
251+
// If we did not shortcut return/continue, we accept the state
252+
// changes from this transaction.
243253
trevm = t.accept_state()
244254
}
245255

crates/bundle/src/send/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
mod bundle;
2-
pub use bundle::{SignetEthBundle, SignetEthBundleResponse};
2+
pub use bundle::{BundleInspector, SignetEthBundle, SignetEthBundleResponse};
33

44
mod driver;
55
pub use driver::{SignetEthBundleDriver, SignetEthBundleError, SignetEthBundleInsp};

crates/test-utils/src/contracts/counter.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use alloy::{
2-
primitives::{b256, bytes, Address, Bytes, B256},
2+
primitives::{b256, bytes, Address, Bytes, B256, U256},
33
providers::Provider,
44
};
55

@@ -14,6 +14,9 @@ alloy::sol! {
1414
}
1515
}
1616

17+
/// The storage slot where the counter value is stored in the Counter contract.
18+
pub const COUNTER_SLOT: U256 = U256::ZERO;
19+
1720
/// A test address for the Counter.sol contract, which will be pre-deployed in
1821
/// test EVMs.
1922
pub const COUNTER_TEST_ADDRESS: Address = Address::repeat_byte(0x49);
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1+
/// A Counter contract that can be incremented.
12
pub mod counter;
23

4+
/// Orders, Passage, and Token system contracts.
35
pub mod system;
46

7+
/// A token contract, deployed for Wbtc and Weth.
58
pub mod token;
9+
10+
/// A contract that always reverts.
11+
pub mod reverts;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use crate::contracts::counter::Counter;
2+
use alloy::{
3+
primitives::{bytes, Address, Bytes},
4+
providers::Provider,
5+
};
6+
7+
/// A test address for the Revert contract, which will be pre-deployed in
8+
/// test EVMs.
9+
pub const REVERT_TEST_ADDRESS: Address = Address::repeat_byte(0x50);
10+
11+
/// Revert deploycode for testing purposes.
12+
pub const REVERT_DEPLOY_CODE: Bytes = bytes!("0x608060405234601957600e601d565b60c76028823960c790f35b6023565b60405190565b5f80fdfe608060405234156073576013565b60405190565b5f80fd5b60209181520190565b5f7f5265766572742063616c6c656400000000000000000000000000000000000000910152565b6052600d6020926017565b6059816020565b0190565b60709060208101905f8183039101526047565b90565b6079600d565b62461bcd60e51b815280608d60048201605d565b0390fdfea2646970667358221220ca26ecb1412cc6e87fe99712d1085a242349863e03e3e43ef6ee7dde7a3478cc64736f6c634300081a0033");
13+
14+
/// Revert bytecode for testing purposes.
15+
pub const REVERT_BYTECODE: Bytes = bytes!(
16+
"0x608060405234156073576013565b60405190565b5f80fd5b60209181520190565b5f7f5265766572742063616c6c656400000000000000000000000000000000000000910152565b6052600d6020926017565b6059816020565b0190565b60709060208101905f8183039101526047565b90565b6079600d565b62461bcd60e51b815280608d60048201605d565b0390fdfea2646970667358221220ca26ecb1412cc6e87fe99712d1085a242349863e03e3e43ef6ee7dde7a3478cc64736f6c634300081a0033");
17+
18+
/// Get an instance of the pre-deployed Counter contract.
19+
pub fn revert<P: Provider>(p: P) -> Counter::CounterInstance<P> {
20+
Counter::CounterInstance::new(REVERT_TEST_ADDRESS, p)
21+
}

crates/test-utils/src/evm.rs

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,57 @@
1-
use signet_constants::test_utils::*;
2-
use trevm::revm::{
3-
context::CfgEnv, database::in_memory_db::InMemoryDB, primitives::hardfork::SpecId,
4-
state::Bytecode,
1+
use crate::{
2+
contracts::{
3+
counter::{COUNTER_BYTECODE, COUNTER_TEST_ADDRESS},
4+
reverts::{REVERT_BYTECODE, REVERT_TEST_ADDRESS},
5+
system::{RU_ORDERS_BYTECODE, RU_PASSAGE_BYTECODE},
6+
token::{
7+
MINTER, MINTER_SLOT, NAME_SLOT, SYMBOL_SLOT, TOKEN_BYTECODE, WBTC_NAME, WBTC_SYMBOL,
8+
WETH_NAME, WETH_SYMBOL,
9+
},
10+
},
11+
users::TEST_USERS,
512
};
6-
7-
use crate::contracts::{
8-
counter::{COUNTER_BYTECODE, COUNTER_TEST_ADDRESS},
9-
system::{RU_ORDERS_BYTECODE, RU_PASSAGE_BYTECODE},
10-
token::{
11-
MINTER, MINTER_SLOT, NAME_SLOT, SYMBOL_SLOT, TOKEN_BYTECODE, WBTC_NAME, WBTC_SYMBOL,
12-
WETH_NAME, WETH_SYMBOL,
13+
use alloy::{consensus::constants::ETH_TO_WEI, primitives::U256};
14+
use signet_constants::test_utils::*;
15+
use trevm::{
16+
helpers::Ctx,
17+
revm::{
18+
context::CfgEnv, database::in_memory_db::InMemoryDB, inspector::NoOpInspector,
19+
primitives::hardfork::SpecId, state::Bytecode, Inspector,
1320
},
1421
};
1522

16-
/// Create a new Signet EVM with an in-memory database for testing. Deploy
17-
/// system contracts and pre-deployed tokens.
23+
/// Create a new Signet EVM with an in-memory database for testing.
24+
///
25+
/// Performs initial setup to
26+
/// - Deploy [`RU_ORDERS`] and and [`RU_PASSAGE`] system contracts
27+
/// - Deploy a [`COUNTER`] contract for testing at [`COUNTER_TEST_ADDRESS`].
28+
/// - Deploy Token contracts for WBTC and WETH with their respective bytecodes
29+
/// and storage.
30+
/// - Deploy a `Revert` contract for testing at [`REVERT_TEST_ADDRESS`].
31+
/// - Fund the [`TEST_USERS`] with 1000 ETH each.
32+
///
33+
/// [`COUNTER`]: crate::contracts::counter::Counter
1834
pub fn test_signet_evm() -> signet_evm::EvmNeedsBlock<InMemoryDB> {
19-
let mut evm = signet_evm::signet_evm(InMemoryDB::default(), TEST_SYS).fill_cfg(&TestCfg);
35+
test_signet_evm_with_inspector(NoOpInspector)
36+
}
37+
38+
/// Create a new Signet EVM with an in-memory database for testing.
39+
///
40+
/// Performs initial setup to
41+
/// - Deploy [`RU_ORDERS`] and and [`RU_PASSAGE`] system contracts
42+
/// - Deploy a [`COUNTER`] contract for testing at [`COUNTER_TEST_ADDRESS`].
43+
/// - Deploy Token contracts for WBTC and WETH with their respective bytecodes
44+
/// and storage.
45+
/// - Deploy a `Revert` contract for testing at [`REVERT_TEST_ADDRESS`].
46+
/// - Fund the [`TEST_USERS`] with 1000 ETH each.
47+
///
48+
/// [`COUNTER`]: crate::contracts::counter::Counter
49+
pub fn test_signet_evm_with_inspector<I>(inspector: I) -> signet_evm::EvmNeedsBlock<InMemoryDB, I>
50+
where
51+
I: Inspector<Ctx<InMemoryDB>>,
52+
{
53+
let mut evm = signet_evm::signet_evm_with_inspector(InMemoryDB::default(), inspector, TEST_SYS)
54+
.fill_cfg(&TestCfg);
2055

2156
// Set the bytecode for system contracts
2257
evm.set_bytecode_unchecked(TEST_SYS.ru_orders(), Bytecode::new_legacy(RU_ORDERS_BYTECODE));
@@ -37,6 +72,14 @@ pub fn test_signet_evm() -> signet_evm::EvmNeedsBlock<InMemoryDB> {
3772
// Set the bytecode for the Counter contract
3873
evm.set_bytecode_unchecked(COUNTER_TEST_ADDRESS, Bytecode::new_legacy(COUNTER_BYTECODE));
3974

75+
// Set the bytecode for the Revert contract
76+
evm.set_bytecode_unchecked(REVERT_TEST_ADDRESS, Bytecode::new_legacy(REVERT_BYTECODE));
77+
78+
// increment the balance for each test signer
79+
TEST_USERS.iter().copied().for_each(|user| {
80+
evm.set_balance_unchecked(user, U256::from(1000 * ETH_TO_WEI));
81+
});
82+
4083
evm
4184
}
4285

crates/test-utils/src/specs/mod.rs

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,22 @@ mod ru_spec;
88
pub use ru_spec::RuBlockSpec;
99

1010
use alloy::{
11-
consensus::{constants::GWEI_TO_WEI, SignableTransaction, TxEip1559},
11+
consensus::{
12+
constants::GWEI_TO_WEI, SignableTransaction, TxEip1559, TxEnvelope, TypedTransaction,
13+
},
14+
eips::Encodable2718,
1215
primitives::{Address, TxKind, B256, U256},
16+
rpc::types::mev::EthSendBundle,
1317
signers::{local::PrivateKeySigner, SignerSync},
18+
sol_types::SolCall,
1419
};
15-
use signet_types::primitives::{Transaction, TransactionSigned};
20+
use signet_bundle::SignetEthBundle;
21+
use signet_types::SignedFill;
1622

1723
/// Sign a transaction with a wallet.
18-
pub fn sign_tx_with_key_pair(wallet: &PrivateKeySigner, tx: Transaction) -> TransactionSigned {
24+
pub fn sign_tx_with_key_pair(wallet: &PrivateKeySigner, tx: TypedTransaction) -> TxEnvelope {
1925
let signature = wallet.sign_hash_sync(&tx.signature_hash()).unwrap();
20-
TransactionSigned::new_unhashed(tx, signature)
26+
TxEnvelope::new_unhashed(tx, signature)
2127
}
2228

2329
/// Make a wallet with a deterministic keypair.
@@ -26,7 +32,7 @@ pub fn make_wallet(i: u8) -> PrivateKeySigner {
2632
}
2733

2834
/// Make a simple send transaction.
29-
pub fn simple_send(to: Address, amount: U256, nonce: u64, ru_chain_id: u64) -> Transaction {
35+
pub fn simple_send(to: Address, amount: U256, nonce: u64, ru_chain_id: u64) -> TypedTransaction {
3036
TxEip1559 {
3137
nonce,
3238
gas_limit: 21_000,
@@ -39,3 +45,54 @@ pub fn simple_send(to: Address, amount: U256, nonce: u64, ru_chain_id: u64) -> T
3945
}
4046
.into()
4147
}
48+
49+
/// Make a simple contract call transaction.
50+
pub fn simple_call<T>(
51+
to: Address,
52+
input: &T,
53+
value: U256,
54+
nonce: u64,
55+
ru_chain_id: u64,
56+
) -> TypedTransaction
57+
where
58+
T: SolCall,
59+
{
60+
TxEip1559 {
61+
nonce,
62+
gas_limit: 2_100_000,
63+
to: TxKind::Call(to),
64+
value,
65+
chain_id: ru_chain_id,
66+
max_fee_per_gas: GWEI_TO_WEI as u128 * 100,
67+
max_priority_fee_per_gas: GWEI_TO_WEI as u128,
68+
input: input.abi_encode().into(),
69+
..Default::default()
70+
}
71+
.into()
72+
}
73+
74+
/// Create a simple bundle from a list of transactions.
75+
pub fn simple_bundle<'a>(
76+
txs: impl IntoIterator<Item = &'a TxEnvelope>,
77+
host_fills: Option<SignedFill>,
78+
block_number: u64,
79+
) -> SignetEthBundle {
80+
let txs = txs.into_iter().map(|tx| tx.encoded_2718().into()).collect();
81+
82+
SignetEthBundle {
83+
bundle: EthSendBundle {
84+
txs,
85+
block_number,
86+
min_timestamp: None,
87+
max_timestamp: None,
88+
reverting_tx_hashes: vec![],
89+
replacement_uuid: None,
90+
dropping_tx_hashes: vec![],
91+
refund_percent: None,
92+
refund_recipient: None,
93+
refund_tx_hashes: vec![],
94+
extra_fields: Default::default(),
95+
},
96+
host_fills,
97+
}
98+
}

crates/test-utils/src/specs/ru_spec.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,7 @@ use alloy::{
88
};
99
use signet_constants::test_utils::*;
1010
use signet_extract::{Extractable, Extracts};
11-
use signet_types::{
12-
constants::{KnownChains, ParseChainError, SignetSystemConstants},
13-
primitives::TransactionSigned,
14-
};
11+
use signet_types::constants::{KnownChains, ParseChainError, SignetSystemConstants};
1512
use signet_zenith::Zenith::{self};
1613
use std::str::FromStr;
1714

@@ -66,7 +63,7 @@ impl RuBlockSpec {
6663
}
6764

6865
/// Add a transaction to the block.
69-
pub fn add_tx(&mut self, tx: &TransactionSigned) {
66+
pub fn add_tx(&mut self, tx: &TxEnvelope) {
7067
self.tx.push(tx.encoded_2718());
7168
}
7269

@@ -81,7 +78,7 @@ impl RuBlockSpec {
8178
}
8279

8380
/// Add a transaction to the block, returning self.
84-
pub fn tx(mut self, tx: &TransactionSigned) -> Self {
81+
pub fn tx(mut self, tx: &TxEnvelope) -> Self {
8582
self.add_tx(tx);
8683
self
8784
}
@@ -99,7 +96,7 @@ impl RuBlockSpec {
9996
to: Address,
10097
amount: U256,
10198
nonce: u64,
102-
) -> TransactionSigned {
99+
) -> TxEnvelope {
103100
let tx = sign_tx_with_key_pair(
104101
wallet,
105102
simple_send(to, amount, nonce, self.constants.ru_chain_id()),

0 commit comments

Comments
 (0)