Skip to content

test(evm): pin chain-id enforcement for typed transactions - #3895

Open
SatisLoeb wants to merge 1 commit into
sei-protocol:mainfrom
SatisLoeb:test/chainid-enforcement-typed-tx
Open

test(evm): pin chain-id enforcement for typed transactions#3895
SatisLoeb wants to merge 1 commit into
sei-protocol:mainfrom
SatisLoeb:test/chainid-enforcement-typed-tx

Conversation

@SatisLoeb

Copy link
Copy Markdown

Describe your changes and provide context

Adds a regression test pinning chain-id enforcement for typed (non-legacy) transactions across all three executor paths: the synchronous giga driver, the giga OCC driver, and v2.

To be clear up front: this is not a vulnerability report. Current behaviour is correct on every path. The reason for the test is that the enforcement is load-bearing while living in only one place, and nothing currently covers it.

Sender recovery is chain-id agnostic. helpers.RecoverAddressesFromTx builds the sig hash from the signer's chain id (modernSigner.Hash is tx.inner.sigHash(s.chainID)) and recovers the key, which is the last step of go-ethereum's modernSigner.Sender. Sender additionally rejects tx.ChainId() != s.chainID; Sei performs that comparison in EvmStatelessChecks (app/ante/evm_checktx.go) instead, which both giga drivers call before execution (app/app.go synchronous driver and makeGigaDeliverTx).

Two consequences seemed worth pinning:

  1. Because the sig hash uses the signer's chain id, the ChainId field of a typed tx is not covered by the signature — it can be edited on an already-signed transaction without invalidating it.
  2. Because recovery does not compare chain ids, EvmStatelessChecks is the only step that rejects a mismatch on a typed tx. A legacy tx would still be caught independently by AdjustV (V - 2*chainID - 8 leaves V outside {0,1} on a mismatch), but a typed tx has no such second line.

So the test asserts both halves rather than just the outcome: recovery still returns the true sender for an edited ChainId, and the transaction is nevertheless rejected with ErrInvalidChainID, moving no value and consuming no nonce. A correctly signed transaction in the same setup still executes, so the guard is shown not to be over-broad.

Happy to move this under app/ante/ or reshape it if you'd rather the coverage live closer to the check itself — it sits in giga/tests because that harness can exercise all three executors through a real block.

Testing performed to validate your change

go test ./giga/tests/ -run TestChainIDEnforced_TypedTx -count=1 -v — passes on GigaSequential, GigaOCC and V2withOCC.

I also verified the test actually detects the regression it claims to, rather than passing regardless. Temporarily disabling the non-legacy branch in EvmStatelessChecks locally makes the test fail on all three executors, and the mismatched-chain-id transaction then executes successfully (code 0) on both giga drivers:

--- FAIL: TestChainIDEnforced_TypedTx/GigaSequential   expected: 0x1c  actual: 0x0
--- FAIL: TestChainIDEnforced_TypedTx/GigaOCC          expected: 0x1c  actual: 0x0
--- FAIL: TestChainIDEnforced_TypedTx/V2withOCC        expected: 0x1c  actual: 0x5

The guard was restored before committing; the diff contains only the new test file. gofmt -s -l is clean on it.

@cursor

cursor Bot commented Aug 11, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Test-only change with no production logic modified; it documents and guards existing ante behavior.

Overview
Adds giga/tests/chainid_enforcement_test.go with TestChainIDEnforced_TypedTx, a regression test that typed (EIP-1559) txs with a tampered ChainId field (same signature) are rejected via EvmStatelessChecks on GigaSequential, GigaOCC, and V2withOCC, without moving funds or bumping nonce.

The test documents why this matters: Sei’s sender recovery is chain-id agnostic, so the mismatch is only caught in ante—not by go-ethereum-style Sender or legacy AdjustV. It also checks that a correctly signed tx in the same setup still succeeds, plus small helpers (encodeEthTx, weiBalanceOf).

Reviewed by Cursor Bugbot for commit 43c9e0c. Bugbot is set up for automated code reviews on this repo. Configure here.

Sender recovery is chain-id agnostic. helpers.RecoverAddressesFromTx
builds the sig hash from the signer's chain id and recovers the key,
which is the last step of go-ethereum's modernSigner.Sender; the
tx.ChainId() != s.chainID comparison that Sender also performs is done
in EvmStatelessChecks instead.

Two consequences are worth pinning. The ChainId field of a typed tx is
not covered by the signature, so it can be edited on a signed tx
without invalidating it. And because recovery does not compare chain
ids, EvmStatelessChecks is the only step that rejects a mismatch on a
typed tx, where a legacy tx would still be caught by AdjustV.

The test asserts both halves on the synchronous giga driver, the giga
OCC driver and v2: recovery still returns the true sender for an edited
ChainId, and the transaction is nevertheless rejected with
ErrInvalidChainID, moving no value and consuming no nonce. A correctly
signed tx in the same setup still executes, so the guard is not
over-broad.
@SatisLoeb
SatisLoeb force-pushed the test/chainid-enforcement-typed-tx branch from aba4f40 to 43c9e0c Compare August 11, 2026 09:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant