1- use alloy_consensus:: TxEip1559 ;
1+ use alloy_consensus:: { Transaction , TxEip1559 } ;
22use alloy_eips:: { Encodable2718 , eip7623:: TOTAL_COST_FLOOR_PER_TOKEN } ;
33use alloy_evm:: Database ;
4+ use alloy_op_evm:: OpEvm ;
45use alloy_primitives:: {
5- Address , B256 , Log , TxKind , U256 ,
6- map:: foldhash:: { HashSet , HashSetExt } ,
6+ Address , B256 , Bytes , TxKind , U256 ,
7+ map:: foldhash:: { HashMap , HashSet , HashSetExt } ,
78} ;
89use core:: fmt:: Debug ;
910use op_alloy_consensus:: OpTypedTransaction ;
10- use op_revm:: OpTransactionError ;
11- use reth_evm:: { ConfigureEvm , Evm , eth:: receipt_builder:: ReceiptBuilderCtx } ;
11+ use op_revm:: { OpHaltReason , OpTransactionError } ;
12+ use reth_evm:: {
13+ ConfigureEvm , Evm , EvmError , eth:: receipt_builder:: ReceiptBuilderCtx ,
14+ precompiles:: PrecompilesMap ,
15+ } ;
1216use reth_node_api:: PayloadBuilderError ;
1317use reth_optimism_primitives:: OpTransactionSigned ;
1418use reth_primitives:: Recovered ;
1519use reth_provider:: { ProviderError , StateProvider } ;
1620use reth_revm:: { State , database:: StateProviderDatabase } ;
1721use revm:: {
1822 DatabaseCommit ,
19- context:: result:: { EVMError , ResultAndState } ,
23+ context:: result:: { EVMError , ExecutionResult , ResultAndState } ,
24+ inspector:: NoOpInspector ,
25+ state:: Account ,
2026} ;
2127use tracing:: warn;
2228
2329use crate :: {
2430 builders:: context:: OpPayloadBuilderCtx , primitives:: reth:: ExecutionInfo , tx_signer:: Signer ,
2531} ;
2632
33+ #[ derive( Debug , Default ) ]
34+ pub struct SimulationSuccessResult {
35+ pub gas_used : u64 ,
36+ pub output : Bytes ,
37+ pub state_changes : HashMap < Address , Account > ,
38+ }
39+
2740#[ derive( Debug , Clone ) ]
2841pub struct BuilderTransactionCtx {
2942 pub gas_used : u64 ,
@@ -46,6 +59,14 @@ impl BuilderTransactionCtx {
4659 }
4760}
4861
62+ #[ derive( Debug , thiserror:: Error ) ]
63+ pub enum InvalidContractDataError {
64+ #[ error( "did not find expected log {0:?} in emitted logs" ) ]
65+ InvalidLogs ( B256 ) ,
66+ #[ error( "could not decode output from contract call" ) ]
67+ OutputAbiDecodeError ,
68+ }
69+
4970/// Possible error variants during construction of builder txs.
5071#[ derive( Debug , thiserror:: Error ) ]
5172pub enum BuilderTransactionError {
@@ -55,9 +76,15 @@ pub enum BuilderTransactionError {
5576 /// Signature signing fails
5677 #[ error( "failed to sign transaction: {0}" ) ]
5778 SigningError ( secp256k1:: Error ) ,
58- /// Invalid contract data returned
59- #[ error( "invalid contract data returned {0}" ) ]
60- InvalidContract ( Address ) ,
79+ /// Invalid contract errors indicating the contract is incorrect
80+ #[ error( "contract {0} may be incorrect, invalid contract data: {1}" ) ]
81+ InvalidContract ( Address , InvalidContractDataError ) ,
82+ /// Transaction halted execution
83+ #[ error( "transaction halted {0:?}" ) ]
84+ TransactionHalted ( OpHaltReason ) ,
85+ /// Transaction reverted
86+ #[ error( "transaction reverted {0}" ) ]
87+ TransactionReverted ( Box < dyn core:: error:: Error + Send + Sync > ) ,
6188 /// Invalid tx errors during evm execution.
6289 #[ error( "invalid transaction error {0}" ) ]
6390 InvalidTransactionError ( Box < dyn core:: error:: Error + Send + Sync > ) ,
@@ -93,12 +120,13 @@ impl From<BuilderTransactionError> for PayloadBuilderError {
93120}
94121
95122impl BuilderTransactionError {
96- pub fn other < E > ( error : E ) -> Self
97- where
98- E : core:: error:: Error + Send + Sync + ' static ,
99- {
123+ pub fn other ( error : impl core:: error:: Error + Send + Sync + ' static ) -> Self {
100124 BuilderTransactionError :: Other ( Box :: new ( error) )
101125 }
126+
127+ pub fn msg ( msg : impl core:: fmt:: Display ) -> Self {
128+ Self :: Other ( msg. to_string ( ) . into ( ) )
129+ }
102130}
103131
104132pub trait BuilderTransactions < ExtraCtx : Debug + Default = ( ) , Extra : Debug + Default = ( ) > {
@@ -210,6 +238,84 @@ pub trait BuilderTransactions<ExtraCtx: Debug + Default = (), Extra: Debug + Def
210238
211239 Ok ( simulation_state)
212240 }
241+
242+ fn sign_tx (
243+ & self ,
244+ to : Address ,
245+ from : Signer ,
246+ gas_used : Option < u64 > ,
247+ calldata : Bytes ,
248+ ctx : & OpPayloadBuilderCtx < ExtraCtx > ,
249+ db : & mut State < impl Database > ,
250+ ) -> Result < Recovered < OpTransactionSigned > , BuilderTransactionError > {
251+ let nonce = get_nonce ( db, from. address ) ?;
252+ // Create the EIP-1559 transaction
253+ let tx = OpTypedTransaction :: Eip1559 ( TxEip1559 {
254+ chain_id : ctx. chain_id ( ) ,
255+ nonce,
256+ // Due to EIP-150, 63/64 of available gas is forwarded to external calls so need to add a buffer
257+ gas_limit : gas_used
258+ . map ( |gas| gas * 64 / 63 )
259+ . unwrap_or ( ctx. block_gas_limit ( ) ) ,
260+ max_fee_per_gas : ctx. base_fee ( ) . into ( ) ,
261+ to : TxKind :: Call ( to) ,
262+ input : calldata,
263+ ..Default :: default ( )
264+ } ) ;
265+ Ok ( from. sign_tx ( tx) ?)
266+ }
267+
268+ fn simulate_call (
269+ & self ,
270+ signed_tx : Recovered < OpTransactionSigned > ,
271+ expected_topic : Option < B256 > ,
272+ revert_handler : impl FnOnce ( Bytes ) -> BuilderTransactionError ,
273+ evm : & mut OpEvm <
274+ & mut State < StateProviderDatabase < impl StateProvider > > ,
275+ NoOpInspector ,
276+ PrecompilesMap ,
277+ > ,
278+ ) -> Result < SimulationSuccessResult , BuilderTransactionError > {
279+ let ResultAndState { result, state } = match evm. transact ( & signed_tx) {
280+ Ok ( res) => res,
281+ Err ( err) => {
282+ if err. is_invalid_tx_err ( ) {
283+ return Err ( BuilderTransactionError :: InvalidTransactionError ( Box :: new (
284+ err,
285+ ) ) ) ;
286+ } else {
287+ return Err ( BuilderTransactionError :: EvmExecutionError ( Box :: new ( err) ) ) ;
288+ }
289+ }
290+ } ;
291+
292+ match result {
293+ ExecutionResult :: Success {
294+ logs,
295+ gas_used,
296+ output,
297+ ..
298+ } => {
299+ if let Some ( topic) = expected_topic
300+ && !logs. iter ( ) . any ( |log| log. topics ( ) . first ( ) == Some ( & topic) )
301+ {
302+ return Err ( BuilderTransactionError :: InvalidContract (
303+ signed_tx. to ( ) . unwrap_or_default ( ) ,
304+ InvalidContractDataError :: InvalidLogs ( topic) ,
305+ ) ) ;
306+ }
307+ Ok ( SimulationSuccessResult {
308+ gas_used,
309+ output : output. into_data ( ) ,
310+ state_changes : state,
311+ } )
312+ }
313+ ExecutionResult :: Revert { output, .. } => Err ( revert_handler ( output) ) ,
314+ ExecutionResult :: Halt { reason, .. } => Err ( BuilderTransactionError :: other (
315+ BuilderTransactionError :: TransactionHalted ( reason) ,
316+ ) ) ,
317+ }
318+ }
213319}
214320
215321#[ derive( Debug , Clone ) ]
@@ -322,7 +428,3 @@ pub fn get_balance(
322428 . map ( |acc| acc. account_info ( ) . unwrap_or_default ( ) . balance )
323429 . map_err ( |_| BuilderTransactionError :: AccountLoadFailed ( address) )
324430}
325-
326- pub fn log_exists ( logs : & [ Log ] , topic : & B256 ) -> bool {
327- logs. iter ( ) . any ( |log| log. topics ( ) . first ( ) == Some ( topic) )
328- }
0 commit comments