Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions integration_test/evm_module/rpc_io_test/FAILED_TEST_ANALYSIS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Failed RPC IO analysis (brief)

**Principle:** Fixtures encode **Ethereum-expected behavior**. A test must **fail** when Sei RPC diverges. Fix the **RPC**, not the fixture.

## Latest run (evm_rpc_tests.sh)

| Metric | Count |
| --------- | ----- |
| Total | 162 |
| Passed | 133 |
| Failed | 29 |
| Skipped | 0 |
| Pass rate | 82.1% |

## Failed tests by endpoint (29)

| Endpoint | # | Fixtures / cause |
| -------- | - | ---------------- |
| debug_getRawBlock | 2 | get-block-n.iox, get-genesis.iox |
| debug_getRawHeader | 2 | get-block-n.iox, get-genesis.iox |
| debug_getRawReceipts | 2 | get-block-n.iox, get-genesis.iox |
| debug_getRawTransaction | 1 | get-tx.iox |
| eth_blobBaseFee | 1 | get-current-blobfee.iox |
| eth_call | 1 | call-callenv-options-eip1559.iox (EIP1559 params; Sei returns error) |
| eth_createAccessList | 3 | create-al-abi-revert, create-al-contract-eip1559, create-al-contract (insufficient funds / gas fee) |
| eth_estimateGas | 2 | estimate-with-eip4844.iox, estimate-with-eip7702.iox (parse error) |
| eth_estimateGasAfterCalls | 1 | estimateGasAfterCalls.iox (insufficient funds) |
| eth_getBlockByHash | 2 | get-block-by-empty-hash, get-block-by-notfound-hash (Sei returns error; spec: result=null) |
| eth_getBlockByNumber | 1 | get-block-notfound.iox (height not available vs spec null) |
| eth_getBlockReceipts | 2 | get-block-receipts-empty, get-block-receipts-not-found (Sei returns error; spec: result=null) |
| eth_getBlockTransactionCountByHash | 1 | get-genesis.iox (hash lookup: block from getBlockByNumber("0x0") not found by hash) |
| eth_getLogs | 1 | filter-error-future-block-range.io (Sei returns []; spec: error when range > head) |
| eth_getProof | 3 | get-account-proof-* (cannot find EVM IAVL store) |
| eth_getTransactionByBlockHashAndIndex | 1 | get-block-n.iox (transaction index out of range) |
| eth_getTransactionByBlockNumberAndIndex | 1 | get-block-n.iox (transaction index out of range) |
| eth_newPendingTransactionFilter | 1 | newPendingTransactionFilter.iox |
| eth_syncing | 1 | check-syncing.iox |

## RPC not implemented (-32601)

| Endpoint | Fixture(s) | Note |
| -------- | ---------- | ---- |
| debug_getRawBlock | get-block-n, get-genesis | No GetRawBlock on DebugAPI |
| debug_getRawHeader | get-block-n, get-genesis | No GetRawHeader |
| debug_getRawReceipts | get-block-n, get-genesis | No GetRawReceipts |
| debug_getRawTransaction | get-tx.iox | No GetRawTransaction |
| eth_blobBaseFee | get-current-blobfee.iox | Not exposed on eth API |
| eth_newPendingTransactionFilter | newPendingTransactionFilter.iox | No NewPendingTransactionFilter in FilterAPI |
| eth_syncing | check-syncing.iox | No Syncing on InfoAPI |

## Fix direction (no fixture changes)

| Category | Endpoints / fixtures | Action |
| -------- | -------------------- | ------ |
| **Return null for missing block** | eth_getBlockByHash, eth_getBlockReceipts (empty/notfound) | RPC: return `result: null` instead of -32000 for non-existent block hash |
| **Block hash lookup** | eth_getBlockTransactionCountByHash (get-genesis) | RPC: resolve block by hash when that hash was returned by getBlockByNumber |
| **Block range validation** | eth_getLogs (filter-error-future-block-range) | RPC: return -32602 when toBlock > current head |
| **EIP1559 in eth_call** | eth_call (call-callenv-options-eip1559) | RPC: accept maxFeePerGas/maxPriorityFeePerGas and return result |
| **Other** | eth_createAccessList (3), eth_estimateGas (2), eth_estimateGasAfterCalls, eth_getBlockByNumber (notfound), eth_getProof (3), eth_getTransactionBy*Index (2) | Investigate; fix RPC or env (e.g. funded “from”, parse, IAVL store, tx index) |

*Removed fixtures (not in suite): call-revert-abi-error.io, call-revert-abi-panic.io, estimate-call-abi-error.io, estimate-failed-call.io. Revert coverage: call-revert-abi-error-sei.iox, estimate-call-abi-error-sei.iox (use __REVERTER__). eth_simulateV1 folder is not under testdata.*
203 changes: 74 additions & 129 deletions integration_test/evm_module/rpc_io_test/RPC_IO_README.md

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions integration_test/evm_module/rpc_io_test/io.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,13 @@ func substituteSeedTag(request []byte, seedBlock string) []byte {
return []byte(strings.ReplaceAll(string(request), `"__SEED__"`, `"`+v+`"`))
}

func substituteReverterTag(request []byte, reverterAddr string) []byte {
if reverterAddr == "" {
return request
}
return []byte(strings.ReplaceAll(string(request), `"__REVERTER__"`, `"`+reverterAddr+`"`))
}

func substituteRequest(request []byte, bindings map[string]any) []byte {
if len(bindings) == 0 {
return request
Expand Down
12 changes: 12 additions & 0 deletions integration_test/evm_module/rpc_io_test/io_parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,18 @@ func TestSubstituteSeedTag(t *testing.T) {
}
}

func TestSubstituteReverterTag(t *testing.T) {
req := []byte(`{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"to":"__REVERTER__","data":"0x01"},"latest"]}`)
out := substituteReverterTag(req, "0xabc")
if !bytes.Contains(out, []byte("0xabc")) || bytes.Contains(out, []byte("__REVERTER__")) {
t.Errorf("substituteReverterTag: expected __REVERTER__ replaced with 0xabc, got %s", out)
}
out2 := substituteReverterTag(req, "")
if !bytes.Equal(out2, req) {
t.Errorf("substituteReverterTag with empty addr should return request unchanged, got %s", out2)
}
}

func TestRequestPlaceholders(t *testing.T) {
req := []byte(`{"params":["${blockHash}",false]}`)
got := requestPlaceholders(req)
Expand Down
14 changes: 13 additions & 1 deletion integration_test/evm_module/rpc_io_test/rpc_io_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package rpc_io_test

import (
"bytes"
"encoding/json"
"net/http"
"os"
Expand Down Expand Up @@ -57,6 +58,7 @@ func TestEVMRPCSpec(t *testing.T) {

debug := os.Getenv("SEI_EVM_IO_DEBUG_FILES") != ""
seedBlock := os.Getenv("SEI_EVM_IO_SEED_BLOCK")
reverterAddr := os.Getenv("SEI_EVM_IO_REVERTER_ADDRESS")
var passed, failed, skipped int

for _, rel := range files {
Expand Down Expand Up @@ -88,6 +90,16 @@ func TestEVMRPCSpec(t *testing.T) {
if len(pairs) == 0 {
t.Skip("no request/response pairs in file")
}
needReverter := false
for _, p := range pairs {
if bytes.Contains(p.Request, []byte("__REVERTER__")) {
needReverter = true
break
}
}
if needReverter && reverterAddr == "" {
t.Skip("__REVERTER__ used but SEI_EVM_IO_REVERTER_ADDRESS not set (run evm_rpc_tests.sh to deploy reverter)")
}

bindings := make(map[string]any)
if deployTx := os.Getenv("SEI_EVM_IO_DEPLOY_TX_HASH"); deployTx != "" {
Expand All @@ -112,7 +124,7 @@ func TestEVMRPCSpec(t *testing.T) {
t.Skipf("pair %d: missing binding ${%s}", i+1, missing[0])
}

req := substituteSeedTag(substituteRequest(pair.Request, bindings), seedBlock)
req := substituteSeedTag(substituteRequest(substituteReverterTag(pair.Request, reverterAddr), bindings), seedBlock)
if debug {
t.Logf("[DEBUG] pair %d: request %s", i+1, req)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Data-dependent: eth_call with EIP1559 options to our deployed contract. Run evm_rpc_tests.sh when local.
// Data-dependent: eth_call with EIP1559 options (Ethereum behavior). Run evm_rpc_tests.sh when local.
// Tests that Sei accepts maxFeePerGas/maxPriorityFeePerGas in eth_call like Ethereum. Fails if not.
>> {"jsonrpc":"2.0","id":1,"method":"eth_getBlockByNumber","params":["__SEED__",true]}
<< {"jsonrpc":"2.0","id":1,"result":{}}
@ bind deployTxHash = result.transactions.1.hash
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// calls a contract that reverts with an ABI-encoded Error(string) value
>> {"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"from":"0x0000000000000000000000000000000000000000","gas":"0x186a0","input":"0x01","to":"0x0ee3ab1371c93e7c0c281cc0c2107cdebc8b1930"},"latest"]}
// Self-contained: script deploys reverter contract; runner substitutes __REVERTER__ with its address.
// Expects eth_call to return JSON-RPC error code 3 with ABI-encoded Error("user error").
>> {"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"from":"0x0000000000000000000000000000000000000000","gas":"0x186a0","input":"0x01","to":"__REVERTER__"},"latest"]}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This now only covers the error path; we are missing coverage for panic now that *-panic.io is removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point!
we were only covering the error path. I've restored panic coverage.

<< {"jsonrpc":"2.0","id":1,"error":{"code":3,"message":"execution reverted: user error","data":"0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a75736572206572726f72"}}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Data-dependent: createAccessList for simple value transfer to 0xF87A (our script recipient). Run evm_rpc_tests.sh when local.
>> {"jsonrpc":"2.0","id":1,"method":"eth_createAccessList","params":[{"to":"0xF87A299e6bC7bEba58dbBe5a5Aa21d49bCD16D52","value":"0x1"},"latest"]}
// Data-dependent: createAccessList for simple value transfer to 0xF87A (script recipient). Bind "from" from deploy receipt so the simulator uses a funded account.
>> {"jsonrpc":"2.0","id":1,"method":"eth_getTransactionReceipt","params":["${deployTxHash}"]}
<< {"jsonrpc":"2.0","id":1,"result":{}}
@ bind fromAddress = result.from
>> {"jsonrpc":"2.0","id":1,"method":"eth_createAccessList","params":[{"from":"${fromAddress}","to":"0xF87A299e6bC7bEba58dbBe5a5Aa21d49bCD16D52","value":"0x1"},"latest"]}
<< {"jsonrpc":"2.0","id":1,"result":{}}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// estimates a contract call that reverts using Solidity Error(string) data
// speconly: client response is only checked for schema validity.
>> {"jsonrpc":"2.0","id":1,"method":"eth_estimateGas","params":[{"from":"0x0102030000000000000000000000000000000000","input":"0x01","to":"0x0ee3ab1371c93e7c0c281cc0c2107cdebc8b1930"}]}
// Self-contained: script deploys reverter; runner substitutes __REVERTER__. Expects eth_estimateGas to return error (revert).
>> {"jsonrpc":"2.0","id":1,"method":"eth_estimateGas","params":[{"from":"0x0000000000000000000000000000000000000000","input":"0x01","to":"__REVERTER__"}]}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to eth_call comment, i think now that estimate-failed-call.io is removed we are missing tests for panic and other revert/failure modes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I've added the panic fixture.

<< {"jsonrpc":"2.0","id":1,"error":{"code":3,"message":"execution reverted: user error","data":"0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a75736572206572726f72"}}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// Not data-dependent: empty hash returns null on any chain.
// Ethereum spec: non-existent block hash (e.g. zero) returns result=null, not error. Test fails if Sei returns error.
>> {"jsonrpc":"2.0","id":1,"method":"eth_getBlockByHash","params":["0x0000000000000000000000000000000000000000000000000000000000000000",true]}
<< {"jsonrpc":"2.0","id":1,"result":null}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// Not data-dependent: non-existent block hash returns null on any chain.
// Ethereum spec: non-existent block hash returns result=null, not error. Test fails if Sei returns error.
>> {"jsonrpc":"2.0","id":1,"method":"eth_getBlockByHash","params":["0x00000000000000000000000000000000000000000000000000000000deadbeef",true]}
<< {"jsonrpc":"2.0","id":1,"result":null}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// Not data-dependent: empty block hash returns null on any chain.
// Ethereum spec: non-existent/empty block hash returns result=null, not error. Test fails if Sei returns error.
>> {"jsonrpc":"2.0","id":1,"method":"eth_getBlockReceipts","params":["0x0000000000000000000000000000000000000000000000000000000000000000"]}
<< {"jsonrpc":"2.0","id":1,"result":null}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// Not data-dependent: non-existent block hash returns null on any chain.
// Ethereum spec: non-existent block hash returns result=null, not error. Test fails if Sei returns error.
>> {"jsonrpc":"2.0","id":1,"method":"eth_getBlockReceipts","params":["0x00000000000000000000000000000000000000000000000000000000deadbeef"]}
<< {"jsonrpc":"2.0","id":1,"result":null}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Data-dependent: get genesis block by number, bind hash, then getBlockTransactionCountByHash(genesisHash). Genesis exists on any chain.
// Ethereum: getBlockByNumber("0x0") then getBlockTransactionCountByHash(that hash) must return result (e.g. "0x0"). Fails if Sei returns "block not found" for the same hash (RPC hash lookup bug).
>> {"jsonrpc":"2.0","id":1,"method":"eth_getBlockByNumber","params":["0x0",false]}
<< {"jsonrpc":"2.0","id":1,"result":{}}
@ bind genesisHash = result.hash
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// checks that an error is returned if `toBlock` is greater than the latest block
// Ethereum spec: eth_getLogs must return error (-32602) when block range extends beyond current head. Test fails if Sei returns result (e.g. []).
>> {"jsonrpc":"2.0","id":1,"method":"eth_getLogs","params":[{"fromBlock":"0x29","toBlock":"0x2f"}]}
<< {"jsonrpc":"2.0","id":1,"error":{"code":-32602,"message":"block range extends beyond current head block"}}

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Loading
Loading