@@ -19,6 +19,7 @@ import (
1919 "sync"
2020 "time"
2121
22+ "github.com/ethereum/go-ethereum/common"
2223 "github.com/ethereum/go-ethereum/core"
2324 "github.com/ethereum/go-ethereum/core/tracing"
2425 ethtypes "github.com/ethereum/go-ethereum/core/types"
@@ -151,6 +152,7 @@ import (
151152 "github.com/sei-protocol/sei-chain/x/evm/querier"
152153 "github.com/sei-protocol/sei-chain/x/evm/replay"
153154 evmtypes "github.com/sei-protocol/sei-chain/x/evm/types"
155+ evmethtx "github.com/sei-protocol/sei-chain/x/evm/types/ethtx"
154156 "github.com/sei-protocol/sei-chain/x/mint"
155157 mintclient "github.com/sei-protocol/sei-chain/x/mint/client/cli"
156158 mintkeeper "github.com/sei-protocol/sei-chain/x/mint/keeper"
@@ -1746,122 +1748,25 @@ func (app *App) executeEVMTxWithGigaExecutor(ctx sdk.Context, msg *evmtypes.MsgE
17461748
17471749 _ , isAssociated := app .GigaEvmKeeper .GetEVMAddress (ctx , seiAddr )
17481750
1749- // ============================================================================
1750- // Nonce validation (mirrors V2's ante handler check in x/evm/ante/sig.go)
1751- // V2 rejects with ErrWrongSequence if txNonce != expectedNonce, with NO state changes.
1752- // ============================================================================
1753- expectedNonce := app .GigaEvmKeeper .GetNonce (ctx , sender )
1754- txNonce := ethTx .Nonce ()
1755- if txNonce != expectedNonce {
1756- nonceDirection := "too high"
1757- if txNonce < expectedNonce {
1758- nonceDirection = "too low"
1759- }
1760- return & abci.ExecTxResult {
1761- Code : sdkerrors .ErrWrongSequence .ABCICode (),
1762- GasWanted : int64 (ethTx .Gas ()), //nolint:gosec
1763- Log : fmt .Sprintf ("nonce %s: address %s, tx: %d state: %d: %s" , nonceDirection , sender .Hex (), txNonce , expectedNonce , sdkerrors .ErrWrongSequence .Error ()),
1764- }, nil
1765- }
1766-
1767- // ============================================================================
1768- // Fee validation (mirrors V2's ante handler checks in evm_checktx.go)
1769- // NOTE: In V2, failed transactions still increment nonce and charge gas.
1770- // We track validation errors here but don't return early - we still need to
1771- // create stateDB, increment nonce, and finalize state to match V2 behavior.
1772- // ============================================================================
1773- baseFee := app .GigaEvmKeeper .GetBaseFee (ctx )
1774- if baseFee == nil {
1775- baseFee = new (big.Int ) // default to 0 when base fee is unset
1776- }
1777-
1778- // Track validation errors - we'll skip execution but still finalize state
1779- var validationErr * abci.ExecTxResult
1780-
1781- // 1. Fee cap < base fee check (INSUFFICIENT_MAX_FEE_PER_GAS)
1782- // V2: evm_checktx.go line 284-286
1783- if txData .GetGasFeeCap ().Cmp (baseFee ) < 0 {
1784- validationErr = & abci.ExecTxResult {
1785- Code : sdkerrors .ErrInsufficientFee .ABCICode (),
1786- Log : "max fee per gas less than block base fee" ,
1787- }
1788- }
1789-
1790- // 2. Tip > fee cap check (PRIORITY_GREATER_THAN_MAX_FEE_PER_GAS)
1791- // This is checked in txData.Validate() for DynamicFeeTx, but we also check here
1792- // to ensure consistent rejection before execution.
1793- if validationErr == nil && txData .GetGasTipCap ().Cmp (txData .GetGasFeeCap ()) > 0 {
1794- validationErr = & abci.ExecTxResult {
1795- Code : 1 ,
1796- Log : "max priority fee per gas higher than max fee per gas" ,
1797- }
1798- }
1799-
1800- // 3. Gas limit * gas price overflow check (GASLIMIT_PRICE_PRODUCT_OVERFLOW)
1801- // V2: Uses IsValidInt256(tx.Fee()) in dynamic_fee_tx.go Validate()
1802- // Fee = GasFeeCap * GasLimit, must fit in 256 bits
1803- if validationErr == nil && ! ethtx .IsValidInt256 (txData .Fee ()) {
1804- validationErr = & abci.ExecTxResult {
1805- Code : 1 ,
1806- Log : "fee out of bound" ,
1807- }
1808- }
1809-
1810- // 4. TX gas limit > block gas limit check (GAS_ALLOWANCE_EXCEEDED)
1811- // V2: x/evm/ante/basic.go lines 63-68
1812- if validationErr == nil {
1813- if cp := ctx .ConsensusParams (); cp != nil && cp .Block != nil {
1814- if cp .Block .MaxGas > 0 && ethTx .Gas () > uint64 (cp .Block .MaxGas ) { //nolint:gosec
1815- validationErr = & abci.ExecTxResult {
1816- Code : sdkerrors .ErrOutOfGas .ABCICode (),
1817- Log : fmt .Sprintf ("tx gas limit %d exceeds block max gas %d" , ethTx .Gas (), cp .Block .MaxGas ),
1818- }
1819- }
1820- }
1821- }
1822-
1823- // 5. Insufficient balance check for gas * price + value (INSUFFICIENT_FUNDS_FOR_TRANSFER)
1824- if validationErr == nil {
1825- // BuyGas checks balance against GasLimit * GasFeeCap + Value (see go-ethereum/core/state_transition.go:264-291)
1826- balanceCheck := new (big.Int ).Mul (new (big.Int ).SetUint64 (ethTx .Gas ()), ethTx .GasFeeCap ())
1827- balanceCheck .Add (balanceCheck , ethTx .Value ())
1828-
1829- senderBalance := app .GigaEvmKeeper .GetBalance (ctx , seiAddr )
1830-
1831- // For unassociated addresses, V2's PreprocessDecorator migrates the cast address balance
1832- // BEFORE the fee check (in a CacheMultiStore). We need to include the cast address balance
1833- // in our check to match V2's behavior, even though we defer the actual migration.
1834- if ! isAssociated {
1835- // Cast address is the EVM address bytes interpreted as a Sei address
1836- castAddr := sdk .AccAddress (sender [:])
1837- castBalance := app .GigaEvmKeeper .GetBalance (ctx , castAddr )
1838- senderBalance = new (big.Int ).Add (senderBalance , castBalance )
1839- }
1840-
1841- if senderBalance .Cmp (balanceCheck ) < 0 {
1842- validationErr = & abci.ExecTxResult {
1843- Code : sdkerrors .ErrInsufficientFunds .ABCICode (),
1844- Log : fmt .Sprintf ("insufficient funds for gas * price + value: address %s have %v want %v: insufficient funds" , sender .Hex (), senderBalance , balanceCheck ),
1845- }
1846- }
1847- }
1751+ // Run validation checks (ordered to match V2's priority)
1752+ validation := app .validateGigaEVMTx (ctx , txData , sender , seiAddr , isAssociated )
18481753
18491754 // Prepare context for EVM transaction (set infinite gas meter like original flow)
18501755 ctx = ctx .WithGasMeter (sdk .NewInfiniteGasMeterWithMultiplier (ctx ))
18511756
1852- // If validation failed, increment nonce via keeper (matching V2's DeliverTxCallback behavior
1853- // in x/evm/ante/basic.go). V2 does NOT create stateDB or handle surplus for early failures.
1854- if validationErr != nil {
1855- // Match V2 error handling: bump nonce directly via keeper (not stateDB)
1856- currentNonce := app . GigaEvmKeeper . GetNonce ( ctx , sender )
1857- app .GigaEvmKeeper .SetNonce (ctx , sender , currentNonce + 1 )
1858-
1757+ if validation . err != nil {
1758+ // Validation failed - bump nonce via keeper if it was valid (matches V2's DeliverTxCallback
1759+ // behavior where nonce is incremented even on fee validation failures).
1760+ // For successful txs, the nonce is bumped by the EVM during execution.
1761+ if validation . nonceValid {
1762+ app .GigaEvmKeeper .SetNonce (ctx , sender , validation . currentNonce + 1 )
1763+ }
18591764 // V2 reports intrinsic gas as gasUsed even on validation failure (for metrics),
18601765 // but no actual balance is deducted
18611766 intrinsicGas , _ := core .IntrinsicGas (ethTx .Data (), ethTx .AccessList (), ethTx .SetCodeAuthorizations (), ethTx .To () == nil , true , true , true )
1862- validationErr .GasUsed = int64 (intrinsicGas ) //nolint:gosec
1863- validationErr .GasWanted = int64 (ethTx .Gas ()) //nolint:gosec
1864- return validationErr , nil
1767+ validation . err .GasUsed = int64 (intrinsicGas ) //nolint:gosec
1768+ validation . err .GasWanted = int64 (ethTx .Gas ()) //nolint:gosec
1769+ return validation . err , nil
18651770 }
18661771
18671772 if ! isAssociated {
@@ -1895,7 +1800,7 @@ func (app *App) executeEVMTxWithGigaExecutor(ctx sdk.Context, msg *evmtypes.MsgE
18951800 // V2 charges fees in the ante handler, then runs the EVM with feeAlreadyCharged=true
18961801 // which skips buyGas/refundGas/coinbase. Without this, GasUsed differs between Giga
18971802 // and V2, causing LastResultsHash → AppHash divergence.
1898- effectiveGasPrice := new (big.Int ).Add (new (big.Int ).Set (ethTx .GasTipCap ()), baseFee )
1803+ effectiveGasPrice := new (big.Int ).Add (new (big.Int ).Set (ethTx .GasTipCap ()), validation . baseFee )
18991804 if effectiveGasPrice .Cmp (ethTx .GasFeeCap ()) > 0 {
19001805 effectiveGasPrice .Set (ethTx .GasFeeCap ())
19011806 }
@@ -2563,6 +2468,116 @@ func (app *App) inplacetestnetInitializer(pk cryptotypes.PubKey) error {
25632468 return nil
25642469}
25652470
2471+ // gigaValidationResult holds the result of EVM transaction validation.
2472+ type gigaValidationResult struct {
2473+ err * abci.ExecTxResult // nil if validation passed
2474+ nonceValid bool // true if tx nonce matches expected nonce
2475+ currentNonce uint64 // the expected nonce at time of validation
2476+ baseFee * big.Int // the base fee used for validation
2477+ }
2478+
2479+ // validateGigaEVMTx validates an EVM tx, returning the first error found.
2480+ // Checks are ordered to match V2's priority.
2481+ func (app * App ) validateGigaEVMTx (
2482+ ctx sdk.Context ,
2483+ txData evmethtx.TxData ,
2484+ sender common.Address ,
2485+ seiAddr sdk.AccAddress ,
2486+ isAssociated bool ,
2487+ ) gigaValidationResult {
2488+ var validationErr * abci.ExecTxResult
2489+
2490+ // 1. Gas limit exceeds block max
2491+ if cp := ctx .ConsensusParams (); cp != nil && cp .Block != nil {
2492+ if cp .Block .MaxGas > 0 && txData .GetGas () > uint64 (cp .Block .MaxGas ) { //nolint:gosec
2493+ validationErr = & abci.ExecTxResult {
2494+ Code : sdkerrors .ErrOutOfGas .ABCICode (),
2495+ Log : fmt .Sprintf ("tx gas limit %d exceeds block max gas %d" , txData .GetGas (), cp .Block .MaxGas ),
2496+ }
2497+ }
2498+ }
2499+
2500+ // 2. Negative gas tip cap (Immunefi 35146)
2501+ if validationErr == nil && txData .GetGasTipCap ().Sign () < 0 {
2502+ validationErr = & abci.ExecTxResult {
2503+ Code : sdkerrors .ErrInvalidRequest .ABCICode (),
2504+ Log : "gas tip cap cannot be negative" ,
2505+ }
2506+ }
2507+
2508+ // 3. Tip exceeds fee cap
2509+ if validationErr == nil && txData .GetGasTipCap ().Cmp (txData .GetGasFeeCap ()) > 0 {
2510+ validationErr = & abci.ExecTxResult {
2511+ Code : 1 ,
2512+ Log : "max priority fee per gas higher than max fee per gas" ,
2513+ }
2514+ }
2515+
2516+ // 4. Fee overflow
2517+ if validationErr == nil && ! ethtx .IsValidInt256 (txData .Fee ()) {
2518+ validationErr = & abci.ExecTxResult {
2519+ Code : 1 ,
2520+ Log : "fee out of bound" ,
2521+ }
2522+ }
2523+
2524+ // 5. Fee cap below base fee
2525+ baseFee := app .GigaEvmKeeper .GetBaseFee (ctx )
2526+ if baseFee == nil {
2527+ baseFee = new (big.Int )
2528+ }
2529+ if validationErr == nil && txData .GetGasFeeCap ().Cmp (baseFee ) < 0 {
2530+ validationErr = & abci.ExecTxResult {
2531+ Code : sdkerrors .ErrInsufficientFee .ABCICode (),
2532+ Log : "max fee per gas less than block base fee" ,
2533+ }
2534+ }
2535+
2536+ // 6. Invalid nonce
2537+ currentNonce := app .GigaEvmKeeper .GetNonce (ctx , sender )
2538+ txNonce := txData .GetNonce ()
2539+ nonceValid := txNonce == currentNonce
2540+ if validationErr == nil && ! nonceValid {
2541+ nonceDirection := "too high"
2542+ if txNonce < currentNonce {
2543+ nonceDirection = "too low"
2544+ }
2545+ validationErr = & abci.ExecTxResult {
2546+ Code : sdkerrors .ErrWrongSequence .ABCICode (),
2547+ Log : fmt .Sprintf ("nonce %s: address %s, tx: %d state: %d: %s" , nonceDirection , sender .Hex (), txNonce , currentNonce , sdkerrors .ErrWrongSequence .Error ()),
2548+ }
2549+ }
2550+
2551+ // 7. Insufficient balance for gas + value
2552+ if validationErr == nil {
2553+ balanceCheck := new (big.Int ).Mul (new (big.Int ).SetUint64 (txData .GetGas ()), txData .GetGasFeeCap ())
2554+ balanceCheck .Add (balanceCheck , txData .GetValue ())
2555+
2556+ senderBalance := app .GigaEvmKeeper .GetBalance (ctx , seiAddr )
2557+
2558+ // Include cast address balance for unassociated addresses (matches V2 PreprocessDecorator)
2559+ if ! isAssociated {
2560+ castAddr := sdk .AccAddress (sender [:])
2561+ castBalance := app .GigaEvmKeeper .GetBalance (ctx , castAddr )
2562+ senderBalance = new (big.Int ).Add (senderBalance , castBalance )
2563+ }
2564+
2565+ if senderBalance .Cmp (balanceCheck ) < 0 {
2566+ validationErr = & abci.ExecTxResult {
2567+ Code : sdkerrors .ErrInsufficientFunds .ABCICode (),
2568+ Log : fmt .Sprintf ("insufficient funds for gas * price + value: address %s have %v want %v: insufficient funds" , sender .Hex (), senderBalance , balanceCheck ),
2569+ }
2570+ }
2571+ }
2572+
2573+ return gigaValidationResult {
2574+ err : validationErr ,
2575+ nonceValid : nonceValid ,
2576+ currentNonce : currentNonce ,
2577+ baseFee : baseFee ,
2578+ }
2579+ }
2580+
25662581func init () {
25672582 // override max wasm size to 2MB
25682583 wasmtypes .MaxWasmSize = 2 * 1024 * 1024
0 commit comments