@@ -2,13 +2,15 @@ package sim_test
22
33import (
44 "context"
5+ "encoding/hex"
56 "encoding/json"
67 "net/http"
78 "net/http/httptest"
89 "strings"
910 "testing"
1011
1112 "github.com/ethereum/go-ethereum/common"
13+ "github.com/ethereum/go-ethereum/core/types"
1214 "github.com/primev/mev-commit/tools/preconf-rpc/sim"
1315)
1416
@@ -255,6 +257,188 @@ var traceCallResponseBalancer = `{
255257 ]
256258}`
257259
260+ // Real mainnet transaction test vectors for e2e validation of parsing and sender recovery.
261+ var realTxVectors = []struct {
262+ name string
263+ rawHex string
264+ expectedType uint8
265+ expectedSender string
266+ hasChainID bool
267+ }{
268+ {
269+ // Pre-EIP-155 legacy transaction (no chain ID replay protection)
270+ // Block 46147 - early mainnet transaction
271+ name : "PreEIP155_Legacy" ,
272+ rawHex : "f86780862d79883d2000825208945df9b87991262f6ba471f09758cde1c0fc1de734827a69801ca088ff6cf0fefd94db46111149ae4bfc179e9b94721fffd821d38d16464b3f71d0a045e0aff800961cfce805daef7016f9ae479c0a24afba38dd33c2ecdbb01dcacf" ,
273+ expectedType : types .LegacyTxType ,
274+ expectedSender : "0xD3678D173368032b34E00AE057C31b083FBAb830" ,
275+ hasChainID : false ,
276+ },
277+ {
278+ // EIP-1559 dynamic fee transaction (type 2)
279+ name : "EIP1559_DynamicFee" ,
280+ rawHex : "02f8730101843b9aca00850c92a69c0082520894d8da6bf26964af9d7eed9e03e53415d37aa9604588016345785d8a000080c001a0a9f0aabbfa2b831dd37d0f8d48d941f35f4fd40a1f2e2fa74a7df3e60aa534c8a0488e799fae157d086b8e0b624ab63627f14509482fe037e88f516a3725070896" ,
281+ expectedType : types .DynamicFeeTxType ,
282+ expectedSender : "0xcEC000D467698070C6D8D73D8ff1F60FD7DCb531" ,
283+ hasChainID : true ,
284+ },
285+ }
286+
287+ func TestTransactionParsingAndSenderRecovery (t * testing.T ) {
288+ for _ , tc := range realTxVectors {
289+ t .Run (tc .name , func (t * testing.T ) {
290+ rawBytes , err := hex .DecodeString (tc .rawHex )
291+ if err != nil {
292+ t .Fatalf ("failed to decode hex: %v" , err )
293+ }
294+
295+ tx := new (types.Transaction )
296+ if err := tx .UnmarshalBinary (rawBytes ); err != nil {
297+ t .Fatalf ("failed to parse tx: %v" , err )
298+ }
299+
300+ if tx .Type () != tc .expectedType {
301+ t .Errorf ("expected tx type %d, got %d" , tc .expectedType , tx .Type ())
302+ }
303+
304+ if tc .hasChainID {
305+ if tx .ChainId ().Sign () == 0 {
306+ t .Error ("expected non-zero chainId" )
307+ }
308+ } else {
309+ if tx .ChainId ().Sign () != 0 {
310+ t .Errorf ("expected chainId 0, got %s" , tx .ChainId ().String ())
311+ }
312+ }
313+
314+ sender , err := recoverSenderForTest (tx )
315+ if err != nil {
316+ t .Fatalf ("failed to recover sender: %v" , err )
317+ }
318+
319+ if sender == (common.Address {}) {
320+ t .Error ("recovered zero address" )
321+ }
322+
323+ if tc .expectedSender != "" {
324+ expected := common .HexToAddress (tc .expectedSender )
325+ if sender != expected {
326+ t .Errorf ("sender mismatch: got %s, want %s" , sender .Hex (), expected .Hex ())
327+ }
328+ }
329+
330+ t .Logf ("tx=%s sender=%s chainId=%s" , tc .name , sender .Hex (), tx .ChainId ().String ())
331+ })
332+ }
333+ }
334+
335+ func recoverSenderForTest (tx * types.Transaction ) (common.Address , error ) {
336+ var signer types.Signer
337+ switch tx .Type () {
338+ case types .LegacyTxType :
339+ if tx .ChainId ().Sign () == 0 {
340+ signer = types.HomesteadSigner {}
341+ } else {
342+ signer = types .NewEIP155Signer (tx .ChainId ())
343+ }
344+ case types .AccessListTxType :
345+ signer = types .NewEIP2930Signer (tx .ChainId ())
346+ case types .DynamicFeeTxType :
347+ signer = types .NewLondonSigner (tx .ChainId ())
348+ case types .BlobTxType :
349+ signer = types .NewCancunSigner (tx .ChainId ())
350+ default :
351+ signer = types .LatestSignerForChainID (tx .ChainId ())
352+ }
353+ return types .Sender (signer , tx )
354+ }
355+
356+ // TestSimulateWithRealTransactions tests the full simulation pipeline with real transaction hex.
357+ // This verifies parsing, sender recovery, call object construction, and RPC interaction.
358+ func TestSimulateWithRealTransactions (t * testing.T ) {
359+ // Create mock server that accepts any valid simulation request
360+ srv := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
361+ var req struct {
362+ Method string `json:"method"`
363+ Params []json.RawMessage `json:"params"`
364+ ID int `json:"id"`
365+ }
366+ if err := json .NewDecoder (r .Body ).Decode (& req ); err != nil {
367+ http .Error (w , "bad request" , http .StatusBadRequest )
368+ return
369+ }
370+ defer func () { _ = r .Body .Close () }()
371+
372+ w .Header ().Set ("Content-Type" , "application/json" )
373+
374+ // Return successful simulation response for eth_simulateV1
375+ if req .Method == "eth_simulateV1" {
376+ response := map [string ]interface {}{
377+ "jsonrpc" : "2.0" ,
378+ "id" : req .ID ,
379+ "result" : json .RawMessage (simulateV1ResponseSimple ),
380+ }
381+ _ = json .NewEncoder (w ).Encode (response )
382+ return
383+ }
384+
385+ // Fallback to debug_traceCall
386+ if req .Method == "debug_traceCall" {
387+ response := map [string ]interface {}{
388+ "jsonrpc" : "2.0" ,
389+ "id" : req .ID ,
390+ "result" : json .RawMessage (traceCallResponseSimple ),
391+ }
392+ _ = json .NewEncoder (w ).Encode (response )
393+ return
394+ }
395+
396+ // Unknown method
397+ response := map [string ]interface {}{
398+ "jsonrpc" : "2.0" ,
399+ "id" : req .ID ,
400+ "error" : map [string ]interface {}{
401+ "code" : - 32601 ,
402+ "message" : "Method not found" ,
403+ },
404+ }
405+ _ = json .NewEncoder (w ).Encode (response )
406+ }))
407+ defer srv .Close ()
408+
409+ simulator , err := sim .NewInlineSimulator ([]string {srv .URL }, nil )
410+ if err != nil {
411+ t .Fatalf ("failed to create simulator: %v" , err )
412+ }
413+ defer func () { _ = simulator .Close () }()
414+
415+ for _ , tc := range realTxVectors {
416+ t .Run (tc .name , func (t * testing.T ) {
417+ // Test full simulation pipeline with real tx hex
418+ logs , isSwap , err := simulator .Simulate (context .Background (), tc .rawHex , sim .Latest )
419+ if err != nil {
420+ t .Fatalf ("simulation failed: %v" , err )
421+ }
422+
423+ // Verify we got a response (mock returns simple transfer with no logs)
424+ if logs == nil {
425+ t .Error ("expected non-nil logs slice" )
426+ }
427+
428+ t .Logf ("tx=%s simulated successfully, logs=%d, isSwap=%v" , tc .name , len (logs ), isSwap )
429+ })
430+ }
431+
432+ // Test with pending state
433+ t .Run ("PendingState" , func (t * testing.T ) {
434+ logs , isSwap , err := simulator .Simulate (context .Background (), realTxVectors [0 ].rawHex , sim .Pending )
435+ if err != nil {
436+ t .Fatalf ("simulation with pending state failed: %v" , err )
437+ }
438+ t .Logf ("pending state simulation: logs=%d, isSwap=%v" , len (logs ), isSwap )
439+ })
440+ }
441+
258442func TestInlineSimulator (t * testing.T ) {
259443 // eth_simulateV1 responses
260444 simV1Responses := map [string ]string {
@@ -461,10 +645,7 @@ func TestInlineSimulator(t *testing.T) {
461645
462646// TestSwapDetection tests the swap detector with realistic trace responses
463647func TestSwapDetection (t * testing.T ) {
464- // Test nested trace logs collection from aggregator multi-hop
465648 t .Run ("NestedTraceLogCollection" , func (t * testing.T ) {
466- // Simulate what happens in a multi-hop swap
467- // The logs are nested inside calls
468649 logs := []sim.TraceLog {
469650 // First hop - SushiSwap (uses same signature as Uniswap V2 Swap)
470651 {
@@ -523,7 +704,6 @@ func TestSwapDetection(t *testing.T) {
523704}
524705
525706func TestSwapSignatures (t * testing.T ) {
526- // Test all swap event signatures from rethsim
527707 swapTests := []struct {
528708 name string
529709 topicHash string
@@ -580,7 +760,6 @@ func TestSwapSignatures(t *testing.T) {
580760 })
581761 }
582762
583- // Test multiple swap events in one transaction (aggregator scenario)
584763 t .Run ("DetectMultipleSwaps" , func (t * testing.T ) {
585764 logs := []sim.TraceLog {
586765 {
@@ -608,7 +787,6 @@ func TestSwapSignatures(t *testing.T) {
608787 }
609788 })
610789
611- // Test deduplication of same swap type
612790 t .Run ("DeduplicateSameSwapType" , func (t * testing.T ) {
613791 logs := []sim.TraceLog {
614792 {
0 commit comments