Skip to content

Commit 95eeb60

Browse files
committed
fix(giga): order fee validation checks correctly
1 parent 662d751 commit 95eeb60

1 file changed

Lines changed: 125 additions & 110 deletions

File tree

app/app.go

Lines changed: 125 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
25662581
func init() {
25672582
// override max wasm size to 2MB
25682583
wasmtypes.MaxWasmSize = 2 * 1024 * 1024

0 commit comments

Comments
 (0)